1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.tv.dvr.ui;
18 
19 import android.content.Context;
20 import android.media.tv.TvInputManager.TvInputCallback;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.support.v17.leanback.app.BrowseFragment;
24 import android.support.v17.leanback.widget.ArrayObjectAdapter;
25 import android.support.v17.leanback.widget.ClassPresenterSelector;
26 import android.support.v17.leanback.widget.HeaderItem;
27 import android.support.v17.leanback.widget.ListRow;
28 import android.support.v17.leanback.widget.ListRowPresenter;
29 import android.support.v17.leanback.widget.Presenter;
30 import android.support.v17.leanback.widget.TitleViewAdapter;
31 import android.text.TextUtils;
32 import android.util.Log;
33 
34 import com.android.tv.ApplicationSingletons;
35 import com.android.tv.R;
36 import com.android.tv.TvApplication;
37 import com.android.tv.data.GenreItems;
38 import com.android.tv.dvr.DvrDataManager;
39 import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
40 import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener;
41 import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
42 import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
43 import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
44 import com.android.tv.dvr.DvrScheduleManager;
45 import com.android.tv.dvr.RecordedProgram;
46 import com.android.tv.dvr.ScheduledRecording;
47 import com.android.tv.dvr.SeriesRecording;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Comparator;
52 import java.util.HashMap;
53 import java.util.List;
54 
55 /**
56  * {@link BrowseFragment} for DVR functions.
57  */
58 public class DvrBrowseFragment extends BrowseFragment implements
59         RecordedProgramListener, ScheduledRecordingListener, SeriesRecordingListener,
60         OnDvrScheduleLoadFinishedListener, OnRecordedProgramLoadFinishedListener {
61     private static final String TAG = "DvrBrowseFragment";
62     private static final boolean DEBUG = false;
63 
64     private static final int MAX_RECENT_ITEM_COUNT = 10;
65     private static final int MAX_SCHEDULED_ITEM_COUNT = 4;
66 
67     private RecordedProgramAdapter mRecentAdapter;
68     private ScheduleAdapter mScheduleAdapter;
69     private SeriesAdapter mSeriesAdapter;
70     private RecordedProgramAdapter[] mGenreAdapters =
71             new RecordedProgramAdapter[GenreItems.getGenreCount() + 1];
72     private ListRow mRecentRow;
73     private ListRow mSeriesRow;
74     private ListRow[] mGenreRows = new ListRow[GenreItems.getGenreCount() + 1];
75     private List<String> mGenreLabels;
76     private DvrDataManager mDvrDataManager;
77     private DvrScheduleManager mDvrScheudleManager;
78     private ArrayObjectAdapter mRowsAdapter;
79     private ClassPresenterSelector mPresenterSelector;
80     private final HashMap<String, RecordedProgram> mSeriesId2LatestProgram = new HashMap<>();
81     private final Handler mHandler = new Handler();
82 
83     private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR = new Comparator<Object>() {
84         @Override
85         public int compare(Object lhs, Object rhs) {
86             if (lhs instanceof SeriesRecording) {
87                 lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId());
88             }
89             if (rhs instanceof SeriesRecording) {
90                 rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId());
91             }
92             if (lhs instanceof RecordedProgram) {
93                 if (rhs instanceof RecordedProgram) {
94                     return RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed()
95                             .compare((RecordedProgram) lhs, (RecordedProgram) rhs);
96                 } else {
97                     return -1;
98                 }
99             } else if (rhs instanceof RecordedProgram) {
100                 return 1;
101             } else {
102                 return 0;
103             }
104         }
105     };
106 
107     private final Comparator<Object> SCHEDULE_COMPARATOR = new Comparator<Object>() {
108         @Override
109         public int compare(Object lhs, Object rhs) {
110             if (lhs instanceof ScheduledRecording) {
111                 if (rhs instanceof ScheduledRecording) {
112                     return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
113                             .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs);
114                 } else {
115                     return -1;
116                 }
117             } else if (rhs instanceof ScheduledRecording) {
118                 return 1;
119             } else {
120                 return 0;
121             }
122         }
123     };
124 
125     private final DvrScheduleManager.OnConflictStateChangeListener mOnConflictStateChangeListener =
126             new DvrScheduleManager.OnConflictStateChangeListener() {
127         @Override
128         public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) {
129             if (mScheduleAdapter != null) {
130                 for (ScheduledRecording schedule : schedules) {
131                     onScheduledRecordingStatusChanged(schedule);
132                 }
133             }
134         }
135     };
136 
137     private final Runnable mUpdateRowsRunnable = new Runnable() {
138         @Override
139         public void run() {
140             updateRows();
141         }
142     };
143 
144     @Override
onCreate(Bundle savedInstanceState)145     public void onCreate(Bundle savedInstanceState) {
146         if (DEBUG) Log.d(TAG, "onCreate");
147         super.onCreate(savedInstanceState);
148         Context context = getContext();
149         ApplicationSingletons singletons = TvApplication.getSingletons(context);
150         mDvrDataManager = singletons.getDvrDataManager();
151         mDvrScheudleManager = singletons.getDvrScheduleManager();
152         mPresenterSelector = new ClassPresenterSelector()
153                 .addClassPresenter(ScheduledRecording.class,
154                         new ScheduledRecordingPresenter(context))
155                 .addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context))
156                 .addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context))
157                 .addClassPresenter(FullScheduleCardHolder.class, new FullSchedulesCardPresenter());
158         mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context)));
159         mGenreLabels.add(getString(R.string.dvr_main_others));
160         setupUiElements();
161         setupAdapters();
162         mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
163         prepareEntranceTransition();
164         if (mDvrDataManager.isInitialized()) {
165             startEntranceTransition();
166         } else {
167             if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
168                 mDvrDataManager.addDvrScheduleLoadFinishedListener(this);
169             }
170             if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
171                 mDvrDataManager.addRecordedProgramLoadFinishedListener(this);
172             }
173         }
174     }
175 
176     @Override
onDestroy()177     public void onDestroy() {
178         if (DEBUG) Log.d(TAG, "onDestroy");
179         mHandler.removeCallbacks(mUpdateRowsRunnable);
180         mDvrScheudleManager.removeOnConflictStateChangeListener(mOnConflictStateChangeListener);
181         mDvrDataManager.removeRecordedProgramListener(this);
182         mDvrDataManager.removeScheduledRecordingListener(this);
183         mDvrDataManager.removeSeriesRecordingListener(this);
184         mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
185         mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
186         mRowsAdapter.clear();
187         mSeriesId2LatestProgram.clear();
188         for (Presenter presenter : mPresenterSelector.getPresenters()) {
189             if (presenter instanceof DvrItemPresenter) {
190                 ((DvrItemPresenter) presenter).unbindAllViewHolders();
191             }
192         }
193         super.onDestroy();
194     }
195 
196     @Override
onDvrScheduleLoadFinished()197     public void onDvrScheduleLoadFinished() {
198         List<ScheduledRecording> scheduledRecordings = mDvrDataManager.getAllScheduledRecordings();
199         onScheduledRecordingAdded(ScheduledRecording.toArray(scheduledRecordings));
200         List<SeriesRecording> seriesRecordings = mDvrDataManager.getSeriesRecordings();
201         onSeriesRecordingAdded(SeriesRecording.toArray(seriesRecordings));
202         if (mDvrDataManager.isInitialized()) {
203             startEntranceTransition();
204         }
205         mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
206     }
207 
208     @Override
onRecordedProgramLoadFinished()209     public void onRecordedProgramLoadFinished() {
210         for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
211             handleRecordedProgramAdded(recordedProgram, true);
212         }
213         updateRows();
214         if (mDvrDataManager.isInitialized()) {
215             startEntranceTransition();
216         }
217         mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
218     }
219 
220     @Override
onRecordedProgramsAdded(RecordedProgram... recordedPrograms)221     public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
222         for (RecordedProgram recordedProgram : recordedPrograms) {
223             handleRecordedProgramAdded(recordedProgram, true);
224         }
225         postUpdateRows();
226     }
227 
228     @Override
onRecordedProgramsChanged(RecordedProgram... recordedPrograms)229     public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
230         for (RecordedProgram recordedProgram : recordedPrograms) {
231             handleRecordedProgramChanged(recordedProgram);
232         }
233         postUpdateRows();
234     }
235 
236     @Override
onRecordedProgramsRemoved(RecordedProgram... recordedPrograms)237     public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
238         for (RecordedProgram recordedProgram : recordedPrograms) {
239             handleRecordedProgramRemoved(recordedProgram);
240         }
241         postUpdateRows();
242     }
243 
244     // No need to call updateRows() during ScheduledRecordings' change because
245     // the row for ScheduledRecordings is always displayed.
246     @Override
onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings)247     public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
248         for (ScheduledRecording scheduleRecording : scheduledRecordings) {
249             if (needToShowScheduledRecording(scheduleRecording)) {
250                 mScheduleAdapter.add(scheduleRecording);
251             }
252         }
253     }
254 
255     @Override
onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings)256     public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
257         for (ScheduledRecording scheduleRecording : scheduledRecordings) {
258             mScheduleAdapter.remove(scheduleRecording);
259         }
260     }
261 
262     @Override
onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings)263     public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
264         for (ScheduledRecording scheduleRecording : scheduledRecordings) {
265             if (needToShowScheduledRecording(scheduleRecording)) {
266                 mScheduleAdapter.change(scheduleRecording);
267             } else {
268                 mScheduleAdapter.removeWithId(scheduleRecording);
269             }
270         }
271     }
272 
273     @Override
onSeriesRecordingAdded(SeriesRecording... seriesRecordings)274     public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
275         handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings));
276         postUpdateRows();
277     }
278 
279     @Override
onSeriesRecordingRemoved(SeriesRecording... seriesRecordings)280     public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
281         handleSeriesRecordingsRemoved(Arrays.asList(seriesRecordings));
282         postUpdateRows();
283     }
284 
285     @Override
onSeriesRecordingChanged(SeriesRecording... seriesRecordings)286     public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
287         handleSeriesRecordingsChanged(Arrays.asList(seriesRecordings));
288         postUpdateRows();
289     }
290 
291     // Workaround of b/29108300
292     @Override
showTitle(int flags)293     public void showTitle(int flags) {
294         flags &= ~TitleViewAdapter.SEARCH_VIEW_VISIBLE;
295         super.showTitle(flags);
296     }
297 
setupUiElements()298     private void setupUiElements() {
299         setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge));
300         setHeadersState(HEADERS_ENABLED);
301         setHeadersTransitionOnBackEnabled(false);
302         setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null));
303     }
304 
setupAdapters()305     private void setupAdapters() {
306         mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
307         mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
308         mSeriesAdapter = new SeriesAdapter();
309         for (int i = 0; i < mGenreAdapters.length; i++) {
310             mGenreAdapters[i] = new RecordedProgramAdapter();
311         }
312         // Schedule Recordings.
313         List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
314         onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
315         mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
316         // Recorded Programs.
317         for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
318             handleRecordedProgramAdded(recordedProgram, false);
319         }
320         // Series Recordings. Series recordings should be added after recorded programs, because
321         // we build series recordings' latest program information while adding recorded programs.
322         List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
323         handleSeriesRecordingsAdded(recordings);
324         mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
325         mRecentRow = new ListRow(new HeaderItem(
326                 getString(R.string.dvr_main_recent)), mRecentAdapter);
327         mRowsAdapter.add(new ListRow(new HeaderItem(
328                 getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
329         mSeriesRow = new ListRow(new HeaderItem(
330                 getString(R.string.dvr_main_series)), mSeriesAdapter);
331         updateRows();
332         mDvrDataManager.addRecordedProgramListener(this);
333         mDvrDataManager.addScheduledRecordingListener(this);
334         mDvrDataManager.addSeriesRecordingListener(this);
335         setAdapter(mRowsAdapter);
336     }
337 
handleRecordedProgramAdded(RecordedProgram recordedProgram, boolean updateSeriesRecording)338     private void handleRecordedProgramAdded(RecordedProgram recordedProgram,
339             boolean updateSeriesRecording) {
340         mRecentAdapter.add(recordedProgram);
341         String seriesId = recordedProgram.getSeriesId();
342         SeriesRecording seriesRecording = null;
343         if (seriesId != null) {
344             seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
345             RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
346             if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR
347                     .compare(latestProgram, recordedProgram) < 0) {
348                 mSeriesId2LatestProgram.put(seriesId, recordedProgram);
349                 if (updateSeriesRecording && seriesRecording != null) {
350                     onSeriesRecordingChanged(seriesRecording);
351                 }
352             }
353         }
354         if (seriesRecording == null) {
355             for (RecordedProgramAdapter adapter
356                     : getGenreAdapters(recordedProgram.getCanonicalGenres())) {
357                 adapter.add(recordedProgram);
358             }
359         }
360     }
361 
handleRecordedProgramRemoved(RecordedProgram recordedProgram)362     private void handleRecordedProgramRemoved(RecordedProgram recordedProgram) {
363         mRecentAdapter.remove(recordedProgram);
364         String seriesId = recordedProgram.getSeriesId();
365         if (seriesId != null) {
366             SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
367             RecordedProgram latestProgram =
368                     mSeriesId2LatestProgram.get(recordedProgram.getSeriesId());
369             if (latestProgram != null && latestProgram.getId() == recordedProgram.getId()) {
370                 if (seriesRecording != null) {
371                     updateLatestRecordedProgram(seriesRecording);
372                     onSeriesRecordingChanged(seriesRecording);
373                 }
374             }
375         }
376         for (RecordedProgramAdapter adapter
377                 : getGenreAdapters(recordedProgram.getCanonicalGenres())) {
378             adapter.remove(recordedProgram);
379         }
380     }
381 
handleRecordedProgramChanged(RecordedProgram recordedProgram)382     private void handleRecordedProgramChanged(RecordedProgram recordedProgram) {
383         mRecentAdapter.change(recordedProgram);
384         String seriesId = recordedProgram.getSeriesId();
385         SeriesRecording seriesRecording = null;
386         if (seriesId != null) {
387             seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
388             RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
389             if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR
390                     .compare(latestProgram, recordedProgram) <= 0) {
391                 mSeriesId2LatestProgram.put(seriesId, recordedProgram);
392                 if (seriesRecording != null) {
393                     onSeriesRecordingChanged(seriesRecording);
394                 }
395             } else if (latestProgram.getId() == recordedProgram.getId()) {
396                 if (seriesRecording != null) {
397                     updateLatestRecordedProgram(seriesRecording);
398                     onSeriesRecordingChanged(seriesRecording);
399                 }
400             }
401         }
402         if (seriesRecording == null) {
403             updateGenreAdapters(getGenreAdapters(
404                     recordedProgram.getCanonicalGenres()), recordedProgram);
405         } else {
406             updateGenreAdapters(new ArrayList<>(), recordedProgram);
407         }
408     }
409 
handleSeriesRecordingsAdded(List<SeriesRecording> seriesRecordings)410     private void handleSeriesRecordingsAdded(List<SeriesRecording> seriesRecordings) {
411         for (SeriesRecording seriesRecording : seriesRecordings) {
412             mSeriesAdapter.add(seriesRecording);
413             if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) {
414                 for (RecordedProgramAdapter adapter
415                         : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
416                     adapter.add(seriesRecording);
417                 }
418             }
419         }
420     }
421 
handleSeriesRecordingsRemoved(List<SeriesRecording> seriesRecordings)422     private void handleSeriesRecordingsRemoved(List<SeriesRecording> seriesRecordings) {
423         for (SeriesRecording seriesRecording : seriesRecordings) {
424             mSeriesAdapter.remove(seriesRecording);
425             for (RecordedProgramAdapter adapter
426                     : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
427                 adapter.remove(seriesRecording);
428             }
429         }
430     }
431 
handleSeriesRecordingsChanged(List<SeriesRecording> seriesRecordings)432     private void handleSeriesRecordingsChanged(List<SeriesRecording> seriesRecordings) {
433         for (SeriesRecording seriesRecording : seriesRecordings) {
434             mSeriesAdapter.change(seriesRecording);
435             if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) {
436                 updateGenreAdapters(getGenreAdapters(
437                         seriesRecording.getCanonicalGenreIds()), seriesRecording);
438             } else {
439                 // Remove series recording from all genre rows if it has no recorded program
440                 updateGenreAdapters(new ArrayList<>(), seriesRecording);
441             }
442         }
443     }
444 
getGenreAdapters(String[] genres)445     private List<RecordedProgramAdapter> getGenreAdapters(String[] genres) {
446         List<RecordedProgramAdapter> result = new ArrayList<>();
447         if (genres == null || genres.length == 0) {
448             result.add(mGenreAdapters[mGenreAdapters.length - 1]);
449         } else {
450             for (String genre : genres) {
451                 int genreId = GenreItems.getId(genre);
452                 if(genreId >= mGenreAdapters.length) {
453                     Log.d(TAG, "Wrong Genre ID: " + genreId);
454                 } else {
455                     result.add(mGenreAdapters[genreId]);
456                 }
457             }
458         }
459         return result;
460     }
461 
getGenreAdapters(int[] genreIds)462     private List<RecordedProgramAdapter> getGenreAdapters(int[] genreIds) {
463         List<RecordedProgramAdapter> result = new ArrayList<>();
464         if (genreIds == null || genreIds.length == 0) {
465             result.add(mGenreAdapters[mGenreAdapters.length - 1]);
466         } else {
467             for (int genreId : genreIds) {
468                 if(genreId >= mGenreAdapters.length) {
469                     Log.d(TAG, "Wrong Genre ID: " + genreId);
470                 } else {
471                     result.add(mGenreAdapters[genreId]);
472                 }
473             }
474         }
475         return result;
476     }
477 
updateGenreAdapters(List<RecordedProgramAdapter> adapters, Object r)478     private void updateGenreAdapters(List<RecordedProgramAdapter> adapters, Object r) {
479         for (RecordedProgramAdapter adapter : mGenreAdapters) {
480             if (adapters.contains(adapter)) {
481                 adapter.change(r);
482             } else {
483                 adapter.remove(r);
484             }
485         }
486     }
487 
postUpdateRows()488     private void postUpdateRows() {
489         mHandler.removeCallbacks(mUpdateRowsRunnable);
490         mHandler.post(mUpdateRowsRunnable);
491     }
492 
updateRows()493     private void updateRows() {
494         int visibleRowsCount = 1;  // Schedule's Row will never be empty
495         if (mRecentAdapter.isEmpty()) {
496             mRowsAdapter.remove(mRecentRow);
497         } else {
498             if (mRowsAdapter.indexOf(mRecentRow) < 0) {
499                 mRowsAdapter.add(0, mRecentRow);
500             }
501             visibleRowsCount++;
502         }
503         if (mSeriesAdapter.isEmpty()) {
504             mRowsAdapter.remove(mSeriesRow);
505         } else {
506             if (mRowsAdapter.indexOf(mSeriesRow) < 0) {
507                 mRowsAdapter.add(visibleRowsCount, mSeriesRow);
508             }
509             visibleRowsCount++;
510         }
511         for (int i = 0; i < mGenreAdapters.length; i++) {
512             RecordedProgramAdapter adapter = mGenreAdapters[i];
513             if (adapter != null) {
514                 if (adapter.isEmpty()) {
515                     mRowsAdapter.remove(mGenreRows[i]);
516                 } else {
517                     if (mGenreRows[i] == null || mRowsAdapter.indexOf(mGenreRows[i]) < 0) {
518                         mGenreRows[i] = new ListRow(new HeaderItem(mGenreLabels.get(i)), adapter);
519                         mRowsAdapter.add(visibleRowsCount, mGenreRows[i]);
520                     }
521                     visibleRowsCount++;
522                 }
523             }
524         }
525     }
526 
needToShowScheduledRecording(ScheduledRecording recording)527     private boolean needToShowScheduledRecording(ScheduledRecording recording) {
528         int state = recording.getState();
529         return state == ScheduledRecording.STATE_RECORDING_IN_PROGRESS
530                 || state == ScheduledRecording.STATE_RECORDING_NOT_STARTED;
531     }
532 
updateLatestRecordedProgram(SeriesRecording seriesRecording)533     private void updateLatestRecordedProgram(SeriesRecording seriesRecording) {
534         RecordedProgram latestProgram = null;
535         for (RecordedProgram program :
536                 mDvrDataManager.getRecordedPrograms(seriesRecording.getId())) {
537             if (latestProgram == null || RecordedProgram
538                     .START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) < 0) {
539                 latestProgram = program;
540             }
541         }
542         mSeriesId2LatestProgram.put(seriesRecording.getSeriesId(), latestProgram);
543     }
544 
545     private class ScheduleAdapter extends SortedArrayAdapter<Object> {
ScheduleAdapter(int maxItemCount)546         ScheduleAdapter(int maxItemCount) {
547             super(mPresenterSelector, SCHEDULE_COMPARATOR, maxItemCount);
548         }
549 
550         @Override
getId(Object item)551         public long getId(Object item) {
552             if (item instanceof ScheduledRecording) {
553                 return ((ScheduledRecording) item).getId();
554             } else {
555                 return -1;
556             }
557         }
558     }
559 
560     private class SeriesAdapter extends SortedArrayAdapter<SeriesRecording> {
SeriesAdapter()561         SeriesAdapter() {
562             super(mPresenterSelector, new Comparator<SeriesRecording>() {
563                 @Override
564                 public int compare(SeriesRecording lhs, SeriesRecording rhs) {
565                     if (lhs.isStopped() && !rhs.isStopped()) {
566                         return 1;
567                     } else if (!lhs.isStopped() && rhs.isStopped()) {
568                         return -1;
569                     }
570                     return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs);
571                 }
572             });
573         }
574 
575         @Override
getId(SeriesRecording item)576         public long getId(SeriesRecording item) {
577             return item.getId();
578         }
579     }
580 
581     private class RecordedProgramAdapter extends SortedArrayAdapter<Object> {
RecordedProgramAdapter()582         RecordedProgramAdapter() {
583             this(Integer.MAX_VALUE);
584         }
585 
RecordedProgramAdapter(int maxItemCount)586         RecordedProgramAdapter(int maxItemCount) {
587             super(mPresenterSelector, RECORDED_PROGRAM_COMPARATOR, maxItemCount);
588         }
589 
590         @Override
getId(Object item)591         public long getId(Object item) {
592             if (item instanceof SeriesRecording) {
593                 return ((SeriesRecording) item).getId();
594             } else if (item instanceof RecordedProgram) {
595                 return ((RecordedProgram) item).getId();
596             } else {
597                 return -1;
598             }
599         }
600     }
601 }