• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.browse;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.support.v17.leanback.app.BrowseFragment;
23 import android.support.v17.leanback.widget.ArrayObjectAdapter;
24 import android.support.v17.leanback.widget.ClassPresenterSelector;
25 import android.support.v17.leanback.widget.HeaderItem;
26 import android.support.v17.leanback.widget.ListRow;
27 import android.support.v17.leanback.widget.Presenter;
28 import android.support.v17.leanback.widget.TitleViewAdapter;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
32 
33 import com.android.tv.ApplicationSingletons;
34 import com.android.tv.R;
35 import com.android.tv.TvApplication;
36 import com.android.tv.data.GenreItems;
37 import com.android.tv.dvr.DvrDataManager;
38 import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
39 import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener;
40 import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
41 import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
42 import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
43 import com.android.tv.dvr.DvrScheduleManager;
44 import com.android.tv.dvr.data.RecordedProgram;
45 import com.android.tv.dvr.data.ScheduledRecording;
46 import com.android.tv.dvr.data.SeriesRecording;
47 import com.android.tv.dvr.ui.SortedArrayAdapter;
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 boolean mShouldShowScheduleRow;
68     private boolean mEntranceTransitionEnded;
69 
70     private RecordedProgramAdapter mRecentAdapter;
71     private ScheduleAdapter mScheduleAdapter;
72     private SeriesAdapter mSeriesAdapter;
73     private RecordedProgramAdapter[] mGenreAdapters =
74             new RecordedProgramAdapter[GenreItems.getGenreCount() + 1];
75     private ListRow mRecentRow;
76     private ListRow mScheduledRow;
77     private ListRow mSeriesRow;
78     private ListRow[] mGenreRows = new ListRow[GenreItems.getGenreCount() + 1];
79     private List<String> mGenreLabels;
80     private DvrDataManager mDvrDataManager;
81     private DvrScheduleManager mDvrScheudleManager;
82     private ArrayObjectAdapter mRowsAdapter;
83     private ClassPresenterSelector mPresenterSelector;
84     private final HashMap<String, RecordedProgram> mSeriesId2LatestProgram = new HashMap<>();
85     private final Handler mHandler = new Handler();
86     private final OnGlobalFocusChangeListener mOnGlobalFocusChangeListener =
87             new OnGlobalFocusChangeListener() {
88                 @Override
89                 public void onGlobalFocusChanged(View oldFocus, View newFocus) {
90                     if (oldFocus instanceof RecordingCardView) {
91                         ((RecordingCardView) oldFocus).expandTitle(false, true);
92                     }
93                     if (newFocus instanceof RecordingCardView) {
94                         // If the header transition is ongoing, expand cards immediately without
95                         // animation to make a smooth transition.
96                         ((RecordingCardView) newFocus).expandTitle(true, !isInHeadersTransition());
97                     }
98                 }
99             };
100 
101     private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR = new Comparator<Object>() {
102         @Override
103         public int compare(Object lhs, Object rhs) {
104             if (lhs instanceof SeriesRecording) {
105                 lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId());
106             }
107             if (rhs instanceof SeriesRecording) {
108                 rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId());
109             }
110             if (lhs instanceof RecordedProgram) {
111                 if (rhs instanceof RecordedProgram) {
112                     return RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed()
113                             .compare((RecordedProgram) lhs, (RecordedProgram) rhs);
114                 } else {
115                     return -1;
116                 }
117             } else if (rhs instanceof RecordedProgram) {
118                 return 1;
119             } else {
120                 return 0;
121             }
122         }
123     };
124 
125     private static final Comparator<Object> SCHEDULE_COMPARATOR = new Comparator<Object>() {
126         @Override
127         public int compare(Object lhs, Object rhs) {
128             if (lhs instanceof ScheduledRecording) {
129                 if (rhs instanceof ScheduledRecording) {
130                     return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
131                             .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs);
132                 } else {
133                     return -1;
134                 }
135             } else if (rhs instanceof ScheduledRecording) {
136                 return 1;
137             } else {
138                 return 0;
139             }
140         }
141     };
142 
143     private final DvrScheduleManager.OnConflictStateChangeListener mOnConflictStateChangeListener =
144             new DvrScheduleManager.OnConflictStateChangeListener() {
145         @Override
146         public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) {
147             if (mScheduleAdapter != null) {
148                 for (ScheduledRecording schedule : schedules) {
149                     onScheduledRecordingConflictStatusChanged(schedule);
150                 }
151             }
152         }
153     };
154 
155     private final Runnable mUpdateRowsRunnable = new Runnable() {
156         @Override
157         public void run() {
158             updateRows();
159         }
160     };
161 
162     @Override
onCreate(Bundle savedInstanceState)163     public void onCreate(Bundle savedInstanceState) {
164         if (DEBUG) Log.d(TAG, "onCreate");
165         super.onCreate(savedInstanceState);
166         Context context = getContext();
167         ApplicationSingletons singletons = TvApplication.getSingletons(context);
168         mDvrDataManager = singletons.getDvrDataManager();
169         mDvrScheudleManager = singletons.getDvrScheduleManager();
170         mPresenterSelector = new ClassPresenterSelector()
171                 .addClassPresenter(ScheduledRecording.class,
172                         new ScheduledRecordingPresenter(context))
173                 .addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context))
174                 .addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context))
175                 .addClassPresenter(FullScheduleCardHolder.class,
176                         new FullSchedulesCardPresenter(context));
177         mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context)));
178         mGenreLabels.add(getString(R.string.dvr_main_others));
179         prepareUiElements();
180         if (!startBrowseIfDvrInitialized()) {
181             if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
182                 mDvrDataManager.addDvrScheduleLoadFinishedListener(this);
183             }
184             if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
185                 mDvrDataManager.addRecordedProgramLoadFinishedListener(this);
186             }
187         }
188     }
189 
190     @Override
onViewCreated(View view, Bundle savedInstanceState)191     public void onViewCreated(View view, Bundle savedInstanceState) {
192         super.onViewCreated(view, savedInstanceState);
193         view.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
194     }
195 
196     @Override
onDestroyView()197     public void onDestroyView() {
198         getView().getViewTreeObserver()
199                 .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
200         super.onDestroyView();
201     }
202 
203     @Override
onDestroy()204     public void onDestroy() {
205         if (DEBUG) Log.d(TAG, "onDestroy");
206         mHandler.removeCallbacks(mUpdateRowsRunnable);
207         mDvrScheudleManager.removeOnConflictStateChangeListener(mOnConflictStateChangeListener);
208         mDvrDataManager.removeRecordedProgramListener(this);
209         mDvrDataManager.removeScheduledRecordingListener(this);
210         mDvrDataManager.removeSeriesRecordingListener(this);
211         mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
212         mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
213         mRowsAdapter.clear();
214         mSeriesId2LatestProgram.clear();
215         for (Presenter presenter : mPresenterSelector.getPresenters()) {
216             if (presenter instanceof DvrItemPresenter) {
217                 ((DvrItemPresenter) presenter).unbindAllViewHolders();
218             }
219         }
220         super.onDestroy();
221     }
222 
223     @Override
onDvrScheduleLoadFinished()224     public void onDvrScheduleLoadFinished() {
225         startBrowseIfDvrInitialized();
226         mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
227     }
228 
229     @Override
onRecordedProgramLoadFinished()230     public void onRecordedProgramLoadFinished() {
231         startBrowseIfDvrInitialized();
232         mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
233     }
234 
235     @Override
onRecordedProgramsAdded(RecordedProgram... recordedPrograms)236     public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
237         for (RecordedProgram recordedProgram : recordedPrograms) {
238             handleRecordedProgramAdded(recordedProgram, true);
239         }
240         postUpdateRows();
241     }
242 
243     @Override
onRecordedProgramsChanged(RecordedProgram... recordedPrograms)244     public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
245         for (RecordedProgram recordedProgram : recordedPrograms) {
246             handleRecordedProgramChanged(recordedProgram);
247         }
248         postUpdateRows();
249     }
250 
251     @Override
onRecordedProgramsRemoved(RecordedProgram... recordedPrograms)252     public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
253         for (RecordedProgram recordedProgram : recordedPrograms) {
254             handleRecordedProgramRemoved(recordedProgram);
255         }
256         postUpdateRows();
257     }
258 
259     // No need to call updateRows() during ScheduledRecordings' change because
260     // the row for ScheduledRecordings is always displayed.
261     @Override
onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings)262     public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
263         for (ScheduledRecording scheduleRecording : scheduledRecordings) {
264             if (needToShowScheduledRecording(scheduleRecording)) {
265                 mScheduleAdapter.add(scheduleRecording);
266             }
267         }
268     }
269 
270     @Override
onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings)271     public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
272         for (ScheduledRecording scheduleRecording : scheduledRecordings) {
273             mScheduleAdapter.remove(scheduleRecording);
274         }
275     }
276 
277     @Override
onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings)278     public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
279         for (ScheduledRecording scheduleRecording : scheduledRecordings) {
280             if (needToShowScheduledRecording(scheduleRecording)) {
281                 mScheduleAdapter.change(scheduleRecording);
282             } else {
283                 mScheduleAdapter.removeWithId(scheduleRecording);
284             }
285         }
286     }
287 
onScheduledRecordingConflictStatusChanged(ScheduledRecording... schedules)288     private void onScheduledRecordingConflictStatusChanged(ScheduledRecording... schedules) {
289         for (ScheduledRecording schedule : schedules) {
290             if (needToShowScheduledRecording(schedule)) {
291                 if (mScheduleAdapter.contains(schedule)) {
292                     mScheduleAdapter.change(schedule);
293                 }
294             } else {
295                 mScheduleAdapter.removeWithId(schedule);
296             }
297         }
298     }
299 
300     @Override
onSeriesRecordingAdded(SeriesRecording... seriesRecordings)301     public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
302         handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings));
303         postUpdateRows();
304     }
305 
306     @Override
onSeriesRecordingRemoved(SeriesRecording... seriesRecordings)307     public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
308         handleSeriesRecordingsRemoved(Arrays.asList(seriesRecordings));
309         postUpdateRows();
310     }
311 
312     @Override
onSeriesRecordingChanged(SeriesRecording... seriesRecordings)313     public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
314         handleSeriesRecordingsChanged(Arrays.asList(seriesRecordings));
315         postUpdateRows();
316     }
317 
318     // Workaround of b/29108300
319     @Override
showTitle(int flags)320     public void showTitle(int flags) {
321         flags &= ~TitleViewAdapter.SEARCH_VIEW_VISIBLE;
322         super.showTitle(flags);
323     }
324 
325     @Override
onEntranceTransitionEnd()326     protected void onEntranceTransitionEnd() {
327         super.onEntranceTransitionEnd();
328         if (mShouldShowScheduleRow) {
329             showScheduledRowInternal();
330         }
331         mEntranceTransitionEnded = true;
332     }
333 
showScheduledRow()334     void showScheduledRow() {
335         if (!mEntranceTransitionEnded) {
336             setHeadersState(HEADERS_HIDDEN);
337             mShouldShowScheduleRow = true;
338         } else {
339             showScheduledRowInternal();
340         }
341     }
342 
showScheduledRowInternal()343     private void showScheduledRowInternal() {
344         setSelectedPosition(mRowsAdapter.indexOf(mScheduledRow), true, null);
345         if (getHeadersState() == HEADERS_ENABLED) {
346             startHeadersTransition(false);
347         }
348         mShouldShowScheduleRow = false;
349     }
350 
prepareUiElements()351     private void prepareUiElements() {
352         setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge));
353         setHeadersState(HEADERS_ENABLED);
354         setHeadersTransitionOnBackEnabled(false);
355         setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null));
356         mRowsAdapter = new ArrayObjectAdapter(new DvrListRowPresenter(getContext()));
357         setAdapter(mRowsAdapter);
358         prepareEntranceTransition();
359     }
360 
startBrowseIfDvrInitialized()361     private boolean startBrowseIfDvrInitialized() {
362         if (mDvrDataManager.isInitialized()) {
363             // Setup rows
364             mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
365             mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
366             mSeriesAdapter = new SeriesAdapter();
367             for (int i = 0; i < mGenreAdapters.length; i++) {
368                 mGenreAdapters[i] = new RecordedProgramAdapter();
369             }
370             // Schedule Recordings.
371             List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
372             onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
373             mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
374             // Recorded Programs.
375             for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
376                 handleRecordedProgramAdded(recordedProgram, false);
377             }
378             // Series Recordings. Series recordings should be added after recorded programs, because
379             // we build series recordings' latest program information while adding recorded programs.
380             List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
381             handleSeriesRecordingsAdded(recordings);
382             mRecentRow = new ListRow(new HeaderItem(
383                     getString(R.string.dvr_main_recent)), mRecentAdapter);
384             mScheduledRow = new ListRow(new HeaderItem(
385                     getString(R.string.dvr_main_scheduled)), mScheduleAdapter);
386             mSeriesRow = new ListRow(new HeaderItem(
387                     getString(R.string.dvr_main_series)), mSeriesAdapter);
388             mRowsAdapter.add(mScheduledRow);
389             updateRows();
390             // Initialize listeners
391             mDvrDataManager.addRecordedProgramListener(this);
392             mDvrDataManager.addScheduledRecordingListener(this);
393             mDvrDataManager.addSeriesRecordingListener(this);
394             mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
395             startEntranceTransition();
396             return true;
397         }
398         return false;
399     }
400 
handleRecordedProgramAdded(RecordedProgram recordedProgram, boolean updateSeriesRecording)401     private void handleRecordedProgramAdded(RecordedProgram recordedProgram,
402             boolean updateSeriesRecording) {
403         mRecentAdapter.add(recordedProgram);
404         String seriesId = recordedProgram.getSeriesId();
405         SeriesRecording seriesRecording = null;
406         if (seriesId != null) {
407             seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
408             RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
409             if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR
410                     .compare(latestProgram, recordedProgram) < 0) {
411                 mSeriesId2LatestProgram.put(seriesId, recordedProgram);
412                 if (updateSeriesRecording && seriesRecording != null) {
413                     onSeriesRecordingChanged(seriesRecording);
414                 }
415             }
416         }
417         if (seriesRecording == null) {
418             for (RecordedProgramAdapter adapter
419                     : getGenreAdapters(recordedProgram.getCanonicalGenres())) {
420                 adapter.add(recordedProgram);
421             }
422         }
423     }
424 
handleRecordedProgramRemoved(RecordedProgram recordedProgram)425     private void handleRecordedProgramRemoved(RecordedProgram recordedProgram) {
426         mRecentAdapter.remove(recordedProgram);
427         String seriesId = recordedProgram.getSeriesId();
428         if (seriesId != null) {
429             SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
430             RecordedProgram latestProgram =
431                     mSeriesId2LatestProgram.get(recordedProgram.getSeriesId());
432             if (latestProgram != null && latestProgram.getId() == recordedProgram.getId()) {
433                 if (seriesRecording != null) {
434                     updateLatestRecordedProgram(seriesRecording);
435                     onSeriesRecordingChanged(seriesRecording);
436                 }
437             }
438         }
439         for (RecordedProgramAdapter adapter
440                 : getGenreAdapters(recordedProgram.getCanonicalGenres())) {
441             adapter.remove(recordedProgram);
442         }
443     }
444 
handleRecordedProgramChanged(RecordedProgram recordedProgram)445     private void handleRecordedProgramChanged(RecordedProgram recordedProgram) {
446         mRecentAdapter.change(recordedProgram);
447         String seriesId = recordedProgram.getSeriesId();
448         SeriesRecording seriesRecording = null;
449         if (seriesId != null) {
450             seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
451             RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
452             if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR
453                     .compare(latestProgram, recordedProgram) <= 0) {
454                 mSeriesId2LatestProgram.put(seriesId, recordedProgram);
455                 if (seriesRecording != null) {
456                     onSeriesRecordingChanged(seriesRecording);
457                 }
458             } else if (latestProgram.getId() == recordedProgram.getId()) {
459                 if (seriesRecording != null) {
460                     updateLatestRecordedProgram(seriesRecording);
461                     onSeriesRecordingChanged(seriesRecording);
462                 }
463             }
464         }
465         if (seriesRecording == null) {
466             updateGenreAdapters(getGenreAdapters(
467                     recordedProgram.getCanonicalGenres()), recordedProgram);
468         } else {
469             updateGenreAdapters(new ArrayList<>(), recordedProgram);
470         }
471     }
472 
handleSeriesRecordingsAdded(List<SeriesRecording> seriesRecordings)473     private void handleSeriesRecordingsAdded(List<SeriesRecording> seriesRecordings) {
474         for (SeriesRecording seriesRecording : seriesRecordings) {
475             mSeriesAdapter.add(seriesRecording);
476             if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) {
477                 for (RecordedProgramAdapter adapter
478                         : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
479                     adapter.add(seriesRecording);
480                 }
481             }
482         }
483     }
484 
handleSeriesRecordingsRemoved(List<SeriesRecording> seriesRecordings)485     private void handleSeriesRecordingsRemoved(List<SeriesRecording> seriesRecordings) {
486         for (SeriesRecording seriesRecording : seriesRecordings) {
487             mSeriesAdapter.remove(seriesRecording);
488             for (RecordedProgramAdapter adapter
489                     : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
490                 adapter.remove(seriesRecording);
491             }
492         }
493     }
494 
handleSeriesRecordingsChanged(List<SeriesRecording> seriesRecordings)495     private void handleSeriesRecordingsChanged(List<SeriesRecording> seriesRecordings) {
496         for (SeriesRecording seriesRecording : seriesRecordings) {
497             mSeriesAdapter.change(seriesRecording);
498             if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) {
499                 updateGenreAdapters(getGenreAdapters(
500                         seriesRecording.getCanonicalGenreIds()), seriesRecording);
501             } else {
502                 // Remove series recording from all genre rows if it has no recorded program
503                 updateGenreAdapters(new ArrayList<>(), seriesRecording);
504             }
505         }
506     }
507 
getGenreAdapters(String[] genres)508     private List<RecordedProgramAdapter> getGenreAdapters(String[] genres) {
509         List<RecordedProgramAdapter> result = new ArrayList<>();
510         if (genres == null || genres.length == 0) {
511             result.add(mGenreAdapters[mGenreAdapters.length - 1]);
512         } else {
513             for (String genre : genres) {
514                 int genreId = GenreItems.getId(genre);
515                 if(genreId >= mGenreAdapters.length) {
516                     Log.d(TAG, "Wrong Genre ID: " + genreId);
517                 } else {
518                     result.add(mGenreAdapters[genreId]);
519                 }
520             }
521         }
522         return result;
523     }
524 
getGenreAdapters(int[] genreIds)525     private List<RecordedProgramAdapter> getGenreAdapters(int[] genreIds) {
526         List<RecordedProgramAdapter> result = new ArrayList<>();
527         if (genreIds == null || genreIds.length == 0) {
528             result.add(mGenreAdapters[mGenreAdapters.length - 1]);
529         } else {
530             for (int genreId : genreIds) {
531                 if(genreId >= mGenreAdapters.length) {
532                     Log.d(TAG, "Wrong Genre ID: " + genreId);
533                 } else {
534                     result.add(mGenreAdapters[genreId]);
535                 }
536             }
537         }
538         return result;
539     }
540 
updateGenreAdapters(List<RecordedProgramAdapter> adapters, Object r)541     private void updateGenreAdapters(List<RecordedProgramAdapter> adapters, Object r) {
542         for (RecordedProgramAdapter adapter : mGenreAdapters) {
543             if (adapters.contains(adapter)) {
544                 adapter.change(r);
545             } else {
546                 adapter.remove(r);
547             }
548         }
549     }
550 
postUpdateRows()551     private void postUpdateRows() {
552         mHandler.removeCallbacks(mUpdateRowsRunnable);
553         mHandler.post(mUpdateRowsRunnable);
554     }
555 
updateRows()556     private void updateRows() {
557         int visibleRowsCount = 1;  // Schedule's Row will never be empty
558         if (mRecentAdapter.isEmpty()) {
559             mRowsAdapter.remove(mRecentRow);
560         } else {
561             if (mRowsAdapter.indexOf(mRecentRow) < 0) {
562                 mRowsAdapter.add(0, mRecentRow);
563             }
564             visibleRowsCount++;
565         }
566         if (mSeriesAdapter.isEmpty()) {
567             mRowsAdapter.remove(mSeriesRow);
568         } else {
569             if (mRowsAdapter.indexOf(mSeriesRow) < 0) {
570                 mRowsAdapter.add(visibleRowsCount, mSeriesRow);
571             }
572             visibleRowsCount++;
573         }
574         for (int i = 0; i < mGenreAdapters.length; i++) {
575             RecordedProgramAdapter adapter = mGenreAdapters[i];
576             if (adapter != null) {
577                 if (adapter.isEmpty()) {
578                     mRowsAdapter.remove(mGenreRows[i]);
579                 } else {
580                     if (mGenreRows[i] == null || mRowsAdapter.indexOf(mGenreRows[i]) < 0) {
581                         mGenreRows[i] = new ListRow(new HeaderItem(mGenreLabels.get(i)), adapter);
582                         mRowsAdapter.add(visibleRowsCount, mGenreRows[i]);
583                     }
584                     visibleRowsCount++;
585                 }
586             }
587         }
588     }
589 
needToShowScheduledRecording(ScheduledRecording recording)590     private boolean needToShowScheduledRecording(ScheduledRecording recording) {
591         int state = recording.getState();
592         return state == ScheduledRecording.STATE_RECORDING_IN_PROGRESS
593                 || state == ScheduledRecording.STATE_RECORDING_NOT_STARTED;
594     }
595 
updateLatestRecordedProgram(SeriesRecording seriesRecording)596     private void updateLatestRecordedProgram(SeriesRecording seriesRecording) {
597         RecordedProgram latestProgram = null;
598         for (RecordedProgram program :
599                 mDvrDataManager.getRecordedPrograms(seriesRecording.getId())) {
600             if (latestProgram == null || RecordedProgram
601                     .START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) < 0) {
602                 latestProgram = program;
603             }
604         }
605         mSeriesId2LatestProgram.put(seriesRecording.getSeriesId(), latestProgram);
606     }
607 
608     private class ScheduleAdapter extends SortedArrayAdapter<Object> {
ScheduleAdapter(int maxItemCount)609         ScheduleAdapter(int maxItemCount) {
610             super(mPresenterSelector, SCHEDULE_COMPARATOR, maxItemCount);
611         }
612 
613         @Override
getId(Object item)614         public long getId(Object item) {
615             if (item instanceof ScheduledRecording) {
616                 return ((ScheduledRecording) item).getId();
617             } else {
618                 return -1;
619             }
620         }
621     }
622 
623     private class SeriesAdapter extends SortedArrayAdapter<SeriesRecording> {
SeriesAdapter()624         SeriesAdapter() {
625             super(mPresenterSelector, new Comparator<SeriesRecording>() {
626                 @Override
627                 public int compare(SeriesRecording lhs, SeriesRecording rhs) {
628                     if (lhs.isStopped() && !rhs.isStopped()) {
629                         return 1;
630                     } else if (!lhs.isStopped() && rhs.isStopped()) {
631                         return -1;
632                     }
633                     return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs);
634                 }
635             });
636         }
637 
638         @Override
getId(SeriesRecording item)639         public long getId(SeriesRecording item) {
640             return item.getId();
641         }
642     }
643 
644     private class RecordedProgramAdapter extends SortedArrayAdapter<Object> {
RecordedProgramAdapter()645         RecordedProgramAdapter() {
646             this(Integer.MAX_VALUE);
647         }
648 
RecordedProgramAdapter(int maxItemCount)649         RecordedProgramAdapter(int maxItemCount) {
650             super(mPresenterSelector, RECORDED_PROGRAM_COMPARATOR, maxItemCount);
651         }
652 
653         @Override
getId(Object item)654         public long getId(Object item) {
655             // We takes the inverse number for the ID of recorded programs to make the ID stable.
656             if (item instanceof SeriesRecording) {
657                 return ((SeriesRecording) item).getId();
658             } else if (item instanceof RecordedProgram) {
659                 return -((RecordedProgram) item).getId() - 1;
660             } else {
661                 return -1;
662             }
663         }
664     }
665 }