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 17package com.android.tv.dvr.ui.browse; 18 19import android.content.Context; 20import android.os.Bundle; 21import android.os.Handler; 22import android.support.v17.leanback.app.BrowseFragment; 23import android.support.v17.leanback.widget.ArrayObjectAdapter; 24import android.support.v17.leanback.widget.ClassPresenterSelector; 25import android.support.v17.leanback.widget.HeaderItem; 26import android.support.v17.leanback.widget.ListRow; 27import android.support.v17.leanback.widget.Presenter; 28import android.support.v17.leanback.widget.TitleViewAdapter; 29import android.util.Log; 30import android.view.View; 31import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; 32 33import com.android.tv.ApplicationSingletons; 34import com.android.tv.R; 35import com.android.tv.TvApplication; 36import com.android.tv.data.GenreItems; 37import com.android.tv.dvr.DvrDataManager; 38import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; 39import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener; 40import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; 41import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; 42import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; 43import com.android.tv.dvr.DvrScheduleManager; 44import com.android.tv.dvr.data.RecordedProgram; 45import com.android.tv.dvr.data.ScheduledRecording; 46import com.android.tv.dvr.data.SeriesRecording; 47import com.android.tv.dvr.ui.SortedArrayAdapter; 48 49import java.util.ArrayList; 50import java.util.Arrays; 51import java.util.Comparator; 52import java.util.HashMap; 53import java.util.List; 54 55/** 56 * {@link BrowseFragment} for DVR functions. 57 */ 58public 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 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 191 public void onViewCreated(View view, Bundle savedInstanceState) { 192 super.onViewCreated(view, savedInstanceState); 193 view.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener); 194 } 195 196 @Override 197 public void onDestroyView() { 198 getView().getViewTreeObserver() 199 .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener); 200 super.onDestroyView(); 201 } 202 203 @Override 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 224 public void onDvrScheduleLoadFinished() { 225 startBrowseIfDvrInitialized(); 226 mDvrDataManager.removeDvrScheduleLoadFinishedListener(this); 227 } 228 229 @Override 230 public void onRecordedProgramLoadFinished() { 231 startBrowseIfDvrInitialized(); 232 mDvrDataManager.removeRecordedProgramLoadFinishedListener(this); 233 } 234 235 @Override 236 public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { 237 for (RecordedProgram recordedProgram : recordedPrograms) { 238 handleRecordedProgramAdded(recordedProgram, true); 239 } 240 postUpdateRows(); 241 } 242 243 @Override 244 public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { 245 for (RecordedProgram recordedProgram : recordedPrograms) { 246 handleRecordedProgramChanged(recordedProgram); 247 } 248 postUpdateRows(); 249 } 250 251 @Override 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 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 271 public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { 272 for (ScheduledRecording scheduleRecording : scheduledRecordings) { 273 mScheduleAdapter.remove(scheduleRecording); 274 } 275 } 276 277 @Override 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 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 301 public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { 302 handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings)); 303 postUpdateRows(); 304 } 305 306 @Override 307 public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { 308 handleSeriesRecordingsRemoved(Arrays.asList(seriesRecordings)); 309 postUpdateRows(); 310 } 311 312 @Override 313 public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { 314 handleSeriesRecordingsChanged(Arrays.asList(seriesRecordings)); 315 postUpdateRows(); 316 } 317 318 // Workaround of b/29108300 319 @Override 320 public void showTitle(int flags) { 321 flags &= ~TitleViewAdapter.SEARCH_VIEW_VISIBLE; 322 super.showTitle(flags); 323 } 324 325 @Override 326 protected void onEntranceTransitionEnd() { 327 super.onEntranceTransitionEnd(); 328 if (mShouldShowScheduleRow) { 329 showScheduledRowInternal(); 330 } 331 mEntranceTransitionEnded = true; 332 } 333 334 void showScheduledRow() { 335 if (!mEntranceTransitionEnded) { 336 setHeadersState(HEADERS_HIDDEN); 337 mShouldShowScheduleRow = true; 338 } else { 339 showScheduledRowInternal(); 340 } 341 } 342 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 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 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 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 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 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 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 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 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 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 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 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 551 private void postUpdateRows() { 552 mHandler.removeCallbacks(mUpdateRowsRunnable); 553 mHandler.post(mUpdateRowsRunnable); 554 } 555 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 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 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> { 609 ScheduleAdapter(int maxItemCount) { 610 super(mPresenterSelector, SCHEDULE_COMPARATOR, maxItemCount); 611 } 612 613 @Override 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> { 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 639 public long getId(SeriesRecording item) { 640 return item.getId(); 641 } 642 } 643 644 private class RecordedProgramAdapter extends SortedArrayAdapter<Object> { 645 RecordedProgramAdapter() { 646 this(Integer.MAX_VALUE); 647 } 648 649 RecordedProgramAdapter(int maxItemCount) { 650 super(mPresenterSelector, RECORDED_PROGRAM_COMPARATOR, maxItemCount); 651 } 652 653 @Override 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}