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