aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/exoplayer/MediaFormatUtil.java76
-rw-r--r--src/com/android/exoplayer/MediaSoftwareCodecUtil.java280
-rw-r--r--src/com/android/exoplayer/text/SubtitleView.java324
-rw-r--r--src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java127
-rw-r--r--src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java84
-rw-r--r--src/com/android/tv/AudioManagerHelper.java91
-rw-r--r--src/com/android/tv/ChannelTuner.java106
-rw-r--r--src/com/android/tv/Features.java204
-rw-r--r--src/com/android/tv/InputSessionManager.java284
-rw-r--r--src/com/android/tv/LauncherActivity.java38
-rw-r--r--src/com/android/tv/MainActivity.java1281
-rw-r--r--src/com/android/tv/MainActivityWrapper.java56
-rw-r--r--src/com/android/tv/MediaSessionWrapper.java116
-rw-r--r--src/com/android/tv/SelectInputActivity.java50
-rw-r--r--src/com/android/tv/SetupPassthroughActivity.java140
-rw-r--r--src/com/android/tv/Starter.java42
-rw-r--r--src/com/android/tv/TimeShiftManager.java591
-rw-r--r--src/com/android/tv/TvActivity.java1
-rw-r--r--src/com/android/tv/TvApplication.java453
-rw-r--r--src/com/android/tv/TvFeatures.java104
-rw-r--r--src/com/android/tv/TvOptionsManager.java52
-rw-r--r--src/com/android/tv/TvSingletons.java (renamed from src/com/android/tv/ApplicationSingletons.java)47
-rw-r--r--src/com/android/tv/analytics/Analytics.java14
-rw-r--r--src/com/android/tv/analytics/ConfigurationInfo.java4
-rw-r--r--src/com/android/tv/analytics/HasTrackerLabel.java4
-rw-r--r--src/com/android/tv/analytics/SendChannelStatusRunnable.java76
-rw-r--r--src/com/android/tv/analytics/SendConfigInfoRunnable.java10
-rw-r--r--src/com/android/tv/analytics/StubAnalytics.java7
-rw-r--r--src/com/android/tv/analytics/StubTracker.java73
-rw-r--r--src/com/android/tv/analytics/Tracker.java62
-rw-r--r--src/com/android/tv/app/LiveTvApplication.java139
-rw-r--r--src/com/android/tv/config/DefaultConfigManager.java61
-rw-r--r--src/com/android/tv/config/RemoteConfig.java51
-rw-r--r--src/com/android/tv/config/RemoteConfigFeature.java43
-rw-r--r--src/com/android/tv/config/RemoteConfigUtils.java42
-rw-r--r--src/com/android/tv/customization/CustomAction.java77
-rw-r--r--src/com/android/tv/customization/TvCustomizationManager.java261
-rw-r--r--src/com/android/tv/data/BaseProgram.java183
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java320
-rw-r--r--src/com/android/tv/data/ChannelImpl.java (renamed from src/com/android/tv/data/Channel.java)397
-rw-r--r--src/com/android/tv/data/ChannelLogoFetcher.java59
-rw-r--r--src/com/android/tv/data/ChannelNumber.java54
-rw-r--r--src/com/android/tv/data/DisplayMode.java7
-rw-r--r--src/com/android/tv/data/GenreItems.java23
-rw-r--r--src/com/android/tv/data/InternalDataUtils.java34
-rw-r--r--src/com/android/tv/data/Lineup.java104
-rw-r--r--src/com/android/tv/data/OnCurrentProgramUpdatedListener.java4
-rw-r--r--src/com/android/tv/data/ParcelableList.java40
-rw-r--r--src/com/android/tv/data/PreviewDataManager.java204
-rw-r--r--src/com/android/tv/data/PreviewProgramContent.java132
-rw-r--r--src/com/android/tv/data/Program.java325
-rw-r--r--src/com/android/tv/data/ProgramDataManager.java358
-rw-r--r--src/com/android/tv/data/StreamInfo.java14
-rw-r--r--src/com/android/tv/data/TvInputNewComparator.java6
-rw-r--r--src/com/android/tv/data/WatchedHistoryManager.java110
-rw-r--r--src/com/android/tv/data/api/Channel.java130
-rw-r--r--src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java86
-rw-r--r--src/com/android/tv/data/epg/EpgFetchHelper.java109
-rw-r--r--src/com/android/tv/data/epg/EpgFetchService.java70
-rw-r--r--src/com/android/tv/data/epg/EpgFetcher.java710
-rw-r--r--src/com/android/tv/data/epg/EpgFetcherImpl.java811
-rw-r--r--src/com/android/tv/data/epg/EpgInputWhiteList.java103
-rw-r--r--src/com/android/tv/data/epg/EpgReader.java46
-rw-r--r--src/com/android/tv/data/epg/StubEpgReader.java26
-rw-r--r--src/com/android/tv/dialog/DvrHistoryDialogFragment.java126
-rw-r--r--src/com/android/tv/dialog/FullscreenDialogFragment.java25
-rw-r--r--src/com/android/tv/dialog/HalfSizedDialogFragment.java48
-rw-r--r--src/com/android/tv/dialog/PinDialogFragment.java329
-rw-r--r--src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java117
-rw-r--r--src/com/android/tv/dialog/SafeDismissDialogFragment.java16
-rw-r--r--src/com/android/tv/dialog/WebDialogFragment.java20
-rw-r--r--src/com/android/tv/dvr/BaseDvrDataManager.java106
-rw-r--r--src/com/android/tv/dvr/DvrDataManager.java152
-rw-r--r--src/com/android/tv/dvr/DvrDataManagerImpl.java439
-rw-r--r--src/com/android/tv/dvr/DvrManager.java391
-rw-r--r--src/com/android/tv/dvr/DvrScheduleManager.java596
-rw-r--r--src/com/android/tv/dvr/DvrStorageStatusManager.java334
-rw-r--r--src/com/android/tv/dvr/DvrWatchedPositionManager.java61
-rw-r--r--src/com/android/tv/dvr/WritableDvrDataManager.java37
-rw-r--r--src/com/android/tv/dvr/data/IdGenerator.java20
-rw-r--r--src/com/android/tv/dvr/data/RecordedProgram.java473
-rw-r--r--src/com/android/tv/dvr/data/ScheduledRecording.java659
-rw-r--r--src/com/android/tv/dvr/data/SeasonEpisodeNumber.java27
-rw-r--r--src/com/android/tv/dvr/data/SeriesInfo.java28
-rw-r--r--src/com/android/tv/dvr/data/SeriesRecording.java399
-rw-r--r--src/com/android/tv/dvr/provider/AsyncDvrDbTask.java60
-rw-r--r--src/com/android/tv/dvr/provider/DvrContract.java120
-rw-r--r--src/com/android/tv/dvr/provider/DvrDatabaseHelper.java285
-rw-r--r--src/com/android/tv/dvr/provider/DvrDbSync.java202
-rw-r--r--src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java157
-rw-r--r--src/com/android/tv/dvr/recorder/ConflictChecker.java113
-rw-r--r--src/com/android/tv/dvr/recorder/DvrRecordingService.java58
-rw-r--r--src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java12
-rw-r--r--src/com/android/tv/dvr/recorder/InputTaskScheduler.java172
-rw-r--r--src/com/android/tv/dvr/recorder/RecordingScheduler.java118
-rw-r--r--src/com/android/tv/dvr/recorder/RecordingTask.java362
-rw-r--r--src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java16
-rw-r--r--src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java283
-rw-r--r--src/com/android/tv/dvr/ui/BigArguments.java20
-rw-r--r--src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java24
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java47
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java57
-rw-r--r--src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java57
-rw-r--r--src/com/android/tv/dvr/ui/DvrConflictFragment.java159
-rw-r--r--src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java15
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java92
-rw-r--r--src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java62
-rw-r--r--src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java83
-rw-r--r--src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java24
-rw-r--r--src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java86
-rw-r--r--src/com/android/tv/dvr/ui/DvrScheduleFragment.java114
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java13
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java126
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java13
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java119
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java32
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java151
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java57
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java9
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java40
-rw-r--r--src/com/android/tv/dvr/ui/DvrUiHelper.java547
-rw-r--r--src/com/android/tv/dvr/ui/FadeBackground.java17
-rw-r--r--src/com/android/tv/dvr/ui/SortedArrayAdapter.java34
-rw-r--r--src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java34
-rw-r--r--src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java28
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContent.java172
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java188
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java17
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java11
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java339
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java48
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java151
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrHistoryCardHolder.java (renamed from src/com/android/tv/config/ConfigKeys.java)16
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java64
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java41
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java1
-rw-r--r--src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java10
-rw-r--r--src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java35
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java71
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java55
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingCardView.java92
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java27
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java45
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java102
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java104
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java44
-rw-r--r--src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java39
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java166
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java353
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java47
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java11
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java15
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java117
-rw-r--r--src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java19
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRow.java111
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java174
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java611
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java63
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java198
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java66
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java71
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java19
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java14
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java132
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java171
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java188
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java59
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlayer.java361
-rw-r--r--src/com/android/tv/experiments/ExperimentFlag.java70
-rw-r--r--src/com/android/tv/experiments/Experiments.java45
-rw-r--r--src/com/android/tv/guide/GenreListAdapter.java59
-rw-r--r--src/com/android/tv/guide/GuideUtils.java42
-rw-r--r--src/com/android/tv/guide/ProgramGrid.java40
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java684
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java522
-rw-r--r--src/com/android/tv/guide/ProgramListAdapter.java14
-rw-r--r--src/com/android/tv/guide/ProgramManager.java379
-rw-r--r--src/com/android/tv/guide/ProgramRow.java129
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java303
-rw-r--r--src/com/android/tv/guide/TimeListAdapter.java13
-rw-r--r--src/com/android/tv/guide/TimelineGridView.java17
-rw-r--r--src/com/android/tv/guide/TimelineRow.java13
-rw-r--r--src/com/android/tv/license/LicenseDialogFragment.java1
-rw-r--r--src/com/android/tv/license/LicenseSideFragment.java2
-rw-r--r--src/com/android/tv/license/LicenseUtils.java17
-rw-r--r--src/com/android/tv/license/Licenses.java3
-rw-r--r--src/com/android/tv/livetv/receiver/GlobalKeyReceiver.java (renamed from src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl)24
-rw-r--r--src/com/android/tv/menu/ActionCardView.java7
-rw-r--r--src/com/android/tv/menu/AppLinkCardView.java124
-rw-r--r--src/com/android/tv/menu/BaseCardView.java132
-rw-r--r--src/com/android/tv/menu/ChannelCardView.java29
-rw-r--r--src/com/android/tv/menu/ChannelsPosterPrefetcher.java55
-rw-r--r--src/com/android/tv/menu/ChannelsRow.java53
-rw-r--r--src/com/android/tv/menu/ChannelsRowAdapter.java138
-rw-r--r--src/com/android/tv/menu/ChannelsRowItem.java34
-rw-r--r--src/com/android/tv/menu/CustomizableOptionsRowAdapter.java13
-rw-r--r--src/com/android/tv/menu/IMenuView.java23
-rw-r--r--src/com/android/tv/menu/ItemListRow.java22
-rw-r--r--src/com/android/tv/menu/ItemListRowView.java47
-rw-r--r--src/com/android/tv/menu/Menu.java171
-rw-r--r--src/com/android/tv/menu/MenuAction.java51
-rw-r--r--src/com/android/tv/menu/MenuLayoutManager.java540
-rw-r--r--src/com/android/tv/menu/MenuRow.java72
-rw-r--r--src/com/android/tv/menu/MenuRowFactory.java68
-rw-r--r--src/com/android/tv/menu/MenuRowView.java63
-rw-r--r--src/com/android/tv/menu/MenuUpdater.java78
-rw-r--r--src/com/android/tv/menu/MenuView.java64
-rw-r--r--src/com/android/tv/menu/OptionsRowAdapter.java51
-rw-r--r--src/com/android/tv/menu/PartnerOptionsRowAdapter.java7
-rw-r--r--src/com/android/tv/menu/PlayControlsButton.java64
-rw-r--r--src/com/android/tv/menu/PlayControlsRow.java13
-rw-r--r--src/com/android/tv/menu/PlayControlsRowView.java459
-rw-r--r--src/com/android/tv/menu/PlaybackProgressBar.java22
-rw-r--r--src/com/android/tv/menu/SimpleCardView.java4
-rw-r--r--src/com/android/tv/menu/TvOptionsRowAdapter.java42
-rw-r--r--src/com/android/tv/onboarding/NewSourcesFragment.java36
-rw-r--r--src/com/android/tv/onboarding/OnboardingActivity.java160
-rw-r--r--src/com/android/tv/onboarding/SetupSourcesFragment.java235
-rw-r--r--src/com/android/tv/onboarding/WelcomeFragment.java1227
-rw-r--r--src/com/android/tv/parental/ContentRatingLevelPolicy.java22
-rw-r--r--src/com/android/tv/parental/ContentRatingSystem.java87
-rw-r--r--src/com/android/tv/parental/ContentRatingsManager.java28
-rw-r--r--src/com/android/tv/parental/ContentRatingsParser.java169
-rw-r--r--src/com/android/tv/parental/ParentalControlSettings.java92
-rw-r--r--src/com/android/tv/perf/EventNames.java9
-rw-r--r--src/com/android/tv/perf/PerformanceMonitor.java5
-rw-r--r--src/com/android/tv/receiver/AbstractGlobalKeyReceiver.java (renamed from src/com/android/tv/receiver/GlobalKeyReceiver.java)25
-rw-r--r--src/com/android/tv/receiver/AudioCapabilitiesReceiver.java35
-rw-r--r--src/com/android/tv/receiver/BootCompletedReceiver.java27
-rw-r--r--src/com/android/tv/receiver/PackageIntentsReceiver.java16
-rw-r--r--src/com/android/tv/recommendation/ChannelPreviewUpdater.java151
-rw-r--r--src/com/android/tv/recommendation/ChannelRecord.java19
-rw-r--r--src/com/android/tv/recommendation/FavoriteChannelEvaluator.java5
-rw-r--r--src/com/android/tv/recommendation/NotificationService.java269
-rw-r--r--src/com/android/tv/recommendation/RecentChannelEvaluator.java11
-rw-r--r--src/com/android/tv/recommendation/RecommendationDataManager.java292
-rw-r--r--src/com/android/tv/recommendation/Recommender.java81
-rw-r--r--src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java113
-rw-r--r--src/com/android/tv/recommendation/RoutineWatchEvaluator.java88
-rw-r--r--src/com/android/tv/search/AutoValue_LocalSearchProvider_SearchResult.java363
-rw-r--r--src/com/android/tv/search/DataManagerSearch.java185
-rw-r--r--src/com/android/tv/search/LocalSearchProvider.java224
-rw-r--r--src/com/android/tv/search/ProgramGuideSearchFragment.java177
-rw-r--r--src/com/android/tv/search/SearchInterface.java11
-rw-r--r--src/com/android/tv/search/TvProviderSearch.java386
-rw-r--r--src/com/android/tv/setup/SystemSetupActivity.java101
-rw-r--r--src/com/android/tv/tuner/ChannelScanFileParser.java101
-rw-r--r--src/com/android/tv/tuner/DvbDeviceAccessor.java223
-rw-r--r--src/com/android/tv/tuner/DvbTunerHal.java179
-rw-r--r--src/com/android/tv/tuner/TunerHal.java352
-rw-r--r--src/com/android/tv/tuner/TunerInputController.java480
-rw-r--r--src/com/android/tv/tuner/TunerPreferenceProvider.java203
-rw-r--r--src/com/android/tv/tuner/TunerPreferences.java428
-rw-r--r--src/com/android/tv/tuner/cc/CaptionLayout.java76
-rw-r--r--src/com/android/tv/tuner/cc/CaptionTrackRenderer.java344
-rw-r--r--src/com/android/tv/tuner/cc/CaptionWindowLayout.java650
-rw-r--r--src/com/android/tv/tuner/cc/Cea708Parser.java820
-rw-r--r--src/com/android/tv/tuner/data/Cea708Data.java320
-rw-r--r--src/com/android/tv/tuner/data/PsiData.java94
-rw-r--r--src/com/android/tv/tuner/data/PsipData.java820
-rw-r--r--src/com/android/tv/tuner/data/TunerChannel.java511
-rw-r--r--src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java302
-rw-r--r--src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java41
-rw-r--r--src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java552
-rw-r--r--src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java138
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java696
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java75
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java335
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java196
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java101
-rw-r--r--src/com/android/tv/tuner/exoplayer/SampleExtractor.java136
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioClock.java107
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java70
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java129
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java176
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java235
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java735
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java94
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java692
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java392
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java309
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java437
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java461
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java71
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java75
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java180
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java147
-rw-r--r--src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java249
-rw-r--r--src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java205
-rw-r--r--src/com/android/tv/tuner/layout/ScaledLayout.java274
-rw-r--r--src/com/android/tv/tuner/setup/ConnectionTypeFragment.java101
-rw-r--r--src/com/android/tv/tuner/setup/PostalCodeFragment.java178
-rw-r--r--src/com/android/tv/tuner/setup/ScanFragment.java523
-rw-r--r--src/com/android/tv/tuner/setup/ScanResultFragment.java127
-rw-r--r--src/com/android/tv/tuner/setup/TunerSetupActivity.java543
-rw-r--r--src/com/android/tv/tuner/setup/WelcomeFragment.java120
-rw-r--r--src/com/android/tv/tuner/source/FileTsStreamer.java484
-rw-r--r--src/com/android/tv/tuner/source/TsDataSource.java50
-rw-r--r--src/com/android/tv/tuner/source/TsDataSourceManager.java143
-rw-r--r--src/com/android/tv/tuner/source/TsStreamWriter.java237
-rw-r--r--src/com/android/tv/tuner/source/TsStreamer.java56
-rw-r--r--src/com/android/tv/tuner/source/TunerTsStreamer.java408
-rw-r--r--src/com/android/tv/tuner/source/TunerTsStreamerManager.java304
-rw-r--r--src/com/android/tv/tuner/ts/SectionParser.java1759
-rw-r--r--src/com/android/tv/tuner/ts/TsParser.java520
-rw-r--r--src/com/android/tv/tuner/tvinput/ChannelDataManager.java734
-rw-r--r--src/com/android/tv/tuner/tvinput/EventDetector.java334
-rw-r--r--src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java249
-rw-r--r--src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java42
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerDebug.java150
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerRecordingSession.java104
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java662
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSession.java324
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSessionWorker.java1754
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java174
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerTvInputService.java123
-rw-r--r--src/com/android/tv/tuner/util/ByteArrayBuffer.java149
-rw-r--r--src/com/android/tv/tuner/util/ConvertUtils.java35
-rw-r--r--src/com/android/tv/tuner/util/GlobalSettingsUtils.java36
-rw-r--r--src/com/android/tv/tuner/util/Ints.java28
-rw-r--r--src/com/android/tv/tuner/util/PostalCodeUtils.java138
-rw-r--r--src/com/android/tv/tuner/util/StatusTextUtils.java119
-rw-r--r--src/com/android/tv/tuner/util/SystemPropertiesProxy.java77
-rw-r--r--src/com/android/tv/tuner/util/TisConfiguration.java22
-rw-r--r--src/com/android/tv/tuner/util/TunerInputInfoUtils.java115
-rw-r--r--src/com/android/tv/ui/AppLayerTvView.java21
-rw-r--r--src/com/android/tv/ui/BlockScreenView.java97
-rw-r--r--src/com/android/tv/ui/ChannelBannerView.java364
-rw-r--r--src/com/android/tv/ui/DialogUtils.java34
-rw-r--r--src/com/android/tv/ui/FullscreenDialogView.java99
-rw-r--r--src/com/android/tv/ui/GuidedActionsStylistWithDivider.java14
-rw-r--r--src/com/android/tv/ui/InputBannerView.java36
-rw-r--r--src/com/android/tv/ui/IntroView.java18
-rw-r--r--src/com/android/tv/ui/KeypadChannelSwitchView.java192
-rw-r--r--src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java26
-rw-r--r--src/com/android/tv/ui/SelectInputView.java207
-rw-r--r--src/com/android/tv/ui/TunableTvView.java922
-rw-r--r--src/com/android/tv/ui/TunableTvViewPlayingApi.java56
-rw-r--r--src/com/android/tv/ui/TvOverlayManager.java720
-rw-r--r--src/com/android/tv/ui/TvTransitionManager.java152
-rw-r--r--src/com/android/tv/ui/TvViewUiManager.java318
-rw-r--r--src/com/android/tv/ui/ViewUtils.java53
-rw-r--r--src/com/android/tv/ui/hideable/AutoHideScheduler.java98
-rw-r--r--src/com/android/tv/ui/sidepanel/ActionItem.java3
-rw-r--r--src/com/android/tv/ui/sidepanel/ChannelCheckItem.java51
-rw-r--r--src/com/android/tv/ui/sidepanel/CheckBoxItem.java16
-rw-r--r--src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java54
-rw-r--r--src/com/android/tv/ui/sidepanel/CompoundButtonItem.java11
-rw-r--r--src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java177
-rw-r--r--src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java83
-rw-r--r--src/com/android/tv/ui/sidepanel/DisplayModeFragment.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/DividerItem.java11
-rw-r--r--src/com/android/tv/ui/sidepanel/Item.java23
-rw-r--r--src/com/android/tv/ui/sidepanel/MultiAudioFragment.java10
-rw-r--r--src/com/android/tv/ui/sidepanel/RadioButtonItem.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/SettingsFragment.java182
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragment.java49
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragmentManager.java122
-rw-r--r--src/com/android/tv/ui/sidepanel/SimpleActionItem.java7
-rw-r--r--src/com/android/tv/ui/sidepanel/SubMenuItem.java1
-rw-r--r--src/com/android/tv/ui/sidepanel/SwitchItem.java6
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java108
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java181
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java64
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java39
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java92
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java25
-rw-r--r--src/com/android/tv/util/AsyncDbTask.java240
-rw-r--r--src/com/android/tv/util/CaptionSettings.java13
-rw-r--r--src/com/android/tv/util/Clock.java65
-rw-r--r--src/com/android/tv/util/CompositeComparator.java4
-rw-r--r--src/com/android/tv/util/Debug.java60
-rw-r--r--src/com/android/tv/util/DurationTimer.java91
-rw-r--r--src/com/android/tv/util/Filter.java8
-rw-r--r--src/com/android/tv/util/LocationUtils.java143
-rw-r--r--src/com/android/tv/util/MainThreadExecutor.java19
-rw-r--r--src/com/android/tv/util/MultiLongSparseArray.java21
-rw-r--r--src/com/android/tv/util/NamedThreadFactory.java48
-rw-r--r--src/com/android/tv/util/NetworkTrafficTags.java64
-rw-r--r--src/com/android/tv/util/NetworkUtils.java11
-rw-r--r--src/com/android/tv/util/OnboardingUtils.java43
-rw-r--r--src/com/android/tv/util/Partner.java3
-rw-r--r--src/com/android/tv/util/PermissionUtils.java55
-rw-r--r--src/com/android/tv/util/RecurringRunner.java39
-rw-r--r--src/com/android/tv/util/SetupUtils.java245
-rw-r--r--src/com/android/tv/util/SqlParams.java74
-rw-r--r--src/com/android/tv/util/StringUtils.java38
-rw-r--r--src/com/android/tv/util/SystemProperties.java68
-rw-r--r--src/com/android/tv/util/TimeShiftUtils.java18
-rw-r--r--src/com/android/tv/util/ToastUtils.java9
-rw-r--r--src/com/android/tv/util/TvInputManagerHelper.java440
-rw-r--r--src/com/android/tv/util/TvSettings.java126
-rw-r--r--src/com/android/tv/util/TvTrackInfoUtils.java50
-rw-r--r--src/com/android/tv/util/TvUriMatcher.java31
-rw-r--r--src/com/android/tv/util/Utils.java484
-rw-r--r--src/com/android/tv/util/ViewCache.java52
-rw-r--r--src/com/android/tv/util/account/AccountHelper.java38
-rw-r--r--src/com/android/tv/util/account/AccountHelperImpl.java (renamed from src/com/android/tv/util/AccountHelper.java)52
-rw-r--r--src/com/android/tv/util/images/BitmapUtils.java (renamed from src/com/android/tv/util/BitmapUtils.java)103
-rw-r--r--src/com/android/tv/util/images/ImageCache.java (renamed from src/com/android/tv/util/ImageCache.java)74
-rw-r--r--src/com/android/tv/util/images/ImageLoader.java (renamed from src/com/android/tv/util/ImageLoader.java)205
404 files changed, 21489 insertions, 43070 deletions
diff --git a/src/com/android/exoplayer/MediaFormatUtil.java b/src/com/android/exoplayer/MediaFormatUtil.java
deleted file mode 100644
index d7a981f6..00000000
--- a/src/com/android/exoplayer/MediaFormatUtil.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer;
-
-import android.support.annotation.Nullable;
-
-import com.google.android.exoplayer.util.MimeTypes;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/** {@link MediaFormat} creation helper util */
-public class MediaFormatUtil {
-
- /**
- * Creates {@link MediaFormat} from {@link android.media.MediaFormat}.
- * Since {@link com.google.android.exoplayer.TrackRenderer} uses {@link MediaFormat},
- * {@link android.media.MediaFormat} should be converted to be used with ExoPlayer.
- */
- public static MediaFormat createMediaFormat(android.media.MediaFormat format) {
- String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
- String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE);
- int maxInputSize =
- getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
- int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
- int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
- int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees");
- int channelCount =
- getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
- int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
- int encoderDelay = getOptionalIntegerV16(format, "encoder-delay");
- int encoderPadding = getOptionalIntegerV16(format, "encoder-padding");
- ArrayList<byte[]> initializationData = new ArrayList<>();
- for (int i = 0; format.containsKey("csd-" + i); i++) {
- ByteBuffer buffer = format.getByteBuffer("csd-" + i);
- byte[] data = new byte[buffer.limit()];
- buffer.get(data);
- initializationData.add(data);
- buffer.flip();
- }
- long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
- ? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
- int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT
- : MediaFormat.NO_VALUE;
- MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE,
- maxInputSize, durationUs, width, height, rotationDegrees, MediaFormat.NO_VALUE,
- channelCount, sampleRate, language, MediaFormat.OFFSET_SAMPLE_RELATIVE,
- initializationData, false, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, pcmEncoding,
- encoderDelay, encoderPadding, null, MediaFormat.NO_VALUE);
- mediaFormat.setFrameworkFormatV16(format);
- return mediaFormat;
- }
-
- @Nullable
- private static String getOptionalStringV16(android.media.MediaFormat format, String key) {
- return format.containsKey(key) ? format.getString(key) : null;
- }
-
- private static int getOptionalIntegerV16(android.media.MediaFormat format, String key) {
- return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE;
- }
-
-}
diff --git a/src/com/android/exoplayer/MediaSoftwareCodecUtil.java b/src/com/android/exoplayer/MediaSoftwareCodecUtil.java
deleted file mode 100644
index 8c2509d4..00000000
--- a/src/com/android/exoplayer/MediaSoftwareCodecUtil.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer;
-
-import android.annotation.TargetApi;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.util.MimeTypes;
-
-import java.util.HashMap;
-
-/**
- * Mostly copied from {@link com.google.android.exoplayer.MediaCodecUtil} in order to choose
- * software codec over hardware codec.
- */
-public class MediaSoftwareCodecUtil {
- private static final String TAG = "MediaSoftwareCodecUtil";
-
- /**
- * Thrown when an error occurs querying the device for its underlying media capabilities.
- * <p>
- * Such failures are not expected in normal operation and are normally temporary (e.g. if the
- * mediaserver process has crashed and is yet to restart).
- */
- public static class DecoderQueryException extends Exception {
-
- private DecoderQueryException(Throwable cause) {
- super("Failed to query underlying media codecs", cause);
- }
-
- }
-
- private static final HashMap<CodecKey, Pair<String, MediaCodecInfo.CodecCapabilities>>
- sSwCodecs = new HashMap<>();
-
- /**
- * Gets information about the software decoder that will be used for a given mime type.
- */
- public static DecoderInfo getSoftwareDecoderInfo(String mimeType, boolean secure)
- throws DecoderQueryException {
- // TODO: Add a test for this method.
- Pair<String, MediaCodecInfo.CodecCapabilities> info =
- getMediaSoftwareCodecInfo(mimeType, secure);
- if (info == null) {
- return null;
- }
- return new DecoderInfo(info.first, info.second);
- }
-
- /**
- * Returns the name of the software decoder and its capabilities for the given mimeType.
- */
- private static synchronized Pair<String, MediaCodecInfo.CodecCapabilities>
- getMediaSoftwareCodecInfo(String mimeType, boolean secure) throws DecoderQueryException {
- CodecKey key = new CodecKey(mimeType, secure);
- if (sSwCodecs.containsKey(key)) {
- return sSwCodecs.get(key);
- }
- MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure);
- Pair<String, MediaCodecInfo.CodecCapabilities> codecInfo =
- getMediaSoftwareCodecInfo(key, mediaCodecList);
- if (secure && codecInfo == null) {
- // Some devices don't list secure decoders on API level 21. Try the legacy path.
- mediaCodecList = new MediaCodecListCompatV16();
- codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList);
- if (codecInfo != null) {
- Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType
- + ". Assuming: " + codecInfo.first);
- }
- }
- return codecInfo;
- }
-
- private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo(
- CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException {
- try {
- return getMediaSoftwareCodecInfoInternal(key, mediaCodecList);
- } catch (Exception e) {
- // If the underlying mediaserver is in a bad state, we may catch an
- // IllegalStateException or an IllegalArgumentException here.
- throw new DecoderQueryException(e);
- }
- }
-
- private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfoInternal(
- CodecKey key, MediaCodecListCompat mediaCodecList) {
- String mimeType = key.mimeType;
- int numberOfCodecs = mediaCodecList.getCodecCount();
- boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit();
- // Note: MediaCodecList is sorted by the framework such that the best decoders come first.
- for (int i = 0; i < numberOfCodecs; i++) {
- MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i);
- String codecName = info.getName();
- if (!info.isEncoder() && codecName.startsWith("OMX.google.")
- && (secureDecodersExplicit || !codecName.endsWith(".secure"))) {
- String[] supportedTypes = info.getSupportedTypes();
- for (String supportedType : supportedTypes) {
- if (supportedType.equalsIgnoreCase(mimeType)) {
- MediaCodecInfo.CodecCapabilities capabilities =
- info.getCapabilitiesForType(supportedType);
- boolean secure = mediaCodecList.isSecurePlaybackSupported(
- key.mimeType, capabilities);
- if (!secureDecodersExplicit) {
- // Cache variants for both insecure and (if we think it's supported)
- // secure playback.
- sSwCodecs.put(key.secure ? new CodecKey(mimeType, false) : key,
- Pair.create(codecName, capabilities));
- if (secure) {
- sSwCodecs.put(key.secure ? key : new CodecKey(mimeType, true),
- Pair.create(codecName + ".secure", capabilities));
- }
- } else {
- // Only cache this variant. If both insecure and secure decoders are
- // available, they should both be listed separately.
- sSwCodecs.put(
- key.secure == secure ? key: new CodecKey(mimeType, secure),
- Pair.create(codecName, capabilities));
- }
- if (sSwCodecs.containsKey(key)) {
- return sSwCodecs.get(key);
- }
- }
- }
- }
- }
- sSwCodecs.put(key, null);
- return null;
- }
-
- private interface MediaCodecListCompat {
-
- /**
- * Returns the number of codecs in the list.
- */
- int getCodecCount();
-
- /**
- * Returns the info at the specified index in the list.
- *
- * @param index The index.
- */
- MediaCodecInfo getCodecInfoAt(int index);
-
- /**
- * Returns whether secure decoders are explicitly listed, if present.
- */
- boolean secureDecodersExplicit();
-
- /**
- * Returns true if secure playback is supported for the given
- * {@link android.media.MediaCodecInfo.CodecCapabilities}, which should
- * have been obtained from a {@link MediaCodecInfo} obtained from this list.
- */
- boolean isSecurePlaybackSupported(String mimeType,
- MediaCodecInfo.CodecCapabilities capabilities);
-
- }
-
- @TargetApi(21)
- private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {
-
- private final int codecKind;
-
- private MediaCodecInfo[] mediaCodecInfos;
-
- public MediaCodecListCompatV21(boolean includeSecure) {
- codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
- }
-
- @Override
- public int getCodecCount() {
- ensureMediaCodecInfosInitialized();
- return mediaCodecInfos.length;
- }
-
- @Override
- public MediaCodecInfo getCodecInfoAt(int index) {
- ensureMediaCodecInfosInitialized();
- return mediaCodecInfos[index];
- }
-
- @Override
- public boolean secureDecodersExplicit() {
- return true;
- }
-
- @Override
- public boolean isSecurePlaybackSupported(String mimeType,
- MediaCodecInfo.CodecCapabilities capabilities) {
- return capabilities.isFeatureSupported(
- MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback);
- }
-
- private void ensureMediaCodecInfosInitialized() {
- if (mediaCodecInfos == null) {
- mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos();
- }
- }
-
- }
-
- @SuppressWarnings("deprecation")
- private static final class MediaCodecListCompatV16 implements MediaCodecListCompat {
-
- @Override
- public int getCodecCount() {
- return MediaCodecList.getCodecCount();
- }
-
- @Override
- public MediaCodecInfo getCodecInfoAt(int index) {
- return MediaCodecList.getCodecInfoAt(index);
- }
-
- @Override
- public boolean secureDecodersExplicit() {
- return false;
- }
-
- @Override
- public boolean isSecurePlaybackSupported(String mimeType,
- MediaCodecInfo.CodecCapabilities capabilities) {
- // Secure decoders weren't explicitly listed prior to API level 21. We assume that
- // a secure H264 decoder exists.
- return MimeTypes.VIDEO_H264.equals(mimeType);
- }
-
- }
-
- private static final class CodecKey {
-
- public final String mimeType;
- public final boolean secure;
-
- public CodecKey(String mimeType, boolean secure) {
- this.mimeType = mimeType;
- this.secure = secure;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode());
- result = 2 * result + (secure ? 0 : 1);
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof CodecKey)) {
- return false;
- }
- CodecKey other = (CodecKey) obj;
- return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure;
- }
-
- }
-
-}
diff --git a/src/com/android/exoplayer/text/SubtitleView.java b/src/com/android/exoplayer/text/SubtitleView.java
deleted file mode 100644
index 37926eda..00000000
--- a/src/com/android/exoplayer/text/SubtitleView.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.text;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Join;
-import android.graphics.Paint.Style;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.text.Layout.Alignment;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.View;
-
-import com.google.android.exoplayer.util.Util;
-
-import java.util.ArrayList;
-
-/**
- * Since this class does not exist in recent version of ExoPlayer and used by
- * {@link com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from
- * older version of ExoPlayer.
- * A view for rendering a single caption.
- */
-@Deprecated
-public class SubtitleView extends View {
- /**
- * Ratio of inner padding to font size.
- */
- private static final float INNER_PADDING_RATIO = 0.125f;
-
- /**
- * Temporary rectangle used for computing line bounds.
- */
- private final RectF mLineBounds = new RectF();
-
- // Styled dimensions.
- private final float mCornerRadius;
- private final float mOutlineWidth;
- private final float mShadowRadius;
- private final float mShadowOffset;
-
- private final TextPaint mTextPaint;
- private final Paint mPaint;
-
- private CharSequence mText;
-
- private int mForegroundColor;
- private int mBackgroundColor;
- private int mEdgeColor;
- private int mEdgeType;
-
- private boolean mHasMeasurements;
- private int mLastMeasuredWidth;
- private StaticLayout mLayout;
-
- private Alignment mAlignment;
- private final float mSpacingMult;
- private final float mSpacingAdd;
- private int mInnerPaddingX;
- private float mWhiteSpaceWidth;
- private ArrayList<Integer> mPrefixSpaces = new ArrayList<>();
-
- public SubtitleView(Context context) {
- this(context, null);
- }
-
- public SubtitleView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- int[] viewAttr = {android.R.attr.text, android.R.attr.textSize,
- android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier};
- TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0);
- CharSequence text = a.getText(0);
- int textSize = a.getDimensionPixelSize(1, 15);
- mSpacingAdd = a.getDimensionPixelSize(2, 0);
- mSpacingMult = a.getFloat(3, 1);
- a.recycle();
-
- Resources resources = getContext().getResources();
- DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- int twoDpInPx =
- Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
- mCornerRadius = twoDpInPx;
- mOutlineWidth = twoDpInPx;
- mShadowRadius = twoDpInPx;
- mShadowOffset = twoDpInPx;
-
- mTextPaint = new TextPaint();
- mTextPaint.setAntiAlias(true);
- mTextPaint.setSubpixelText(true);
-
- mAlignment = Alignment.ALIGN_CENTER;
-
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
-
- mInnerPaddingX = 0;
- setText(text);
- setTextSize(textSize);
- setStyle(CaptionStyleCompat.DEFAULT);
- }
-
- @Override
- public void setBackgroundColor(int color) {
- mBackgroundColor = color;
- forceUpdate(false);
- }
-
- /**
- * Sets the text to be displayed by the view.
- *
- * @param text The text to display.
- */
- public void setText(CharSequence text) {
- this.mText = text;
- forceUpdate(true);
- }
-
- /**
- * Sets the text size in pixels.
- *
- * @param size The text size in pixels.
- */
- public void setTextSize(float size) {
- if (mTextPaint.getTextSize() != size) {
- mTextPaint.setTextSize(size);
- mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
- mWhiteSpaceWidth -= mInnerPaddingX * 2;
- forceUpdate(true);
- }
- }
-
- /**
- * Sets the text alignment.
- *
- * @param textAlignment The text alignment.
- */
- public void setTextAlignment(Alignment textAlignment) {
- mAlignment = textAlignment;
- }
-
- /**
- * Configures the view according to the given style.
- *
- * @param style A style for the view.
- */
- public void setStyle(CaptionStyleCompat style) {
- mForegroundColor = style.foregroundColor;
- mBackgroundColor = style.backgroundColor;
- mEdgeType = style.edgeType;
- mEdgeColor = style.edgeColor;
- setTypeface(style.typeface);
- super.setBackgroundColor(style.windowColor);
- forceUpdate(true);
- }
-
- public void setPrefixSpaces(ArrayList<Integer> prefixSpaces) {
- mPrefixSpaces = prefixSpaces;
- }
-
- public void setWhiteSpaceWidth(float whiteSpaceWidth) {
- mWhiteSpaceWidth = whiteSpaceWidth;
- }
-
- private void setTypeface(Typeface typeface) {
- if (mTextPaint.getTypeface() != typeface) {
- mTextPaint.setTypeface(typeface);
- forceUpdate(true);
- }
- }
-
- private void forceUpdate(boolean needsLayout) {
- if (needsLayout) {
- mHasMeasurements = false;
- requestLayout();
- }
- invalidate();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
-
- if (computeMeasurements(widthSpec)) {
- final StaticLayout layout = this.mLayout;
- final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
- final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom();
- int width = 0;
- int lineCount = layout.getLineCount();
- for (int i = 0; i < lineCount; i++) {
- width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width);
- }
- width += paddingX;
- setMeasuredDimension(width, height);
- } else if (Util.SDK_INT >= 11) {
- setTooSmallMeasureDimensionV11();
- } else {
- setMeasuredDimension(0, 0);
- }
- }
-
- @TargetApi(11)
- private void setTooSmallMeasureDimensionV11() {
- setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
- }
-
- @Override
- public void onLayout(boolean changed, int l, int t, int r, int b) {
- final int width = r - l;
- computeMeasurements(width);
- }
-
- private boolean computeMeasurements(int maxWidth) {
- if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
- return true;
- }
-
- // Account for padding.
- final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
- maxWidth -= paddingX;
- if (maxWidth <= 0) {
- return false;
- }
-
- mHasMeasurements = true;
- mLastMeasuredWidth = maxWidth;
- mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment,
- mSpacingMult, mSpacingAdd, true);
- return true;
- }
-
- @Override
- protected void onDraw(Canvas c) {
- final StaticLayout layout = this.mLayout;
- if (layout == null) {
- return;
- }
-
- final int saveCount = c.save();
- final int innerPaddingX = this.mInnerPaddingX;
- c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop());
-
- final int lineCount = layout.getLineCount();
- final Paint textPaint = this.mTextPaint;
- final Paint paint = this.mPaint;
- final RectF bounds = mLineBounds;
-
- if (Color.alpha(mBackgroundColor) > 0) {
- final float cornerRadius = this.mCornerRadius;
- float previousBottom = layout.getLineTop(0);
-
- paint.setColor(mBackgroundColor);
- paint.setStyle(Style.FILL);
-
- for (int i = 0; i < lineCount; i++) {
- float spacesPadding = 0.0f;
- if (i < mPrefixSpaces.size()) {
- spacesPadding += mPrefixSpaces.get(i) * mWhiteSpaceWidth;
- }
- bounds.left = layout.getLineLeft(i) - innerPaddingX + spacesPadding;
- bounds.right = layout.getLineRight(i) + innerPaddingX;
- bounds.top = previousBottom;
- bounds.bottom = layout.getLineBottom(i);
- previousBottom = bounds.bottom;
-
- c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
- }
- }
-
- if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
- textPaint.setStrokeJoin(Join.ROUND);
- textPaint.setStrokeWidth(mOutlineWidth);
- textPaint.setColor(mEdgeColor);
- textPaint.setStyle(Style.FILL_AND_STROKE);
- layout.draw(c);
- } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
- textPaint.setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
- } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
- || mEdgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) {
- boolean raised = mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED;
- int colorUp = raised ? Color.WHITE : mEdgeColor;
- int colorDown = raised ? mEdgeColor : Color.WHITE;
- float offset = mShadowRadius / 2f;
- textPaint.setColor(mForegroundColor);
- textPaint.setStyle(Style.FILL);
- textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
- layout.draw(c);
- textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
- }
-
- textPaint.setColor(mForegroundColor);
- textPaint.setStyle(Style.FILL);
- layout.draw(c);
- textPaint.setShadowLayer(0, 0, 0, 0);
- c.restoreToCount(saveCount);
- }
-
-}
diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
deleted file mode 100644
index 2b7817dc..00000000
--- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.ffmpeg;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
-import com.google.android.exoplayer2.util.MimeTypes;
-import com.android.tv.common.SoftPreconditions;
-
-import java.nio.ByteBuffer;
-
-/**
- * Audio decoder which uses ffmpeg extension of ExoPlayer2. Since {@link FfmpegDecoder} is package
- * private, expose the decoder via this class. Supported formats are AC3 and MP2.
- */
-public class FfmpegAudioDecoder {
- private static final int NUM_DECODER_BUFFERS = 1;
-
- // The largest AC3 sample size. This is bigger than the largest MP2 sample size (1729).
- private static final int INITIAL_INPUT_BUFFER_SIZE = 2560;
- private static boolean AVAILABLE;
-
- static {
- AVAILABLE =
- FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_AC3)
- && FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_MPEG_L2);
- }
-
- private FfmpegDecoder mDecoder;
- private DecoderInputBuffer mInputBuffer;
- private SimpleOutputBuffer mOutputBuffer;
- private boolean mStarted;
-
- /** Return whether Ffmpeg based software audio decoder is available. */
- public static boolean isAvailable() {
- return AVAILABLE;
- }
-
- /** Creates an Ffmpeg based software audio decoder. */
- public FfmpegAudioDecoder(Context context) {
- if (context.checkSelfPermission("android.permission.INTERNET")
- == PackageManager.PERMISSION_GRANTED) {
- throw new IllegalStateException("This code should run in an isolated process");
- }
- }
-
- /**
- * Decodes an audio sample.
- *
- * @param timeUs presentation timestamp of the sample
- * @param sample data
- */
- public void decode(long timeUs, byte[] sample) {
- SoftPreconditions.checkState(AVAILABLE);
- mInputBuffer.data.clear();
- mInputBuffer.data.put(sample);
- mInputBuffer.data.flip();
- mInputBuffer.timeUs = timeUs;
- mDecoder.decode(mInputBuffer, mOutputBuffer, !mStarted);
- if (!mStarted) {
- mStarted = true;
- }
- }
-
- /** Returns a decoded sample from decoder. */
- public ByteBuffer getDecodedSample() {
- return mOutputBuffer.data;
- }
-
- /** Returns the presentation time for the decoded sample. */
- public long getDecodedTimeUs() {
- return mOutputBuffer.timeUs;
- }
-
- /**
- * Clear previous decode state if any. Prepares to decode samples of the specified encoding.
- * This method should be called before using decode.
- *
- * @param mime audio encoding
- */
- public void resetDecoderState(String mime) {
- SoftPreconditions.checkState(AVAILABLE);
- release();
- try {
- mDecoder =
- new FfmpegDecoder(
- NUM_DECODER_BUFFERS,
- NUM_DECODER_BUFFERS,
- INITIAL_INPUT_BUFFER_SIZE,
- mime,
- null);
- mStarted = false;
- mInputBuffer = mDecoder.createInputBuffer();
- // Since native JNI requires direct buffer, we should allocate it by #allocateDirect.
- mInputBuffer.data = ByteBuffer.allocateDirect(INITIAL_INPUT_BUFFER_SIZE);
- mOutputBuffer = mDecoder.createOutputBuffer();
- } catch (FfmpegDecoderException e) {
- // if AVAILABLE is {@code true}, this will not happen.
- }
- }
-
- /** Releases all the resource. */
- public void release() {
- SoftPreconditions.checkState(AVAILABLE);
- if (mDecoder != null) {
- mDecoder.release();
- mInputBuffer = null;
- mOutputBuffer = null;
- mDecoder = null;
- }
- }
-}
diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
deleted file mode 100644
index daa77340..00000000
--- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.ffmpeg;
-
-import com.google.android.exoplayer2.util.LibraryLoader;
-import com.google.android.exoplayer2.util.MimeTypes;
-
-/**
- * This class is based on com.google.android.exoplayer2.ext.ffmpeg.FfmpegLibrary from ExoPlayer2
- * in order to support mp2 decoder.
- * Configures and queries the underlying native library.
- */
-public final class FfmpegLibrary {
-
- private static final LibraryLoader LOADER =
- new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg");
-
- private FfmpegLibrary() {}
-
- /**
- * Overrides the names of the FFmpeg native libraries. If an application wishes to call this
- * method, it must do so before calling any other method defined by this class, and before
- * instantiating a {@link FfmpegAudioRenderer} instance.
- */
- public static void setLibraries(String... libraries) {
- LOADER.setLibraries(libraries);
- }
-
- /**
- * Returns whether the underlying library is available, loading it if necessary.
- */
- public static boolean isAvailable() {
- return LOADER.isAvailable();
- }
-
- /**
- * Returns the version of the underlying library if available, or null otherwise.
- */
- public static String getVersion() {
- return isAvailable() ? ffmpegGetVersion() : null;
- }
-
- /**
- * Returns whether the underlying library supports the specified MIME type.
- */
- public static boolean supportsFormat(String mimeType) {
- if (!isAvailable()) {
- return false;
- }
- String codecName = getCodecName(mimeType);
- return codecName != null && ffmpegHasDecoder(codecName);
- }
-
- /**
- * Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}.
- */
- /* package */ static String getCodecName(String mimeType) {
- switch (mimeType) {
- case MimeTypes.AUDIO_MPEG_L2:
- return "mp2";
- case MimeTypes.AUDIO_AC3:
- return "ac3";
- default:
- return null;
- }
- }
-
- private static native String ffmpegGetVersion();
- private static native boolean ffmpegHasDecoder(String codecName);
-
-}
diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java
index 4fca06ac..942d431d 100644
--- a/src/com/android/tv/AudioManagerHelper.java
+++ b/src/com/android/tv/AudioManagerHelper.java
@@ -1,66 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.tv;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
-
import com.android.tv.receiver.AudioCapabilitiesReceiver;
import com.android.tv.ui.TunableTvView;
+import com.android.tv.ui.TunableTvViewPlayingApi;
-/**
- * A helper class to help {@link MainActivity} to handle audio-related stuffs.
- */
+/** A helper class to help {@link MainActivity} to handle audio-related stuffs. */
class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener {
private static final float AUDIO_MAX_VOLUME = 1.0f;
private static final float AUDIO_MIN_VOLUME = 0.0f;
private static final float AUDIO_DUCKING_VOLUME = 0.3f;
private final Activity mActivity;
- private final TunableTvView mTvView;
+ private final TunableTvViewPlayingApi mTvView;
private final AudioManager mAudioManager;
private final AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
private boolean mAc3PassthroughSupported;
private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
- AudioManagerHelper(Activity activity, TunableTvView tvView) {
+ AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView) {
mActivity = activity;
mTvView = tvView;
mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
- mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(activity,
- new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
- @Override
- public void onAc3PassthroughCapabilityChange(boolean capability) {
- mAc3PassthroughSupported = capability;
- }
- });
+ mAudioCapabilitiesReceiver =
+ new AudioCapabilitiesReceiver(
+ activity,
+ new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
+ @Override
+ public void onAc3PassthroughCapabilityChange(boolean capability) {
+ mAc3PassthroughSupported = capability;
+ }
+ });
mAudioCapabilitiesReceiver.register();
}
/**
- * Sets suitable volume to {@link TunableTvView} according to the current audio focus.
- * If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP
- * mode, this method will finish the activity.
+ * Sets suitable volume to {@link TunableTvView} according to the current audio focus. If the
+ * focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP mode, this
+ * method will finish the activity.
*/
void setVolumeByAudioFocusStatus() {
if (mTvView.isPlaying()) {
switch (mAudioFocusStatus) {
case AudioManager.AUDIOFOCUS_GAIN:
- mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
+ if (mTvView.isTimeShiftAvailable()) {
+ mTvView.timeshiftPlay();
+ } else {
+ mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
+ }
break;
case AudioManager.AUDIOFOCUS_LOSS:
- if (Features.PICTURE_IN_PICTURE.isEnabled(mActivity)
+ if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(mActivity)
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& mActivity.isInPictureInPictureMode()) {
mActivity.finish();
break;
}
+ // fall through
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ if (mTvView.isTimeShiftAvailable()) {
+ mTvView.timeshiftPause();
+ } else {
+ mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ }
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
+ if (mTvView.isTimeShiftAvailable()) {
+ mTvView.timeshiftPause();
+ } else {
+ mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
+ }
break;
}
}
@@ -71,31 +99,28 @@ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener {
* returned result.
*/
void requestAudioFocus() {
- int result = mAudioManager.requestAudioFocus(this,
- AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
- mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ?
- AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS;
+ int result =
+ mAudioManager.requestAudioFocus(
+ this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ mAudioFocusStatus =
+ (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
+ ? AudioManager.AUDIOFOCUS_GAIN
+ : AudioManager.AUDIOFOCUS_LOSS;
setVolumeByAudioFocusStatus();
}
- /**
- * Abandons audio focus.
- */
+ /** Abandons audio focus. */
void abandonAudioFocus() {
mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
mAudioManager.abandonAudioFocus(this);
}
- /**
- * Returns {@code true} if the device supports AC3 pass-through.
- */
+ /** Returns {@code true} if the device supports AC3 pass-through. */
boolean isAc3PassthroughSupported() {
return mAc3PassthroughSupported;
}
- /**
- * Release the resources the helper class may occupied.
- */
+ /** Release the resources the helper class may occupied. */
void release() {
mAudioCapabilitiesReceiver.unregister();
}
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java
index faa27bbd..8ab145a4 100644
--- a/src/com/android/tv/ChannelTuner.java
+++ b/src/com/android/tv/ChannelTuner.java
@@ -24,12 +24,10 @@ import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.util.ArraySet;
import android.util.Log;
-
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -57,11 +55,9 @@ public class ChannelTuner {
private final Handler mHandler = new Handler();
private final ChannelDataManager mChannelDataManager;
private final Set<Listener> mListeners = new ArraySet<>();
- @Nullable
- private Channel mCurrentChannel;
+ @Nullable private Channel mCurrentChannel;
private final TvInputManagerHelper mInputManager;
- @Nullable
- private TvInputInfo mCurrentChannelInputInfo;
+ @Nullable private TvInputInfo mCurrentChannelInputInfo;
private final ChannelDataManager.Listener mChannelDataManagerListener =
new ChannelDataManager.Listener() {
@@ -86,16 +82,14 @@ public class ChannelTuner {
l.onBrowsableChannelListChanged();
}
}
- };
+ };
public ChannelTuner(ChannelDataManager channelDataManager, TvInputManagerHelper inputManager) {
mChannelDataManager = channelDataManager;
mInputManager = inputManager;
}
- /**
- * Starts ChannelTuner. It cannot be called twice before calling {@link #stop}.
- */
+ /** Starts ChannelTuner. It cannot be called twice before calling {@link #stop}. */
public void start() {
if (mStarted) {
throw new IllegalStateException("start is called twice");
@@ -103,18 +97,17 @@ public class ChannelTuner {
mStarted = true;
mChannelDataManager.addListener(mChannelDataManagerListener);
if (mChannelDataManager.isDbLoadFinished()) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mChannelDataManagerListener.onLoadFinished();
- }
- });
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mChannelDataManagerListener.onLoadFinished();
+ }
+ });
}
}
- /**
- * Stops ChannelTuner.
- */
+ /** Stops ChannelTuner. */
public void stop() {
if (!mStarted) {
return;
@@ -130,30 +123,22 @@ public class ChannelTuner {
mChannelDataManagerLoaded = false;
}
- /**
- * Returns true, if all the channels are loaded.
- */
+ /** Returns true, if all the channels are loaded. */
public boolean areAllChannelsLoaded() {
return mChannelDataManagerLoaded;
}
- /**
- * Returns browsable channel lists.
- */
+ /** Returns browsable channel lists. */
public List<Channel> getBrowsableChannelList() {
return Collections.unmodifiableList(mBrowsableChannels);
}
- /**
- * Returns the number of browsable channels.
- */
+ /** Returns the number of browsable channels. */
public int getBrowsableChannelCount() {
return mBrowsableChannels.size();
}
- /**
- * Returns the current channel.
- */
+ /** Returns the current channel. */
@Nullable
public Channel getCurrentChannel() {
return mCurrentChannel;
@@ -169,16 +154,12 @@ public class ChannelTuner {
mCurrentChannel = currentChannel;
}
- /**
- * Returns the current channel's ID.
- */
+ /** Returns the current channel's ID. */
public long getCurrentChannelId() {
return mCurrentChannel != null ? mCurrentChannel.getId() : Channel.INVALID_ID;
}
- /**
- * Returns the current channel's URI
- */
+ /** Returns the current channel's URI */
public Uri getCurrentChannelUri() {
if (mCurrentChannel == null) {
return null;
@@ -190,17 +171,13 @@ public class ChannelTuner {
}
}
- /**
- * Returns the current {@link TvInputInfo}.
- */
+ /** Returns the current {@link TvInputInfo}. */
@Nullable
public TvInputInfo getCurrentInputInfo() {
return mCurrentChannelInputInfo;
}
- /**
- * Returns true, if the current channel is for a passthrough TV input.
- */
+ /** Returns true, if the current channel is for a passthrough TV input. */
public boolean isCurrentChannelPassthrough() {
return mCurrentChannel != null && mCurrentChannel.isPassthrough();
}
@@ -208,8 +185,8 @@ public class ChannelTuner {
/**
* Moves the current channel to the next (or previous) browsable channel.
*
- * @return true, if the channel is changed to the adjacent channel. If there is no
- * browsable channel, it returns false.
+ * @return true, if the channel is changed to the adjacent channel. If there is no browsable
+ * channel, it returns false.
*/
public boolean moveToAdjacentBrowsableChannel(boolean up) {
Channel channel = getAdjacentBrowsableChannel(up);
@@ -221,8 +198,8 @@ public class ChannelTuner {
}
/**
- * Returns a next browsable channel. It doesn't change the current channel unlike
- * {@link #moveToAdjacentBrowsableChannel}.
+ * Returns a next browsable channel. It doesn't change the current channel unlike {@link
+ * #moveToAdjacentBrowsableChannel}.
*/
public Channel getAdjacentBrowsableChannel(boolean up) {
if (isCurrentChannelPassthrough() || getBrowsableChannelCount() == 0) {
@@ -240,8 +217,7 @@ public class ChannelTuner {
}
int size = mChannels.size();
for (int i = 0; i < size; ++i) {
- int nextChannelIndex = up ? channelIndex + 1 + i
- : channelIndex - 1 - i + size;
+ int nextChannelIndex = up ? channelIndex + 1 + i : channelIndex - 1 - i + size;
if (nextChannelIndex >= size) {
nextChannelIndex -= size;
}
@@ -289,7 +265,7 @@ public class ChannelTuner {
* as a browsable channel.
*
* @return true, the channel change is success. But, if the channel doesn't exist, the channel
- * change will be failed and it will return false.
+ * change will be failed and it will return false.
*/
public boolean moveToChannel(Channel channel) {
if (channel == null) {
@@ -308,43 +284,29 @@ public class ChannelTuner {
return false;
}
- /**
- * Resets the current channel to {@code null}.
- */
+ /** Resets the current channel to {@code null}. */
public void resetCurrentChannel() {
setCurrentChannelAndNotify(null);
}
- /**
- * Adds {@link Listener}.
- */
+ /** Adds {@link Listener}. */
public void addListener(Listener listener) {
mListeners.add(listener);
}
- /**
- * Removes {@link Listener}.
- */
+ /** Removes {@link Listener}. */
public void removeListener(Listener listener) {
mListeners.remove(listener);
}
public interface Listener {
- /**
- * Called when all the channels are loaded.
- */
+ /** Called when all the channels are loaded. */
void onLoadFinished();
- /**
- * Called when the browsable channel list is changed.
- */
+ /** Called when the browsable channel list is changed. */
void onBrowsableChannelListChanged();
- /**
- * Called when the current channel is removed.
- */
+ /** Called when the current channel is removed. */
void onCurrentChannelUnavailable(Channel channel);
- /**
- * Called when the current channel is changed.
- */
+ /** Called when the current channel is changed. */
void onChannelChanged(Channel previousChannel, Channel currentChannel);
}
diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java
deleted file mode 100644
index 2052f2e7..00000000
--- a/src/com/android/tv/Features.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.tv.common.feature.Feature;
-import com.android.tv.common.feature.GServiceFeature;
-import com.android.tv.common.feature.PropertyFeature;
-import com.android.tv.config.RemoteConfig;
-import com.android.tv.experiments.Experiments;
-import com.android.tv.util.LocationUtils;
-import com.android.tv.util.PermissionUtils;
-import com.android.tv.util.Utils;
-
-import java.util.Locale;
-
-import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE;
-import static com.android.tv.common.feature.FeatureUtils.AND;
-import static com.android.tv.common.feature.FeatureUtils.OFF;
-import static com.android.tv.common.feature.FeatureUtils.ON;
-import static com.android.tv.common.feature.FeatureUtils.OR;
-
-/**
- * List of {@link Feature} for the Live TV App.
- *
- * <p>Remove the {@code Feature} once it is launched.
- */
-public final class Features {
- private static final String TAG = "Features";
- private static final boolean DEBUG = false;
-
- /**
- * UI for opting in to analytics.
- *
- * <p>Do not turn this on until the splash screen asking existing users to opt-in is launched.
- * See <a href="http://b/20228119">b/20228119</a>
- */
- public static final Feature ANALYTICS_OPT_IN = ENG_ONLY_FEATURE;
-
- /**
- * Analytics that include sensitive information such as channel or program identifiers.
- *
- * <p>See <a href="http://b/22062676">b/22062676</a>
- */
- public static final Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN);
-
- public static final Feature EPG_SEARCH =
- new PropertyFeature("feature_tv_use_epg_search", false);
-
- public static final Feature TUNER =
- new Feature() {
- @Override
- public boolean isEnabled(Context context) {
-
- if (Utils.isDeveloper()) {
- // we enable tuner for developers to test tuner in any platform.
- return true;
- }
-
- // This is special handling just for USB Tuner.
- // It does not require any N API's but relies on a improvements in N for AC3 support
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
- }
- };
-
- /**
- * Use network tuner if it is available and there is no other tuner types.
- */
- public static final Feature NETWORK_TUNER =
- new Feature() {
- @Override
- public boolean isEnabled(Context context) {
- if (!TUNER.isEnabled(context)) {
- return false;
- }
- if (Utils.isDeveloper()) {
- // Network tuner will be enabled for developers.
- return true;
- }
- return Locale.US.getCountry().equalsIgnoreCase(
- LocationUtils.getCurrentCountry(context));
- }
- };
-
- private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide";
- /**
- * A flag which indicates that LC app is unhidden even when there is no input.
- */
- public static final Feature UNHIDE =
- OR(new GServiceFeature(GSERVICE_KEY_UNHIDE, false), new Feature() {
- @Override
- public boolean isEnabled(Context context) {
- // If LC app runs as non-system app, we unhide the app.
- return !PermissionUtils.hasAccessAllEpg(context);
- }
- });
-
- public static final Feature PICTURE_IN_PICTURE =
- new Feature() {
- private Boolean mEnabled;
-
- @Override
- public boolean isEnabled(Context context) {
- if (mEnabled == null) {
- mEnabled =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
- && context.getPackageManager()
- .hasSystemFeature(
- PackageManager.FEATURE_PICTURE_IN_PICTURE);
- }
- return mEnabled;
- }
- };
-
- /** Use AC3 software decode. */
- public static final Feature AC3_SOFTWARE_DECODE =
- new Feature() {
- private final String[] SUPPORTED_REGIONS = {};
-
- private Boolean mEnabled;
-
- @Override
- public boolean isEnabled(Context context) {
- if (mEnabled == null) {
- if (mEnabled == null) {
- // We will not cache the result of fallback solution.
- String country = LocationUtils.getCurrentCountry(context);
- for (int i = 0; i < SUPPORTED_REGIONS.length; ++i) {
- if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) {
- return true;
- }
- }
- if (DEBUG) Log.d(TAG, "AC3 flag false after country check");
- return false;
- }
- }
- if (DEBUG) Log.d(TAG, "AC3 flag " + mEnabled);
- return mEnabled;
- }
- };
-
- /** Show postal code fragment before channel scan. */
- public static final Feature ENABLE_CLOUD_EPG_REGION =
- new Feature() {
- private final String[] SUPPORTED_REGIONS = {
- };
-
-
- @Override
- public boolean isEnabled(Context context) {
- if (!Experiments.CLOUD_EPG.get()) {
- if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false");
- return false;
- }
- String country = LocationUtils.getCurrentCountry(context);
- for (int i = 0; i < SUPPORTED_REGIONS.length; i++) {
- if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) {
- return true;
- }
- }
- if (DEBUG) Log.d(TAG, "EPG flag false after country check");
- return false;
- }
- };
-
- /** Enable a conflict dialog between currently watched channel and upcoming recording. */
- public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF;
-
- /**
- * Use input blacklist to disable partner's tuner input.
- */
- public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON;
-
- /**
- * Enable Dvb parsers and listeners.
- */
- public static final Feature ENABLE_FILE_DVB = OFF;
-
- @VisibleForTesting
- public static final Feature TEST_FEATURE = new PropertyFeature("test_feature", false);
-
- private Features() {
- }
-}
diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java
index 2978f409..4f298ed6 100644
--- a/src/com/android/tv/InputSessionManager.java
+++ b/src/com/android/tv/InputSessionManager.java
@@ -36,28 +36,27 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.OnTuneListener;
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
- * Manages input sessions.
- * Responsible for:
+ * Manages input sessions. Responsible for:
+ *
* <ul>
- * <li>Manage {@link TvView} sessions and recording sessions</li>
- * <li>Manage capabilities (conflict)</li>
+ * <li>Manage {@link TvView} sessions and recording sessions
+ * <li>Manage capabilities (conflict)
* </ul>
- * <p>
- * As TvView's methods should be called on the main thread and the {@link RecordingSession} should
- * look at the state of the {@link TvViewSession} when it calls the framework methods, the framework
- * calls in RecordingSession are made on the main thread not to introduce the multi-thread problems.
+ *
+ * <p>As TvView's methods should be called on the main thread and the {@link RecordingSession}
+ * should look at the state of the {@link TvViewSession} when it calls the framework methods, the
+ * framework calls in RecordingSession are made on the main thread not to introduce the multi-thread
+ * problems.
*/
@TargetApi(Build.VERSION_CODES.N)
public class InputSessionManager {
@@ -77,27 +76,25 @@ public class InputSessionManager {
public InputSessionManager(Context context) {
mContext = context.getApplicationContext();
- mInputManager = TvApplication.getSingletons(context).getTvInputManagerHelper();
+ mInputManager = TvSingletons.getSingletons(context).getTvInputManagerHelper();
}
/**
* Creates the session for {@link TvView}.
- * <p>
- * Do not call {@link TvView#setCallback} after the session is created.
+ *
+ * <p>Do not call {@link TvView#setCallback} after the session is created.
*/
@MainThread
@NonNull
- public TvViewSession createTvViewSession(TvView tvView, TunableTvView tunableTvView,
- TvInputCallback callback) {
+ public TvViewSession createTvViewSession(
+ TvView tvView, TunableTvView tunableTvView, TvInputCallback callback) {
TvViewSession session = new TvViewSession(tvView, tunableTvView, callback);
mTvViewSessions.add(session);
if (DEBUG) Log.d(TAG, "TvView session created: " + session);
return session;
}
- /**
- * Releases the {@link TvView} session.
- */
+ /** Releases the {@link TvView} session. */
@MainThread
public void releaseTvViewSession(TvViewSession session) {
mTvViewSessions.remove(session);
@@ -105,12 +102,14 @@ public class InputSessionManager {
if (DEBUG) Log.d(TAG, "TvView session released: " + session);
}
- /**
- * Creates the session for recording.
- */
+ /** Creates the session for recording. */
@NonNull
- public RecordingSession createRecordingSession(String inputId, String tag,
- RecordingCallback callback, Handler handler, long endTimeMs) {
+ public RecordingSession createRecordingSession(
+ String inputId,
+ String tag,
+ RecordingCallback callback,
+ Handler handler,
+ long endTimeMs) {
RecordingSession session = new RecordingSession(inputId, tag, callback, handler, endTimeMs);
mRecordingSessions.add(session);
if (DEBUG) Log.d(TAG, "Recording session created: " + session);
@@ -120,9 +119,7 @@ public class InputSessionManager {
return session;
}
- /**
- * Releases the recording session.
- */
+ /** Releases the recording session. */
public void releaseRecordingSession(RecordingSession session) {
mRecordingSessions.remove(session);
session.release();
@@ -132,17 +129,13 @@ public class InputSessionManager {
}
}
- /**
- * Adds the {@link OnTvViewChannelChangeListener}.
- */
+ /** Adds the {@link OnTvViewChannelChangeListener}. */
@MainThread
public void addOnTvViewChannelChangeListener(OnTvViewChannelChangeListener listener) {
mOnTvViewChannelChangeListeners.add(listener);
}
- /**
- * Removes the {@link OnTvViewChannelChangeListener}.
- */
+ /** Removes the {@link OnTvViewChannelChangeListener}. */
@MainThread
public void removeOnTvViewChannelChangeListener(OnTvViewChannelChangeListener listener) {
mOnTvViewChannelChangeListeners.remove(listener);
@@ -176,9 +169,7 @@ public class InputSessionManager {
return null;
}
- /**
- * Retruns the earliest end time of recording sessions in progress of the certain TV input.
- */
+ /** Retruns the earliest end time of recording sessions in progress of the certain TV input. */
@MainThread
public Long getEarliestRecordingSessionEndTimeMs(String inputId) {
long timeMs = Long.MAX_VALUE;
@@ -240,8 +231,8 @@ public class InputSessionManager {
/**
* The session for {@link TvView}.
- * <p>
- * The methods which create or release session for the TV input should be called through this
+ *
+ * <p>The methods which create or release session for the TV input should be called through this
* session.
*/
@MainThread
@@ -261,31 +252,32 @@ public class InputSessionManager {
mTvView = tvView;
mTunableTvView = tunableTvView;
mCallback = callback;
- mTvView.setCallback(new DelegateTvInputCallback(mCallback) {
- @Override
- public void onConnectionFailed(String inputId) {
- if (DEBUG) Log.d(TAG, "TvViewSession: connection failed");
- mTuned = false;
- mNeedToBeRetuned = false;
- super.onConnectionFailed(inputId);
- notifyTvViewChannelChange(null);
- }
+ mTvView.setCallback(
+ new DelegateTvInputCallback(mCallback) {
+ @Override
+ public void onConnectionFailed(String inputId) {
+ if (DEBUG) Log.d(TAG, "TvViewSession: connection failed");
+ mTuned = false;
+ mNeedToBeRetuned = false;
+ super.onConnectionFailed(inputId);
+ notifyTvViewChannelChange(null);
+ }
- @Override
- public void onDisconnected(String inputId) {
- if (DEBUG) Log.d(TAG, "TvViewSession: disconnected");
- mTuned = false;
- mNeedToBeRetuned = false;
- super.onDisconnected(inputId);
- notifyTvViewChannelChange(null);
- }
- });
+ @Override
+ public void onDisconnected(String inputId) {
+ if (DEBUG) Log.d(TAG, "TvViewSession: disconnected");
+ mTuned = false;
+ mNeedToBeRetuned = false;
+ super.onDisconnected(inputId);
+ notifyTvViewChannelChange(null);
+ }
+ });
}
/**
* Tunes to the channel.
- * <p>
- * As this is called only for the warming up, there's no need to be retuned.
+ *
+ * <p>As this is called only for the warming up, there's no need to be retuned.
*/
public void tune(String inputId, Uri channelUri) {
if (DEBUG) {
@@ -299,13 +291,22 @@ public class InputSessionManager {
notifyTvViewChannelChange(channelUri);
}
- /**
- * Tunes to the channel.
- */
+ /** Tunes to the channel. */
public void tune(Channel channel, Bundle params, OnTuneListener listener) {
if (DEBUG) {
- Log.d(TAG, "tune: {session=" + this + ", channel=" + channel + ", params=" + params
- + ", listener=" + listener + ", mTuned=" + mTuned + "}");
+ Log.d(
+ TAG,
+ "tune: {session="
+ + this
+ + ", channel="
+ + channel
+ + ", params="
+ + params
+ + ", listener="
+ + listener
+ + ", mTuned="
+ + mTuned
+ + "}");
}
mChannel = channel;
mInputId = channel.getInputId();
@@ -313,8 +314,10 @@ public class InputSessionManager {
mParams = params;
mOnTuneListener = listener;
TvInputInfo input = mInputManager.getTvInputInfo(mInputId);
- if (input == null || (input.canRecord() && !isTunedForRecording(mChannelUri)
- && getTunedRecordingSessionCount(mInputId) >= input.getTunerCount())) {
+ if (input == null
+ || (input.canRecord()
+ && !isTunedForRecording(mChannelUri)
+ && getTunedRecordingSessionCount(mInputId) >= input.getTunerCount())) {
if (DEBUG) {
if (input == null) {
Log.d(TAG, "Can't find input for input ID: " + mInputId);
@@ -354,9 +357,7 @@ public class InputSessionManager {
notifyTvViewChannelChange(null);
}
- /**
- * Resets this TvView.
- */
+ /** Resets this TvView. */
public void reset() {
if (DEBUG) Log.d(TAG, "Reset TvView session");
mTuned = false;
@@ -366,8 +367,8 @@ public class InputSessionManager {
}
void resetByRecording() {
- mCallback.onVideoUnavailable(mInputId,
- TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE);
+ mCallback.onVideoUnavailable(
+ mInputId, TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE);
if (mTuned) {
if (DEBUG) Log.d(TAG, "Reset TvView session by recording");
mTunableTvView.resetByRecording();
@@ -379,8 +380,8 @@ public class InputSessionManager {
/**
* The session for recording.
- * <p>
- * The caller is responsible for releasing the session when the error occurs.
+ *
+ * <p>The caller is responsible for releasing the session when the error occurs.
*/
public class RecordingSession {
private final String mInputId;
@@ -391,8 +392,12 @@ public class InputSessionManager {
private TvRecordingClient mClient;
private boolean mTuned;
- RecordingSession(String inputId, String tag, RecordingCallback callback,
- Handler handler, long endTimeMs) {
+ RecordingSession(
+ String inputId,
+ String tag,
+ RecordingCallback callback,
+ Handler handler,
+ long endTimeMs) {
mInputId = inputId;
mCallback = callback;
mHandler = handler;
@@ -402,83 +407,90 @@ public class InputSessionManager {
void release() {
if (DEBUG) Log.d(TAG, "Release of recording session requested.");
- runOnHandler(mMainThreadHandler, new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Releasing of recording session.");
- mTuned = false;
- mClient.release();
- mClient = null;
- for (TvViewSession session : mTvViewSessions) {
- if (DEBUG) {
- Log.d(TAG, "Finding TvView sessions for retune: {tuned="
- + session.mTuned + ", inputId=" + session.mInputId
- + ", session=" + session + "}");
- }
- if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) {
- session.retune();
- break;
+ runOnHandler(
+ mMainThreadHandler,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Releasing of recording session.");
+ mTuned = false;
+ mClient.release();
+ mClient = null;
+ for (TvViewSession session : mTvViewSessions) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Finding TvView sessions for retune: {tuned="
+ + session.mTuned
+ + ", inputId="
+ + session.mInputId
+ + ", session="
+ + session
+ + "}");
+ }
+ if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) {
+ session.retune();
+ break;
+ }
+ }
}
- }
- }
- });
+ });
}
- /**
- * Tunes to the channel for recording.
- */
+ /** Tunes to the channel for recording. */
public void tune(String inputId, Uri channelUri) {
- runOnHandler(mMainThreadHandler, new Runnable() {
- @Override
- public void run() {
- int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId);
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- if (input == null || !input.canRecord()
- || input.getTunerCount() <= tunedRecordingSessionCount) {
- runOnHandler(mHandler, new Runnable() {
- @Override
- public void run() {
- mCallback.onConnectionFailed(inputId);
+ runOnHandler(
+ mMainThreadHandler,
+ new Runnable() {
+ @Override
+ public void run() {
+ int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId);
+ TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+ if (input == null
+ || !input.canRecord()
+ || input.getTunerCount() <= tunedRecordingSessionCount) {
+ runOnHandler(
+ mHandler,
+ new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onConnectionFailed(inputId);
+ }
+ });
+ return;
}
- });
- return;
- }
- mTuned = true;
- int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId);
- if (!isTunedForTvView(channelUri) && tunedTuneSessionCount > 0
- && tunedRecordingSessionCount + tunedTuneSessionCount
- >= input.getTunerCount()) {
- for (TvViewSession session : mTvViewSessions) {
- if (session.mTuned && Objects.equals(session.mInputId, inputId)
- && !isTunedForRecording(session.mChannelUri)) {
- session.resetByRecording();
- break;
+ mTuned = true;
+ int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId);
+ if (!isTunedForTvView(channelUri)
+ && tunedTuneSessionCount > 0
+ && tunedRecordingSessionCount + tunedTuneSessionCount
+ >= input.getTunerCount()) {
+ for (TvViewSession session : mTvViewSessions) {
+ if (session.mTuned
+ && Objects.equals(session.mInputId, inputId)
+ && !isTunedForRecording(session.mChannelUri)) {
+ session.resetByRecording();
+ break;
+ }
+ }
}
+ mChannelUri = channelUri;
+ mClient.tune(inputId, channelUri);
}
- }
- mChannelUri = channelUri;
- mClient.tune(inputId, channelUri);
- }
- });
+ });
}
- /**
- * Starts recording.
- */
+ /** Starts recording. */
public void startRecording(Uri programHintUri) {
mClient.startRecording(programHintUri);
}
- /**
- * Stops recording.
- */
+ /** Stops recording. */
public void stopRecording() {
mClient.stopRecording();
}
- /**
- * Sets recording session's ending time.
- */
+ /** Sets recording session's ending time. */
public void setEndTimeMs(long endTimeMs) {
mEndTimeMs = endTimeMs;
}
@@ -555,9 +567,7 @@ public class InputSessionManager {
}
}
- /**
- * Called when the {@link TvView} channel is changed.
- */
+ /** Called when the {@link TvView} channel is changed. */
public interface OnTvViewChannelChangeListener {
void onTvViewChannelChange(@Nullable Uri channelUri);
}
diff --git a/src/com/android/tv/LauncherActivity.java b/src/com/android/tv/LauncherActivity.java
index e03952da..679d612d 100644
--- a/src/com/android/tv/LauncherActivity.java
+++ b/src/com/android/tv/LauncherActivity.java
@@ -26,8 +26,8 @@ import android.util.Log;
/**
* An activity to launch a new activity.
*
- * <p>In the case when {@link MainActivity} starts a new activity using
- * {@link Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is
+ * <p>In the case when {@link MainActivity} starts a new activity using {@link
+ * Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is
* terminated if the new activity crashes. That's because the {@link android.app.ActivityManager}
* terminates the activity which is just below the crashed activity in the activity stack. To avoid
* this, we need to locate an additional activity between these activities in the activity stack.
@@ -35,8 +35,7 @@ import android.util.Log;
public class LauncherActivity extends Activity {
private static final String TAG = "LauncherActivity";
- public static final String ERROR_MESSAGE
- = "com.android.tv.LauncherActivity.ErrorMessage";
+ public static final String ERROR_MESSAGE = "com.android.tv.LauncherActivity.ErrorMessage";
private static final int REQUEST_CODE_DEFAULT = 0;
private static final int REQUEST_START_ACTIVITY = 100;
@@ -45,34 +44,16 @@ public class LauncherActivity extends Activity {
private static final String EXTRA_REQUEST_RESULT =
"com.android.tv.LauncherActivity.REQUEST_RESULT";
- /**
- * Starts an activity by calling {@link Activity#startActivity}.
- */
+ /** Starts an activity by calling {@link Activity#startActivity}. */
public static void startActivitySafe(Activity baseActivity, Intent intentToLaunch) {
// To avoid the app termination when the new activity crashes, LauncherActivity should be
// started by calling startActivityForResult().
- baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, false),
- REQUEST_CODE_DEFAULT);
+ baseActivity.startActivityForResult(
+ createIntent(baseActivity, intentToLaunch, false), REQUEST_CODE_DEFAULT);
}
- /**
- * Starts an activity by calling {@link Activity#startActivityForResult}.
- *
- * <p>Note: {@code requestCode} should not be 0. The value is reserved for internal use.
- */
- public static void startActivityForResultSafe(Activity baseActivity, Intent intentToLaunch,
- int requestCode) {
- if (requestCode == REQUEST_CODE_DEFAULT) {
- throw new IllegalArgumentException("requestCode should not be 0.");
- }
- // To avoid the app termination when the new activity crashes, LauncherActivity should be
- // started by calling startActivityForResult().
- baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, true),
- requestCode);
- }
-
- private static Intent createIntent(Context context, Intent intentToLaunch,
- boolean requestResult) {
+ private static Intent createIntent(
+ Context context, Intent intentToLaunch, boolean requestResult) {
Intent intent = new Intent(context, LauncherActivity.class);
intent.putExtra(EXTRA_INTENT, intentToLaunch);
if (requestResult) {
@@ -98,8 +79,7 @@ public class LauncherActivity extends Activity {
}
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Activity not found for " + intent);
- intent.putExtra(ERROR_MESSAGE,
- getResources().getString(R.string.msg_missing_app));
+ intent.putExtra(ERROR_MESSAGE, getResources().getString(R.string.msg_missing_app));
setResult(Activity.RESULT_CANCELED, intent);
finish();
}
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index ed5f79a1..94a86cce 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -17,9 +17,12 @@
package com.android.tv;
import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
@@ -62,26 +65,31 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.Toast;
-
import com.android.tv.analytics.SendChannelStatusRunnable;
import com.android.tv.analytics.SendConfigInfoRunnable;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.BuildConfig;
-import com.android.tv.common.MemoryManageable;
+import com.android.tv.common.CommonPreferences;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.memory.MemoryManageable;
import com.android.tv.common.ui.setup.OnActionClickListener;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.ContentUriUtils;
+import com.android.tv.common.util.Debug;
+import com.android.tv.common.util.DurationTimer;
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.common.util.SystemProperties;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelImpl;
import com.android.tv.data.OnCurrentProgramUpdatedListener;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.WatchedHistoryManager;
-import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.api.Channel;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
@@ -97,15 +105,10 @@ import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.perf.EventNames;
import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.StubPerformanceMonitor;
import com.android.tv.perf.TimerEvent;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.NotificationService;
import com.android.tv.search.ProgramGuideSearchFragment;
-import com.android.tv.tuner.TunerInputController;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.setup.TunerSetupActivity;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
import com.android.tv.ui.ChannelBannerView;
import com.android.tv.ui.InputBannerView;
import com.android.tv.ui.KeypadChannelSwitchView;
@@ -124,21 +127,18 @@ import com.android.tv.ui.sidepanel.MultiAudioFragment;
import com.android.tv.ui.sidepanel.SettingsFragment;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
-import com.android.tv.util.AccountHelper;
+import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.CaptionSettings;
-import com.android.tv.util.Debug;
-import com.android.tv.util.DurationTimer;
-import com.android.tv.util.ImageCache;
import com.android.tv.util.OnboardingUtils;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.RecurringRunner;
import com.android.tv.util.SetupUtils;
-import com.android.tv.util.SystemProperties;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvTrackInfoUtils;
import com.android.tv.util.Utils;
import com.android.tv.util.ViewCache;
+import com.android.tv.util.account.AccountHelper;
+import com.android.tv.util.images.ImageCache;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -148,19 +148,23 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
-/**
- * The main activity for the Live TV app.
- */
+/** The main activity for the Live TV app. */
public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener {
private static final String TAG = "MainActivity";
private static final boolean DEBUG = false;
@Retention(RetentionPolicy.SOURCE)
- @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
- KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY})
+ @IntDef({
+ KEY_EVENT_HANDLER_RESULT_PASSTHROUGH,
+ KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
+ KEY_EVENT_HANDLER_RESULT_HANDLED,
+ KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY
+ })
public @interface KeyHandlerResultType {}
+
public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0;
public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1;
public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2;
@@ -171,12 +175,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private static final float FRAME_RATE_FOR_FILM = 23.976f;
private static final float FRAME_RATE_EPSILON = 0.1f;
-
private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
// Tracker screen names.
public static final String SCREEN_NAME = "Main";
+ private static final String SCREEN_PIP = "PIP";
private static final String SCREEN_BEHIND_NAME = "Behind";
private static final float REFRESH_RATE_EPSILON = 0.01f;
@@ -196,8 +200,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW);
}
-
private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter();
+
static {
SYSTEM_INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
@@ -206,6 +210,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
+ private static final int REQUEST_CODE_NOW_PLAYING = 2;
private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id";
@@ -240,14 +245,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private final DurationTimer mTuneDurationTimer = new DurationTimer();
private DvrManager mDvrManager;
private ConflictChecker mDvrConflictChecker;
+ private SetupUtils mSetupUtils;
private View mContentView;
private TunableTvView mTvView;
private Bundle mTuneParams;
- @Nullable
- private Uri mInitChannelUri;
- @Nullable
- private String mParentInputIdWhenScreenOff;
+ @Nullable private Uri mInitChannelUri;
+ @Nullable private String mParentInputIdWhenScreenOff;
private boolean mScreenOffIntentReceived;
private boolean mShowProgramGuide;
private boolean mShowSelectInputView;
@@ -274,6 +278,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private boolean mOtherActivityLaunched;
private PerformanceMonitor mPerformanceMonitor;
+ private boolean mIsInPIPMode;
private boolean mIsFilmModeSet;
private float mDefaultRefreshRate;
@@ -302,69 +307,74 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private final Handler mHandler = new MainActivityHandler(this);
private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case Intent.ACTION_SCREEN_OFF:
- if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
- // We need to stop TvView, when the screen is turned off. If not and TIS uses
- // MediaPlayer, a device may not go to the sleep mode and audio can be heard,
- // because MediaPlayer keeps playing media by its wake lock.
- mScreenOffIntentReceived = true;
- markCurrentChannelDuringScreenOff();
- stopAll(true);
- break;
- case Intent.ACTION_SCREEN_ON:
- if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
- if (!mActivityResumed && mVisibleBehind) {
- // ACTION_SCREEN_ON is usually called after onResume. But, if media is
- // played under launcher with requestVisibleBehind(true), onResume will
- // not be called. In this case, we need to resume TvView explicitly.
- resumeTvIfNeeded();
- }
- break;
- case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
- if (DEBUG) Log.d(TAG, "Received parental control settings change");
- applyParentalControlSettings();
- checkChannelLockNeeded(mTvView, null);
- break;
- case Intent.ACTION_TIME_CHANGED:
- // Re-tune the current channel to prevent incorrect behavior of trick-play.
- // See: b/37393628
- if (mChannelTuner.getCurrentChannel() != null) {
- tune(true);
+ private final BroadcastReceiver mBroadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_SCREEN_OFF:
+ if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
+ // We need to stop TvView, when the screen is turned off. If not and TIS
+ // uses MediaPlayer, a device may not go to the sleep mode and audio
+ // can be heard, because MediaPlayer keeps playing media by its wake
+ // lock.
+ mScreenOffIntentReceived = true;
+ markCurrentChannelDuringScreenOff();
+ stopAll(true);
+ break;
+ case Intent.ACTION_SCREEN_ON:
+ if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
+ if (!mActivityResumed && mVisibleBehind) {
+ // ACTION_SCREEN_ON is usually called after onResume. But, if media
+ // is played under launcher with requestVisibleBehind(true),
+ // onResume will not be called. In this case, we need to resume
+ // TvView explicitly.
+ resumeTvIfNeeded();
+ }
+ break;
+ case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
+ if (DEBUG) Log.d(TAG, "Received parental control settings change");
+ applyParentalControlSettings();
+ checkChannelLockNeeded(mTvView, null);
+ break;
+ case Intent.ACTION_TIME_CHANGED:
+ // Re-tune the current channel to prevent incorrect behavior of
+ // trick-play.
+ // See: b/37393628
+ if (mChannelTuner.getCurrentChannel() != null) {
+ tune(true);
+ }
+ break;
+ default: // fall out
}
- break;
- }
- }
- };
+ }
+ };
private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener =
new OnCurrentProgramUpdatedListener() {
- @Override
- public void onCurrentProgramUpdated(long channelId, Program program) {
- // Do not update channel banner by this notification
- // when the time shifting is available.
- if (mTimeShiftManager.isAvailable()) {
- return;
- }
- Channel channel = mTvView.getCurrentChannel();
- if (channel != null && channel.getId() == channelId) {
- mOverlayManager.updateChannelBannerAndShowIfNeeded(
- TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
- mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program);
- }
- }
- };
+ @Override
+ public void onCurrentProgramUpdated(long channelId, Program program) {
+ // Do not update channel banner by this notification
+ // when the time shifting is available.
+ if (mTimeShiftManager.isAvailable()) {
+ return;
+ }
+ Channel channel = mTvView.getCurrentChannel();
+ if (channel != null && channel.getId() == channelId) {
+ mOverlayManager.updateChannelBannerAndShowIfNeeded(
+ TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program);
+ }
+ }
+ };
private final ChannelTuner.Listener mChannelTunerListener =
new ChannelTuner.Listener() {
@Override
public void onLoadFinished() {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "MainActivity.mChannelTunerListener.onLoadFinished");
- SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable();
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log("MainActivity.mChannelTunerListener.onLoadFinished");
+ mSetupUtils.markNewChannelsBrowsable();
if (mActivityResumed) {
resumeTvIfNeeded();
}
@@ -389,30 +399,35 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
};
- private final Runnable mRestoreMainViewRunnable = new Runnable() {
- @Override
- public void run() {
- restoreMainTvView();
- }
- };
+ private final Runnable mRestoreMainViewRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ restoreMainTvView();
+ }
+ };
private ProgramGuideSearchFragment mSearchFragment;
- private final TvInputCallback mTvInputCallback = new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- if (Features.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId)
- && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) {
- Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this);
- startActivity(intent);
- TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false);
- SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId);
- }
- }
- };
+ private final TvInputCallback mTvInputCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ if (TvFeatures.TUNER.isEnabled(MainActivity.this)
+ && mTunerInputId.equals(inputId)
+ && CommonPreferences.shouldShowSetupActivity(MainActivity.this)) {
+ Intent intent =
+ TvSingletons.getSingletons(MainActivity.this)
+ .getTunerSetupIntent(MainActivity.this);
+ startActivity(intent);
+ CommonPreferences.setShouldShowSetupActivity(MainActivity.this, false);
+ mSetupUtils.markAsKnownInput(mTunerInputId);
+ }
+ }
+ };
private void applyParentalControlSettings() {
- boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
- .isParentalControlsEnabled();
+ boolean parentalControlEnabled =
+ mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled();
mTvView.onParentalControlChanged(parentalControlEnabled);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ChannelPreviewUpdater.getInstance(this).updatePreviewDataForChannelsImmediately();
@@ -421,7 +436,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
@Override
protected void onCreate(Bundle savedInstanceState) {
- TimerEvent timer = StubPerformanceMonitor.startBootstrapTimer();
+ mAccessibilityManager =
+ (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
+ TvSingletons tvSingletons = TvSingletons.getSingletons(this);
+ mPerformanceMonitor = tvSingletons.getPerformanceMonitor();
+ TimerEvent timer = mPerformanceMonitor.startTimer();
DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER);
if (!startUpDebugTimer.isStarted()
|| startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) {
@@ -430,30 +449,33 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
startUpDebugTimer.start();
}
startUpDebugTimer.log("MainActivity.onCreate");
- if (DEBUG) Log.d(TAG,"onCreate()");
- TvApplication.setCurrentRunningProcess(this, true);
+ if (DEBUG) {
+ Log.d(TAG, "onCreate()");
+ }
+ Starter.start(this);
super.onCreate(savedInstanceState);
- ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this);
- if (!applicationSingletons.getTvInputManagerHelper().hasTvInputManager()) {
+ if (!tvSingletons.getTvInputManagerHelper().hasTvInputManager()) {
Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
finishAndRemoveTask();
return;
}
- mPerformanceMonitor = applicationSingletons.getPerformanceMonitor();
+ mPerformanceMonitor = tvSingletons.getPerformanceMonitor();
+ mSetupUtils = tvSingletons.getSetupUtils();
TvApplication tvApplication = (TvApplication) getApplication();
mChannelDataManager = tvApplication.getChannelDataManager();
// In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
boolean isPassthroughInput =
TvContract.isChannelUriForPassthroughInput(getIntent().getData());
- boolean tuneToPassthroughInput = Intent.ACTION_VIEW.equals(getIntent().getAction())
- && isPassthroughInput;
- boolean channelLoadedAndNoChannelAvailable = mChannelDataManager.isDbLoadFinished()
- && mChannelDataManager.getChannelCount() <= 0;
+ boolean tuneToPassthroughInput =
+ Intent.ACTION_VIEW.equals(getIntent().getAction()) && isPassthroughInput;
+ boolean channelLoadedAndNoChannelAvailable =
+ mChannelDataManager.isDbLoadFinished()
+ && mChannelDataManager.getChannelCount() <= 0;
if ((OnboardingUtils.isFirstRunWithCurrentVersion(this)
- || channelLoadedAndNoChannelAvailable)
+ || channelLoadedAndNoChannelAvailable)
&& !tuneToPassthroughInput
- && !TvCommonUtils.isRunningInTest()) {
+ && !CommonUtils.isRunningInTest()) {
startOnboardingActivity();
return;
}
@@ -462,31 +484,38 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
mTvView.initialize(mProgramDataManager, mTvInputManagerHelper);
- mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
- @Override
- public boolean onUnhandledInputEvent(InputEvent event) {
- if (isKeyEventBlocked()) {
- return true;
- }
- if (event instanceof KeyEvent) {
- KeyEvent keyEvent = (KeyEvent) event;
- if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) {
- if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
+ mTvView.setOnUnhandledInputEventListener(
+ new OnUnhandledInputEventListener() {
+ @Override
+ public boolean onUnhandledInputEvent(InputEvent event) {
+ if (DEBUG) {
+ Log.d(TAG, "onUnhandledInputEvent " + event);
+ }
+ if (isKeyEventBlocked()) {
return true;
}
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && keyEvent.isLongPress()) {
+ if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
+ return true;
+ }
+ }
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ return onKeyUp(keyEvent.getKeyCode(), keyEvent);
+ } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ return onKeyDown(keyEvent.getKeyCode(), keyEvent);
+ }
+ }
+ return false;
}
- if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
- return onKeyUp(keyEvent.getKeyCode(), keyEvent);
- } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
- return onKeyDown(keyEvent.getKeyCode(), keyEvent);
- }
- }
- return false;
- }
- });
+ });
+ mTvView.setOnTalkBackDpadKeyListener(keycode -> handleUpDownKeys(keycode, null));
long channelId = Utils.getLastWatchedChannelId(this);
String inputId = Utils.getLastWatchedTunerInputId(this);
- if (!isPassthroughInput && inputId != null
+ if (!isPassthroughInput
+ && inputId != null
&& channelId != Channel.INVALID_ID) {
mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId));
}
@@ -496,12 +525,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
}
mTracker = tvApplication.getTracker();
- if (Features.TUNER.isEnabled(this)) {
+ if (TvFeatures.TUNER.isEnabled(this)) {
mTvInputManagerHelper.addCallback(mTvInputCallback);
}
- mTunerInputId = TunerTvInputService.getInputId(this);
- mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
- mOnCurrentProgramUpdatedListener);
+ mTunerInputId = tvSingletons.getEmbeddedTunerInputId();
+ mProgramDataManager.addOnCurrentProgramUpdatedListener(
+ Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
mProgramDataManager.setPrefetchEnabled(true);
mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper);
mChannelTuner.addListener(mChannelTunerListener);
@@ -512,91 +541,126 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
if (CommonFeatures.DVR.isEnabled(this)) {
mDvrManager = tvApplication.getDvrManager();
}
- mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker,
- new OnCurrentProgramUpdatedListener() {
- @Override
- public void onCurrentProgramUpdated(long channelId, Program program) {
- mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(),
- program);
- switch (mTimeShiftManager.getLastActionId()) {
- case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
- case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
- case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
- case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
- mOverlayManager.updateChannelBannerAndShowIfNeeded(
- TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
- break;
- case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE:
- case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY:
- default:
- mOverlayManager.updateChannelBannerAndShowIfNeeded(
- TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
- break;
- }
- }
- });
+ mTimeShiftManager =
+ new TimeShiftManager(
+ this,
+ mTvView,
+ mProgramDataManager,
+ mTracker,
+ new OnCurrentProgramUpdatedListener() {
+ @Override
+ public void onCurrentProgramUpdated(long channelId, Program program) {
+ mMediaSessionWrapper.update(
+ mTvView.isBlocked(), getCurrentChannel(), program);
+ switch (mTimeShiftManager.getLastActionId()) {
+ case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
+ case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
+ case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
+ case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
+ mOverlayManager.updateChannelBannerAndShowIfNeeded(
+ TvOverlayManager
+ .UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
+ break;
+ case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE:
+ case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY:
+ default:
+ mOverlayManager.updateChannelBannerAndShowIfNeeded(
+ TvOverlayManager
+ .UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ break;
+ }
+ }
+ });
DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
mDefaultRefreshRate = display.getRefreshRate();
if (!PermissionUtils.hasAccessWatchedHistory(this)) {
- WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager(
- getApplicationContext());
+ WatchedHistoryManager watchedHistoryManager =
+ new WatchedHistoryManager(getApplicationContext());
watchedHistoryManager.start();
mTvView.setWatchedHistoryManager(watchedHistoryManager);
}
- mTvViewUiManager = new TvViewUiManager(this, mTvView,
- (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
+ mTvViewUiManager =
+ new TvViewUiManager(
+ this,
+ mTvView,
+ (FrameLayout) findViewById(android.R.id.content),
+ mTvOptionsManager);
mContentView = findViewById(android.R.id.content);
ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
- ChannelBannerView channelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
- R.layout.channel_banner, sceneContainer, false);
- KeypadChannelSwitchView keypadChannelSwitchView = (KeypadChannelSwitchView)
- getLayoutInflater().inflate(R.layout.keypad_channel_switch, sceneContainer, false);
- InputBannerView inputBannerView = (InputBannerView) getLayoutInflater()
- .inflate(R.layout.input_banner, sceneContainer, false);
- SelectInputView selectInputView = (SelectInputView) getLayoutInflater()
- .inflate(R.layout.select_input, sceneContainer, false);
- selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
- @Override
- public void onTunerInputSelected() {
- Channel currentChannel = mChannelTuner.getCurrentChannel();
- if (currentChannel != null && !currentChannel.isPassthrough()) {
- hideOverlays();
- } else {
- tuneToLastWatchedChannelForTunerInput();
- }
- }
+ ChannelBannerView channelBannerView =
+ (ChannelBannerView)
+ getLayoutInflater().inflate(R.layout.channel_banner, sceneContainer, false);
+ KeypadChannelSwitchView keypadChannelSwitchView =
+ (KeypadChannelSwitchView)
+ getLayoutInflater()
+ .inflate(R.layout.keypad_channel_switch, sceneContainer, false);
+ InputBannerView inputBannerView =
+ (InputBannerView)
+ getLayoutInflater().inflate(R.layout.input_banner, sceneContainer, false);
+ SelectInputView selectInputView =
+ (SelectInputView)
+ getLayoutInflater().inflate(R.layout.select_input, sceneContainer, false);
+ selectInputView.setOnInputSelectedCallback(
+ new OnInputSelectedCallback() {
+ @Override
+ public void onTunerInputSelected() {
+ Channel currentChannel = mChannelTuner.getCurrentChannel();
+ if (currentChannel != null && !currentChannel.isPassthrough()) {
+ hideOverlays();
+ } else {
+ tuneToLastWatchedChannelForTunerInput();
+ }
+ }
- @Override
- public void onPassthroughInputSelected(@NonNull TvInputInfo input) {
- Channel currentChannel = mChannelTuner.getCurrentChannel();
- String currentInputId = currentChannel == null ? null : currentChannel.getInputId();
- if (TextUtils.equals(input.getId(), currentInputId)) {
- hideOverlays();
- } else {
- tuneToChannel(Channel.createPassthroughChannel(input.getId()));
- }
- }
+ @Override
+ public void onPassthroughInputSelected(@NonNull TvInputInfo input) {
+ Channel currentChannel = mChannelTuner.getCurrentChannel();
+ String currentInputId =
+ currentChannel == null ? null : currentChannel.getInputId();
+ if (TextUtils.equals(input.getId(), currentInputId)) {
+ hideOverlays();
+ } else {
+ tuneToChannel(ChannelImpl.createPassthroughChannel(input.getId()));
+ }
+ }
- private void hideOverlays() {
- getOverlayManager().hideOverlays(
- TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
- }
- });
+ private void hideOverlays() {
+ getOverlayManager()
+ .hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager
+ .FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager
+ .FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
+ | TvOverlayManager
+ .FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ }
+ });
mSearchFragment = new ProgramGuideSearchFragment();
- mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager,
- keypadChannelSwitchView, channelBannerView, inputBannerView,
- selectInputView, sceneContainer, mSearchFragment);
+ mOverlayManager =
+ new TvOverlayManager(
+ this,
+ mChannelTuner,
+ mTvView,
+ mTvOptionsManager,
+ keypadChannelSwitchView,
+ channelBannerView,
+ inputBannerView,
+ selectInputView,
+ sceneContainer,
+ mSearchFragment);
+ mAccessibilityManager.addAccessibilityStateChangeListener(mOverlayManager);
mAudioManagerHelper = new AudioManagerHelper(this, mTvView);
- mMediaSessionWrapper = new MediaSessionWrapper(this);
+ Intent nowPlayingIntent = new Intent(this, MainActivity.class);
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(this, REQUEST_CODE_NOW_PLAYING, nowPlayingIntent, 0);
+ mMediaSessionWrapper = new MediaSessionWrapper(this, pendingIntent);
mTvViewUiManager.restoreDisplayMode(false);
if (!handleIntent(getIntent())) {
@@ -604,18 +668,21 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return;
}
- mAccessibilityManager =
- (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
- mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
- new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null);
+ mSendConfigInfoRecurringRunner =
+ new RecurringRunner(
+ this,
+ TimeUnit.DAYS.toMillis(1),
+ new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper),
+ null);
mSendConfigInfoRecurringRunner.start();
- mChannelStatusRecurringRunner = SendChannelStatusRunnable
- .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager);
+ mChannelStatusRecurringRunner =
+ SendChannelStatusRunnable.startChannelStatusRecurringRunner(
+ this, mTracker, mChannelDataManager);
// To avoid not updating Rating systems when changing language.
mTvInputManagerHelper.getContentRatingsManager().update();
if (CommonFeatures.DVR.isEnabled(this)
- && Features.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
+ && TvFeatures.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
mDvrConflictChecker = new ConflictChecker(this);
}
initForTest();
@@ -632,13 +699,14 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
float density = getResources().getDisplayMetrics().density;
- mTvViewUiManager.onConfigurationChanged((int) (newConfig.screenWidthDp * density),
+ mTvViewUiManager.onConfigurationChanged(
+ (int) (newConfig.screenWidthDp * density),
(int) (newConfig.screenHeightDp * density));
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Start reload of dependent data
@@ -650,14 +718,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
finish();
startActivity(intent);
} else {
- Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
- Toast.LENGTH_LONG).show();
+ Toast.makeText(
+ this,
+ R.string.msg_read_tv_listing_permission_denied,
+ Toast.LENGTH_LONG)
+ .show();
finish();
}
}
}
- @BlockScreenType private int getDesiredBlockScreenType() {
+ @BlockScreenType
+ private int getDesiredBlockScreenType() {
if (!mActivityResumed) {
return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
}
@@ -689,7 +761,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
@Override
protected void onNewIntent(Intent intent) {
- if (DEBUG) Log.d(TAG,"onNewIntent(): " + intent);
+ if (DEBUG) {
+ Log.d(TAG, "onNewIntent(): " + intent);
+ }
if (mOverlayManager == null) {
// It's called before onCreate. The intent will be handled at onCreate. b/30725058
return;
@@ -705,7 +779,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
@Override
protected void onStart() {
TimerEvent timer = mPerformanceMonitor.startTimer();
- if (DEBUG) Log.d(TAG,"onStart()");
+ if (DEBUG) {
+ Log.d(TAG, "onStart()");
+ }
super.onStart();
mScreenOffIntentReceived = false;
mActivityStarted = true;
@@ -720,9 +796,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
startService(notificationIntent);
}
- TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this);
-
- EpgFetcher.getInstance(this).fetchImmediatelyIfNeeded();
+ TvSingletons singletons = TvSingletons.getSingletons(this);
+ singletons.getTunerInputController().executeNetworkTunerDiscoveryAsyncTask(this);
+ singletons.getEpgFetcher().fetchImmediatelyIfNeeded();
mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART);
}
@@ -732,10 +808,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start");
if (DEBUG) Log.d(TAG, "onResume()");
super.onResume();
+ mIsInPIPMode = false;
if (!PermissionUtils.hasAccessAllEpg(this)
&& checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS},
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ new String[] {PERMISSION_READ_TV_LISTINGS},
PERMISSIONS_REQUEST_READ_TV_LISTINGS);
}
mTracker.sendScreenView(SCREEN_NAME);
@@ -755,19 +833,20 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
Set<String> failedScheduledRecordingInfoSet =
Utils.getFailedScheduledRecordingInfoSet(getApplicationContext());
if (Utils.hasRecordingFailedReason(
- getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
+ getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
&& !failedScheduledRecordingInfoSet.isEmpty()) {
- runAfterAttachedToWindow(new Runnable() {
- @Override
- public void run() {
- DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this,
- failedScheduledRecordingInfoSet);
- }
- });
+ runAfterAttachedToWindow(
+ new Runnable() {
+ @Override
+ public void run() {
+ DvrUiHelper.showDvrInsufficientSpaceErrorDialog(
+ MainActivity.this, failedScheduledRecordingInfoSet);
+ }
+ });
}
if (mChannelTuner.areAllChannelsLoaded()) {
- SetupUtils.getInstance(this).markNewChannelsBrowsable();
+ mSetupUtils.markNewChannelsBrowsable();
resumeTvIfNeeded();
}
mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
@@ -779,28 +858,29 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mInputToSetUp = null;
} else if (mShowProgramGuide) {
mShowProgramGuide = false;
- mHandler.post(new Runnable() {
- // This will delay the start of the animation until after the Live Channel app is
- // shown. Without this the animation is completed before it is actually visible on
- // the screen.
- @Override
- public void run() {
- mOverlayManager.showProgramGuide();
- }
- });
+ // This will delay the start of the animation until after the Live Channel app is
+ // shown. Without this the animation is completed before it is actually visible on
+ // the screen.
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mOverlayManager.showProgramGuide();
+ }
+ });
} else if (mShowSelectInputView) {
mShowSelectInputView = false;
- mHandler.post(new Runnable() {
- // mShowSelectInputView is true when the activity is started/resumed because the
- // TV_INPUT button was pressed in a different app.
- // This will delay the start of the animation until after the Live Channel app is
- // shown. Without this the animation is completed before it is actually visible on
- // the screen.
- @Override
- public void run() {
- mOverlayManager.showSelectInputView();
- }
- });
+ // mShowSelectInputView is true when the activity is started/resumed because the
+ // TV_INPUT button was pressed in a different app. This will delay the start of
+ // the animation until after the Live Channel app is shown. Without this the
+ // animation is completed before it is actually visible on the screen.
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mOverlayManager.showSelectInputView();
+ }
+ });
}
if (mDvrConflictChecker != null) {
mDvrConflictChecker.start();
@@ -823,25 +903,26 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mShowLockedChannelsTemporarily = false;
mShouldTuneToTunerChannel = false;
if (!mVisibleBehind) {
- mAudioManagerHelper.abandonAudioFocus();
- mMediaSessionWrapper.setPlaybackState(false);
- mTracker.sendScreenView("");
+ if (mIsInPIPMode) {
+ mTracker.sendScreenView(SCREEN_PIP);
+ } else {
+ mTracker.sendScreenView("");
+ mAudioManagerHelper.abandonAudioFocus();
+ mMediaSessionWrapper.setPlaybackState(false);
+ }
} else {
mTracker.sendScreenView(SCREEN_BEHIND_NAME);
}
+ TvSingletons.getSingletons(this).getExperimentLoader().asyncRefreshExperiments(this);
super.onPause();
}
- /**
- * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet.
- */
+ /** Returns true if {@link #onResume} is called and {@link #onPause} is not called yet. */
public boolean isActivityResumed() {
return mActivityResumed;
}
- /**
- * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet.
- */
+ /** Returns true if {@link #onStart} is called and {@link #onStop} is not called yet. */
public boolean isActivityStarted() {
return mActivityStarted;
}
@@ -867,12 +948,14 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mTvView.unblockContent(unblockedRating);
break;
case PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN:
- mOverlayManager.getSideFragmentManager()
+ mOverlayManager
+ .getSideFragmentManager()
.show(new ParentalControlsFragment(), false);
- // Pass through.
+ // fall through.
case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN:
mOverlayManager.getSideFragmentManager().showSidePanel(true);
break;
+ default: // fall out
}
} else if (type == PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN) {
mOverlayManager.getSideFragmentManager().hideAll(false);
@@ -881,7 +964,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private void resumeTvIfNeeded() {
if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()");
- if (!mTvView.isPlaying() || mInitChannelUri != null
+ if (!mTvView.isPlaying()
+ || mInitChannelUri != null
|| (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) {
if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
// The target input may not be ready yet, especially, just after screen on.
@@ -917,9 +1001,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
// is playing, we stop the passthrough TV input.
stopTv();
}
- SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri)
- || mChannelTuner.areAllChannelsLoaded(),
- TAG, "startTV assumes that ChannelDataManager is already loaded.");
+ SoftPreconditions.checkState(
+ TvContract.isChannelUriForPassthroughInput(channelUri)
+ || mChannelTuner.areAllChannelsLoaded(),
+ TAG,
+ "startTV assumes that ChannelDataManager is already loaded.");
if (mTvView.isPlaying()) {
// TV has already started.
if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) {
@@ -945,15 +1031,19 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
} else {
if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
- Channel channel = Channel.createPassthroughChannel(channelUri);
+ ChannelImpl channel = ChannelImpl.createPassthroughChannel(channelUri);
mChannelTuner.moveToChannel(channel);
} else {
long channelId = ContentUris.parseId(channelUri);
Channel channel = mChannelDataManager.getChannel(channelId);
if (channel == null || !mChannelTuner.moveToChannel(channel)) {
mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
- Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
- + "The first channel will be tuned to.");
+ Log.w(
+ TAG,
+ "The requested channel (id="
+ + channelId
+ + ") doesn't exist. "
+ + "The first channel will be tuned to.");
}
}
}
@@ -985,9 +1075,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
super.onStop();
}
- /**
- * Handles screen off to keep the current channel for next screen on.
- */
+ /** Handles screen off to keep the current channel for next screen on. */
private void markCurrentChannelDuringScreenOff() {
mInitChannelUri = mChannelTuner.getCurrentChannelUri();
if (mChannelTuner.isCurrentChannelPassthrough()) {
@@ -1015,7 +1103,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
* @param calledByPopup If true, startSetupActivity is invoked from the setup fragment.
*/
public void startSetupActivity(TvInputInfo input, boolean calledByPopup) {
- Intent intent = TvCommonUtils.createSetupIntent(input);
+ Intent intent = CommonUtils.createSetupIntent(input);
if (intent == null) {
Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
return;
@@ -1038,16 +1126,23 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
} catch (ActivityNotFoundException e) {
mInputIdUnderSetup = null;
- Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity,
- input.loadLabel(this)), Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ this,
+ getString(
+ R.string.msg_unable_to_start_setup_activity,
+ input.loadLabel(this)),
+ Toast.LENGTH_SHORT)
+ .show();
return;
}
if (calledByPopup) {
- mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ mOverlayManager.hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
} else {
- mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
+ mOverlayManager.hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
}
}
@@ -1060,8 +1155,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
try {
startActivitySafe(intent);
} catch (ActivityNotFoundException e) {
- Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ this,
+ getString(R.string.msg_unable_to_start_system_captioning_settings),
+ Toast.LENGTH_SHORT)
+ .show();
}
}
@@ -1085,16 +1183,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return mTimeShiftManager;
}
- /**
- * Returns the instance of {@link TvOverlayManager}.
- */
+ /** Returns the instance of {@link TvOverlayManager}. */
public TvOverlayManager getOverlayManager() {
return mOverlayManager;
}
- /**
- * Returns the {@link ConflictChecker}.
- */
+ /** Returns the {@link ConflictChecker}. */
@Nullable
public ConflictChecker getDvrConflictChecker() {
return mDvrConflictChecker;
@@ -1109,9 +1203,10 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
/**
- * Returns the current program which the user is watching right now.<p>
+ * Returns the current program which the user is watching right now.
*
- * It might be a live program. If the time shifting is available, it can be a past program, too.
+ * <p>It might be a live program. If the time shifting is available, it can be a past program,
+ * too.
*/
public Program getCurrentProgram() {
if (!isChannelChangeKeyDownReceived() && mTimeShiftManager.isAvailable()) {
@@ -1122,9 +1217,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
/**
- * Returns the current playing time in milliseconds.<p>
+ * Returns the current playing time in milliseconds.
*
- * If the time shifting is available, the time is the playing position of the program,
+ * <p>If the time shifting is available, the time is the playing position of the program,
* otherwise, the system current time.
*/
public long getCurrentPlayingPosition() {
@@ -1152,18 +1247,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
LauncherActivity.startActivitySafe(this, intent);
}
- /**
- * Call {@link Activity#startActivityForResult} in a safe way.
- *
- * @see LauncherActivity
- */
- private void startActivityForResultSafe(Intent intent, int requestCode) {
- LauncherActivity.startActivityForResultSafe(this, intent, requestCode);
- }
-
- /**
- * Show settings fragment.
- */
+ /** Show settings fragment. */
public void showSettingsFragment() {
if (!mChannelTuner.areAllChannelsLoaded()) {
// Show ChannelSourcesFragment only if all the channels are loaded.
@@ -1180,8 +1264,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
* It is called when shrunken TvView is desired, such as EditChannelFragment and
* ChannelsLockedFragment.
*/
- public void startShrunkenTvView(boolean showLockedChannelsTemporarily,
- boolean willMainViewBeTunerInput) {
+ public void startShrunkenTvView(
+ boolean showLockedChannelsTemporarily, boolean willMainViewBeTunerInput) {
mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel();
mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser;
mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel;
@@ -1214,19 +1298,21 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
// The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel.
if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) {
final Channel channel = returnChannel;
- Runnable tuneAction = new Runnable() {
- @Override
- public void run() {
- tuneToChannel(channel);
- if (mChannelBeforeShrunkenTvView == null
- || !mChannelBeforeShrunkenTvView.equals(channel)) {
- Utils.setLastWatchedChannel(MainActivity.this, channel);
- }
- mIsCompletingShrunkenTvView = false;
- mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
- mTvView.setBlockScreenType(getDesiredBlockScreenType());
- }
- };
+ Runnable tuneAction =
+ new Runnable() {
+ @Override
+ public void run() {
+ tuneToChannel(channel);
+ if (mChannelBeforeShrunkenTvView == null
+ || !mChannelBeforeShrunkenTvView.equals(channel)) {
+ Utils.setLastWatchedChannel(MainActivity.this, channel);
+ }
+ mIsCompletingShrunkenTvView = false;
+ mIsCurrentChannelUnblockedByUser =
+ mWasChannelUnblockedBeforeShrunkenByUser;
+ mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ }
+ };
mTvViewUiManager.fadeOutTvView(tuneAction);
// Will automatically fade-in when video becomes available.
} else {
@@ -1247,35 +1333,45 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
*/
public boolean isScreenBlockedByResourceConflictOrParentalControl() {
return mTvView.getVideoUnavailableReason()
- == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE || mTvView.isBlocked();
+ == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE
+ || mTvView.isBlocked();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_CODE_START_SETUP_ACTIVITY) {
- if (resultCode == RESULT_OK) {
- int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup);
- String text;
- if (count > 0) {
- text = getResources().getQuantityString(R.plurals.msg_channel_added,
- count, count);
+ switch (requestCode) {
+ case REQUEST_CODE_START_SETUP_ACTIVITY:
+ if (resultCode == RESULT_OK) {
+ int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup);
+ String text;
+ if (count > 0) {
+ text =
+ getResources()
+ .getQuantityString(
+ R.plurals.msg_channel_added, count, count);
+ } else {
+ text = getString(R.string.msg_no_channel_added);
+ }
+ Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
+ mInputIdUnderSetup = null;
+ if (mChannelTuner.getCurrentChannel() == null) {
+ mChannelTuner.moveToAdjacentBrowsableChannel(true);
+ }
+ if (mTunePending) {
+ tune(true);
+ }
} else {
- text = getString(R.string.msg_no_channel_added);
+ mInputIdUnderSetup = null;
}
- Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
- mInputIdUnderSetup = null;
- if (mChannelTuner.getCurrentChannel() == null) {
- mChannelTuner.moveToAdjacentBrowsableChannel(true);
+ if (!mIsSetupActivityCalledByPopup) {
+ mOverlayManager.getSideFragmentManager().showSidePanel(false);
}
- if (mTunePending) {
- tune(true);
- }
- } else {
- mInputIdUnderSetup = null;
- }
- if (!mIsSetupActivityCalledByPopup) {
- mOverlayManager.getSideFragmentManager().showSidePanel(false);
- }
+ break;
+ case REQUEST_CODE_NOW_PLAYING:
+ // nothing needs to be done. onResume will restore everything.
+ break;
+ default:
+ // do nothing
}
if (data != null) {
String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE);
@@ -1325,16 +1421,15 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event);
}
- /**
- * Notifies the key input focus is changed to the TV view.
- */
+ /** Notifies the key input focus is changed to the TV view. */
public void updateKeyInputFocus() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mTvView.setBlockScreenType(getDesiredBlockScreenType());
- }
- });
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ }
+ });
}
// It should be called before onResume.
@@ -1360,12 +1455,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
if (TvInputManager.ACTION_SETUP_INPUTS.equals(intent.getAction())) {
- runAfterAttachedToWindow(new Runnable() {
- @Override
- public void run() {
- mOverlayManager.showSetupFragment();
- }
- });
+ runAfterAttachedToWindow(
+ new Runnable() {
+ @Override
+ public void run() {
+ mOverlayManager.showSetupFragment();
+ }
+ });
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
if (Utils.isProgramsUri(uri)) {
@@ -1388,18 +1484,34 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
if ((!Utils.isChannelUriForOneChannel(mInitChannelUri)
&& !Utils.isChannelUriForInput(mInitChannelUri))) {
- Log.w(TAG, "Malformed channel uri " + mInitChannelUri
- + " tuning to default instead");
+ Log.w(
+ TAG,
+ "Malformed channel uri " + mInitChannelUri + " tuning to default instead");
mInitChannelUri = null;
return true;
}
mTuneParams = intent.getExtras();
+ String programUriString = intent.getStringExtra(SearchManager.EXTRA_DATA_KEY);
+ Uri programUriFromIntent =
+ programUriString == null ? null : Uri.parse(programUriString);
+ long channelIdFromIntent = ContentUriUtils.safeParseId(mInitChannelUri);
+ if (programUriFromIntent != null && channelIdFromIntent != Channel.INVALID_ID) {
+ new AsyncQueryProgramTask(
+ TvSingletons.getSingletons(this).getDbExecutor(),
+ getContentResolver(),
+ programUriFromIntent,
+ Program.PROJECTION,
+ null,
+ null,
+ null,
+ channelIdFromIntent)
+ .executeOnDbThread();
+ }
if (mTuneParams == null) {
mTuneParams = new Bundle();
}
if (Utils.isChannelUriForTunerInput(mInitChannelUri)) {
- long channelId = ContentUris.parseId(mInitChannelUri);
- mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
+ mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelIdFromIntent);
} else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
// If mInitChannelUri is for a passthrough TV input.
String inputId = mInitChannelUri.getPathSegments().get(1);
@@ -1419,16 +1531,20 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
String inputId = mInitChannelUri.getQueryParameter("input");
long channelId = Utils.getLastWatchedChannelIdForInput(this, inputId);
if (channelId == Channel.INVALID_ID) {
- String[] projection = { Channels._ID };
+ String[] projection = {Channels._ID};
long time = System.currentTimeMillis();
- try (Cursor cursor = getContentResolver().query(uri, projection,
- null, null, null)) {
+ try (Cursor cursor =
+ getContentResolver().query(uri, projection, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
channelId = cursor.getLong(0);
}
}
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity queries DB for "
- + "last channel check (" + (System.currentTimeMillis() - time) + "ms)");
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log(
+ "MainActivity queries DB for "
+ + "last channel check ("
+ + (System.currentTimeMillis() - time)
+ + "ms)");
}
if (channelId == Channel.INVALID_ID) {
// Couldn't find any channel probably because the input hasn't been set up.
@@ -1444,6 +1560,63 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return true;
}
+ private class AsyncQueryProgramTask extends AsyncDbTask.AsyncQueryTask<Program> {
+ private final long mChannelIdFromIntent;
+
+ public AsyncQueryProgramTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String orderBy,
+ long channelId) {
+ super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy);
+ mChannelIdFromIntent = channelId;
+ }
+
+ @Override
+ protected Program onQuery(Cursor c) {
+ Program program = null;
+ if (c != null && c.moveToNext()) {
+ program = Program.fromCursor(c);
+ }
+ return program;
+ }
+
+ @Override
+ protected void onPostExecute(Program program) {
+ if (program == null || program.getStartTimeUtcMillis() <= System.currentTimeMillis()) {
+ // null or current program
+ return;
+ }
+ Channel channel = mChannelDataManager.getChannel(mChannelIdFromIntent);
+ if (channel != null) {
+ ScheduledRecording scheduledRecording =
+ TvSingletons.getSingletons(MainActivity.this)
+ .getDvrDataManager()
+ .getScheduledRecordingForProgramId(program.getId());
+ DvrUiHelper.checkStorageStatusAndShowErrorMessage(
+ MainActivity.this,
+ channel.getInputId(),
+ new Runnable() {
+ @Override
+ public void run() {
+ if (CommonFeatures.DVR.isEnabled(MainActivity.this)
+ && scheduledRecording == null
+ && mDvrManager.isProgramRecordable(program)) {
+ DvrUiHelper.requestRecordingFutureProgram(
+ MainActivity.this, program, false);
+ } else {
+ DvrUiHelper.showProgramInfoDialog(MainActivity.this, program);
+ }
+ }
+ });
+ }
+ }
+ }
+
private void stopTv() {
stopTv(null, false);
}
@@ -1462,7 +1635,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mAudioManagerHelper.abandonAudioFocus();
mMediaSessionWrapper.setPlaybackState(false);
}
- TvApplication.getSingletons(this).getMainActivityWrapper()
+ TvSingletons.getSingletons(this)
+ .getMainActivityWrapper()
.notifyCurrentChannelChange(this, null);
mChannelTuner.resetCurrentChannel();
mTunePending = false;
@@ -1473,9 +1647,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS);
}
- /**
- * Says {@code text} when accessibility is turned on.
- */
+ /** Says {@code text} when accessibility is turned on. */
private void sendAccessibilityText(String text) {
if (mAccessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain();
@@ -1510,8 +1682,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
finish();
return;
}
- SetupUtils setupUtils = SetupUtils.getInstance(this);
- if (setupUtils.isFirstTune()) {
+
+ if (mSetupUtils.isFirstTune()) {
if (!mChannelTuner.areAllChannelsLoaded()) {
// tune() will be called, once all channels are loaded.
stopTv("tune()", false);
@@ -1532,29 +1704,33 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return;
}
if (mTvInputManagerHelper.getTunerTvInputSize() == 1) {
- mOverlayManager.getSideFragmentManager().show(
- new CustomizeChannelListFragment());
+ mOverlayManager
+ .getSideFragmentManager()
+ .show(new CustomizeChannelListFragment());
} else {
mOverlayManager.showSetupFragment();
}
return;
}
- if (!TvCommonUtils.isRunningInTest() && mShowNewSourcesFragment
- && setupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) {
+ if (!CommonUtils.isRunningInTest()
+ && mShowNewSourcesFragment
+ && mSetupUtils.hasUnrecognizedInput(mTvInputManagerHelper)) {
// Show new channel sources fragment.
- runAfterAttachedToWindow(new Runnable() {
- @Override
- public void run() {
- mOverlayManager.runAfterOverlaysAreClosed(new Runnable() {
+ runAfterAttachedToWindow(
+ new Runnable() {
@Override
public void run() {
- mOverlayManager.showNewSourcesFragment();
+ mOverlayManager.runAfterOverlaysAreClosed(
+ new Runnable() {
+ @Override
+ public void run() {
+ mOverlayManager.showNewSourcesFragment();
+ }
+ });
}
});
- }
- });
}
- setupUtils.onTuned();
+ mSetupUtils.onTuned();
if (mTuneParams != null) {
Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID);
if (initChannelId == channel.getId()) {
@@ -1571,9 +1747,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
// For every tune, we need to inform the tuned channel or input to a user,
// if Talkback is turned on.
- sendAccessibilityText(!mChannelTuner.isCurrentChannelPassthrough() ?
- Utils.loadLabel(this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId()))
- : channel.getDisplayText());
+ sendAccessibilityText(
+ mChannelTuner.isCurrentChannelPassthrough()
+ ? Utils.loadLabel(
+ this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId()))
+ : channel.getDisplayText());
boolean success = mTvView.tuneTo(channel, mTuneParams, mOnTuneListener);
mOnTuneListener.onTune(channel, isUnderShrunkenTvView());
@@ -1592,7 +1770,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
addToRecentChannels(channel.getId());
}
Utils.setLastWatchedChannel(this, channel);
- TvApplication.getSingletons(this).getMainActivityWrapper()
+ TvSingletons.getSingletons(this)
+ .getMainActivityWrapper()
.notifyCurrentChannelChange(this, channel);
}
// We have to provide channel here instead of using TvView's channel, because TvView's
@@ -1619,34 +1798,42 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
// If the activity is paused shortly, runnable may not be called because all the fragments
// should be closed when the activity is paused.
private void runAfterAttachedToWindow(final Runnable runnable) {
- final Runnable runOnlyIfActivityIsResumed = new Runnable() {
- @Override
- public void run() {
- if (mActivityResumed) {
- runnable.run();
- }
- }
- };
+ final Runnable runOnlyIfActivityIsResumed =
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mActivityResumed) {
+ runnable.run();
+ }
+ }
+ };
if (mContentView.isAttachedToWindow()) {
mHandler.post(runOnlyIfActivityIsResumed);
} else {
- mContentView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mContentView.getViewTreeObserver().removeOnWindowAttachListener(this);
- mHandler.post(runOnlyIfActivityIsResumed);
- }
+ mContentView
+ .getViewTreeObserver()
+ .addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mContentView
+ .getViewTreeObserver()
+ .removeOnWindowAttachListener(this);
+ mHandler.post(runOnlyIfActivityIsResumed);
+ }
- @Override
- public void onWindowDetached() { }
- });
+ @Override
+ public void onWindowDetached() {}
+ });
}
}
boolean isNowPlayingProgram(Channel channel, Program program) {
- return program == null ? (channel != null && getCurrentProgram() == null
- && channel.equals(getCurrentChannel())) : program.equals(getCurrentProgram());
+ return program == null
+ ? (channel != null
+ && getCurrentProgram() == null
+ && channel.equals(getCurrentChannel()))
+ : program.equals(getCurrentProgram());
}
private void addToRecentChannels(long channelId) {
@@ -1659,9 +1846,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mOverlayManager.getMenu().onRecentChannelsChanged();
}
- /**
- * Returns the recently tuned channels.
- */
+ /** Returns the recently tuned channels. */
public ArrayDeque<Long> getRecentChannels() {
return mRecentChannels;
}
@@ -1694,9 +1879,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
}
- /**
- * Hide the overlays when tuning to a channel from the menu (e.g. Channels).
- */
+ /** Hide the overlays when tuning to a channel from the menu (e.g. Channels). */
public void hideOverlaysForTune() {
mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
}
@@ -1712,8 +1895,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
setPreferredRefreshRate(mDefaultRefreshRate);
mIsFilmModeSet = false;
} else if (!mIsFilmModeSet && is24Fps) {
- DisplayManager displayManager = (DisplayManager) getSystemService(
- Context.DISPLAY_SERVICE);
+ DisplayManager displayManager =
+ (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
float[] refreshRates = display.getSupportedRefreshRates();
@@ -1745,8 +1928,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
String id = TvSettings.getMultiAudioId(this);
String language = TvSettings.getMultiAudioLanguage(this);
int channelCount = TvSettings.getMultiAudioChannelCount(this);
- TvTrackInfo bestTrack = TvTrackInfoUtils
- .getBestTrackInfo(tracks, id, language, channelCount);
+ TvTrackInfo bestTrack =
+ TvTrackInfoUtils.getBestTrackInfo(tracks, id, language, channelCount);
if (bestTrack != null) {
String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
if (!bestTrack.getId().equals(selectedTrack)) {
@@ -1787,8 +1970,13 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
mTvOptionsManager.onClosedCaptionsChanged(track, i);
}
if (DEBUG) {
- Log.d(TAG, "Subtitle Track Selected {id=" + track.getId()
- + ", language=" + track.getLanguage() + "}");
+ Log.d(
+ TAG,
+ "Subtitle Track Selected {id="
+ + track.getId()
+ + ", language="
+ + track.getLanguage()
+ + "}");
}
return;
} else if (alternativeTrack == null) {
@@ -1801,12 +1989,17 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
if (!alternativeTrack.getId().equals(selectedTrackId)) {
selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack, alternativeTrackIndex);
} else {
- mTvOptionsManager
- .onClosedCaptionsChanged(alternativeTrack, alternativeTrackIndex);
+ mTvOptionsManager.onClosedCaptionsChanged(
+ alternativeTrack, alternativeTrackIndex);
}
if (DEBUG) {
- Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId()
- + ", language=" + alternativeTrack.getLanguage() + "}");
+ Log.d(
+ TAG,
+ "Subtitle Track Selected {id="
+ + alternativeTrack.getId()
+ + ", language="
+ + alternativeTrack.getLanguage()
+ + "}");
}
return;
}
@@ -1820,8 +2013,11 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
public void showProgramGuideSearchFragment() {
- getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment)
- .addToBackStack(null).commit();
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.fragment_container, mSearchFragment)
+ .addToBackStack(null)
+ .commit();
}
@Override
@@ -1853,6 +2049,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
}
if (mOverlayManager != null) {
+ mAccessibilityManager.removeAccessibilityStateChangeListener(mOverlayManager);
mOverlayManager.release();
}
mMemoryManageables.clear();
@@ -1874,7 +2071,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
if (mTvInputManagerHelper != null) {
mTvInputManagerHelper.clearTvInputLabels();
- if (Features.TUNER.isEnabled(this)) {
+ if (TvFeatures.TUNER.isEnabled(this)) {
mTvInputManagerHelper.removeCallback(mTvInputCallback);
}
}
@@ -1895,7 +2092,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return false;
case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
default:
- // pass through
+ // fall through
}
if (mSearchFragment.isVisible()) {
return super.onKeyDown(keyCode, event);
@@ -1903,35 +2100,51 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
if (!mChannelTuner.areAllChannelsLoaded()) {
return false;
}
+ if (handleUpDownKeys(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private boolean handleUpDownKeys(int keyCode, @Nullable KeyEvent event) {
if (!mChannelTuner.isCurrentChannelPassthrough()) {
switch (keyCode) {
case KeyEvent.KEYCODE_CHANNEL_UP:
case KeyEvent.KEYCODE_DPAD_UP:
- if (event.getRepeatCount() == 0
+ if ((event == null || event.getRepeatCount() == 0)
&& mChannelTuner.getBrowsableChannelCount() > 0) {
// message sending should be done before moving channel, because we use the
// existence of message to decide if users are switching channel.
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED,
- System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ if (event != null) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(
+ MSG_CHANNEL_UP_PRESSED, System.currentTimeMillis()),
+ CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ }
moveToAdjacentChannel(true, false);
mTracker.sendChannelUp();
}
return true;
case KeyEvent.KEYCODE_CHANNEL_DOWN:
case KeyEvent.KEYCODE_DPAD_DOWN:
- if (event.getRepeatCount() == 0
+ if ((event == null || event.getRepeatCount() == 0)
&& mChannelTuner.getBrowsableChannelCount() > 0) {
// message sending should be done before moving channel, because we use the
// existence of message to decide if users are switching channel.
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED,
- System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ if (event != null) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(
+ MSG_CHANNEL_DOWN_PRESSED, System.currentTimeMillis()),
+ CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ }
moveToAdjacentChannel(false, false);
mTracker.sendChannelDown();
}
return true;
+ default: // fall out
}
}
- return super.onKeyDown(keyCode, event);
+ return false;
}
@Override
@@ -1969,7 +2182,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return false;
case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
default:
- // pass through
+ // fall through
}
if (mSearchFragment.isVisible()) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
@@ -1999,6 +2212,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
case KeyEvent.KEYCODE_MENU:
showSettingsFragment();
return true;
+ default: // fall out
}
} else {
if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
@@ -2009,9 +2223,9 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (!mTvView.isVideoOrAudioAvailable()
&& mTvView.getVideoUnavailableReason()
- == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) {
- DvrUiHelper.startSchedulesActivityForTuneConflict(this,
- mChannelTuner.getCurrentChannel());
+ == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) {
+ DvrUiHelper.startSchedulesActivityForTuneConflict(
+ this, mChannelTuner.getCurrentChannel());
return true;
}
if (!PermissionUtils.hasModifyParentalControls(this)) {
@@ -2019,16 +2233,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
PinDialogFragment dialog = null;
if (mTvView.isScreenBlocked()) {
- dialog = PinDialogFragment
- .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL);
+ dialog =
+ PinDialogFragment.create(
+ PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL);
} else if (mTvView.isContentBlocked()) {
- dialog = PinDialogFragment
- .create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
+ dialog =
+ PinDialogFragment.create(
+ PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
mTvView.getBlockedContentRating().flattenToString());
}
if (dialog != null) {
- mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog,
- false);
+ mOverlayManager.showDialogFragment(
+ PinDialogFragment.DIALOG_TAG, dialog, false);
}
return true;
case KeyEvent.KEYCODE_WINDOW:
@@ -2064,49 +2280,56 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
break;
}
- // Pass through.
- case KeyEvent.KEYCODE_CAPTIONS: {
+ // fall through.
+ case KeyEvent.KEYCODE_CAPTIONS:
mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment());
return true;
- }
case KeyEvent.KEYCODE_A:
if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
break;
}
- // Pass through.
- case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ // fall through.
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment());
return true;
- }
- case KeyEvent.KEYCODE_INFO: {
+ case KeyEvent.KEYCODE_INFO:
mOverlayManager.showBanner();
return true;
- }
case KeyEvent.KEYCODE_MEDIA_RECORD:
- case KeyEvent.KEYCODE_V: {
+ case KeyEvent.KEYCODE_V:
Channel currentChannel = getCurrentChannel();
if (currentChannel != null && mDvrManager != null) {
boolean isRecording =
mDvrManager.getCurrentRecording(currentChannel.getId()) != null;
if (!isRecording) {
if (!mDvrManager.isChannelRecordable(currentChannel)) {
- Toast.makeText(this, R.string.dvr_msg_cannot_record_program,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ this,
+ R.string.dvr_msg_cannot_record_program,
+ Toast.LENGTH_SHORT)
+ .show();
} else {
- Program program = mProgramDataManager
- .getCurrentProgram(currentChannel.getId());
- DvrUiHelper.checkStorageStatusAndShowErrorMessage(this,
- currentChannel.getInputId(), new Runnable() {
+ Program program =
+ mProgramDataManager.getCurrentProgram(
+ currentChannel.getId());
+ DvrUiHelper.checkStorageStatusAndShowErrorMessage(
+ this,
+ currentChannel.getInputId(),
+ new Runnable() {
@Override
public void run() {
DvrUiHelper.requestRecordingCurrentProgram(
MainActivity.this,
- currentChannel, program, false);
+ currentChannel,
+ program,
+ false);
}
});
}
} else {
- DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(),
+ DvrUiHelper.showStopRecordingDialog(
+ this,
+ currentChannel.getId(),
DvrStopRecordingFragment.REASON_USER_STOP,
new HalfSizedDialogFragment.OnActionClickListener() {
@Override
@@ -2124,7 +2347,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
}
return true;
- }
+ default: // fall out
}
}
if (keyCode == KeyEvent.KEYCODE_WINDOW) {
@@ -2162,6 +2385,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
case KeyEvent.KEYCODE_D:
mOverlayManager.getSideFragmentManager().show(new DeveloperOptionFragment());
return true;
+ default: // fall out
}
}
return super.onKeyUp(keyCode, event);
@@ -2196,13 +2420,19 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
// We need to hide overlay first, before moving the activity to PIP. If not, UI will
// be shown during PIP stack resizing, because UI and its animation is stuck during
// PIP resizing.
- mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- MainActivity.super.enterPictureInPictureMode();
- }
- });
+ mIsInPIPMode = true;
+ if (mOverlayManager.isOverlayOpened()) {
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ MainActivity.super.enterPictureInPictureMode();
+ }
+ });
+ } else {
+ MainActivity.super.enterPictureInPictureMode();
+ }
}
@Override
@@ -2248,7 +2478,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
if (isKeyEventBlocked()) {
if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK
- || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) && mNeedShowBackKeyGuide) {
+ || event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B)
+ && mNeedShowBackKeyGuide) {
// KeyEvent.KEYCODE_BUTTON_B is also used like the back button.
Toast.makeText(this, R.string.msg_back_key_guide, Toast.LENGTH_SHORT).show();
mNeedShowBackKeyGuide = false;
@@ -2283,7 +2514,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
} else if (channel.equals(mTvView.getCurrentChannel())) {
mOverlayManager.updateChannelBannerAndShowIfNeeded(
TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
- } else if (channel == mChannelTuner.getCurrentChannel()) {
+ } else if (channel.equals(mChannelTuner.getCurrentChannel())) {
// Channel banner is already updated in moveToAdjacentChannel
tune(false);
} else if (mChannelTuner.moveToChannel(channel)) {
@@ -2296,24 +2527,23 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
/**
- * This method just moves the channel in the channel map and updates the channel banner,
- * but doesn't actually tune to the channel.
- * The caller of this method should call {@link #tune} in the end.
+ * This method just moves the channel in the channel map and updates the channel banner, but
+ * doesn't actually tune to the channel. The caller of this method should call {@link #tune} in
+ * the end.
*
* @param channelUp {@code true} for channel up, and {@code false} for channel down.
* @param fastTuning {@code true} if fast tuning is requested.
*/
private void moveToAdjacentChannel(boolean channelUp, boolean fastTuning) {
if (mChannelTuner.moveToAdjacentBrowsableChannel(channelUp)) {
- mOverlayManager.updateChannelBannerAndShowIfNeeded(fastTuning ?
- TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST
- : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
+ mOverlayManager.updateChannelBannerAndShowIfNeeded(
+ fastTuning
+ ? TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST
+ : TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_TUNE);
}
}
- /**
- * Set the main TV view which holds HDMI-CEC active source based on the sound mode
- */
+ /** Set the main TV view which holds HDMI-CEC active source based on the sound mode */
private void restoreMainTvView() {
mTvView.setMain();
}
@@ -2355,8 +2585,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private void selectTrack(int type, TvTrackInfo track, int trackIndex) {
mTvView.selectTrack(type, track == null ? null : track.getId());
if (type == TvTrackInfo.TYPE_AUDIO) {
- mTvOptionsManager.onMultiAudioChanged(track == null ? null :
- Utils.getMultiAudioString(this, track, false));
+ mTvOptionsManager.onMultiAudioChanged(
+ track == null ? null : Utils.getMultiAudioString(this, track, false));
} else if (type == TvTrackInfo.TYPE_SUBTITLE) {
mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex);
}
@@ -2414,7 +2644,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
private void updateAvailabilityToast() {
if (mTvView.isVideoAvailable()
- || mTvView.getCurrentChannel() != mChannelTuner.getCurrentChannel()) {
+ || !Objects.equals(
+ mTvView.getCurrentChannel(), mChannelTuner.getCurrentChannel())) {
return;
}
@@ -2428,50 +2659,38 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
default:
- Toast.makeText(this, R.string.msg_channel_unavailable_unknown,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.msg_channel_unavailable_unknown, Toast.LENGTH_SHORT)
+ .show();
break;
}
}
- /**
- * Returns {@code true} if some overlay UI will be shown when the activity is resumed.
- */
+ /** Returns {@code true} if some overlay UI will be shown when the activity is resumed. */
public boolean willShowOverlayUiWhenResume() {
return mInputToSetUp != null || mShowProgramGuide || mShowSelectInputView;
}
- /**
- * Returns the current parental control settings.
- */
+ /** Returns the current parental control settings. */
public ParentalControlSettings getParentalControlSettings() {
return mTvInputManagerHelper.getParentalControlSettings();
}
- /**
- * Returns a ContentRatingsManager instance.
- */
+ /** Returns a ContentRatingsManager instance. */
public ContentRatingsManager getContentRatingsManager() {
return mTvInputManagerHelper.getContentRatingsManager();
}
- /**
- * Returns the current captioning settings.
- */
+ /** Returns the current captioning settings. */
public CaptionSettings getCaptionSettings() {
return mCaptionSettings;
}
- /**
- * Adds the {@link OnActionClickListener}.
- */
+ /** Adds the {@link OnActionClickListener}. */
public void addOnActionClickListener(OnActionClickListener listener) {
mOnActionClickListeners.add(listener);
}
- /**
- * Removes the {@link OnActionClickListener}.
- */
+ /** Removes the {@link OnActionClickListener}. */
public void removeOnActionClickListener(OnActionClickListener listener) {
mOnActionClickListeners.remove(listener);
}
@@ -2490,7 +2709,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
// Initialize TV app for test. The setup process should be finished before the Live TV app is
// started. We only enable all the channels here.
private void initForTest() {
- if (!TvCommonUtils.isRunningInTest()) {
+ if (!CommonUtils.isRunningInTest()) {
return;
}
@@ -2505,16 +2724,18 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
}
mLazyInitialized = true;
// Running initialization.
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mActivityStarted) {
- initAnimations();
- initSideFragments();
- initMenuItemViews();
- }
- }
- }, LAZY_INITIALIZATION_DELAY);
+ mHandler.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mActivityStarted) {
+ initAnimations();
+ initSideFragments();
+ initMenuItemViews();
+ }
+ }
+ },
+ LAZY_INITIALIZATION_DELAY);
}
private void initAnimations() {
@@ -2560,6 +2781,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
mainActivity.moveToAdjacentChannel(true, true);
break;
+ default: // fall out
}
}
@@ -2594,17 +2816,19 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
if (mTvView.isFadedOut()) {
mTvView.removeFadeEffect();
}
- Toast.makeText(MainActivity.this, R.string.msg_channel_unavailable_unknown,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ MainActivity.this,
+ R.string.msg_channel_unavailable_unknown,
+ Toast.LENGTH_SHORT)
+ .show();
}
@Override
public void onStreamInfoChanged(StreamInfo info) {
if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) {
- mTracker.sendChannelTuneTime(info.getCurrentChannel(),
- mTuneDurationTimer.reset());
+ mTracker.sendChannelTuneTime(info.getCurrentChannel(), mTuneDurationTimer.reset());
}
- if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) {
+ if (info.isVideoOrAudioAvailable() && mChannel.equals(getCurrentChannel())) {
mOverlayManager.updateChannelBannerAndShowIfNeeded(
TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO);
}
@@ -2629,10 +2853,12 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
return;
}
Channel currentChannel =
- mChannelDataManager.getChannel(ContentUris.parseId(channel));
+ mChannelDataManager.getChannel(ContentUriUtils.safeParseId(channel));
if (currentChannel == null) {
- Log.e(TAG, "onChannelRetuned is called but can't find a channel with the URI "
- + channel);
+ Log.e(
+ TAG,
+ "onChannelRetuned is called but can't find a channel with the URI "
+ + channel);
return;
}
if (isChannelChangeKeyDownReceived()) {
@@ -2647,15 +2873,16 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP
@Override
public void onContentBlocked() {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "MainActivity.MyOnTuneListener.onContentBlocked removes timer");
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log("MainActivity.MyOnTuneListener.onContentBlocked removes timer");
Debug.removeTimer(Debug.TAG_START_UP_TIMER);
mTuneDurationTimer.reset();
TvContentRating rating = mTvView.getBlockedContentRating();
// When tuneTo was called while TV view was shrunken, if the channel id is the same
// with the channel watched before shrunken, we allow the rating which was allowed
// before.
- if (mWasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken
+ if (mWasUnderShrunkenTvView
+ && mUnlockAllowedRatingBeforeShrunken
&& mChannelBeforeShrunkenTvView.equals(mChannel)
&& rating.equals(mAllowedRatingBeforeShrunken)) {
mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
diff --git a/src/com/android/tv/MainActivityWrapper.java b/src/com/android/tv/MainActivityWrapper.java
index 5af5079f..6cecb436 100644
--- a/src/com/android/tv/MainActivityWrapper.java
+++ b/src/com/android/tv/MainActivityWrapper.java
@@ -20,14 +20,12 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.ArraySet;
-
-import com.android.tv.data.Channel;
-
+import com.android.tv.data.api.Channel;
import java.util.Set;
/**
- * A wrapper for safely getting the current {@link MainActivity}.
- * Note that this class is not thread-safe. All the public methods should be called on main thread.
+ * A wrapper for safely getting the current {@link MainActivity}. Note that this class is not
+ * thread-safe. All the public methods should be called on main thread.
*/
@MainThread
public final class MainActivityWrapper {
@@ -36,39 +34,31 @@ public final class MainActivityWrapper {
private final Set<OnCurrentChannelChangeListener> mListeners = new ArraySet<>();
/**
- * Returns the current main activity.
- * <b>WARNING</b> do not keep a reference to MainActivity, leaking activities is expensive.
+ * Returns the current main activity. <b>WARNING</b> do not keep a reference to MainActivity,
+ * leaking activities is expensive.
*/
MainActivity getMainActivity() {
return mActivity;
}
- /**
- * Checks if the given {@code activity} is the current main activity.
- */
+ /** Checks if the given {@code activity} is the current main activity. */
boolean isCurrent(MainActivity activity) {
return activity != null && mActivity == activity;
}
- /**
- * Sets the currently created main activity instance.
- */
+ /** Sets the currently created main activity instance. */
public void onMainActivityCreated(@NonNull MainActivity activity) {
mActivity = activity;
}
- /**
- * Unsets the main activity instance.
- */
+ /** Unsets the main activity instance. */
public void onMainActivityDestroyed(@NonNull MainActivity activity) {
if (mActivity == activity) {
mActivity = null;
}
}
- /**
- * Notifies the current channel change.
- */
+ /** Notifies the current channel change. */
void notifyCurrentChannelChange(@NonNull MainActivity caller, @Nullable Channel channel) {
if (mActivity == caller) {
for (OnCurrentChannelChangeListener listener : mListeners) {
@@ -77,48 +67,34 @@ public final class MainActivityWrapper {
}
}
- /**
- * Checks if the main activity is created.
- */
+ /** Checks if the main activity is created. */
public boolean isCreated() {
return mActivity != null;
}
- /**
- * Checks if the main activity is started.
- */
+ /** Checks if the main activity is started. */
public boolean isStarted() {
return mActivity != null && mActivity.isActivityStarted();
}
- /**
- * Checks if the main activity is resumed.
- */
+ /** Checks if the main activity is resumed. */
public boolean isResumed() {
return mActivity != null && mActivity.isActivityResumed();
}
- /**
- * Adds OnCurrentChannelChangeListener.
- */
+ /** Adds OnCurrentChannelChangeListener. */
public void addOnCurrentChannelChangeListener(OnCurrentChannelChangeListener listener) {
mListeners.add(listener);
}
- /**
- * Removes OnCurrentChannelChangeListener.
- */
+ /** Removes OnCurrentChannelChangeListener. */
public void removeOnCurrentChannelChangeListener(OnCurrentChannelChangeListener listener) {
mListeners.remove(listener);
}
- /**
- * Listener for the current channel change in main activity.
- */
+ /** Listener for the current channel change in main activity. */
public interface OnCurrentChannelChangeListener {
- /**
- * Called when the current channel changes.
- */
+ /** Called when the current channel changes. */
void onCurrentChannelChange(@Nullable Channel channel);
}
}
diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java
index da6ad2a4..43cd74dd 100644
--- a/src/com/android/tv/MediaSessionWrapper.java
+++ b/src/com/android/tv/MediaSessionWrapper.java
@@ -16,6 +16,7 @@
package com.android.tv;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -28,12 +29,12 @@ import android.media.tv.TvInputInfo;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
-
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-import com.android.tv.util.ImageLoader;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.Utils;
+import com.android.tv.util.images.ImageLoader;
/**
* A wrapper class for {@link MediaSession} to support common operations on media sessions for
@@ -41,34 +42,47 @@ import com.android.tv.util.Utils;
*/
class MediaSessionWrapper {
private static final String MEDIA_SESSION_TAG = "com.android.tv.mediasession";
- private static PlaybackState MEDIA_SESSION_STATE_PLAYING = new PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f)
- .build();
- private static PlaybackState MEDIA_SESSION_STATE_STOPPED = new PlaybackState.Builder()
- .setState(PlaybackState.STATE_STOPPED, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f)
- .build();
+
+ private static final PlaybackState MEDIA_SESSION_STATE_PLAYING =
+ new PlaybackState.Builder()
+ .setState(
+ PlaybackState.STATE_PLAYING,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ 1.0f)
+ .build();
+
+ private static final PlaybackState MEDIA_SESSION_STATE_STOPPED =
+ new PlaybackState.Builder()
+ .setState(
+ PlaybackState.STATE_STOPPED,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ 0.0f)
+ .build();
private final Context mContext;
private final MediaSession mMediaSession;
private int mNowPlayingCardWidth;
private int mNowPlayingCardHeight;
- MediaSessionWrapper(Context context) {
+ MediaSessionWrapper(Context context, PendingIntent pendingIntent) {
mContext = context;
mMediaSession = new MediaSession(context, MEDIA_SESSION_TAG);
- mMediaSession.setCallback(new MediaSession.Callback() {
- @Override
- public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
- // Consume the media button event here. Should not send it to other apps.
- return true;
- }
- });
- mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
- MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mNowPlayingCardWidth = mContext.getResources().getDimensionPixelSize(
- R.dimen.notif_card_img_max_width);
- mNowPlayingCardHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.notif_card_img_height);
+ mMediaSession.setCallback(
+ new MediaSession.Callback() {
+ @Override
+ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+ // Consume the media button event here. Should not send it to other apps.
+ return true;
+ }
+ });
+ mMediaSession.setFlags(
+ MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mMediaSession.setSessionActivity(pendingIntent);
+ mNowPlayingCardWidth =
+ mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
+ mNowPlayingCardHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
}
/**
@@ -90,8 +104,8 @@ class MediaSessionWrapper {
/**
* Updates media session according to the current TV playback status.
*
- * @param blocked {@code true} if the current channel is blocked, either by user settings or
- * the current program's content ratings.
+ * @param blocked {@code true} if the current channel is blocked, either by user settings or the
+ * current program's content ratings.
* @param currentChannel The currently playing channel.
* @param currentProgram The currently playing program.
*/
@@ -103,10 +117,12 @@ class MediaSessionWrapper {
// If the channel is blocked, display a lock and a short text on the Now Playing Card
if (blocked) {
- Bitmap art = BitmapFactory.decodeResource(mContext.getResources(),
- R.drawable.ic_message_lock_preview);
- updateMediaMetadata(mContext.getResources()
- .getString(R.string.channel_banner_locked_channel_title), art);
+ Bitmap art =
+ BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.ic_message_lock_preview);
+ updateMediaMetadata(
+ mContext.getResources().getString(R.string.channel_banner_locked_channel_title),
+ art);
setPlaybackState(true);
return;
}
@@ -139,22 +155,32 @@ class MediaSessionWrapper {
private String getChannelName(Channel channel) {
if (channel.isPassthrough()) {
- TvInputInfo input = TvApplication.getSingletons(mContext).getTvInputManagerHelper()
- .getTvInputInfo(channel.getInputId());
+ TvInputInfo input =
+ TvSingletons.getSingletons(mContext)
+ .getTvInputManagerHelper()
+ .getTvInputInfo(channel.getInputId());
return Utils.loadLabel(mContext, input);
} else {
return channel.getDisplayName();
}
}
- private void updatePosterArt(Channel currentChannel, Program currentProgram,
- String cardTitleText, @Nullable Bitmap posterArt, @Nullable String posterArtUri) {
+ private void updatePosterArt(
+ Channel currentChannel,
+ Program currentProgram,
+ String cardTitleText,
+ @Nullable Bitmap posterArt,
+ @Nullable String posterArtUri) {
if (posterArt != null) {
updateMediaMetadata(cardTitleText, posterArt);
} else if (posterArtUri != null) {
- ImageLoader.loadBitmap(mContext, posterArtUri, mNowPlayingCardWidth,
- mNowPlayingCardHeight, new ProgramPosterArtCallback(this, currentChannel,
- currentProgram, cardTitleText));
+ ImageLoader.loadBitmap(
+ mContext,
+ posterArtUri,
+ mNowPlayingCardWidth,
+ mNowPlayingCardHeight,
+ new ProgramPosterArtCallback(
+ this, currentChannel, currentProgram, cardTitleText));
} else {
updateMediaMetadata(cardTitleText, R.drawable.default_now_card);
}
@@ -176,7 +202,7 @@ class MediaSessionWrapper {
}
private void updateMediaMetadata(final String title, final int imageResId) {
- new AsyncTask<Void, Void, Void> () {
+ new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... arg0) {
MediaMetadata.Builder builder = new MediaMetadata.Builder();
@@ -192,14 +218,22 @@ class MediaSessionWrapper {
}.execute();
}
- private static class ProgramPosterArtCallback extends
- ImageLoader.ImageLoaderCallback<MediaSessionWrapper> {
+ @VisibleForTesting
+ MediaSession getMediaSession() {
+ return mMediaSession;
+ }
+
+ private static class ProgramPosterArtCallback
+ extends ImageLoader.ImageLoaderCallback<MediaSessionWrapper> {
private final Channel mChannel;
private final Program mProgram;
private final String mCardTitleText;
- ProgramPosterArtCallback(MediaSessionWrapper sessionWrapper, Channel channel,
- Program program, String cardTitleText) {
+ ProgramPosterArtCallback(
+ MediaSessionWrapper sessionWrapper,
+ Channel channel,
+ Program program,
+ String cardTitleText) {
super(sessionWrapper);
mChannel = channel;
mProgram = program;
diff --git a/src/com/android/tv/SelectInputActivity.java b/src/com/android/tv/SelectInputActivity.java
index c68a1ad0..56747044 100644
--- a/src/com/android/tv/SelectInputActivity.java
+++ b/src/com/android/tv/SelectInputActivity.java
@@ -23,15 +23,12 @@ import android.media.tv.TvInputInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
-
-import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelImpl;
import com.android.tv.ui.SelectInputView;
import com.android.tv.ui.SelectInputView.OnInputSelectedCallback;
import com.android.tv.util.Utils;
-/**
- * An activity to select input.
- */
+/** An activity to select input. */
public class SelectInputActivity extends Activity {
private SelectInputView mSelectInputView;
@@ -41,30 +38,37 @@ public class SelectInputActivity extends Activity {
((TvApplication) getApplicationContext()).setSelectInputActivity(this);
setContentView(R.layout.activity_select_input);
mSelectInputView = (SelectInputView) findViewById(R.id.scene_transition_common);
- mSelectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
- @Override
- public void onTunerInputSelected() {
- startTvWithChannel(TvContract.Channels.CONTENT_URI);
- }
+ mSelectInputView.setOnInputSelectedCallback(
+ new OnInputSelectedCallback() {
+ @Override
+ public void onTunerInputSelected() {
+ startTvWithChannel(TvContract.Channels.CONTENT_URI);
+ }
- @Override
- public void onPassthroughInputSelected(TvInputInfo input) {
- startTvWithChannel(TvContract.buildChannelUriForPassthroughInput(input.getId()));
- }
+ @Override
+ public void onPassthroughInputSelected(TvInputInfo input) {
+ startTvWithChannel(
+ TvContract.buildChannelUriForPassthroughInput(input.getId()));
+ }
- private void startTvWithChannel(Uri channelUri) {
- Intent intent = new Intent(Intent.ACTION_VIEW, channelUri,
- SelectInputActivity.this, MainActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- finish();
- }
- });
+ private void startTvWithChannel(Uri channelUri) {
+ Intent intent =
+ new Intent(
+ Intent.ACTION_VIEW,
+ channelUri,
+ SelectInputActivity.this,
+ MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ }
+ });
String channelUriString = Utils.getLastWatchedChannelUri(this);
if (channelUriString != null) {
Uri channelUri = Uri.parse(channelUriString);
if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
- mSelectInputView.setCurrentChannel(Channel.createPassthroughChannel(channelUri));
+ mSelectInputView.setCurrentChannel(
+ ChannelImpl.createPassthroughChannel(channelUri));
}
// No need to set the tuner channel because it's the default selection.
}
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
index f0f54413..199ea51d 100644
--- a/src/com/android/tv/SetupPassthroughActivity.java
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -26,23 +26,23 @@ import android.os.Handler;
import android.os.Looper;
import android.support.annotation.MainThread;
import android.util.Log;
-
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonConstants;
+import com.android.tv.common.actions.InputSetupActionUtils;
+import com.android.tv.common.experiments.Experiments;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.Listener;
import com.android.tv.data.epg.EpgFetcher;
-import com.android.tv.experiments.Experiments;
+import com.android.tv.data.epg.EpgInputWhiteList;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
+import com.google.android.tv.partner.support.EpgContract;
import java.util.concurrent.TimeUnit;
/**
* An activity to launch a TV input setup activity.
*
- * <p> After setup activity is finished, all channels will be browsable.
+ * <p>After setup activity is finished, all channels will be browsable.
*/
public class SetupPassthroughActivity extends Activity {
private static final String TAG = "SetupPassthroughAct";
@@ -55,35 +55,45 @@ public class SetupPassthroughActivity extends Activity {
private TvInputInfo mTvInputInfo;
private Intent mActivityAfterCompletion;
private boolean mEpgFetcherDuringScan;
+ private EpgInputWhiteList mEpgInputWhiteList;
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
- ApplicationSingletons appSingletons = TvApplication.getSingletons(this);
- TvInputManagerHelper inputManager = appSingletons.getTvInputManagerHelper();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(this);
+ TvInputManagerHelper inputManager = tvSingletons.getTvInputManagerHelper();
Intent intent = getIntent();
- String inputId = intent.getStringExtra(TvCommonConstants.EXTRA_INPUT_ID);
+ String inputId = intent.getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID);
mTvInputInfo = inputManager.getTvInputInfo(inputId);
- mActivityAfterCompletion = intent.getParcelableExtra(
- TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION);
- boolean needToFetchEpg = Utils.isInternalTvInput(this, mTvInputInfo.getId())
- && Experiments.CLOUD_EPG.get();
+ mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getRemoteConfig());
+ mActivityAfterCompletion = InputSetupActionUtils.getExtraActivityAfter(intent);
+ boolean needToFetchEpg =
+ mTvInputInfo != null
+ && Utils.isInternalTvInput(this, mTvInputInfo.getId())
+ && Experiments.CLOUD_EPG.get();
if (needToFetchEpg) {
// In case when the activity is restored, this flag should be restored as well.
mEpgFetcherDuringScan = true;
}
if (savedInstanceState == null) {
- SoftPreconditions.checkState(
- intent.getAction().equals(TvCommonConstants.INTENT_ACTION_INPUT_SETUP));
+ SoftPreconditions.checkArgument(
+ InputSetupActionUtils.hasInputSetupAction(intent),
+ TAG,
+ "Unsupported action %s",
+ intent.getAction());
if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo);
if (mTvInputInfo == null) {
Log.w(TAG, "There is no input with the ID " + inputId + ".");
finish();
return;
}
- Intent setupIntent =
- intent.getExtras().getParcelable(TvCommonConstants.EXTRA_SETUP_INTENT);
+ if (intent.getExtras() == null) {
+ Log.w(TAG, "There is no extra info in the intent");
+ finish();
+ return;
+ }
+ Intent setupIntent = InputSetupActionUtils.getExtraSetupIntent(intent);
if (DEBUG) Log.d(TAG, "Setup activity launch intent: " + setupIntent);
if (setupIntent == null) {
Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup.");
@@ -95,7 +105,7 @@ public class SetupPassthroughActivity extends Activity {
// If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during
// setupIntent.putExtras(intent.getExtras()).
Bundle extras = intent.getExtras();
- extras.remove(TvCommonConstants.EXTRA_SETUP_INTENT);
+ InputSetupActionUtils.removeSetupIntent(extras);
setupIntent.putExtras(extras);
try {
startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
@@ -109,48 +119,54 @@ public class SetupPassthroughActivity extends Activity {
sScanTimeoutMonitor = new ScanTimeoutMonitor(this);
}
sScanTimeoutMonitor.startMonitoring();
- EpgFetcher.getInstance(this).onChannelScanStarted();
+ TvSingletons.getSingletons(this).getEpgFetcher().onChannelScanStarted();
}
}
}
@Override
public void onActivityResult(int requestCode, final int resultCode, final Intent data) {
- if (DEBUG) Log.d(TAG, "onActivityResult");
+ if (DEBUG)
+ Log.d(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")");
if (sScanTimeoutMonitor != null) {
sScanTimeoutMonitor.stopMonitoring();
}
// Note: It's not guaranteed that this method is always called after scanning.
- boolean setupComplete = requestCode == REQUEST_START_SETUP_ACTIVITY
- && resultCode == Activity.RESULT_OK;
+ boolean setupComplete =
+ requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK;
// Tells EpgFetcher that channel source setup is finished.
+ EpgFetcher epgFetcher = TvSingletons.getSingletons(this).getEpgFetcher();
if (mEpgFetcherDuringScan) {
- EpgFetcher.getInstance(this).onChannelScanFinished();
+ epgFetcher.onChannelScanFinished();
}
if (!setupComplete) {
setResult(resultCode, data);
finish();
return;
}
- SetupUtils.getInstance(this).onTvInputSetupFinished(mTvInputInfo.getId(), new Runnable() {
- @Override
- public void run() {
- if (mActivityAfterCompletion != null) {
- try {
- startActivity(mActivityAfterCompletion);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Activity launch failed", e);
- }
- }
- setResult(resultCode, data);
- finish();
- }
- });
+ TvSingletons.getSingletons(this)
+ .getSetupUtils()
+ .onTvInputSetupFinished(
+ mTvInputInfo.getId(),
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mActivityAfterCompletion != null) {
+ try {
+ startActivity(mActivityAfterCompletion);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity launch failed", e);
+ }
+ }
+ setResult(resultCode, data);
+ finish();
+ }
+ });
}
/**
- * Monitors the scan progress and notifies the timeout of the scanning.
- * The purpose of this monitor is to call EpgFetcher.onChannelScanFinished() in case when
+ * Monitors the scan progress and notifies the timeout of the scanning. The purpose of this
+ * monitor is to call EpgFetcher.onChannelScanFinished() in case when
* SetupPassthroughActivity.onActivityResult() is not called properly. b/36008534
*/
@MainThread
@@ -161,33 +177,37 @@ public class SetupPassthroughActivity extends Activity {
private final Context mContext;
private final ChannelDataManager mChannelDataManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Runnable mScanTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- Log.w(TAG, "No channels has been added for a while." +
- " The scan might have finished unexpectedly.");
- onScanTimedOut();
- }
- };
- private final Listener mChannelDataManagerListener = new Listener() {
- @Override
- public void onLoadFinished() {
- setupTimer();
- }
+ private final Runnable mScanTimeoutRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ Log.w(
+ TAG,
+ "No channels has been added for a while."
+ + " The scan might have finished unexpectedly.");
+ onScanTimedOut();
+ }
+ };
+ private final Listener mChannelDataManagerListener =
+ new Listener() {
+ @Override
+ public void onLoadFinished() {
+ setupTimer();
+ }
- @Override
- public void onChannelListUpdated() {
- setupTimer();
- }
+ @Override
+ public void onChannelListUpdated() {
+ setupTimer();
+ }
- @Override
- public void onChannelBrowsableChanged() { }
- };
+ @Override
+ public void onChannelBrowsableChanged() {}
+ };
private boolean mStarted;
private ScanTimeoutMonitor(Context context) {
mContext = context.getApplicationContext();
- mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
+ mChannelDataManager = TvSingletons.getSingletons(context).getChannelDataManager();
}
private void startMonitoring() {
@@ -215,7 +235,7 @@ public class SetupPassthroughActivity extends Activity {
private void onScanTimedOut() {
stopMonitoring();
- EpgFetcher.getInstance(mContext).onChannelScanFinished();
+ TvSingletons.getSingletons(mContext).getEpgFetcher().onChannelScanFinished();
}
}
}
diff --git a/src/com/android/tv/Starter.java b/src/com/android/tv/Starter.java
new file mode 100644
index 00000000..22fda0bd
--- /dev/null
+++ b/src/com/android/tv/Starter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tv;
+
+import android.content.Context;
+import android.util.Log;
+
+/** Initializes TvApplication. */
+public interface Starter {
+
+ /**
+ * Initializes TvApplication.
+ *
+ * <p>Note: it should be called at the beginning of any Service.onCreate, Activity.onCreate, or
+ * BroadcastReceiver.onCreate.
+ */
+ static void start(Context context) {
+ // TODO(b/63064354) TvApplication should not have to know if it is "the main process"
+ if (context.getApplicationContext() instanceof Starter) {
+ Starter starter = (Starter) context.getApplicationContext();
+ starter.start();
+ } else {
+ // Application context can be MockTvApplication.
+ Log.w("Start", "It is not a context of TvApplication");
+ }
+ }
+
+ void start();
+}
diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java
index 70885936..bb3574d7 100644
--- a/src/com/android/tv/TimeShiftManager.java
+++ b/src/com/android/tv/TimeShiftManager.java
@@ -27,20 +27,18 @@ import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Range;
-
import com.android.tv.analytics.Tracker;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
-import com.android.tv.data.Channel;
import com.android.tv.data.OnCurrentProgramUpdatedListener;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.ui.TunableTvView;
-import com.android.tv.ui.TunableTvView.TimeShiftListener;
+import com.android.tv.ui.TunableTvViewPlayingApi.TimeShiftListener;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.TimeShiftUtils;
import com.android.tv.util.Utils;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -53,11 +51,11 @@ import java.util.Queue;
import java.util.concurrent.TimeUnit;
/**
- * A class which manages the time shift feature in Live TV. It consists of two parts.
- * {@link PlayController} controls the playback such as play/pause, rewind and fast-forward using
- * {@link TunableTvView} which communicates with TvInputService through
- * {@link android.media.tv.TvInputService.Session}.
- * {@link ProgramManager} loads programs of the current channel in the background.
+ * A class which manages the time shift feature in Live TV. It consists of two parts. {@link
+ * PlayController} controls the playback such as play/pause, rewind and fast-forward using {@link
+ * TunableTvView} which communicates with TvInputService through {@link
+ * android.media.tv.TvInputService.Session}. {@link ProgramManager} loads programs of the current
+ * channel in the background.
*/
public class TimeShiftManager {
private static final String TAG = "TimeShiftManager";
@@ -66,12 +64,14 @@ public class TimeShiftManager {
@Retention(RetentionPolicy.SOURCE)
@IntDef({PLAY_STATUS_PAUSED, PLAY_STATUS_PLAYING})
public @interface PlayStatus {}
- public static final int PLAY_STATUS_PAUSED = 0;
+
+ public static final int PLAY_STATUS_PAUSED = 0;
public static final int PLAY_STATUS_PLAYING = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({PLAY_SPEED_1X, PLAY_SPEED_2X, PLAY_SPEED_3X, PLAY_SPEED_4X, PLAY_SPEED_5X})
- public @interface PlaySpeed{}
+ public @interface PlaySpeed {}
+
public static final int PLAY_SPEED_1X = 1;
public static final int PLAY_SPEED_2X = 2;
public static final int PLAY_SPEED_3X = 3;
@@ -80,15 +80,25 @@ public class TimeShiftManager {
@Retention(RetentionPolicy.SOURCE)
@IntDef({PLAY_DIRECTION_FORWARD, PLAY_DIRECTION_BACKWARD})
- public @interface PlayDirection{}
- public static final int PLAY_DIRECTION_FORWARD = 0;
+ public @interface PlayDirection {}
+
+ public static final int PLAY_DIRECTION_FORWARD = 0;
public static final int PLAY_DIRECTION_BACKWARD = 1;
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {TIME_SHIFT_ACTION_ID_PLAY, TIME_SHIFT_ACTION_ID_PAUSE,
- TIME_SHIFT_ACTION_ID_REWIND, TIME_SHIFT_ACTION_ID_FAST_FORWARD,
- TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT})
- public @interface TimeShiftActionId{}
+ @IntDef(
+ flag = true,
+ value = {
+ TIME_SHIFT_ACTION_ID_PLAY,
+ TIME_SHIFT_ACTION_ID_PAUSE,
+ TIME_SHIFT_ACTION_ID_REWIND,
+ TIME_SHIFT_ACTION_ID_FAST_FORWARD,
+ TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS,
+ TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
+ }
+ )
+ public @interface TimeShiftActionId {}
+
public static final int TIME_SHIFT_ACTION_ID_PLAY = 1;
public static final int TIME_SHIFT_ACTION_ID_PAUSE = 1 << 1;
public static final int TIME_SHIFT_ACTION_ID_REWIND = 1 << 2;
@@ -100,8 +110,7 @@ public class TimeShiftManager {
private static final int MSG_PREFETCH_PROGRAM = 1001;
private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1);
private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30);
- @VisibleForTesting
- static final long INVALID_TIME = -1;
+ @VisibleForTesting static final long INVALID_TIME = -1;
static final long CURRENT_TIME = -2;
private static final long PREFETCH_TIME_OFFSET_FROM_PROGRAM_END = TimeUnit.MINUTES.toMillis(1);
private static final long PREFETCH_DURATION_FOR_NEXT = TimeUnit.HOURS.toMillis(2);
@@ -109,57 +118,57 @@ public class TimeShiftManager {
private static final long ALLOWED_START_TIME_OFFSET = TimeUnit.DAYS.toMillis(14);
private static final long TWO_WEEKS_MS = TimeUnit.DAYS.toMillis(14);
- @VisibleForTesting
- static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
+ @VisibleForTesting static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
/**
* If the user presses the {@link android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS} button within
* this threshold from the program start time, the play position moves to the start of the
- * previous program.
- * Otherwise, the play position moves to the start of the current program.
+ * previous program. Otherwise, the play position moves to the start of the current program.
* This value is specified in the UX document.
*/
private static final long PROGRAM_START_TIME_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
/**
* If the current position enters within this range from the recording start time, rewind action
- * and jump to previous action is disabled.
- * Similarly, if the current position enters within this range from the current system time,
- * fast forward action and jump to next action is disabled.
- * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least.
+ * and jump to previous action is disabled. Similarly, if the current position enters within
+ * this range from the current system time, fast forward action and jump to next action is
+ * disabled. It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at
+ * least.
*/
private static final long DISABLE_ACTION_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL;
/**
* If the current position goes out of this range from the recording start time, rewind action
- * and jump to previous action is enabled.
- * Similarly, if the current position goes out of this range from the current system time,
- * fast forward action and jump to next action is enabled.
- * Enable threshold and disable threshold must be different because the current position
- * does not have the continuous value. It changes every one second.
+ * and jump to previous action is enabled. Similarly, if the current position goes out of this
+ * range from the current system time, fast forward action and jump to next action is enabled.
+ * Enable threshold and disable threshold must be different because the current position does
+ * not have the continuous value. It changes every one second.
*/
private static final long ENABLE_ACTION_THRESHOLD =
DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL;
/**
- * The current position sent from TIS can not be exactly the same as the current system time
- * due to the elapsed time to pass the message from TIS to Live TV.
- * So the boundary threshold is necessary.
- * The same goes for the recording start time.
- * It's the same {@link #REQUEST_CURRENT_POSITION_INTERVAL}.
+ * The current position sent from TIS can not be exactly the same as the current system time due
+ * to the elapsed time to pass the message from TIS to Live TV. So the boundary threshold
+ * is necessary. The same goes for the recording start time. It's the same {@link
+ * #REQUEST_CURRENT_POSITION_INTERVAL}.
*/
private static final long RECORDING_BOUNDARY_THRESHOLD = REQUEST_CURRENT_POSITION_INTERVAL;
private final PlayController mPlayController;
private final ProgramManager mProgramManager;
private final Tracker mTracker;
+
@VisibleForTesting
final CurrentPositionMediator mCurrentPositionMediator = new CurrentPositionMediator();
private Listener mListener;
private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener;
- private int mEnabledActionIds = TIME_SHIFT_ACTION_ID_PLAY | TIME_SHIFT_ACTION_ID_PAUSE
- | TIME_SHIFT_ACTION_ID_REWIND | TIME_SHIFT_ACTION_ID_FAST_FORWARD
- | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
- @TimeShiftActionId
- private int mLastActionId = 0;
+ private int mEnabledActionIds =
+ TIME_SHIFT_ACTION_ID_PLAY
+ | TIME_SHIFT_ACTION_ID_PAUSE
+ | TIME_SHIFT_ACTION_ID_REWIND
+ | TIME_SHIFT_ACTION_ID_FAST_FORWARD
+ | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS
+ | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
+ @TimeShiftActionId private int mLastActionId = 0;
private final Context mContext;
@@ -169,8 +178,11 @@ public class TimeShiftManager {
private final Handler mHandler = new TimeShiftHandler(this);
- public TimeShiftManager(Context context, TunableTvView tvView,
- ProgramDataManager programDataManager, Tracker tracker,
+ public TimeShiftManager(
+ Context context,
+ TunableTvView tvView,
+ ProgramDataManager programDataManager,
+ Tracker tracker,
OnCurrentProgramUpdatedListener onCurrentProgramUpdatedListener) {
mContext = context;
mPlayController = new PlayController(tvView);
@@ -179,23 +191,17 @@ public class TimeShiftManager {
mOnCurrentProgramUpdatedListener = onCurrentProgramUpdatedListener;
}
- /**
- * Sets a listener which will receive events from this class.
- */
+ /** Sets a listener which will receive events from this class. */
public void setListener(Listener listener) {
mListener = listener;
}
- /**
- * Checks if the trick play is available for the current channel.
- */
+ /** Checks if the trick play is available for the current channel. */
public boolean isAvailable() {
return mPlayController.mAvailable;
}
- /**
- * Returns the current time position in milliseconds.
- */
+ /** Returns the current time position in milliseconds. */
public long getCurrentPositionMs() {
return mCurrentPositionMediator.mCurrentPositionMs;
}
@@ -204,18 +210,15 @@ public class TimeShiftManager {
mCurrentPositionMediator.onCurrentPositionChanged(currentTimeMs);
}
- /**
- * Returns the start time of the recording in milliseconds.
- */
+ /** Returns the start time of the recording in milliseconds. */
public long getRecordStartTimeMs() {
long oldestProgramStartTime = mProgramManager.getOldestProgramStartTime();
- return oldestProgramStartTime == INVALID_TIME ? INVALID_TIME
+ return oldestProgramStartTime == INVALID_TIME
+ ? INVALID_TIME
: mPlayController.mRecordStartTimeMs;
}
- /**
- * Returns the end time of the recording in milliseconds.
- */
+ /** Returns the end time of the recording in milliseconds. */
public long getRecordEndTimeMs() {
if (mPlayController.mRecordEndTimeMs == CURRENT_TIME) {
return System.currentTimeMillis();
@@ -264,9 +267,9 @@ public class TimeShiftManager {
}
/**
- * Plays the media in backward direction. The playback speed is increased by 1x each time
- * this is called. The range of the speed is from 2x to 5x.
- * If the playing position is considered the same as the record start time, it does nothing
+ * Plays the media in backward direction. The playback speed is increased by 1x each time this
+ * is called. The range of the speed is from 2x to 5x. If the playing position is considered the
+ * same as the record start time, it does nothing
*
* @throws IllegalStateException if the trick play is not available.
*/
@@ -281,9 +284,9 @@ public class TimeShiftManager {
}
/**
- * Plays the media in forward direction. The playback speed is increased by 1x each time
- * this is called. The range of the speed is from 2x to 5x.
- * If the playing position is the same as the current time, it does nothing.
+ * Plays the media in forward direction. The playback speed is increased by 1x each time this is
+ * called. The range of the speed is from 2x to 5x. If the playing position is the same as the
+ * current time, it does nothing.
*
* @throws IllegalStateException if the trick play is not available.
*/
@@ -298,11 +301,10 @@ public class TimeShiftManager {
}
/**
- * Jumps to the start of the current program.
- * If the currently playing position is within 3 seconds
- * (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes to
- * the start of the previous program if exists.
- * If the playing position is the same as the record start time, it does nothing.
+ * Jumps to the start of the current program. If the currently playing position is within 3
+ * seconds (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes
+ * to the start of the previous program if exists. If the playing position is the same as the
+ * record start time, it does nothing.
*
* @throws IllegalStateException if the trick play is not available.
*/
@@ -310,8 +312,9 @@ public class TimeShiftManager {
if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
return;
}
- Program program = mProgramManager.getProgramAt(
- mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD);
+ Program program =
+ mProgramManager.getProgramAt(
+ mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD);
if (program == null) {
return;
}
@@ -325,9 +328,9 @@ public class TimeShiftManager {
}
/**
- * Jumps to the start of the next program if exists.
- * If there's no next program, it jumps to the current system time and shows the live TV.
- * If the playing position is considered the same as the current time, it does nothing.
+ * Jumps to the start of the next program if exists. If there's no next program, it jumps to the
+ * current system time and shows the live TV. If the playing position is considered the same as
+ * the current time, it does nothing.
*
* @throws IllegalStateException if the trick play is not available.
*/
@@ -335,8 +338,8 @@ public class TimeShiftManager {
if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
return;
}
- Program currentProgram = mProgramManager.getProgramAt(
- mCurrentPositionMediator.mCurrentPositionMs);
+ Program currentProgram =
+ mProgramManager.getProgramAt(mCurrentPositionMediator.mCurrentPositionMs);
if (currentProgram == null) {
return;
}
@@ -362,10 +365,9 @@ public class TimeShiftManager {
updateActions();
}
- /**
- * Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING.
- */
- @PlayStatus public int getPlayStatus() {
+ /** Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING. */
+ @PlayStatus
+ public int getPlayStatus() {
return mPlayController.mPlayStatus;
}
@@ -373,27 +375,26 @@ public class TimeShiftManager {
* Returns the displayed playback speed. The value is one of PLAY_SPEED_1X, PLAY_SPEED_2X,
* PLAY_SPEED_3X, PLAY_SPEED_4X and PLAY_SPEED_5X.
*/
- @PlaySpeed public int getDisplayedPlaySpeed() {
+ @PlaySpeed
+ public int getDisplayedPlaySpeed() {
return mPlayController.mDisplayedPlaySpeed;
}
/**
* Returns the playback speed. The value is PLAY_DIRECTION_FORWARD or PLAY_DIRECTION_BACKWARD.
*/
- @PlayDirection public int getPlayDirection() {
+ @PlayDirection
+ public int getPlayDirection() {
return mPlayController.mPlayDirection;
}
- /**
- * Returns the ID of the last action..
- */
- @TimeShiftActionId public int getLastActionId() {
+ /** Returns the ID of the last action.. */
+ @TimeShiftActionId
+ public int getLastActionId() {
return mLastActionId;
}
- /**
- * Enables or disables the time-shift actions.
- */
+ /** Enables or disables the time-shift actions. */
@VisibleForTesting
void enableAction(@TimeShiftActionId int actionId, boolean enable) {
int oldEnabledActionIds = mEnabledActionIds;
@@ -402,8 +403,7 @@ public class TimeShiftManager {
} else {
mEnabledActionIds &= ~actionId;
}
- if (mNotificationEnabled && mListener != null
- && oldEnabledActionIds != mEnabledActionIds) {
+ if (mNotificationEnabled && mListener != null && oldEnabledActionIds != mEnabledActionIds) {
mListener.onActionEnabledChanged(actionId, enable);
}
}
@@ -417,17 +417,22 @@ public class TimeShiftManager {
enableAction(TIME_SHIFT_ACTION_ID_PLAY, true);
enableAction(TIME_SHIFT_ACTION_ID_PAUSE, true);
// Rewind action and jump to previous action.
- long threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)
- ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD;
- boolean enabled = mCurrentPositionMediator.mCurrentPositionMs
- - mPlayController.mRecordStartTimeMs > threshold;
+ long threshold =
+ isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)
+ ? DISABLE_ACTION_THRESHOLD
+ : ENABLE_ACTION_THRESHOLD;
+ boolean enabled =
+ mCurrentPositionMediator.mCurrentPositionMs - mPlayController.mRecordStartTimeMs
+ > threshold;
enableAction(TIME_SHIFT_ACTION_ID_REWIND, enabled);
enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, enabled);
// Fast forward action and jump to next action
- threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)
- ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD;
- enabled = getRecordEndTimeMs() - mCurrentPositionMediator.mCurrentPositionMs
- > threshold;
+ threshold =
+ isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)
+ ? DISABLE_ACTION_THRESHOLD
+ : ENABLE_ACTION_THRESHOLD;
+ enabled =
+ getRecordEndTimeMs() - mCurrentPositionMediator.mCurrentPositionMs > threshold;
enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled);
enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled);
} else {
@@ -444,7 +449,7 @@ public class TimeShiftManager {
SoftPreconditions.checkState(isAvailable(), TAG, "Time shift is not available");
SoftPreconditions.checkState(mCurrentPositionMediator.mCurrentPositionMs != INVALID_TIME);
Program currentProgram = getProgramAt(mCurrentPositionMediator.mCurrentPositionMs);
- if (!Program.isValid(currentProgram)) {
+ if (!Program.isProgramValid(currentProgram)) {
currentProgram = null;
}
if (!Objects.equals(mCurrentProgram, currentProgram)) {
@@ -453,8 +458,8 @@ public class TimeShiftManager {
if (mNotificationEnabled && mOnCurrentProgramUpdatedListener != null) {
Channel channel = mPlayController.getCurrentChannel();
if (channel != null) {
- mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(channel.getId(),
- mCurrentProgram);
+ mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(
+ channel.getId(), mCurrentProgram);
mPlayController.onCurrentProgramChanged();
}
}
@@ -472,16 +477,12 @@ public class TimeShiftManager {
&& mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X;
}
- /**
- * Checks if the trick play is available and it's playback status is paused.
- */
+ /** Checks if the trick play is available and it's playback status is paused. */
public boolean isPaused() {
return mPlayController.mAvailable && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED;
}
- /**
- * Returns the program which airs at the given time.
- */
+ /** Returns the program which airs at the given time. */
@NonNull
public Program getProgramAt(long timeMs) {
Program program = mProgramManager.getProgramAt(timeMs);
@@ -495,8 +496,10 @@ public class TimeShiftManager {
void onAvailabilityChanged() {
mCurrentPositionMediator.initialize(mPlayController.mRecordStartTimeMs);
- mProgramManager.onAvailabilityChanged(mPlayController.mAvailable,
- mPlayController.getCurrentChannel(), mPlayController.mRecordStartTimeMs);
+ mProgramManager.onAvailabilityChanged(
+ mPlayController.mAvailable,
+ mPlayController.getCurrentChannel(),
+ mPlayController.mRecordStartTimeMs);
updateActions();
// Availability change notification should be always sent
// even if mNotificationEnabled is false.
@@ -507,8 +510,8 @@ public class TimeShiftManager {
void onRecordTimeRangeChanged() {
if (mPlayController.mAvailable) {
- mProgramManager.onRecordTimeRangeChanged(mPlayController.mRecordStartTimeMs,
- mPlayController.mRecordEndTimeMs);
+ mProgramManager.onRecordTimeRangeChanged(
+ mPlayController.mRecordStartTimeMs, mPlayController.mRecordEndTimeMs);
}
updateActions();
if (mNotificationEnabled && mListener != null) {
@@ -538,10 +541,10 @@ public class TimeShiftManager {
}
/**
- * Returns the current program which airs right now.<p>
+ * Returns the current program which airs right now.
*
- * If the program is a dummy program, which means there's no program information,
- * returns {@code null}.
+ * <p>If the program is a dummy program, which means there's no program information, returns
+ * {@code null}.
*/
@Nullable
public Program getCurrentProgram() {
@@ -558,8 +561,10 @@ public class TimeShiftManager {
long durationMs =
(getCurrentProgram() == null ? 0 : getCurrentProgram().getDurationMillis());
if (mPlayController.mDisplayedPlaySpeed > PLAY_SPEED_5X) {
- Log.w(TAG, "Unknown displayed play speed is chosen : "
- + mPlayController.mDisplayedPlaySpeed);
+ Log.w(
+ TAG,
+ "Unknown displayed play speed is chosen : "
+ + mPlayController.mDisplayedPlaySpeed);
return TimeShiftUtils.getMaxPlaybackSpeed(durationMs);
} else {
return TimeShiftUtils.getPlaybackSpeed(
@@ -568,9 +573,7 @@ public class TimeShiftManager {
}
}
- /**
- * A class which controls the trick play.
- */
+ /** A class which controls the trick play. */
private class PlayController {
private final TunableTvView mTvView;
@@ -585,69 +588,87 @@ public class TimeShiftManager {
private boolean mAvailable;
/**
- * Indicates that the trick play is not playing the current time position.
- * It is set true when {@link PlayController#pause}, {@link PlayController#rewind},
- * {@link PlayController#fastForward} and {@link PlayController#seekTo}
- * is called.
- * If it is true, the current time is equal to System.currentTimeMillis().
+ * Indicates that the trick play is not playing the current time position. It is set true
+ * when {@link PlayController#pause}, {@link PlayController#rewind}, {@link
+ * PlayController#fastForward} and {@link PlayController#seekTo} is called. If it is true,
+ * the current time is equal to System.currentTimeMillis().
*/
private boolean mIsPlayOffsetChanged;
PlayController(TunableTvView tvView) {
mTvView = tvView;
- mTvView.setTimeShiftListener(new TimeShiftListener() {
- @Override
- public void onAvailabilityChanged() {
- if (DEBUG) {
- Log.d(TAG, "onAvailabilityChanged(available="
- + mTvView.isTimeShiftAvailable() + ")");
- }
- PlayController.this.onAvailabilityChanged();
- }
+ mTvView.setTimeShiftListener(
+ new TimeShiftListener() {
+ @Override
+ public void onAvailabilityChanged() {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onAvailabilityChanged(available="
+ + mTvView.isTimeShiftAvailable()
+ + ")");
+ }
+ PlayController.this.onAvailabilityChanged();
+ }
- @Override
- public void onRecordStartTimeChanged(long recordStartTimeMs) {
- if (!SoftPreconditions.checkState(mAvailable, TAG,
- "Trick play is not available.")) {
- return;
- }
- if (recordStartTimeMs < mAvailablityChangedTimeMs - ALLOWED_START_TIME_OFFSET) {
- Log.e(TAG, "The start time is too earlier than the time of availability: {"
- + "startTime: " + recordStartTimeMs + ", availability: "
- + mAvailablityChangedTimeMs);
- return;
- }
- if (recordStartTimeMs > System.currentTimeMillis()) {
- // The time reported by TvInputService might not consistent with system
- // clock,, use system's current time instead.
- Log.e(TAG, "The start time should not be earlier than the current time, "
- + "reset the start time to the system's current time: {"
- + "startTime: " + recordStartTimeMs + ", current time: "
- + System.currentTimeMillis());
- recordStartTimeMs = System.currentTimeMillis();
- }
- if (mRecordStartTimeMs == recordStartTimeMs) {
- return;
- }
- mRecordStartTimeMs = recordStartTimeMs;
- TimeShiftManager.this.onRecordTimeRangeChanged();
-
- // According to the UX guidelines, the stream should be resumed if the
- // recording buffer fills up while paused, which means that the current time
- // position is the same as or before the recording start time.
- // But, for this application and the TIS, it's an erroneous and confusing
- // situation if the current time position is before the recording start time.
- // So, we recommend the TIS to keep the current time position greater than or
- // equal to the recording start time.
- // And here, we assume that the buffer is full if the current time position
- // is nearly equal to the recording start time.
- if (mPlayStatus == PLAY_STATUS_PAUSED &&
- getCurrentPositionMs() - mRecordStartTimeMs
- < RECORDING_BOUNDARY_THRESHOLD) {
- TimeShiftManager.this.play();
- }
- }
- });
+ @Override
+ public void onRecordStartTimeChanged(long recordStartTimeMs) {
+ if (!SoftPreconditions.checkState(
+ mAvailable, TAG, "Trick play is not available.")) {
+ return;
+ }
+ if (recordStartTimeMs
+ < mAvailablityChangedTimeMs - ALLOWED_START_TIME_OFFSET) {
+ Log.e(
+ TAG,
+ "The start time is too earlier than the time of availability: {"
+ + "startTime: "
+ + recordStartTimeMs
+ + ", availability: "
+ + mAvailablityChangedTimeMs);
+ return;
+ }
+ if (recordStartTimeMs > System.currentTimeMillis()) {
+ // The time reported by TvInputService might not consistent with
+ // system
+ // clock,, use system's current time instead.
+ Log.e(
+ TAG,
+ "The start time should not be earlier than the current time, "
+ + "reset the start time to the system's current time: {"
+ + "startTime: "
+ + recordStartTimeMs
+ + ", current time: "
+ + System.currentTimeMillis());
+ recordStartTimeMs = System.currentTimeMillis();
+ }
+ if (mRecordStartTimeMs == recordStartTimeMs) {
+ return;
+ }
+ mRecordStartTimeMs = recordStartTimeMs;
+ TimeShiftManager.this.onRecordTimeRangeChanged();
+
+ // According to the UX guidelines, the stream should be resumed if the
+ // recording buffer fills up while paused, which means that the current
+ // time
+ // position is the same as or before the recording start time.
+ // But, for this application and the TIS, it's an erroneous and
+ // confusing
+ // situation if the current time position is before the recording start
+ // time.
+ // So, we recommend the TIS to keep the current time position greater
+ // than or
+ // equal to the recording start time.
+ // And here, we assume that the buffer is full if the current time
+ // position
+ // is nearly equal to the recording start time.
+ if (mPlayStatus == PLAY_STATUS_PAUSED
+ && getCurrentPositionMs() - mRecordStartTimeMs
+ < RECORDING_BOUNDARY_THRESHOLD) {
+ TimeShiftManager.this.play();
+ }
+ }
+ });
}
void onAvailabilityChanged() {
@@ -672,8 +693,8 @@ public class TimeShiftManager {
mRecordEndTimeMs = CURRENT_TIME;
// When the media availability message has come.
mPlayController.setPlayStatus(PLAY_STATUS_PLAYING);
- mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
- REQUEST_CURRENT_POSITION_INTERVAL);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_GET_CURRENT_POSITION, REQUEST_CURRENT_POSITION_INTERVAL);
} else {
mAvailablityChangedTimeMs = INVALID_TIME;
mIsPlayOffsetChanged = false;
@@ -688,11 +709,14 @@ public class TimeShiftManager {
void handleGetCurrentPosition() {
if (mIsPlayOffsetChanged) {
- long currentTimeMs = mRecordEndTimeMs == CURRENT_TIME ? System.currentTimeMillis()
- : mRecordEndTimeMs;
- long currentPositionMs = Math.max(
- Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs),
- mRecordStartTimeMs);
+ long currentTimeMs =
+ mRecordEndTimeMs == CURRENT_TIME
+ ? System.currentTimeMillis()
+ : mRecordEndTimeMs;
+ long currentPositionMs =
+ Math.max(
+ Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs),
+ mRecordStartTimeMs);
boolean isCurrentTime =
currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD;
long newCurrentPositionMs;
@@ -708,8 +732,8 @@ public class TimeShiftManager {
}
} else {
newCurrentPositionMs = currentPositionMs;
- boolean isRecordStartTime = currentPositionMs - mRecordStartTimeMs
- < RECORDING_BOUNDARY_THRESHOLD;
+ boolean isRecordStartTime =
+ currentPositionMs - mRecordStartTimeMs < RECORDING_BOUNDARY_THRESHOLD;
if (isRecordStartTime && isRewinding()) {
TimeShiftManager.this.play();
}
@@ -721,8 +745,8 @@ public class TimeShiftManager {
}
// Need to send message here just in case there is no or invalid response
// for the current time position request from TIS.
- mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
- REQUEST_CURRENT_POSITION_INTERVAL);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_GET_CURRENT_POSITION, REQUEST_CURRENT_POSITION_INTERVAL);
}
void play() {
@@ -777,12 +801,13 @@ public class TimeShiftManager {
mIsPlayOffsetChanged = true;
}
- /**
- * Moves to the specified time.
- */
+ /** Moves to the specified time. */
void seekTo(long timeMs) {
- mTvView.timeshiftSeekTo(Math.min(mRecordEndTimeMs == CURRENT_TIME
- ? System.currentTimeMillis() : mRecordEndTimeMs,
+ mTvView.timeshiftSeekTo(
+ Math.min(
+ mRecordEndTimeMs == CURRENT_TIME
+ ? System.currentTimeMillis()
+ : mRecordEndTimeMs,
Math.max(mRecordStartTimeMs, timeMs)));
mIsPlayOffsetChanged = true;
}
@@ -853,8 +878,15 @@ public class TimeShiftManager {
void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) {
if (DEBUG) {
- Log.d(TAG, "onAvailabilityChanged(" + available + "+," + channel + ", "
- + currentPositionMs + ")");
+ Log.d(
+ TAG,
+ "onAvailabilityChanged("
+ + available
+ + "+,"
+ + channel
+ + ", "
+ + currentPositionMs
+ + ")");
}
mProgramLoadQueue.clear();
@@ -875,12 +907,14 @@ public class TimeShiftManager {
mPrograms.add(program);
prefetchStartTimeMs = program.getEndTimeUtcMillis();
} else {
- prefetchStartTimeMs = Utils.floorTime(currentPositionMs,
- MAX_DUMMY_PROGRAM_DURATION);
+ prefetchStartTimeMs =
+ Utils.floorTime(currentPositionMs, MAX_DUMMY_PROGRAM_DURATION);
}
// Create dummy program
- mPrograms.addAll(createDummyPrograms(prefetchStartTimeMs,
- currentPositionMs + PREFETCH_DURATION_FOR_NEXT));
+ mPrograms.addAll(
+ createDummyPrograms(
+ prefetchStartTimeMs,
+ currentPositionMs + PREFETCH_DURATION_FOR_NEXT));
schedulePrefetchPrograms();
TimeShiftManager.this.onProgramInfoChanged();
}
@@ -895,8 +929,9 @@ public class TimeShiftManager {
}
long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
- long fetchEndTimeMs = Utils.ceilTime(endTimeMs + PREFETCH_DURATION_FOR_NEXT,
- MAX_DUMMY_PROGRAM_DURATION);
+ long fetchEndTimeMs =
+ Utils.ceilTime(
+ endTimeMs + PREFETCH_DURATION_FOR_NEXT, MAX_DUMMY_PROGRAM_DURATION);
removeOutdatedPrograms(fetchStartTimeMs);
boolean needToLoad = addDummyPrograms(fetchStartTimeMs, fetchEndTimeMs);
if (needToLoad) {
@@ -934,16 +969,16 @@ public class TimeShiftManager {
Range<Long> next = mProgramLoadQueue.poll();
// Extend next to include any overlapping Ranges.
Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
- while(i.hasNext()) {
+ while (i.hasNext()) {
Range<Long> r = i.next();
- if(next.contains(r.getLower()) || next.contains(r.getUpper())){
+ if (next.contains(r.getLower()) || next.contains(r.getUpper())) {
i.remove();
next = next.extend(r);
}
}
if (mChannel != null) {
- mProgramLoadTask = new LoadProgramsForCurrentChannelTask(
- mContext.getContentResolver(), next);
+ mProgramLoadTask =
+ new LoadProgramsForCurrentChannelTask(mContext.getContentResolver(), next);
mProgramLoadTask.executeOnDbThread();
}
}
@@ -969,10 +1004,12 @@ public class TimeShiftManager {
if (!firstProgram.isValid()) {
// Already the firstProgram is dummy.
mPrograms.remove(0);
- mPrograms.addAll(0,
+ mPrograms.addAll(
+ 0,
createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis()));
} else {
- mPrograms.addAll(0,
+ mPrograms.addAll(
+ 0,
createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis()));
}
added = true;
@@ -1055,10 +1092,12 @@ public class TimeShiftManager {
// to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most
// for a dummy program.
private List<Program> createDummyPrograms(long startTimeMs, long endTimeMs) {
- SoftPreconditions.checkArgument(endTimeMs - startTimeMs <= TWO_WEEKS_MS, TAG,
- "createDummyProgram: long duration of dummy programs are requested ("
- + Utils.toTimeString(startTimeMs) + ", "
- + Utils.toTimeString(endTimeMs));
+ SoftPreconditions.checkArgument(
+ endTimeMs - startTimeMs <= TWO_WEEKS_MS,
+ TAG,
+ "createDummyProgram: long duration of dummy programs are requested ( %s , %s)",
+ Utils.toTimeString(startTimeMs),
+ Utils.toTimeString(endTimeMs));
if (startTimeMs >= endTimeMs) {
return Collections.emptyList();
}
@@ -1066,17 +1105,19 @@ public class TimeShiftManager {
long start = startTimeMs;
long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
while (end < endTimeMs) {
- programs.add(new Program.Builder()
- .setStartTimeUtcMillis(start)
- .setEndTimeUtcMillis(end)
- .build());
+ programs.add(
+ new Program.Builder()
+ .setStartTimeUtcMillis(start)
+ .setEndTimeUtcMillis(end)
+ .build());
start = end;
end += MAX_DUMMY_PROGRAM_DURATION;
}
- programs.add(new Program.Builder()
- .setStartTimeUtcMillis(start)
- .setEndTimeUtcMillis(endTimeMs)
- .build());
+ programs.add(
+ new Program.Builder()
+ .setStartTimeUtcMillis(start)
+ .setEndTimeUtcMillis(endTimeMs)
+ .build());
return programs;
}
@@ -1093,7 +1134,7 @@ public class TimeShiftManager {
if (program.getStartTimeUtcMillis() > timeMs) {
return getProgramAt(timeMs, start, mid - 1);
} else if (program.getEndTimeUtcMillis() <= timeMs) {
- return getProgramAt(timeMs, mid+1, end);
+ return getProgramAt(timeMs, mid + 1, end);
} else {
return program;
}
@@ -1125,8 +1166,10 @@ public class TimeShiftManager {
if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram);
final long delay;
if (lastValidProgram != null) {
- delay = lastValidProgram.getEndTimeUtcMillis()
- - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis();
+ delay =
+ lastValidProgram.getEndTimeUtcMillis()
+ - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END
+ - System.currentTimeMillis();
} else {
// Since there might not be any program data delay the retry 5 seconds,
// then 30 seconds then 5 minutes
@@ -1145,7 +1188,8 @@ public class TimeShiftManager {
break;
}
if (DEBUG) {
- Log.d(TAG,
+ Log.d(
+ TAG,
"No last valid program. Already tried " + mEmptyFetchCount + " times");
}
}
@@ -1165,8 +1209,13 @@ public class TimeShiftManager {
long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT;
if (startTimeMs <= endTimeMs) {
if (DEBUG) {
- Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs)
- + ", endTime=" + Utils.toTimeString(endTimeMs) + "}");
+ Log.d(
+ TAG,
+ "Prefetch task starts: {startTime="
+ + Utils.toTimeString(startTimeMs)
+ + ", endTime="
+ + Utils.toTimeString(endTimeMs)
+ + "}");
}
mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs));
}
@@ -1176,20 +1225,28 @@ public class TimeShiftManager {
private class LoadProgramsForCurrentChannelTask
extends AsyncDbTask.LoadProgramsForChannelTask {
- LoadProgramsForCurrentChannelTask(ContentResolver contentResolver,
- Range<Long> period) {
- super(contentResolver, mChannel.getId(), period);
+ LoadProgramsForCurrentChannelTask(ContentResolver contentResolver, Range<Long> period) {
+ super(
+ TvSingletons.getSingletons(mContext).getDbExecutor(),
+ contentResolver,
+ mChannel.getId(),
+ period);
}
@Override
protected void onPostExecute(List<Program> programs) {
if (DEBUG) {
- Log.d(TAG, "Programs are loaded {channelId=" + mChannelId +
- ", from=" + Utils.toTimeString(mPeriod.getLower()) +
- ", to=" + Utils.toTimeString(mPeriod.getUpper()) +
- "}");
+ Log.d(
+ TAG,
+ "Programs are loaded {channelId="
+ + mChannelId
+ + ", from="
+ + Utils.toTimeString(mPeriod.getLower())
+ + ", to="
+ + Utils.toTimeString(mPeriod.getUpper())
+ + "}");
}
- //remove pending tasks that are fully satisfied by this query.
+ // remove pending tasks that are fully satisfied by this query.
Iterator<Range<Long>> it = mProgramLoadQueue.iterator();
while (it.hasNext()) {
Range<Long> r = it.next();
@@ -1207,14 +1264,14 @@ public class TimeShiftManager {
return;
}
mEmptyFetchCount = 0;
- if(!mPrograms.isEmpty()) {
+ if (!mPrograms.isEmpty()) {
removeDummyPrograms();
removeOverlappedPrograms(programs);
Program loadedProgram = programs.get(0);
for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) {
Program program = mPrograms.get(i);
- while (program.getStartTimeUtcMillis() > loadedProgram
- .getStartTimeUtcMillis()) {
+ while (program.getStartTimeUtcMillis()
+ > loadedProgram.getStartTimeUtcMillis()) {
mPrograms.add(i++, loadedProgram);
programs.remove(0);
if (programs.isEmpty()) {
@@ -1234,10 +1291,15 @@ public class TimeShiftManager {
@Override
protected void onCancelled(List<Program> programs) {
if (DEBUG) {
- Log.d(TAG, "Program loading has been canceled {channelId=" + (mChannel == null
- ? "null" : mChannelId) + ", from=" + Utils
- .toTimeString(mPeriod.getLower()) + ", to=" + Utils
- .toTimeString(mPeriod.getUpper()) + "}");
+ Log.d(
+ TAG,
+ "Program loading has been canceled {channelId="
+ + (mChannel == null ? "null" : mChannelId)
+ + ", from="
+ + Utils.toTimeString(mPeriod.getLower())
+ + ", to="
+ + Utils.toTimeString(mPeriod.getUpper())
+ + "}");
}
startNextLoadingIfNeeded();
}
@@ -1247,12 +1309,13 @@ public class TimeShiftManager {
mProgramLoadTask = null;
}
// Need to post to handler, because the task is still running.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- startTaskIfNeeded();
- }
- });
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ startTaskIfNeeded();
+ }
+ });
}
boolean overlaps(Queue<Range<Long>> programLoadQueue) {
@@ -1299,11 +1362,11 @@ public class TimeShiftManager {
} else {
if (getPlayStatus() == PLAY_STATUS_PLAYING) {
if (getPlayDirection() == PLAY_DIRECTION_FORWARD) {
- mCurrentPositionMs += (currentTimeMs - mSeekRequestTimeMs)
- * getPlaybackSpeed();
+ mCurrentPositionMs +=
+ (currentTimeMs - mSeekRequestTimeMs) * getPlaybackSpeed();
} else {
- mCurrentPositionMs -= (currentTimeMs - mSeekRequestTimeMs)
- * getPlaybackSpeed();
+ mCurrentPositionMs -=
+ (currentTimeMs - mSeekRequestTimeMs) * getPlaybackSpeed();
}
}
TimeShiftManager.this.onCurrentPositionChanged();
@@ -1311,9 +1374,7 @@ public class TimeShiftManager {
}
}
- /**
- * The listener used to receive the events by the time-shift manager
- */
+ /** The listener used to receive the events by the time-shift manager */
public interface Listener {
/**
* Called when the availability of the time-shift for the current channel has been changed.
@@ -1323,31 +1384,23 @@ public class TimeShiftManager {
void onAvailabilityChanged();
/**
- * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and
- * {@link #PLAY_STATUS_PAUSED}
+ * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and {@link
+ * #PLAY_STATUS_PAUSED}
*
* @param status The new play state.
*/
void onPlayStatusChanged(int status);
- /**
- * Called when the recordStartTime has been changed.
- */
+ /** Called when the recordStartTime has been changed. */
void onRecordTimeRangeChanged();
- /**
- * Called when the current position is changed.
- */
+ /** Called when the current position is changed. */
void onCurrentPositionChanged();
- /**
- * Called when the program information is updated.
- */
+ /** Called when the program information is updated. */
void onProgramInfoChanged();
- /**
- * Called when an action becomes enabled or disabled.
- */
+ /** Called when an action becomes enabled or disabled. */
void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled);
}
diff --git a/src/com/android/tv/TvActivity.java b/src/com/android/tv/TvActivity.java
index 9a1cea55..aa0f0e8c 100644
--- a/src/com/android/tv/TvActivity.java
+++ b/src/com/android/tv/TvActivity.java
@@ -18,7 +18,6 @@ package com.android.tv;
import android.app.Activity;
import android.content.Intent;
-
import com.android.tv.util.Utils;
public class TvActivity extends Activity {
diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java
index 0c7c0fd1..826317b9 100644
--- a/src/com/android/tv/TvApplication.java
+++ b/src/com/android/tv/TvApplication.java
@@ -18,11 +18,9 @@ package com.android.tv;
import android.annotation.TargetApi;
import android.app.Activity;
-import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -30,32 +28,25 @@ import android.media.tv.TvContract;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.StrictMode;
import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
-
-import com.android.tv.analytics.Analytics;
-import com.android.tv.analytics.StubAnalytics;
-import com.android.tv.analytics.StubAnalytics;
-import com.android.tv.analytics.Tracker;
-import com.android.tv.common.BuildConfig;
-import com.android.tv.common.SharedPreferencesUtils;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.common.BaseApplication;
+import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
-import com.android.tv.config.DefaultConfigManager;
-import com.android.tv.config.RemoteConfig;
+import com.android.tv.common.util.Clock;
+import com.android.tv.common.util.Debug;
+import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.epg.EpgFetcherImpl;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManagerImpl;
import com.android.tv.dvr.DvrManager;
@@ -63,93 +54,81 @@ import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.recorder.RecordingScheduler;
-import com.android.tv.perf.EventNames;
-import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.StubPerformanceMonitor;
-import com.android.tv.perf.TimerEvent;
+import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
import com.android.tv.tuner.TunerInputController;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
import com.android.tv.tuner.util.TunerInputInfoUtils;
-import com.android.tv.util.AccountHelper;
-import com.android.tv.util.Clock;
-import com.android.tv.util.Debug;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.SetupUtils;
-import com.android.tv.util.SystemProperties;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
-public class TvApplication extends Application implements ApplicationSingletons {
+/**
+ * Live TV application.
+ *
+ * <p>This includes all the Google specific hooks.
+ */
+public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter {
private static final String TAG = "TvApplication";
private static final boolean DEBUG = false;
- private static final TimerEvent sAppStartTimer = StubPerformanceMonitor.startBootstrapTimer();
- /**
- * An instance of {@link ApplicationSingletons}. Note that this can be set directly only for the
- * test purpose.
- */
- @VisibleForTesting
- public static ApplicationSingletons sAppSingletons;
+ /** Namespace for LiveChannels configs. LiveChannels configs are kept in piper. */
+ public static final String CONFIGNS_P4 = "configns:p4";
/**
- * Broadcast Action: The user has updated LC to a new version that supports tuner input.
- * {@link com.android.tv.tuner.TunerInputController} will recevice this intent to check
- * the existence of tuner input when the new version is first launched.
+ * Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link
+ * TunerInputController} will receive this intent to check the existence of tuner input when the
+ * new version is first launched.
*/
public static final String ACTION_APPLICATION_FIRST_LAUNCHED =
- "com.android.tv.action.APPLICATION_FIRST_LAUNCHED";
+ " com.android.tv.action.APPLICATION_FIRST_LAUNCHED";
+
private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch";
- private RemoteConfig mRemoteConfig;
+ private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory("tv-app-db");
+ private static final ExecutorService DB_EXECUTOR =
+ Executors.newSingleThreadExecutor(THREAD_FACTORY);
+
private String mVersionName = "";
private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
private SelectInputActivity mSelectInputActivity;
- private Analytics mAnalytics;
- private Tracker mTracker;
- private TvInputManagerHelper mTvInputManagerHelper;
private ChannelDataManager mChannelDataManager;
private volatile ProgramDataManager mProgramDataManager;
private PreviewDataManager mPreviewDataManager;
private DvrManager mDvrManager;
private DvrScheduleManager mDvrScheduleManager;
private DvrDataManager mDvrDataManager;
- private DvrStorageStatusManager mDvrStorageStatusManager;
private DvrWatchedPositionManager mDvrWatchedPositionManager;
private RecordingScheduler mRecordingScheduler;
- @Nullable
- private InputSessionManager mInputSessionManager;
- private AccountHelper mAccountHelper;
+ private RecordingStorageStatusManager mDvrStorageStatusManager;
+ @Nullable private InputSessionManager mInputSessionManager;
+ // STOP-SHIP: Remove this variable when Tuner Process is split to another application.
// When this variable is null, we don't know in which process TvApplication runs.
private Boolean mRunningInMainProcess;
- private PerformanceMonitor mPerformanceMonitor;
+ private TvInputManagerHelper mTvInputManagerHelper;
+ private boolean mStarted;
+ private EpgFetcher mEpgFetcher;
+ private TunerInputController mTunerInputController;
@Override
public void onCreate() {
super.onCreate();
- if (!PermissionUtils.hasInternet(this)) {
- // When an isolated process starts, just skip all the initialization.
- return;
- }
- Debug.getTimer(Debug.TAG_START_UP_TIMER).start();
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start TvApplication.onCreate");
- SharedPreferencesUtils.initialize(this, new Runnable() {
- @Override
- public void run() {
- if (mRunningInMainProcess != null && mRunningInMainProcess) {
- checkTunerServiceOnFirstLaunch();
- }
- }
- });
- // TunerPreferences is used to enable/disable the tuner input even when TUNER feature is
- // disabled.
- TunerPreferences.initialize(this);
+ SharedPreferencesUtils.initialize(
+ this,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mRunningInMainProcess != null && mRunningInMainProcess) {
+ checkTunerServiceOnFirstLaunch();
+ }
+ }
+ });
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
mVersionName = pInfo.versionName;
@@ -159,73 +138,47 @@ public class TvApplication extends Application implements ApplicationSingletons
}
Log.i(TAG, "Starting Live TV " + getVersionName());
- // Only set StrictMode for ENG builds because the build server only produces userdebug
- // builds.
- if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
- StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
- new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog();
- StrictMode.VmPolicy.Builder vmPolicyBuilder =
- new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath();
- if (!TvCommonUtils.isRunningInTest()) {
- threadPolicyBuilder.penaltyDialog();
- }
- StrictMode.setThreadPolicy(threadPolicyBuilder.build());
- StrictMode.setVmPolicy(vmPolicyBuilder.build());
- }
- if (BuildConfig.ENG && !SystemProperties.ALLOW_ANALYTICS_IN_ENG.getValue()) {
- mAnalytics = StubAnalytics.getInstance(this);
- } else {
- mAnalytics = StubAnalytics.getInstance(this);
- }
- mTracker = mAnalytics.getDefaultTracker();
- getTvInputManagerHelper();
// In SetupFragment, transitions are set in the constructor. Because the fragment can be
// created in Activity.onCreate() by the framework, SetupAnimationHelper should be
// initialized here before Activity.onCreate() is called.
+ mEpgFetcher = EpgFetcherImpl.create(this);
SetupAnimationHelper.initialize(this);
-
+ getTvInputManagerHelper();
Log.i(TAG, "Started Live TV " + mVersionName);
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate");
- getPerformanceMonitor().stopTimer(sAppStartTimer, EventNames.APPLICATION_ONCREATE);
}
- private void setCurrentRunningProcess(boolean isMainProcess) {
- if (mRunningInMainProcess != null) {
- SoftPreconditions.checkState(isMainProcess == mRunningInMainProcess);
+ /** Initializes application. It is a noop if called twice. */
+ @Override
+ public void start() {
+ if (mStarted) {
return;
}
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "start TvApplication.setCurrentRunningProcess");
- mRunningInMainProcess = isMainProcess;
- if (CommonFeatures.DVR.isEnabled(this)) {
- mDvrStorageStatusManager = new DvrStorageStatusManager(this, mRunningInMainProcess);
- }
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // Fetch remote config
- getRemoteConfig().fetch(null);
- return null;
- }
- }.execute();
+ mStarted = true;
+ mRunningInMainProcess = true;
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log("start TvApplication.start");
if (mRunningInMainProcess) {
- getTvInputManagerHelper().addCallback(new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- if (Features.TUNER.isEnabled(TvApplication.this) && TextUtils.equals(inputId,
- TunerTvInputService.getInputId(TvApplication.this))) {
- TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this);
- }
- handleInputCountChanged();
- }
-
- @Override
- public void onInputRemoved(String inputId) {
- handleInputCountChanged();
- }
- });
- if (Features.TUNER.isEnabled(this)) {
+ getTvInputManagerHelper()
+ .addCallback(
+ new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ if (TvFeatures.TUNER.isEnabled(TvApplication.this)
+ && TextUtils.equals(
+ inputId, getEmbeddedTunerInputId())) {
+ TunerInputInfoUtils.updateTunerInputInfo(
+ TvApplication.this);
+ }
+ handleInputCountChanged();
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ handleInputCountChanged();
+ }
+ });
+ if (TvFeatures.TUNER.isEnabled(this)) {
// If the tuner input service is added before the app is started, we need to
// handle it here.
TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this);
@@ -235,58 +188,61 @@ public class TvApplication extends Application implements ApplicationSingletons
mDvrManager = new DvrManager(this);
mRecordingScheduler = RecordingScheduler.createScheduler(this);
}
- EpgFetcher.getInstance(this).startRoutineService();
+ mEpgFetcher.startRoutineService();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ChannelPreviewUpdater.getInstance(this).startRoutineService();
RecordedProgramPreviewUpdater.getInstance(this)
.updatePreviewDataForRecordedPrograms();
}
}
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "finish TvApplication.setCurrentRunningProcess");
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.start");
}
private void checkTunerServiceOnFirstLaunch() {
- SharedPreferences sharedPreferences = this.getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
+ SharedPreferences sharedPreferences =
+ this.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true);
if (isFirstLaunch) {
if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!");
- TunerInputController.onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
+ getTunerInputController()
+ .onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false);
editor.apply();
}
}
- /**
- * Returns the {@link DvrManager}.
- */
+ @Override
+ public EpgFetcher getEpgFetcher() {
+ return mEpgFetcher;
+ }
+
+ @Override
+ public synchronized SetupUtils getSetupUtils() {
+ return SetupUtils.createForTvSingletons(this);
+ }
+
+ /** Returns the {@link DvrManager}. */
@Override
public DvrManager getDvrManager() {
return mDvrManager;
}
- /**
- * Returns the {@link DvrScheduleManager}.
- */
+ /** Returns the {@link DvrScheduleManager}. */
@Override
public DvrScheduleManager getDvrScheduleManager() {
return mDvrScheduleManager;
}
- /**
- * Returns the {@link RecordingScheduler}.
- */
+ /** Returns the {@link RecordingScheduler}. */
@Override
@Nullable
public RecordingScheduler getRecordingScheduler() {
return mRecordingScheduler;
}
- /**
- * Returns the {@link DvrWatchedPositionManager}.
- */
+ /** Returns the {@link DvrWatchedPositionManager}. */
@Override
public DvrWatchedPositionManager getDvrWatchedPositionManager() {
if (mDvrWatchedPositionManager == null) {
@@ -304,25 +260,7 @@ public class TvApplication extends Application implements ApplicationSingletons
return mInputSessionManager;
}
- /**
- * Returns the {@link Analytics}.
- */
- @Override
- public Analytics getAnalytics() {
- return mAnalytics;
- }
-
- /**
- * Returns the default tracker.
- */
- @Override
- public Tracker getTracker() {
- return mTracker;
- }
-
- /**
- * Returns {@link ChannelDataManager}.
- */
+ /** Returns {@link ChannelDataManager}. */
@Override
public ChannelDataManager getChannelDataManager() {
if (mChannelDataManager == null) {
@@ -337,23 +275,22 @@ public class TvApplication extends Application implements ApplicationSingletons
return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished();
}
- /**
- * Returns {@link ProgramDataManager}.
- */
+ /** Returns {@link ProgramDataManager}. */
@Override
public ProgramDataManager getProgramDataManager() {
if (mProgramDataManager != null) {
return mProgramDataManager;
}
- Utils.runInMainThreadAndWait(new Runnable() {
- @Override
- public void run() {
- if (mProgramDataManager == null) {
- mProgramDataManager = new ProgramDataManager(TvApplication.this);
- mProgramDataManager.start();
- }
- }
- });
+ Utils.runInMainThreadAndWait(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mProgramDataManager == null) {
+ mProgramDataManager = new ProgramDataManager(TvApplication.this);
+ mProgramDataManager.start();
+ }
+ }
+ });
return mProgramDataManager;
}
@@ -362,9 +299,7 @@ public class TvApplication extends Application implements ApplicationSingletons
return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished();
}
- /**
- * Returns {@link PreviewDataManager}.
- */
+ /** Returns {@link PreviewDataManager}. */
@TargetApi(Build.VERSION_CODES.O)
@Override
public PreviewDataManager getPreviewDataManager() {
@@ -375,9 +310,7 @@ public class TvApplication extends Application implements ApplicationSingletons
return mPreviewDataManager;
}
- /**
- * Returns {@link DvrDataManager}.
- */
+ /** Returns {@link DvrDataManager}. */
@TargetApi(Build.VERSION_CODES.N)
@Override
public DvrDataManager getDvrDataManager() {
@@ -389,53 +322,39 @@ public class TvApplication extends Application implements ApplicationSingletons
return mDvrDataManager;
}
- /**
- * Returns {@link DvrStorageStatusManager}.
- */
- @TargetApi(Build.VERSION_CODES.N)
- @Override
- public DvrStorageStatusManager getDvrStorageStatusManager() {
- return mDvrStorageStatusManager;
- }
-
- /**
- * Returns {@link TvInputManagerHelper}.
- */
@Override
- public TvInputManagerHelper getTvInputManagerHelper() {
- if (mTvInputManagerHelper == null) {
- mTvInputManagerHelper = new TvInputManagerHelper(this);
- mTvInputManagerHelper.start();
+ @TargetApi(Build.VERSION_CODES.N)
+ public RecordingStorageStatusManager getRecordingStorageStatusManager() {
+ if (mDvrStorageStatusManager == null) {
+ mDvrStorageStatusManager = new DvrStorageStatusManager(this);
}
- return mTvInputManagerHelper;
+ return mDvrStorageStatusManager;
}
- /**
- * Returns the main activity information.
- */
+ /** Returns the main activity information. */
@Override
public MainActivityWrapper getMainActivityWrapper() {
return mMainActivityWrapper;
}
- /**
- * Returns the {@link AccountHelper}.
- */
+ /** Returns {@link TvInputManagerHelper}. */
@Override
- public AccountHelper getAccountHelper() {
- if (mAccountHelper == null) {
- mAccountHelper = new AccountHelper(getApplicationContext());
+ public TvInputManagerHelper getTvInputManagerHelper() {
+ if (mTvInputManagerHelper == null) {
+ mTvInputManagerHelper = new TvInputManagerHelper(this);
+ mTvInputManagerHelper.start();
}
- return mAccountHelper;
+ return mTvInputManagerHelper;
}
@Override
- public RemoteConfig getRemoteConfig() {
- if (mRemoteConfig == null) {
- // No need to synchronize this, it does not hurt to create two and throw one away.
- mRemoteConfig = DefaultConfigManager.createInstance(this).getRemoteConfig();
+ public synchronized TunerInputController getTunerInputController() {
+ if (mTunerInputController == null) {
+ mTunerInputController =
+ new TunerInputController(
+ ComponentName.unflattenFromString(getEmbeddedTunerInputId()));
}
- return mRemoteConfig;
+ return mTunerInputController;
}
@Override
@@ -443,17 +362,9 @@ public class TvApplication extends Application implements ApplicationSingletons
return mRunningInMainProcess != null && mRunningInMainProcess;
}
- @Override
- public PerformanceMonitor getPerformanceMonitor() {
- if (mPerformanceMonitor == null) {
- mPerformanceMonitor = StubPerformanceMonitor.initialize(this);
- }
- return mPerformanceMonitor;
- }
-
/**
- * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in
- * {@link SelectInputActivity#onDestroy}.
+ * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in {@link
+ * SelectInputActivity#onDestroy}.
*/
public void setSelectInputActivity(SelectInputActivity activity) {
mSelectInputActivity = activity;
@@ -467,18 +378,19 @@ public class TvApplication extends Application implements ApplicationSingletons
}
}
- /**
- * Handles the global key KEYCODE_TV.
- */
+ /** Handles the global key KEYCODE_TV. */
public void handleTvKey() {
if (!mMainActivityWrapper.isResumed()) {
startMainActivity(null);
}
}
- /**
- * Handles the global key KEYCODE_TV_INPUT.
- */
+ /** Handles the global key KEYCODE_DVR. */
+ public void handleDvrKey() {
+ startActivity(new Intent(this, DvrBrowseActivity.class));
+ }
+
+ /** Handles the global key KEYCODE_TV_INPUT. */
public void handleTvInputKey() {
TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
List<TvInputInfo> tvInputs = tvInputManager.getTvInputList();
@@ -497,22 +409,25 @@ public class TvApplication extends Application implements ApplicationSingletons
if (inputCount < 2) {
return;
}
- Activity activityToHandle = mMainActivityWrapper.isResumed()
- ? mMainActivityWrapper.getMainActivity() : mSelectInputActivity;
+ Activity activityToHandle =
+ mMainActivityWrapper.isResumed()
+ ? mMainActivityWrapper.getMainActivity()
+ : mSelectInputActivity;
if (activityToHandle != null) {
// If startActivity is called, MainActivity.onPause is unnecessarily called. To
// prevent it, MainActivity.dispatchKeyEvent is directly called.
activityToHandle.dispatchKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
- activityToHandle.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
- KeyEvent.KEYCODE_TV_INPUT));
+ activityToHandle.dispatchKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT));
} else if (mMainActivityWrapper.isStarted()) {
Bundle extras = new Bundle();
extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
startMainActivity(extras);
} else {
- startActivity(new Intent(this, SelectInputActivity.class).setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK));
+ startActivity(
+ new Intent(this, SelectInputActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
@@ -520,8 +435,8 @@ public class TvApplication extends Application implements ApplicationSingletons
// The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent
// sent to the root activity. Having said that, we should be fine here since such an intent
// does not carry any important user data.
- Intent intent = new Intent(this, MainActivity.class)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Intent intent =
+ new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (extras != null) {
intent.putExtras(extras);
}
@@ -538,36 +453,39 @@ public class TvApplication extends Application implements ApplicationSingletons
}
/**
- * Checks the input counts and enable/disable TvActivity. Also updates the input list in
- * {@link SetupUtils}.
+ * Checks the input counts and enable/disable TvActivity. Also upda162 the input list in {@link
+ * SetupUtils}.
*/
+ @Override
public void handleInputCountChanged() {
handleInputCountChanged(false, false, false);
}
/**
- * Checks the input counts and enable/disable TvActivity. Also updates the input list in
- * {@link SetupUtils}.
+ * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link
+ * SetupUtils}.
*
- * @param calledByTunerServiceChanged true if it is called when TunerTvInputService
- * is enabled or disabled.
+ * @param calledByTunerServiceChanged true if it is called when BaseTunerTvInputService is
+ * enabled or disabled.
* @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true.
- * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts
- * by default. But, if dontKillApp is true, the app won't restart.
+ * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by
+ * default. But, if dontKillApp is true, the app won't restart.
*/
- public void handleInputCountChanged(boolean calledByTunerServiceChanged,
- boolean tunerServiceEnabled, boolean dontKillApp) {
+ public void handleInputCountChanged(
+ boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp) {
TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
- boolean enable = (calledByTunerServiceChanged && tunerServiceEnabled)
- || Features.UNHIDE.isEnabled(TvApplication.this);
+ boolean enable =
+ (calledByTunerServiceChanged && tunerServiceEnabled)
+ || TvFeatures.UNHIDE.isEnabled(TvApplication.this);
if (!enable) {
List<TvInputInfo> inputs = inputManager.getTvInputList();
boolean skipTunerInputCheck = false;
// Enable the TvActivity only if there is at least one tuner type input.
if (!skipTunerInputCheck) {
for (TvInputInfo input : inputs) {
- if (calledByTunerServiceChanged && !tunerServiceEnabled
- && TunerTvInputService.getInputId(this).equals(input.getId())) {
+ if (calledByTunerServiceChanged
+ && !tunerServiceEnabled
+ && getEmbeddedTunerInputId().equals(input.getId())) {
continue;
}
if (input.getType() == TvInputInfo.TYPE_TUNER) {
@@ -580,43 +498,20 @@ public class TvApplication extends Application implements ApplicationSingletons
}
PackageManager packageManager = getPackageManager();
ComponentName name = new ComponentName(this, TvActivity.class);
- int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ int newState =
+ enable
+ ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (packageManager.getComponentEnabledSetting(name) != newState) {
- packageManager.setComponentEnabledSetting(name, newState,
- dontKillApp ? PackageManager.DONT_KILL_APP : 0);
+ packageManager.setComponentEnabledSetting(
+ name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0);
Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV.");
}
- SetupUtils.getInstance(TvApplication.this).onInputListUpdated(inputManager);
- }
-
- /**
- * Returns the @{@link ApplicationSingletons} using the application context.
- */
- public static ApplicationSingletons getSingletons(Context context) {
- // No need to be "synchronized" because this doesn't create any instance.
- if (sAppSingletons == null) {
- sAppSingletons = (ApplicationSingletons) context.getApplicationContext();
- }
- return sAppSingletons;
+ getSetupUtils().onInputListUpdated(inputManager);
}
- /**
- * Sets true, if TvApplication is running on the main process. If TvApplication runs on
- * tuner process or other process, it sets false.
- *
- * Note: it should be called at the beginning of Service.onCreate Activity.onCreate, or
- * BroadcastReceiver.onCreate. When it is firstly called after launch, it runs process
- * specific initializations.
- */
- public static void setCurrentRunningProcess(Context context, boolean isMainProcess) {
- // TODO(b/63064354) TvApplication should not have to know if it is "the main process"
- if (context.getApplicationContext() instanceof TvApplication) {
- TvApplication tvApplication = (TvApplication) context.getApplicationContext();
- tvApplication.setCurrentRunningProcess(isMainProcess);
- } else {
- // Application context can be MockTvApplication.
- Log.w(TAG, "It is not a context of TvApplication");
- }
+ @Override
+ public Executor getDbExecutor() {
+ return DB_EXECUTOR;
}
}
diff --git a/src/com/android/tv/TvFeatures.java b/src/com/android/tv/TvFeatures.java
new file mode 100644
index 00000000..d2cf76e7
--- /dev/null
+++ b/src/com/android/tv/TvFeatures.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv;
+
+import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE;
+import static com.android.tv.common.feature.FeatureUtils.AND;
+import static com.android.tv.common.feature.FeatureUtils.OFF;
+import static com.android.tv.common.feature.FeatureUtils.ON;
+import static com.android.tv.common.feature.FeatureUtils.OR;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.annotation.VisibleForTesting;
+import com.android.tv.common.experiments.Experiments;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.feature.ExperimentFeature;
+import com.android.tv.common.feature.Feature;
+import com.android.tv.common.feature.FeatureUtils;
+import com.android.tv.common.feature.GServiceFeature;
+import com.android.tv.common.feature.PropertyFeature;
+import com.android.tv.common.feature.Sdk;
+import com.android.tv.common.feature.TestableFeature;
+import com.android.tv.common.util.PermissionUtils;
+
+import com.google.android.tv.partner.support.PartnerCustomizations;
+
+/**
+ * List of {@link Feature} for the Live TV App.
+ *
+ * <p>Remove the {@code Feature} once it is launched.
+ */
+public final class TvFeatures extends CommonFeatures {
+
+ /** When enabled use system setting for turning on analytics. */
+ public static final Feature ANALYTICS_OPT_IN =
+ ExperimentFeature.from(Experiments.ENABLE_ANALYTICS_VIA_CHECKBOX);
+ /** When enabled shows a list of failed recordings */
+ public static final Feature DVR_FAILED_LIST = ENG_ONLY_FEATURE;
+ /**
+ * Analytics that include sensitive information such as channel or program identifiers.
+ *
+ * <p>See <a href="http://b/22062676">b/22062676</a>
+ */
+ public static final Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN);
+
+ public static final Feature EPG_SEARCH =
+ PropertyFeature.create("feature_tv_use_epg_search", false);
+
+ private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide";
+ /** A flag which indicates that LC app is unhidden even when there is no input. */
+ public static final Feature UNHIDE =
+ OR(
+ new GServiceFeature(GSERVICE_KEY_UNHIDE, false),
+ new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ // If LC app runs as non-system app, we unhide the app.
+ return !PermissionUtils.hasAccessAllEpg(context);
+ }
+ });
+
+ public static final Feature PICTURE_IN_PICTURE =
+ new Feature() {
+ private Boolean mEnabled;
+
+ @Override
+ public boolean isEnabled(Context context) {
+ if (mEnabled == null) {
+ mEnabled =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ && context.getPackageManager()
+ .hasSystemFeature(
+ PackageManager.FEATURE_PICTURE_IN_PICTURE);
+ }
+ return mEnabled;
+ }
+ };
+
+ /** Enable a conflict dialog between currently watched channel and upcoming recording. */
+ public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF;
+
+ /** Use input blacklist to disable partner's tuner input. */
+ public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON;
+
+ @VisibleForTesting
+ public static final Feature TEST_FEATURE = PropertyFeature.create("test_feature", false);
+
+ private TvFeatures() {}
+}
diff --git a/src/com/android/tv/TvOptionsManager.java b/src/com/android/tv/TvOptionsManager.java
index 493e039c..4e0636ff 100644
--- a/src/com/android/tv/TvOptionsManager.java
+++ b/src/com/android/tv/TvOptionsManager.java
@@ -20,9 +20,7 @@ import android.content.Context;
import android.media.tv.TvTrackInfo;
import android.support.annotation.IntDef;
import android.util.SparseArray;
-
import com.android.tv.data.DisplayMode;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
@@ -33,9 +31,17 @@ import java.util.Locale;
*/
public class TvOptionsManager {
@Retention(RetentionPolicy.SOURCE)
- @IntDef({OPTION_CLOSED_CAPTIONS, OPTION_DISPLAY_MODE, OPTION_SYSTEMWIDE_PIP, OPTION_MULTI_AUDIO,
- OPTION_MORE_CHANNELS, OPTION_DEVELOPER, OPTION_SETTINGS})
+ @IntDef({
+ OPTION_CLOSED_CAPTIONS,
+ OPTION_DISPLAY_MODE,
+ OPTION_SYSTEMWIDE_PIP,
+ OPTION_MULTI_AUDIO,
+ OPTION_MORE_CHANNELS,
+ OPTION_DEVELOPER,
+ OPTION_SETTINGS
+ })
public @interface OptionType {}
+
public static final int OPTION_CLOSED_CAPTIONS = 0;
public static final int OPTION_DISPLAY_MODE = 1;
public static final int OPTION_SYSTEMWIDE_PIP = 2;
@@ -57,6 +63,7 @@ public class TvOptionsManager {
/**
* Returns a suitable displayed string for the given option type under current settings.
+ *
* @param option the type of option, should be one of {@link OptionType}.
*/
public String getOptionString(@OptionType int option) {
@@ -67,8 +74,9 @@ public class TvOptionsManager {
}
return new Locale(mClosedCaptionsLanguage).getDisplayName();
case OPTION_DISPLAY_MODE:
- return ((MainActivity) mContext).getTvViewUiManager()
- .isDisplayModeAvailable(mDisplayMode)
+ return ((MainActivity) mContext)
+ .getTvViewUiManager()
+ .isDisplayModeAvailable(mDisplayMode)
? DisplayMode.getLabel(mDisplayMode, mContext)
: DisplayMode.getLabel(DisplayMode.MODE_NORMAL, mContext);
case OPTION_MULTI_AUDIO:
@@ -77,27 +85,25 @@ public class TvOptionsManager {
return "";
}
- /**
- * Handles changing selection of closed caption.
- */
+ /** Handles changing selection of closed caption. */
public void onClosedCaptionsChanged(TvTrackInfo track, int trackIndex) {
- mClosedCaptionsLanguage = (track == null) ?
- null : (track.getLanguage() != null) ? track.getLanguage()
- : mContext.getString(R.string.closed_caption_unknown_language, trackIndex + 1);
+ mClosedCaptionsLanguage =
+ (track == null)
+ ? null
+ : (track.getLanguage() != null)
+ ? track.getLanguage()
+ : mContext.getString(
+ R.string.closed_caption_unknown_language, trackIndex + 1);
notifyOptionChanged(OPTION_CLOSED_CAPTIONS);
}
- /**
- * Handles changing selection of display mode.
- */
+ /** Handles changing selection of display mode. */
public void onDisplayModeChanged(int displayMode) {
mDisplayMode = displayMode;
notifyOptionChanged(OPTION_DISPLAY_MODE);
}
- /**
- * Handles changing selection of multi-audio.
- */
+ /** Handles changing selection of multi-audio. */
public void onMultiAudioChanged(String multiAudio) {
mMultiAudio = multiAudio;
notifyOptionChanged(OPTION_MULTI_AUDIO);
@@ -110,17 +116,13 @@ public class TvOptionsManager {
}
}
- /**
- * Sets listeners to changes of the given option type.
- */
+ /** Sets listeners to changes of the given option type. */
public void setOptionChangedListener(int option, OptionChangedListener listener) {
mOptionChangedListeners.put(option, listener);
}
- /**
- * An interface used to monitor option changes.
- */
+ /** An interface used to monitor option changes. */
public interface OptionChangedListener {
void onOptionChanged(@OptionType int optionType, String newString);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ApplicationSingletons.java b/src/com/android/tv/TvSingletons.java
index ac7d4c4a..0c7f78a3 100644
--- a/src/com/android/tv/ApplicationSingletons.java
+++ b/src/com/android/tv/TvSingletons.java
@@ -16,29 +16,42 @@
package com.android.tv;
+import android.content.Context;
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.Tracker;
-import com.android.tv.config.RemoteConfig;
+import com.android.tv.common.BaseApplication;
+import com.android.tv.common.BaseSingletons;
+import com.android.tv.common.experiments.ExperimentLoader;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.recorder.RecordingScheduler;
import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.util.AccountHelper;
+import com.android.tv.tuner.TunerInputController;
+import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.account.AccountHelper;
+import java.util.concurrent.Executor;
+import javax.inject.Provider;
-/**
- * Interface with getters for application scoped singletons.
- */
-public interface ApplicationSingletons {
+/** Interface with getters for application scoped singletons. */
+public interface TvSingletons extends BaseSingletons {
+
+ /** Returns the @{@link TvSingletons} using the application context. */
+ static TvSingletons getSingletons(Context context) {
+ return (TvSingletons) BaseApplication.getSingletons(context);
+ }
Analytics getAnalytics();
+ void handleInputCountChanged();
+
ChannelDataManager getChannelDataManager();
/**
@@ -59,8 +72,6 @@ public interface ApplicationSingletons {
DvrDataManager getDvrDataManager();
- DvrStorageStatusManager getDvrStorageStatusManager();
-
DvrScheduleManager getDvrScheduleManager();
DvrManager getDvrManager();
@@ -73,15 +84,25 @@ public interface ApplicationSingletons {
Tracker getTracker();
- TvInputManagerHelper getTvInputManagerHelper();
-
MainActivityWrapper getMainActivityWrapper();
AccountHelper getAccountHelper();
- RemoteConfig getRemoteConfig();
-
boolean isRunningInMainProcess();
PerformanceMonitor getPerformanceMonitor();
+
+ TvInputManagerHelper getTvInputManagerHelper();
+
+ Provider<EpgReader> providesEpgReader();
+
+ EpgFetcher getEpgFetcher();
+
+ SetupUtils getSetupUtils();
+
+ TunerInputController getTunerInputController();
+
+ ExperimentLoader getExperimentLoader();
+
+ Executor getDbExecutor();
}
diff --git a/src/com/android/tv/analytics/Analytics.java b/src/com/android/tv/analytics/Analytics.java
index 27085de7..e0626423 100644
--- a/src/com/android/tv/analytics/Analytics.java
+++ b/src/com/android/tv/analytics/Analytics.java
@@ -16,21 +16,17 @@
package com.android.tv.analytics;
-/**
- * Provides Trackers used for user activity analysis.
- */
+/** Provides Trackers used for user activity analysis. */
public interface Analytics {
Tracker getDefaultTracker();
- /**
- * Returns whether the state of the application-level opt is on.
- */
+ /** Returns whether the state of the application-level opt is on. */
boolean isAppOptOut();
/**
- * Sets or resets the application-level opt out flag. If set, no hits will be sent.
- * The value of this flag will <i>not</i> persist across application starts. The
- * correct value should thus be set in application initialization code.
+ * Sets or resets the application-level opt out flag. If set, no hits will be sent. The value of
+ * this flag will <i>not</i> persist across application starts. The correct value should thus be
+ * set in application initialization code.
*
* @param optOut {@code true} if application-level opt out should be enforced.
*/
diff --git a/src/com/android/tv/analytics/ConfigurationInfo.java b/src/com/android/tv/analytics/ConfigurationInfo.java
index 41e8baeb..b6bfc5aa 100644
--- a/src/com/android/tv/analytics/ConfigurationInfo.java
+++ b/src/com/android/tv/analytics/ConfigurationInfo.java
@@ -16,9 +16,7 @@
package com.android.tv.analytics;
-/**
- * Data useful for tracking that doesn't change often.
- */
+/** Data useful for tracking that doesn't change often. */
public class ConfigurationInfo {
public final int systemInputCount;
public final int nonSystemInputCount;
diff --git a/src/com/android/tv/analytics/HasTrackerLabel.java b/src/com/android/tv/analytics/HasTrackerLabel.java
index 566e5f1a..04896850 100644
--- a/src/com/android/tv/analytics/HasTrackerLabel.java
+++ b/src/com/android/tv/analytics/HasTrackerLabel.java
@@ -23,8 +23,6 @@ package com.android.tv.analytics;
*/
public interface HasTrackerLabel {
- /**
- * Returns the label.
- */
+ /** Returns the label. */
String getTrackerLabel();
}
diff --git a/src/com/android/tv/analytics/SendChannelStatusRunnable.java b/src/com/android/tv/analytics/SendChannelStatusRunnable.java
index b5b5805c..4a84434c 100644
--- a/src/com/android/tv/analytics/SendChannelStatusRunnable.java
+++ b/src/com/android/tv/analytics/SendChannelStatusRunnable.java
@@ -20,11 +20,9 @@ import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.MainThread;
-
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.RecurringRunner;
-
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -32,53 +30,63 @@ import java.util.concurrent.TimeUnit;
* Periodically sends analytics data with the channel count.
*
* <p>
- * <p>This should only be started from a user activity
- * like {@link com.android.tv.MainActivity}.
+ *
+ * <p>This should only be started from a user activity like {@link com.android.tv.MainActivity}.
*/
@MainThread
public class SendChannelStatusRunnable implements Runnable {
private static final long SEND_CHANNEL_STATUS_INTERVAL_MS = TimeUnit.DAYS.toMillis(1);
- public static RecurringRunner startChannelStatusRecurringRunner(Context context,
- Tracker tracker, ChannelDataManager channelDataManager) {
+ public static RecurringRunner startChannelStatusRecurringRunner(
+ Context context, Tracker tracker, ChannelDataManager channelDataManager) {
- final SendChannelStatusRunnable sendChannelStatusRunnable = new SendChannelStatusRunnable(
- channelDataManager, tracker);
+ final SendChannelStatusRunnable sendChannelStatusRunnable =
+ new SendChannelStatusRunnable(channelDataManager, tracker);
- Runnable onStopRunnable = new Runnable() {
- @Override
- public void run() {
- sendChannelStatusRunnable.setDbLoadListener(null);
- }
- };
- final RecurringRunner recurringRunner = new RecurringRunner(context,
- SEND_CHANNEL_STATUS_INTERVAL_MS, sendChannelStatusRunnable, onStopRunnable);
+ Runnable onStopRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ sendChannelStatusRunnable.setDbLoadListener(null);
+ }
+ };
+ final RecurringRunner recurringRunner =
+ new RecurringRunner(
+ context,
+ SEND_CHANNEL_STATUS_INTERVAL_MS,
+ sendChannelStatusRunnable,
+ onStopRunnable);
if (channelDataManager.isDbLoadFinished()) {
sendChannelStatusRunnable.setDbLoadListener(null);
recurringRunner.start();
} else {
- //Start the recurring runnable after the channel DB is finished loading.
- sendChannelStatusRunnable.setDbLoadListener(new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- // This is called inside an iterator of Listeners so the remove step is done
- // via a post on the main thread
- new Handler(Looper.getMainLooper()).post(new Runnable() {
+ // Start the recurring runnable after the channel DB is finished loading.
+ sendChannelStatusRunnable.setDbLoadListener(
+ new ChannelDataManager.Listener() {
@Override
- public void run() {
- sendChannelStatusRunnable.setDbLoadListener(null);
+ public void onLoadFinished() {
+ // This is called inside an iterator of Listeners so the remove step is
+ // done
+ // via a post on the main thread
+ new Handler(Looper.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ sendChannelStatusRunnable.setDbLoadListener(
+ null);
+ }
+ });
+ recurringRunner.start();
}
- });
- recurringRunner.start();
- }
- @Override
- public void onChannelListUpdated() { }
+ @Override
+ public void onChannelListUpdated() {}
- @Override
- public void onChannelBrowsableChanged() { }
- });
+ @Override
+ public void onChannelBrowsableChanged() {}
+ });
}
return recurringRunner;
}
diff --git a/src/com/android/tv/analytics/SendConfigInfoRunnable.java b/src/com/android/tv/analytics/SendConfigInfoRunnable.java
index 41392a6d..d4674086 100644
--- a/src/com/android/tv/analytics/SendConfigInfoRunnable.java
+++ b/src/com/android/tv/analytics/SendConfigInfoRunnable.java
@@ -17,14 +17,10 @@
package com.android.tv.analytics;
import android.media.tv.TvInputInfo;
-
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.List;
-/**
- * Sends ConfigurationInfo once a day.
- */
+/** Sends ConfigurationInfo once a day. */
public class SendConfigInfoRunnable implements Runnable {
private final Tracker mTracker;
private final TvInputManagerHelper mTvInputManagerHelper;
@@ -46,8 +42,8 @@ public class SendConfigInfoRunnable implements Runnable {
nonSystemInputCount++;
}
}
- ConfigurationInfo configurationInfo = new ConfigurationInfo(systemInputCount,
- nonSystemInputCount);
+ ConfigurationInfo configurationInfo =
+ new ConfigurationInfo(systemInputCount, nonSystemInputCount);
mTracker.sendConfigurationInfo(configurationInfo);
}
}
diff --git a/src/com/android/tv/analytics/StubAnalytics.java b/src/com/android/tv/analytics/StubAnalytics.java
index 99c10d94..2c58b9b8 100644
--- a/src/com/android/tv/analytics/StubAnalytics.java
+++ b/src/com/android/tv/analytics/StubAnalytics.java
@@ -19,9 +19,7 @@ package com.android.tv.analytics;
import android.app.Application;
import android.content.Context;
-/**
- * An implementation of {@link Analytics} that returns a {@link StubTracker}.
- */
+/** An implementation of {@link Analytics} that returns a {@link StubTracker}. */
public final class StubAnalytics implements Analytics {
public static StubAnalytics getInstance(Application application) {
return new StubAnalytics(application);
@@ -30,8 +28,7 @@ public final class StubAnalytics implements Analytics {
private final Tracker mTracker = new StubTracker();
private boolean mOptOut = true;
- private StubAnalytics(Context context) {
- }
+ private StubAnalytics(Context context) {}
@Override
public Tracker getDefaultTracker() {
diff --git a/src/com/android/tv/analytics/StubTracker.java b/src/com/android/tv/analytics/StubTracker.java
index 6e64ebca..e11b91c2 100644
--- a/src/com/android/tv/analytics/StubTracker.java
+++ b/src/com/android/tv/analytics/StubTracker.java
@@ -17,111 +17,108 @@
package com.android.tv.analytics;
import android.support.annotation.VisibleForTesting;
-
import com.android.tv.TimeShiftManager;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
-/**
- * A implementation of Tracker that does nothing.
- */
+/** A implementation of Tracker that does nothing. */
@VisibleForTesting
public class StubTracker implements Tracker {
@Override
- public void sendChannelCount(int browsableChannelCount, int totalChannelCount) { }
+ public void sendChannelCount(int browsableChannelCount, int totalChannelCount) {}
@Override
- public void sendConfigurationInfo(ConfigurationInfo info) { }
+ public void sendConfigurationInfo(ConfigurationInfo info) {}
@Override
- public void sendMainStart() { }
+ public void sendMainStart() {}
@Override
- public void sendMainStop(long durationMs) { }
+ public void sendMainStop(long durationMs) {}
@Override
- public void sendScreenView(String screenName) { }
+ public void sendScreenView(String screenName) {}
@Override
- public void sendChannelViewStart(Channel channel, boolean tunedByRecommendation) { }
+ public void sendChannelViewStart(Channel channel, boolean tunedByRecommendation) {}
@Override
- public void sendChannelTuneTime(Channel channel, long durationMs) { }
+ public void sendChannelTuneTime(Channel channel, long durationMs) {}
@Override
- public void sendChannelViewStop(Channel channel, long durationMs) { }
+ public void sendChannelViewStop(Channel channel, long durationMs) {}
@Override
- public void sendChannelUp() { }
+ public void sendChannelUp() {}
@Override
- public void sendChannelDown() { }
+ public void sendChannelDown() {}
@Override
- public void sendShowMenu() { }
+ public void sendShowMenu() {}
@Override
- public void sendHideMenu(long durationMs) { }
+ public void sendHideMenu(long durationMs) {}
@Override
- public void sendMenuClicked(String label) { }
+ public void sendMenuClicked(String label) {}
@Override
- public void sendMenuClicked(int labelResId) { }
+ public void sendMenuClicked(int labelResId) {}
@Override
- public void sendShowEpg() { }
+ public void sendShowEpg() {}
@Override
- public void sendEpgItemClicked() { }
+ public void sendEpgItemClicked() {}
@Override
- public void sendHideEpg(long durationMs) { }
+ public void sendHideEpg(long durationMs) {}
@Override
- public void sendShowChannelSwitch() { }
+ public void sendShowChannelSwitch() {}
@Override
- public void sendHideChannelSwitch(long durationMs) { }
+ public void sendHideChannelSwitch(long durationMs) {}
@Override
- public void sendChannelNumberInput() { }
+ public void sendChannelNumberInput() {}
@Override
- public void sendChannelInputNavigated() { }
+ public void sendChannelInputNavigated() {}
@Override
- public void sendChannelNumberItemClicked() { }
+ public void sendChannelNumberItemClicked() {}
@Override
- public void sendChannelNumberItemChosenByTimeout() { }
+ public void sendChannelNumberItemChosenByTimeout() {}
@Override
- public void sendChannelVideoUnavailable(Channel channel, int reason) { }
+ public void sendChannelVideoUnavailable(Channel channel, int reason) {}
@Override
- public void sendAc3PassthroughCapabilities(boolean isSupported) { }
+ public void sendAc3PassthroughCapabilities(boolean isSupported) {}
@Override
- public void sendInputConnectionFailure(String inputId) { }
+ public void sendInputConnectionFailure(String inputId) {}
@Override
- public void sendInputDisconnected(String inputId) { }
+ public void sendInputDisconnected(String inputId) {}
@Override
- public void sendShowInputSelection() { }
+ public void sendShowInputSelection() {}
@Override
- public void sendHideInputSelection(long durationMs) { }
+ public void sendHideInputSelection(long durationMs) {}
@Override
- public void sendInputSelected(String inputLabel) { }
+ public void sendInputSelected(String inputLabel) {}
@Override
- public void sendShowSidePanel(HasTrackerLabel trackerLabel) { }
+ public void sendShowSidePanel(HasTrackerLabel trackerLabel) {}
@Override
- public void sendHideSidePanel(HasTrackerLabel trackerLabel, long durationMs) { }
+ public void sendHideSidePanel(HasTrackerLabel trackerLabel, long durationMs) {}
@Override
- public void sendTimeShiftAction(@TimeShiftManager.TimeShiftActionId int actionId) { }
+ public void sendTimeShiftAction(@TimeShiftManager.TimeShiftActionId int actionId) {}
}
diff --git a/src/com/android/tv/analytics/Tracker.java b/src/com/android/tv/analytics/Tracker.java
index 291fc9ce..0fcef5dc 100644
--- a/src/com/android/tv/analytics/Tracker.java
+++ b/src/com/android/tv/analytics/Tracker.java
@@ -17,11 +17,9 @@
package com.android.tv.analytics;
import com.android.tv.TimeShiftManager;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
-/**
- * Interface for sending user activity for analysis.
- */
+/** Interface for sending user activity for analysis. */
public interface Tracker {
/**
@@ -45,9 +43,7 @@ public interface Tracker {
*/
void sendConfigurationInfo(ConfigurationInfo info);
- /**
- * Sends tracking information for starting the MainActivity.
- */
+ /** Sends tracking information for starting the MainActivity. */
void sendMainStart();
/**
@@ -55,11 +51,9 @@ public interface Tracker {
*
* @param durationMs The time main activity was "started" in milliseconds.
*/
- void sendMainStop( long durationMs);
+ void sendMainStop(long durationMs);
- /**
- * Sets the screen name and sends a ScreenView hit.
- */
+ /** Sets the screen name and sends a ScreenView hit. */
void sendScreenView(String screenName);
/**
@@ -86,19 +80,13 @@ public interface Tracker {
*/
void sendChannelViewStop(Channel channel, long durationMs);
- /**
- * Sends tracking information for pressing channel up.
- */
+ /** Sends tracking information for pressing channel up. */
void sendChannelUp();
- /**
- * Sends tracking information for pressing channel down.
- */
+ /** Sends tracking information for pressing channel down. */
void sendChannelDown();
- /**
- * Sends tracking information for showing the main menu.
- */
+ /** Sends tracking information for showing the main menu. */
void sendShowMenu();
/**
@@ -126,14 +114,10 @@ public interface Tracker {
*/
void sendMenuClicked(int labelResId);
- /**
- * Sends tracking information for showing the Electronic Program Guide (EPG).
- */
+ /** Sends tracking information for showing the Electronic Program Guide (EPG). */
void sendShowEpg();
- /**
- * Sends tracking information for clicking an Electronic Program Guide (EPG) item.
- */
+ /** Sends tracking information for clicking an Electronic Program Guide (EPG) item. */
void sendEpgItemClicked();
/**
@@ -143,9 +127,7 @@ public interface Tracker {
*/
void sendHideEpg(long durationMs);
- /**
- * Sends tracking information for showing the channel switch view.
- */
+ /** Sends tracking information for showing the channel switch view. */
void sendShowChannelSwitch();
/**
@@ -155,9 +137,7 @@ public interface Tracker {
*/
void sendHideChannelSwitch(long durationMs);
- /**
- * Sends tracking for each channel number or delimiter pressed.
- */
+ /** Sends tracking for each channel number or delimiter pressed. */
void sendChannelNumberInput();
/**
@@ -167,19 +147,13 @@ public interface Tracker {
*/
void sendChannelInputNavigated();
- /**
- * Sends tracking for channel clicked.
- */
+ /** Sends tracking for channel clicked. */
void sendChannelNumberItemClicked();
- /**
- * Sends tracking for channel chosen (tuned) because the channel switch view timed out.
- */
+ /** Sends tracking for channel chosen (tuned) because the channel switch view timed out. */
void sendChannelNumberItemChosenByTimeout();
- /**
- * Sends tracking for the reason video is unavailable on a channel.
- */
+ /** Sends tracking for the reason video is unavailable on a channel. */
void sendChannelVideoUnavailable(Channel channel, int reason);
/**
@@ -191,6 +165,7 @@ public interface Tracker {
/**
* Sends tracking for input a connection failure.
+ *
* <p><strong>WARNING</strong> callers must ensure no PII is included in the inputId.
*
* @param inputId the input the failure happened on
@@ -199,15 +174,14 @@ public interface Tracker {
/**
* Sends tracking for input disconnected.
+ *
* <p><strong>WARNING</strong> callers must ensure no PII is included in the inputId.
*
* @param inputId the input the failure happened on
*/
void sendInputDisconnected(String inputId);
- /**
- * Sends tracking information for showing the input selection view.
- */
+ /** Sends tracking information for showing the input selection view. */
void sendShowInputSelection();
/**
diff --git a/src/com/android/tv/app/LiveTvApplication.java b/src/com/android/tv/app/LiveTvApplication.java
new file mode 100644
index 00000000..461331d5
--- /dev/null
+++ b/src/com/android/tv/app/LiveTvApplication.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.app;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.tv.TvContract;
+import com.android.tv.TvApplication;
+import com.android.tv.analytics.Analytics;
+import com.android.tv.analytics.StubAnalytics;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.actions.InputSetupActionUtils;
+import com.android.tv.common.config.DefaultConfigManager;
+import com.android.tv.common.config.api.RemoteConfig;
+import com.android.tv.common.experiments.ExperimentLoader;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.data.epg.EpgReader;
+import com.android.tv.data.epg.StubEpgReader;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.StubPerformanceMonitor;
+import com.android.tv.tuner.livetuner.LiveTvTunerTvInputService;
+import com.android.tv.tuner.setup.LiveTvTunerSetupActivity;
+import com.android.tv.util.account.AccountHelper;
+import com.android.tv.util.account.AccountHelperImpl;
+import javax.inject.Provider;
+
+/** The top level application for Live TV. */
+public class LiveTvApplication extends TvApplication {
+ protected static final String TV_ACTIVITY_CLASS_NAME =
+ CommonConstants.BASE_PACKAGE + ".TvActivity";
+
+ private final StubPerformanceMonitor performanceMonitor = new StubPerformanceMonitor();
+ private final Provider<EpgReader> mEpgReaderProvider =
+ new Provider<EpgReader>() {
+
+ @Override
+ public EpgReader get() {
+ return new StubEpgReader(LiveTvApplication.this);
+ }
+ };
+
+ private AccountHelper mAccountHelper;
+ private Analytics mAnalytics;
+ private Tracker mTracker;
+ private String mEmbeddedInputId;
+ private RemoteConfig mRemoteConfig;
+ private ExperimentLoader mExperimentLoader;
+
+ /** Returns the {@link AccountHelperImpl}. */
+ @Override
+ public AccountHelper getAccountHelper() {
+ if (mAccountHelper == null) {
+ mAccountHelper = new AccountHelperImpl(getApplicationContext());
+ }
+ return mAccountHelper;
+ }
+
+ @Override
+ public synchronized PerformanceMonitor getPerformanceMonitor() {
+ return performanceMonitor;
+ }
+
+ @Override
+ public Provider<EpgReader> providesEpgReader() {
+ return mEpgReaderProvider;
+ }
+
+ @Override
+ public ExperimentLoader getExperimentLoader() {
+ mExperimentLoader = new ExperimentLoader();
+ return mExperimentLoader;
+ }
+
+ /** Returns the {@link Analytics}. */
+ @Override
+ public synchronized Analytics getAnalytics() {
+ if (mAnalytics == null) {
+ mAnalytics = StubAnalytics.getInstance(this);
+ }
+ return mAnalytics;
+ }
+
+ /** Returns the default tracker. */
+ @Override
+ public synchronized Tracker getTracker() {
+ if (mTracker == null) {
+ mTracker = getAnalytics().getDefaultTracker();
+ }
+ return mTracker;
+ }
+
+ @Override
+ public Intent getTunerSetupIntent(Context context) {
+ // Make an intent to launch the setup activity of TV tuner input.
+ Intent intent =
+ CommonUtils.createSetupIntent(
+ new Intent(context, LiveTvTunerSetupActivity.class), mEmbeddedInputId);
+ intent.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, mEmbeddedInputId);
+ Intent tvActivityIntent = new Intent();
+ tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME));
+ intent.putExtra(InputSetupActionUtils.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent);
+ return intent;
+ }
+
+ @Override
+ public synchronized String getEmbeddedTunerInputId() {
+ if (mEmbeddedInputId == null) {
+ mEmbeddedInputId =
+ TvContract.buildInputId(
+ new ComponentName(this, LiveTvTunerTvInputService.class));
+ }
+ return mEmbeddedInputId;
+ }
+
+ @Override
+ public RemoteConfig getRemoteConfig() {
+ if (mRemoteConfig == null) {
+ // No need to synchronize this, it does not hurt to create two and throw one away.
+ mRemoteConfig = DefaultConfigManager.createInstance(this).getRemoteConfig();
+ }
+ return mRemoteConfig;
+ }
+}
diff --git a/src/com/android/tv/config/DefaultConfigManager.java b/src/com/android/tv/config/DefaultConfigManager.java
deleted file mode 100644
index bbabc6d4..00000000
--- a/src/com/android/tv/config/DefaultConfigManager.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.config;
-
-import android.content.Context;
-
-/**
- * Stub Remote Config.
- */
-public class DefaultConfigManager {
- public static final long DEFAULT_LONG_VALUE = 0;
- public static DefaultConfigManager createInstance(Context context) {
- return new DefaultConfigManager();
- }
-
- private StubRemoteConfig mRemoteConfig = new StubRemoteConfig();
-
- public RemoteConfig getRemoteConfig() {
- return mRemoteConfig;
- }
-
- private static class StubRemoteConfig implements RemoteConfig {
- @Override
- public void fetch(OnRemoteConfigUpdatedListener listener) {
-
- }
-
- @Override
- public String getString(String key) {
- return null;
- }
-
- @Override
- public boolean getBoolean(String key) {
- return false;
- }
-
- @Override
- public long getLong(String key) {
- return DEFAULT_LONG_VALUE;
- }
- }
-}
-
-
-
-
diff --git a/src/com/android/tv/config/RemoteConfig.java b/src/com/android/tv/config/RemoteConfig.java
deleted file mode 100644
index f7ae87e7..00000000
--- a/src/com/android/tv/config/RemoteConfig.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.config;
-
-/**
- * Manages Live TV Configuration, allowing remote updates.
- *
- * <p>This is a thin wrapper around
- * <a href="https://firebase.google.com/docs/remote-config/"></a>Firebase Remote Config</a>
- */
-public interface RemoteConfig {
-
- /**
- * Notified on successful completion of a {@link #fetch)}
- */
- interface OnRemoteConfigUpdatedListener {
- void onRemoteConfigUpdated();
- }
-
- /**
- * Starts a fetch and notifies {@code listener} on successful completion.
- */
- void fetch(OnRemoteConfigUpdatedListener listener);
-
- /**
- * Gets value as a string corresponding to the specified key.
- */
- String getString(String key);
-
- /**
- * Gets value as a boolean corresponding to the specified key.
- */
- boolean getBoolean(String key);
-
- /** Gets value as a long corresponding to the specified key. */
- long getLong(String key);
-}
diff --git a/src/com/android/tv/config/RemoteConfigFeature.java b/src/com/android/tv/config/RemoteConfigFeature.java
deleted file mode 100644
index 502e6a9c..00000000
--- a/src/com/android/tv/config/RemoteConfigFeature.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.config;
-
-import android.content.Context;
-
-import com.android.tv.TvApplication;
-import com.android.tv.common.feature.Feature;
-
-/**
- * A {@link Feature} controlled by a {@link RemoteConfig} boolean.
- */
-public class RemoteConfigFeature implements Feature {
- private final String mKey;
-
- /** Creates a {@link RemoteConfigFeature for the {@code key}. */
- public static RemoteConfigFeature fromKey(String key) {
- return new RemoteConfigFeature(key);
- }
-
- private RemoteConfigFeature(String key) {
- mKey = key;
- }
-
- @Override
- public boolean isEnabled(Context context) {
- return TvApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey);
- }
-}
diff --git a/src/com/android/tv/config/RemoteConfigUtils.java b/src/com/android/tv/config/RemoteConfigUtils.java
deleted file mode 100644
index 09d85239..00000000
--- a/src/com/android/tv/config/RemoteConfigUtils.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.config;
-
-import android.content.Context;
-import android.util.Log;
-import com.android.tv.TvApplication;
-
-/** A utility class to get the remote config. */
-public class RemoteConfigUtils {
- private static final String TAG = "RemoteConfigUtils";
- private static final boolean DEBUG = false;
-
- private RemoteConfigUtils() {}
-
- public static long getRemoteConfig(Context context, String key, long defaultValue) {
- RemoteConfig remoteConfig = TvApplication.getSingletons(context).getRemoteConfig();
- try {
- long remoteValue = remoteConfig.getLong(key);
- if (DEBUG) Log.d(TAG, "Got " + key + " from remote: " + remoteValue);
- return remoteValue;
- } catch (Exception e) {
- Log.w(TAG, "Cannot get " + key + " from RemoteConfig", e);
- }
- if (DEBUG) Log.d(TAG, "Use default value " + defaultValue);
- return defaultValue;
- }
-}
diff --git a/src/com/android/tv/customization/CustomAction.java b/src/com/android/tv/customization/CustomAction.java
deleted file mode 100644
index b8f4695b..00000000
--- a/src/com/android/tv/customization/CustomAction.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.customization;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
-
-/**
- * Describes a custom option defined in customization package.
- * This will be added to main menu.
- */
-public class CustomAction implements Comparable<CustomAction> {
- private static final int POSITION_THRESHOLD = 100;
-
- private final int mPositionPriority;
- private final String mTitle;
- private final Drawable mIconDrawable;
- private final Intent mIntent;
-
- public CustomAction(int positionPriority, String title, Drawable iconDrawable, Intent intent) {
- mPositionPriority = positionPriority;
- mTitle = title;
- mIconDrawable = iconDrawable;
- mIntent = intent;
- }
-
- /**
- * Returns if this option comes before the existing items.
- * Note that custom options can only be placed at the front or back.
- * (i.e. cannot be added in the middle of existing options.)
- * @return {@code true} if it goes to the beginning. {@code false} if it goes to the end.
- */
- public boolean isFront() {
- return mPositionPriority < POSITION_THRESHOLD;
- }
-
- @Override
- public int compareTo(@NonNull CustomAction another) {
- return mPositionPriority - another.mPositionPriority;
- }
-
- /**
- * Returns title.
- */
- public String getTitle() {
- return mTitle;
- }
-
- /**
- * Returns icon drawable.
- */
- public Drawable getIconDrawable() {
- return mIconDrawable;
- }
-
- /**
- * Returns intent to launch when this option is clicked.
- */
- public Intent getIntent() {
- return mIntent;
- }
-}
diff --git a/src/com/android/tv/customization/TvCustomizationManager.java b/src/com/android/tv/customization/TvCustomizationManager.java
deleted file mode 100644
index ed6b98ca..00000000
--- a/src/com/android/tv/customization/TvCustomizationManager.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.customization;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.IntDef;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class TvCustomizationManager {
- private static final String TAG = "TvCustomizationManager";
- private static final boolean DEBUG = false;
-
- private static final String[] CUSTOMIZE_PERMISSIONS = {
- "com.android.tv.permission.CUSTOMIZE_TV_APP"
- };
-
- private static final String CATEGORY_TV_CUSTOMIZATION =
- "com.android.tv.category";
-
- /**
- * Row IDs to share customized actions.
- * Only rows listed below can have customized action.
- */
- public static final String ID_OPTIONS_ROW = "options_row";
- public static final String ID_PARTNER_ROW = "partner_row";
-
- @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE})
- @Retention(RetentionPolicy.SOURCE)
- public @interface TRICKPLAY_MODE {}
- public static final int TRICKPLAY_MODE_ENABLED = 0;
- public static final int TRICKPLAY_MODE_DISABLED = 1;
- public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2;
-
- private static final String[] TRICKPLAY_MODE_STRINGS = {
- "enabled",
- "disabled",
- "use_external_storage_only"
- };
-
- private static final HashMap<String, String> INTENT_CATEGORY_TO_ROW_ID;
- static {
- INTENT_CATEGORY_TO_ROW_ID = new HashMap<>();
- INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW);
- INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".PARTNER_ROW", ID_PARTNER_ROW);
- }
-
- private static final String RES_ID_PARTNER_ROW_TITLE = "partner_row_title";
- private static final String RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER =
- "has_linux_dvb_built_in_tuner";
- private static final String RES_ID_TRICKPLAY_MODE = "trickplay_mode";
-
- private static final String RES_TYPE_STRING = "string";
- private static final String RES_TYPE_BOOLEAN = "bool";
-
- private static String sCustomizationPackage;
- private static Boolean sHasLinuxDvbBuiltInTuner;
- private static @TRICKPLAY_MODE Integer sTrickplayMode;
-
- private final Context mContext;
- private boolean mInitialized;
-
- private String mPartnerRowTitle;
- private final Map<String, List<CustomAction>> mRowIdToCustomActionsMap = new HashMap<>();
-
- public TvCustomizationManager(Context context) {
- mContext = context;
- mInitialized = false;
- }
-
- /**
- * Returns {@code true} if there's a customization package installed and it specifies built-in
- * tuner devices are available. The built-in tuner should support DVB API to be recognized by
- * Live TV.
- */
- public static boolean hasLinuxDvbBuiltInTuner(Context context) {
- if (sHasLinuxDvbBuiltInTuner == null) {
- if (TextUtils.isEmpty(getCustomizationPackageName(context))) {
- sHasLinuxDvbBuiltInTuner = false;
- } else {
- try {
- Resources res = context.getPackageManager()
- .getResourcesForApplication(sCustomizationPackage);
- int resId = res.getIdentifier(RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER,
- RES_TYPE_BOOLEAN, sCustomizationPackage);
- sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId);
- } catch (NameNotFoundException e) {
- sHasLinuxDvbBuiltInTuner = false;
- }
- }
- }
- return sHasLinuxDvbBuiltInTuner;
- }
-
- public static @TRICKPLAY_MODE int getTrickplayMode(Context context) {
- if (sTrickplayMode == null) {
- if (TextUtils.isEmpty(getCustomizationPackageName(context))) {
- sTrickplayMode = TRICKPLAY_MODE_ENABLED;
- } else {
- try {
- String customization = null;
- Resources res = context.getPackageManager()
- .getResourcesForApplication(sCustomizationPackage);
- int resId = res.getIdentifier(RES_ID_TRICKPLAY_MODE,
- RES_TYPE_STRING, sCustomizationPackage);
- customization = resId == 0 ? null : res.getString(resId);
- sTrickplayMode = TRICKPLAY_MODE_ENABLED;
- if (customization != null) {
- for (int i = 0; i < TRICKPLAY_MODE_STRINGS.length; ++i) {
- if (TRICKPLAY_MODE_STRINGS[i].equalsIgnoreCase(customization)) {
- sTrickplayMode = i;
- break;
- }
- }
- }
- } catch (NameNotFoundException e) {
- sTrickplayMode = TRICKPLAY_MODE_ENABLED;
- }
- }
- }
- return sTrickplayMode;
- }
-
- private static String getCustomizationPackageName(Context context) {
- if (sCustomizationPackage == null) {
- List<PackageInfo> packageInfos = context.getPackageManager()
- .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0);
- sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName;
- }
- return sCustomizationPackage;
- }
-
- /**
- * Initialize TV customization options.
- * Run this API only on the main thread.
- */
- public void initialize() {
- if (mInitialized) {
- return;
- }
- mInitialized = true;
- if (!TextUtils.isEmpty(getCustomizationPackageName(mContext))) {
- buildCustomActions();
- buildPartnerRow();
- }
- }
-
- private void buildCustomActions() {
- mRowIdToCustomActionsMap.clear();
- PackageManager pm = mContext.getPackageManager();
- for (String intentCategory : INTENT_CATEGORY_TO_ROW_ID.keySet()) {
- Intent customOptionIntent = new Intent(Intent.ACTION_MAIN);
- customOptionIntent.addCategory(intentCategory);
-
- List<ResolveInfo> activities = pm.queryIntentActivities(customOptionIntent,
- PackageManager.GET_RECEIVERS | PackageManager.GET_RESOLVED_FILTER
- | PackageManager.GET_META_DATA);
- for (ResolveInfo info : activities) {
- String packageName = info.activityInfo.packageName;
- if (!TextUtils.equals(packageName, sCustomizationPackage)) {
- Log.w(TAG, "A customization package " + sCustomizationPackage
- + " already exist. Ignoring " + packageName);
- continue;
- }
-
- int position = info.filter.getPriority();
- String title = info.loadLabel(pm).toString();
- Drawable drawable = info.loadIcon(pm);
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(intentCategory);
- intent.setClassName(sCustomizationPackage, info.activityInfo.name);
-
- String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory);
- List<CustomAction> actions = mRowIdToCustomActionsMap.get(rowId);
- if (actions == null) {
- actions = new ArrayList<>();
- mRowIdToCustomActionsMap.put(rowId, actions);
- }
- actions.add(new CustomAction(position, title, drawable, intent));
- }
- }
- // Sort items by position
- for (List<CustomAction> actions : mRowIdToCustomActionsMap.values()) {
- Collections.sort(actions);
- }
-
- if (DEBUG) {
- Log.d(TAG, "Dumping custom actions");
- for (String id : mRowIdToCustomActionsMap.keySet()) {
- for (CustomAction action : mRowIdToCustomActionsMap.get(id)) {
- Log.d(TAG, "Custom row rowId=" + id + " title=" + action.getTitle()
- + " class=" + action.getIntent());
- }
- }
- Log.d(TAG, "Dumping custom actions - end of dump");
- }
- }
-
- /**
- * Returns custom actions for given row id.
- *
- * Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW.
- */
- public List<CustomAction> getCustomActions(String rowId) {
- return mRowIdToCustomActionsMap.get(rowId);
- }
-
- private void buildPartnerRow() {
- mPartnerRowTitle = null;
- Resources res;
- try {
- res = mContext.getPackageManager()
- .getResourcesForApplication(sCustomizationPackage);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Could not get resources for package " + sCustomizationPackage);
- return;
- }
- int resId = res.getIdentifier(
- RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage);
- if (resId != 0) {
- mPartnerRowTitle = res.getString(resId);
- }
- if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]");
- }
-
- /**
- * Returns partner row title.
- */
- public String getPartnerRowTitle() {
- return mPartnerRowTitle;
- }
-}
diff --git a/src/com/android/tv/data/BaseProgram.java b/src/com/android/tv/data/BaseProgram.java
index 4e36c80a..0fb1e58d 100644
--- a/src/com/android/tv/data/BaseProgram.java
+++ b/src/com/android/tv/data/BaseProgram.java
@@ -20,14 +20,12 @@ import android.content.Context;
import android.media.tv.TvContentRating;
import android.support.annotation.Nullable;
import android.text.TextUtils;
-
import com.android.tv.R;
-
import java.util.Comparator;
/**
- * Base class for {@link com.android.tv.data.Program} and
- * {@link com.android.tv.dvr.data.RecordedProgram}.
+ * Base class for {@link com.android.tv.data.Program} and {@link
+ * com.android.tv.dvr.data.RecordedProgram}.
*/
public abstract class BaseProgram {
/**
@@ -35,8 +33,7 @@ public abstract class BaseProgram {
* If a program's season or episode number is null, it will be consider "smaller" than programs
* with season or episode numbers.
*/
- public static final Comparator<BaseProgram> EPISODE_COMPARATOR =
- new EpisodeComparator(false);
+ public static final Comparator<BaseProgram> EPISODE_COMPARATOR = new EpisodeComparator(false);
/**
* Comparator used to compare {@link BaseProgram} according to its season and episodes number
@@ -58,8 +55,7 @@ public abstract class BaseProgram {
if (lhs == rhs) {
return 0;
}
- int seasonNumberCompare =
- numberCompare(lhs.getSeasonNumber(), rhs.getSeasonNumber());
+ int seasonNumberCompare = numberCompare(lhs.getSeasonNumber(), rhs.getSeasonNumber());
if (seasonNumberCompare != 0) {
return mReversedSeason ? -seasonNumberCompare : seasonNumberCompare;
} else {
@@ -68,9 +64,7 @@ public abstract class BaseProgram {
}
}
- /**
- * Compares two strings represent season numbers or episode numbers of programs.
- */
+ /** Compares two strings represent season numbers or episode numbers of programs. */
public static int numberCompare(String s1, String s2) {
if (s1 == s2) {
return 0;
@@ -88,121 +82,120 @@ public abstract class BaseProgram {
}
}
- /**
- * Returns ID of the program.
- */
- abstract public long getId();
+ /** Returns ID of the program. */
+ public abstract long getId();
- /**
- * Returns the title of the program.
- */
- abstract public String getTitle();
+ /** Returns the title of the program. */
+ public abstract String getTitle();
- /**
- * Returns the episode title.
- */
- abstract public String getEpisodeTitle();
+ /** Returns the episode title. */
+ public abstract String getEpisodeTitle();
- /**
- * Returns the displayed title of the program episode.
- */
+ /** Returns the displayed title of the program episode. */
public String getEpisodeDisplayTitle(Context context) {
- if (!TextUtils.isEmpty(getEpisodeNumber())) {
- String episodeTitle = getEpisodeTitle() == null ? "" : getEpisodeTitle();
- if (TextUtils.equals(getSeasonNumber(), "0")) {
+ String episodeNumber = getEpisodeNumber();
+ String episodeTitle = getEpisodeTitle();
+ if (!TextUtils.isEmpty(episodeNumber)) {
+ episodeTitle = episodeTitle == null ? "" : episodeTitle;
+ String seasonNumber = getSeasonNumber();
+ if (TextUtils.isEmpty(seasonNumber) || TextUtils.equals(seasonNumber, "0")) {
// Do not show "S0: ".
- return String.format(context.getResources().getString(
- R.string.display_episode_title_format_no_season_number),
- getEpisodeNumber(), episodeTitle);
+ return context.getResources()
+ .getString(
+ R.string.display_episode_title_format_no_season_number,
+ episodeNumber,
+ episodeTitle);
} else {
- return String.format(context.getResources().getString(
- R.string.display_episode_title_format),
- getSeasonNumber(), getEpisodeNumber(), episodeTitle);
+ return context.getResources()
+ .getString(
+ R.string.display_episode_title_format,
+ seasonNumber,
+ episodeNumber,
+ episodeTitle);
}
}
- return getEpisodeTitle();
+ return episodeTitle;
}
/**
- * Returns the description of the program.
- */
- abstract public String getDescription();
+ * Returns the content description of the program episode, suitable for being spoken by an
+ * accessibility service.
+ */
+ public String getEpisodeContentDescription(Context context) {
+ String episodeNumber = getEpisodeNumber();
+ String episodeTitle = getEpisodeTitle();
+ if (!TextUtils.isEmpty(episodeNumber)) {
+ episodeTitle = episodeTitle == null ? "" : episodeTitle;
+ String seasonNumber = getSeasonNumber();
+ if (TextUtils.isEmpty(seasonNumber) || TextUtils.equals(seasonNumber, "0")) {
+ // Do not list season if it is empty or 0
+ return context.getResources()
+ .getString(
+ R.string.content_description_episode_format_no_season_number,
+ episodeNumber,
+ episodeTitle);
+ } else {
+ return context.getResources()
+ .getString(
+ R.string.content_description_episode_format,
+ seasonNumber,
+ episodeNumber,
+ episodeTitle);
+ }
+ }
+ return episodeTitle;
+ }
- /**
- * Returns the long description of the program.
- */
- abstract public String getLongDescription();
+ /** Returns the description of the program. */
+ public abstract String getDescription();
- /**
- * Returns the start time of the program in Milliseconds.
- */
- abstract public long getStartTimeUtcMillis();
+ /** Returns the long description of the program. */
+ public abstract String getLongDescription();
- /**
- * Returns the end time of the program in Milliseconds.
- */
- abstract public long getEndTimeUtcMillis();
+ /** Returns the start time of the program in Milliseconds. */
+ public abstract long getStartTimeUtcMillis();
- /**
- * Returns the duration of the program in Milliseconds.
- */
- abstract public long getDurationMillis();
+ /** Returns the end time of the program in Milliseconds. */
+ public abstract long getEndTimeUtcMillis();
- /**
- * Returns the series ID.
- */
- abstract public String getSeriesId();
+ /** Returns the duration of the program in Milliseconds. */
+ public abstract long getDurationMillis();
- /**
- * Returns the season number.
- */
- abstract public String getSeasonNumber();
+ /** Returns the series ID. */
+ public abstract String getSeriesId();
- /**
- * Returns the episode number.
- */
- abstract public String getEpisodeNumber();
+ /** Returns the season number. */
+ public abstract String getSeasonNumber();
- /**
- * Returns URI of the program's poster.
- */
- abstract public String getPosterArtUri();
+ /** Returns the episode number. */
+ public abstract String getEpisodeNumber();
- /**
- * Returns URI of the program's thumbnail.
- */
- abstract public String getThumbnailUri();
+ /** Returns URI of the program's poster. */
+ public abstract String getPosterArtUri();
- /**
- * Returns the array of the ID's of the canonical genres.
- */
- abstract public int[] getCanonicalGenreIds();
+ /** Returns URI of the program's thumbnail. */
+ public abstract String getThumbnailUri();
+
+ /** Returns the array of the ID's of the canonical genres. */
+ public abstract int[] getCanonicalGenreIds();
/** Returns the array of content ratings. */
@Nullable
- abstract public TvContentRating[] getContentRatings();
+ public abstract TvContentRating[] getContentRatings();
- /**
- * Returns channel's ID of the program.
- */
- abstract public long getChannelId();
+ /** Returns channel's ID of the program. */
+ public abstract long getChannelId();
- /**
- * Returns if the program is valid.
- */
- abstract public boolean isValid();
+ /** Returns if the program is valid. */
+ public abstract boolean isValid();
- /**
- * Checks whether the program is episodic or not.
- */
+ /** Checks whether the program is episodic or not. */
public boolean isEpisodic() {
return getSeriesId() != null;
}
- /**
- * Generates the series ID for the other inputs than the tuner TV input.
- */
+ /** Generates the series ID for the other inputs than the tuner TV input. */
public static String generateSeriesId(String packageName, String title) {
return packageName + "/" + title;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
index 6f93fbd1..1dfcf125 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -38,15 +38,15 @@ import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Log;
import android.util.MutableInt;
-
-import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.common.util.SharedPreferencesUtils;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.AsyncDbTask;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -56,13 +56,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executor;
/**
- * The class to manage channel data.
- * Basic features: reading channel list and each channel's current program, and updating
- * the values of {@link Channels#COLUMN_BROWSABLE}, {@link Channels#COLUMN_LOCKED}.
- * This class is not thread-safe and under an assumption that its public methods are called in
- * only the main thread.
+ * The class to manage channel data. Basic features: reading channel list and each channel's current
+ * program, and updating the values of {@link Channels#COLUMN_BROWSABLE}, {@link
+ * Channels#COLUMN_LOCKED}. This class is not thread-safe and under an assumption that its public
+ * methods are called in only the main thread.
*/
@AnyThread
public class ChannelDataManager {
@@ -73,6 +73,7 @@ public class ChannelDataManager {
private final Context mContext;
private final TvInputManagerHelper mInputManager;
+ private final Executor mDbExecutor;
private boolean mStarted;
private boolean mDbLoadFinished;
private QueryAllChannelsTask mChannelsUpdateTask;
@@ -81,8 +82,8 @@ public class ChannelDataManager {
private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
// Use container class to support multi-thread safety. This value can be set only on the main
// thread.
- volatile private UnmodifiableChannelData mData = new UnmodifiableChannelData();
- private final Channel.DefaultComparator mChannelComparator;
+ private volatile UnmodifiableChannelData mData = new UnmodifiableChannelData();
+ private final ChannelImpl.DefaultComparator mChannelComparator;
private final Handler mHandler;
private final Set<Long> mBrowsableUpdateChannelIds = new HashSet<>();
@@ -93,81 +94,92 @@ public class ChannelDataManager {
private final boolean mStoreBrowsableInSharedPreferences;
private final SharedPreferences mBrowsableSharedPreferences;
- private final TvInputCallback mTvInputCallback = new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- boolean channelAdded = false;
- ChannelData data = new ChannelData(mData);
- for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
- if (channel.mChannel.getInputId().equals(inputId)) {
- channel.mInputRemoved = false;
- addChannel(data, channel.mChannel);
- channelAdded = true;
+ private final TvInputCallback mTvInputCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ boolean channelAdded = false;
+ ChannelData data = new ChannelData(mData);
+ for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
+ if (channel.mChannel.getInputId().equals(inputId)) {
+ channel.mInputRemoved = false;
+ addChannel(data, channel.mChannel);
+ channelAdded = true;
+ }
+ }
+ if (channelAdded) {
+ Collections.sort(data.channels, mChannelComparator);
+ mData = new UnmodifiableChannelData(data);
+ notifyChannelListUpdated();
+ }
}
- }
- if (channelAdded) {
- Collections.sort(data.channels, mChannelComparator);
- mData = new UnmodifiableChannelData(data);
- notifyChannelListUpdated();
- }
- }
- @Override
- public void onInputRemoved(String inputId) {
- boolean channelRemoved = false;
- ArrayList<ChannelWrapper> removedChannels = new ArrayList<>();
- for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
- if (channel.mChannel.getInputId().equals(inputId)) {
- channel.mInputRemoved = true;
- channelRemoved = true;
- removedChannels.add(channel);
- }
- }
- if (channelRemoved) {
- ChannelData data = new ChannelData();
- data.channelWrapperMap.putAll(mData.channelWrapperMap);
- for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) {
- if (!channelWrapper.mInputRemoved) {
- addChannel(data, channelWrapper.mChannel);
+ @Override
+ public void onInputRemoved(String inputId) {
+ boolean channelRemoved = false;
+ ArrayList<ChannelWrapper> removedChannels = new ArrayList<>();
+ for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
+ if (channel.mChannel.getInputId().equals(inputId)) {
+ channel.mInputRemoved = true;
+ channelRemoved = true;
+ removedChannels.add(channel);
+ }
+ }
+ if (channelRemoved) {
+ ChannelData data = new ChannelData();
+ data.channelWrapperMap.putAll(mData.channelWrapperMap);
+ for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) {
+ if (!channelWrapper.mInputRemoved) {
+ addChannel(data, channelWrapper.mChannel);
+ }
+ }
+ Collections.sort(data.channels, mChannelComparator);
+ mData = new UnmodifiableChannelData(data);
+ notifyChannelListUpdated();
+ for (ChannelWrapper channel : removedChannels) {
+ channel.notifyChannelRemoved();
+ }
}
}
- Collections.sort(data.channels, mChannelComparator);
- mData = new UnmodifiableChannelData(data);
- notifyChannelListUpdated();
- for (ChannelWrapper channel : removedChannels) {
- channel.notifyChannelRemoved();
- }
- }
- }
- };
+ };
@MainThread
public ChannelDataManager(Context context, TvInputManagerHelper inputManager) {
- this(context, inputManager, context.getContentResolver());
+ this(
+ context,
+ inputManager,
+ TvSingletons.getSingletons(context).getDbExecutor(),
+ context.getContentResolver());
}
@MainThread
@VisibleForTesting
- ChannelDataManager(Context context, TvInputManagerHelper inputManager,
+ ChannelDataManager(
+ Context context,
+ TvInputManagerHelper inputManager,
+ Executor executor,
ContentResolver contentResolver) {
mContext = context;
mInputManager = inputManager;
+ mDbExecutor = executor;
mContentResolver = contentResolver;
- mChannelComparator = new Channel.DefaultComparator(context, inputManager);
+ mChannelComparator = new ChannelImpl.DefaultComparator(context, inputManager);
// Detect duplicate channels while sorting.
mChannelComparator.setDetectDuplicatesEnabled(true);
mHandler = new ChannelDataManagerHandler(this);
- mChannelObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
- mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
- }
- }
- };
+ mChannelObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
+ }
+ }
+ };
mStoreBrowsableInSharedPreferences = !PermissionUtils.hasAccessAllEpg(mContext);
- mBrowsableSharedPreferences = context.getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE);
+ mBrowsableSharedPreferences =
+ context.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE);
}
@VisibleForTesting
@@ -175,9 +187,7 @@ public class ChannelDataManager {
return mChannelObserver;
}
- /**
- * Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called.
- */
+ /** Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called. */
@MainThread
public void start() {
if (mStarted) {
@@ -187,8 +197,8 @@ public class ChannelDataManager {
// Should be called directly instead of posting MSG_UPDATE_CHANNELS message to the handler.
// If not, other DB tasks can be executed before channel loading.
handleUpdateChannels();
- mContentResolver.registerContentObserver(TvContract.Channels.CONTENT_URI, true,
- mChannelObserver);
+ mContentResolver.registerContentObserver(
+ TvContract.Channels.CONTENT_URI, true, mChannelObserver);
mInputManager.addCallback(mTvInputCallback);
}
@@ -218,9 +228,7 @@ public class ChannelDataManager {
applyUpdatedValuesToDb();
}
- /**
- * Adds a {@link Listener}.
- */
+ /** Adds a {@link Listener}. */
public void addListener(Listener listener) {
if (DEBUG) Log.d(TAG, "addListener " + listener);
SoftPreconditions.checkNotNull(listener);
@@ -229,9 +237,7 @@ public class ChannelDataManager {
}
}
- /**
- * Removes a {@link Listener}.
- */
+ /** Removes a {@link Listener}. */
public void removeListener(Listener listener) {
if (DEBUG) Log.d(TAG, "removeListener " + listener);
SoftPreconditions.checkNotNull(listener);
@@ -252,8 +258,8 @@ public class ChannelDataManager {
}
/**
- * Removes a {@link ChannelListener} for a specific channel with the channel ID
- * {@code channelId}.
+ * Removes a {@link ChannelListener} for a specific channel with the channel ID {@code
+ * channelId}.
*/
public void removeChannelListener(Long channelId, ChannelListener listener) {
ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
@@ -263,30 +269,22 @@ public class ChannelDataManager {
channelWrapper.removeListener(listener);
}
- /**
- * Checks whether data is ready.
- */
+ /** Checks whether data is ready. */
public boolean isDbLoadFinished() {
return mDbLoadFinished;
}
- /**
- * Returns the number of channels.
- */
+ /** Returns the number of channels. */
public int getChannelCount() {
return mData.channels.size();
}
- /**
- * Returns a list of channels.
- */
+ /** Returns a list of channels. */
public List<Channel> getChannelList() {
return new ArrayList<>(mData.channels);
}
- /**
- * Returns a list of browsable channels.
- */
+ /** Returns a list of browsable channels. */
public List<Channel> getBrowsableChannelList() {
List<Channel> channels = new ArrayList<>();
for (Channel channel : mData.channels) {
@@ -329,9 +327,7 @@ public class ChannelDataManager {
return true;
}
- /**
- * Gets the channel with the channel ID {@code channelId}.
- */
+ /** Gets the channel with the channel ID {@code channelId}. */
public Channel getChannel(Long channelId) {
ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null || channelWrapper.mInputRemoved) {
@@ -340,9 +336,7 @@ public class ChannelDataManager {
return channelWrapper.mChannel;
}
- /**
- * The value change will be applied to DB when applyPendingDbOperation is called.
- */
+ /** The value change will be applied to DB when applyPendingDbOperation is called. */
public void updateBrowsable(Long channelId, boolean browsable) {
updateBrowsable(channelId, browsable, false);
}
@@ -351,12 +345,12 @@ public class ChannelDataManager {
* The value change will be applied to DB when applyPendingDbOperation is called.
*
* @param skipNotifyChannelBrowsableChanged If it's true, {@link Listener
- * #onChannelBrowsableChanged()} is not called, when this method is called.
- * {@link #notifyChannelBrowsableChanged} should be directly called, once browsable
- * update is completed.
+ * #onChannelBrowsableChanged()} is not called, when this method is called. {@link
+ * #notifyChannelBrowsableChanged} should be directly called, once browsable update is
+ * completed.
*/
- public void updateBrowsable(Long channelId, boolean browsable,
- boolean skipNotifyChannelBrowsableChanged) {
+ public void updateBrowsable(
+ Long channelId, boolean browsable, boolean skipNotifyChannelBrowsableChanged) {
ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null) {
return;
@@ -396,10 +390,7 @@ public class ChannelDataManager {
}
}
- /**
- * Updates channels from DB. Once the update is done, {@code postRunnable} will
- * be called.
- */
+ /** Updates channels from DB. Once the update is done, {@code postRunnable} will be called. */
public void updateChannels(Runnable postRunnable) {
if (mChannelsUpdateTask != null) {
mChannelsUpdateTask.cancel(true);
@@ -411,9 +402,7 @@ public class ChannelDataManager {
}
}
- /**
- * The value change will be applied to DB when applyPendingDbOperation is called.
- */
+ /** The value change will be applied to DB when applyPendingDbOperation is called. */
public void updateLocked(Long channelId, boolean locked) {
ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null) {
@@ -430,10 +419,7 @@ public class ChannelDataManager {
}
}
- /**
- * Applies the changed values by {@link #updateBrowsable} and {@link #updateLocked}
- * to DB.
- */
+ /** Applies the changed values by {@link #updateBrowsable} and {@link #updateLocked} to DB. */
public void applyUpdatedValuesToDb() {
ChannelData data = mData;
ArrayList<Long> browsableIds = new ArrayList<>();
@@ -493,11 +479,17 @@ public class ChannelDataManager {
}
mLockedUpdateChannelIds.clear();
if (DEBUG) {
- Log.d(TAG, "applyUpdatedValuesToDb"
- + "\n browsableIds size:" + browsableIds.size()
- + "\n unbrowsableIds size:" + unbrowsableIds.size()
- + "\n lockedIds size:" + lockedIds.size()
- + "\n unlockedIds size:" + unlockedIds.size());
+ Log.d(
+ TAG,
+ "applyUpdatedValuesToDb"
+ + "\n browsableIds size:"
+ + browsableIds.size()
+ + "\n unbrowsableIds size:"
+ + unbrowsableIds.size()
+ + "\n lockedIds size:"
+ + lockedIds.size()
+ + "\n unlockedIds size:"
+ + unlockedIds.size());
}
}
@@ -527,48 +519,34 @@ public class ChannelDataManager {
mChannelsUpdateTask.executeOnDbThread();
}
- /**
- * Reloads channel data.
- */
+ /** Reloads channel data. */
public void reload() {
if (mDbLoadFinished && !mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
}
}
- /**
- * A listener for ChannelDataManager. The callbacks are called on the main thread.
- */
+ /** A listener for ChannelDataManager. The callbacks are called on the main thread. */
public interface Listener {
- /**
- * Called when data load is finished.
- */
+ /** Called when data load is finished. */
void onLoadFinished();
/**
- * Called when channels are added, deleted, or updated. But, when browsable is changed,
- * it won't be called. Instead, {@link #onChannelBrowsableChanged} will be called.
+ * Called when channels are added, deleted, or updated. But, when browsable is changed, it
+ * won't be called. Instead, {@link #onChannelBrowsableChanged} will be called.
*/
void onChannelListUpdated();
- /**
- * Called when browsable of channels are changed.
- */
+ /** Called when browsable of channels are changed. */
void onChannelBrowsableChanged();
}
- /**
- * A listener for individual channel change. The callbacks are called on the main thread.
- */
+ /** A listener for individual channel change. The callbacks are called on the main thread. */
public interface ChannelListener {
- /**
- * Called when the channel has been removed in DB.
- */
+ /** Called when the channel has been removed in DB. */
void onChannelRemoved(Channel channel);
- /**
- * Called when values of the channel has been changed.
- */
+ /** Called when values of the channel has been changed. */
void onChannelUpdated(Channel channel);
}
@@ -616,8 +594,10 @@ public class ChannelDataManager {
@Override
protected Boolean doInBackground(Void... params) {
- try (AssetFileDescriptor f = mContext.getContentResolver().openAssetFileDescriptor(
- TvContract.buildChannelLogoUri(mChannel.getId()), "r")) {
+ try (AssetFileDescriptor f =
+ mContext.getContentResolver()
+ .openAssetFileDescriptor(
+ TvContract.buildChannelLogoUri(mChannel.getId()), "r")) {
return true;
} catch (SQLiteException | IOException | NullPointerException e) {
// File not found or asset file not found.
@@ -637,7 +617,7 @@ public class ChannelDataManager {
private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask {
QueryAllChannelsTask(ContentResolver contentResolver) {
- super(contentResolver);
+ super(mDbExecutor, contentResolver);
}
@Override
@@ -663,8 +643,8 @@ public class ChannelDataManager {
for (Channel channel : channels) {
if (mStoreBrowsableInSharedPreferences) {
String browsableKey = getBrowsableKey(channel);
- channel.setBrowsable(mBrowsableSharedPreferences.getBoolean(browsableKey,
- false));
+ channel.setBrowsable(
+ mBrowsableSharedPreferences.getBoolean(browsableKey, false));
deletedBrowsableMap.remove(browsableKey);
}
long channelId = channel.getId();
@@ -698,7 +678,8 @@ public class ChannelDataManager {
}
}
}
- if (mStoreBrowsableInSharedPreferences && !deletedBrowsableMap.isEmpty()
+ if (mStoreBrowsableInSharedPreferences
+ && !deletedBrowsableMap.isEmpty()
&& PermissionUtils.hasReadTvListings(mContext)) {
// If hasReadTvListings(mContext) is false, the given channel list would
// empty. In this case, we skip the browsable data clean up process.
@@ -745,24 +726,26 @@ public class ChannelDataManager {
}
/**
- * Updates a column {@code columnName} of DB table {@code uri} with the value
- * {@code columnValue}. The selective rows in the ID list {@code ids} will be updated.
- * The DB operations will run on {@link AsyncDbTask#getExecutor()}.
+ * Updates a column {@code columnName} of DB table {@code uri} with the value {@code
+ * columnValue}. The selective rows in the ID list {@code ids} will be updated. The DB
+ * operations will run on {@link TvSingletons#getDbExecutor()}.
*/
private void updateOneColumnValue(
final String columnName, final int columnValue, final List<Long> ids) {
if (!PermissionUtils.hasAccessAllEpg(mContext)) {
return;
}
- AsyncDbTask.executeOnDbThread(new Runnable() {
- @Override
- public void run() {
- String selection = Utils.buildSelectionForIds(Channels._ID, ids);
- ContentValues values = new ContentValues();
- values.put(columnName, columnValue);
- mContentResolver.update(TvContract.Channels.CONTENT_URI, values, selection, null);
- }
- });
+ mDbExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ String selection = Utils.buildSelectionForIds(Channels._ID, ids);
+ ContentValues values = new ContentValues();
+ values.put(columnName, columnValue);
+ mContentResolver.update(
+ TvContract.Channels.CONTENT_URI, values, selection, null);
+ }
+ });
}
private String getBrowsableKey(Channel channel) {
@@ -784,9 +767,8 @@ public class ChannelDataManager {
}
/**
- * Container class which includes channel data that needs to be synced. This class is
- * modifiable and used for changing channel data.
- * e.g. TvInputCallback, or AsyncDbTask.onPostExecute.
+ * Container class which includes channel data that needs to be synced. This class is modifiable
+ * and used for changing channel data. e.g. TvInputCallback, or AsyncDbTask.onPostExecute.
*/
@MainThread
private static class ChannelData {
@@ -806,8 +788,10 @@ public class ChannelDataManager {
channels = new ArrayList<>(data.channels);
}
- ChannelData(Map<Long, ChannelWrapper> channelWrapperMap,
- Map<String, MutableInt> channelCountMap, List<Channel> channels) {
+ ChannelData(
+ Map<Long, ChannelWrapper> channelWrapperMap,
+ Map<String, MutableInt> channelCountMap,
+ List<Channel> channels) {
this.channelWrapperMap = channelWrapperMap;
this.channelCountMap = channelCountMap;
this.channels = channels;
@@ -818,13 +802,15 @@ public class ChannelDataManager {
@MainThread
private static class UnmodifiableChannelData extends ChannelData {
UnmodifiableChannelData() {
- super(Collections.unmodifiableMap(new HashMap<>()),
+ super(
+ Collections.unmodifiableMap(new HashMap<>()),
Collections.unmodifiableMap(new HashMap<>()),
Collections.unmodifiableList(new ArrayList<>()));
}
UnmodifiableChannelData(ChannelData data) {
- super(Collections.unmodifiableMap(data.channelWrapperMap),
+ super(
+ Collections.unmodifiableMap(data.channelWrapperMap),
Collections.unmodifiableMap(data.channelCountMap),
Collections.unmodifiableList(data.channels));
}
diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/ChannelImpl.java
index 4a391ae7..703f69c9 100644
--- a/src/com/android/tv/data/Channel.java
+++ b/src/com/android/tv/data/ChannelImpl.java
@@ -28,93 +28,63 @@ import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
-
-import com.android.tv.common.TvCommonConstants;
-import com.android.tv.util.ImageLoader;
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
+import com.android.tv.util.images.ImageLoader;
import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-/**
- * A convenience class to create and insert channel entries into the database.
- */
-public final class Channel {
- private static final String TAG = "Channel";
+/** A convenience class to create and insert channel entries into the database. */
+public final class ChannelImpl implements Channel {
+ private static final String TAG = "ChannelImpl";
- public static final long INVALID_ID = -1;
- public static final int LOAD_IMAGE_TYPE_CHANNEL_LOGO = 1;
- public static final int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2;
- public static final int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3;
-
- /**
- * Compares the channel numbers of channels which belong to the same input.
- */
- public static final Comparator<Channel> CHANNEL_NUMBER_COMPARATOR = new Comparator<Channel>() {
- @Override
- public int compare(Channel lhs, Channel rhs) {
- return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
- }
- };
-
- /**
- * When a TIS doesn't provide any information about app link, and it doesn't have a leanback
- * launch intent, there will be no app link card for the TIS.
- */
- public static final int APP_LINK_TYPE_NONE = -1;
- /**
- * When a TIS provide a specific app link information, the app link card will be
- * {@code APP_LINK_TYPE_CHANNEL} which contains all the provided information.
- */
- public static final int APP_LINK_TYPE_CHANNEL = 1;
- /**
- * When a TIS doesn't provide a specific app link information, but the app has a leanback launch
- * intent, the app link card will be {@code APP_LINK_TYPE_APP} which launches the application.
- */
- public static final int APP_LINK_TYPE_APP = 2;
+ /** Compares the channel numbers of channels which belong to the same input. */
+ public static final Comparator<Channel> CHANNEL_NUMBER_COMPARATOR =
+ new Comparator<Channel>() {
+ @Override
+ public int compare(Channel lhs, Channel rhs) {
+ return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
+ }
+ };
private static final int APP_LINK_TYPE_NOT_SET = 0;
private static final String INVALID_PACKAGE_NAME = "packageName";
public static final String[] PROJECTION = {
- // Columns must match what is read in Channel.fromCursor()
- TvContract.Channels._ID,
- TvContract.Channels.COLUMN_PACKAGE_NAME,
- TvContract.Channels.COLUMN_INPUT_ID,
- TvContract.Channels.COLUMN_TYPE,
- TvContract.Channels.COLUMN_DISPLAY_NUMBER,
- TvContract.Channels.COLUMN_DISPLAY_NAME,
- TvContract.Channels.COLUMN_DESCRIPTION,
- TvContract.Channels.COLUMN_VIDEO_FORMAT,
- TvContract.Channels.COLUMN_BROWSABLE,
- TvContract.Channels.COLUMN_SEARCHABLE,
- TvContract.Channels.COLUMN_LOCKED,
- TvContract.Channels.COLUMN_APP_LINK_TEXT,
- TvContract.Channels.COLUMN_APP_LINK_COLOR,
- TvContract.Channels.COLUMN_APP_LINK_ICON_URI,
- TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI,
- TvContract.Channels.COLUMN_APP_LINK_INTENT_URI,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, // Only used in bundled input
+ // Columns must match what is read in ChannelImpl.fromCursor()
+ TvContract.Channels._ID,
+ TvContract.Channels.COLUMN_PACKAGE_NAME,
+ TvContract.Channels.COLUMN_INPUT_ID,
+ TvContract.Channels.COLUMN_TYPE,
+ TvContract.Channels.COLUMN_DISPLAY_NUMBER,
+ TvContract.Channels.COLUMN_DISPLAY_NAME,
+ TvContract.Channels.COLUMN_DESCRIPTION,
+ TvContract.Channels.COLUMN_VIDEO_FORMAT,
+ TvContract.Channels.COLUMN_BROWSABLE,
+ TvContract.Channels.COLUMN_SEARCHABLE,
+ TvContract.Channels.COLUMN_LOCKED,
+ TvContract.Channels.COLUMN_APP_LINK_TEXT,
+ TvContract.Channels.COLUMN_APP_LINK_COLOR,
+ TvContract.Channels.COLUMN_APP_LINK_ICON_URI,
+ TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI,
+ TvContract.Channels.COLUMN_APP_LINK_INTENT_URI,
+ TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, // Only used in bundled input
};
/**
- * Channel number delimiter between major and minor parts.
- */
- public static final char CHANNEL_NUMBER_DELIMITER = '-';
-
- /**
- * Creates {@code Channel} object from cursor.
+ * Creates {@code ChannelImpl} object from cursor.
*
* <p>The query that created the cursor MUST use {@link #PROJECTION}
- *
*/
- public static Channel fromCursor(Cursor cursor) {
+ public static ChannelImpl fromCursor(Cursor cursor) {
// Columns read must match the order of {@link #PROJECTION}
- Channel channel = new Channel();
+ ChannelImpl channel = new ChannelImpl();
int index = 0;
channel.mId = cursor.getLong(index++);
channel.mPackageName = Utils.intern(cursor.getString(index++));
@@ -132,21 +102,20 @@ public final class Channel {
channel.mAppLinkIconUri = cursor.getString(index++);
channel.mAppLinkPosterArtUri = cursor.getString(index++);
channel.mAppLinkIntentUri = cursor.getString(index++);
- if (Utils.isBundledInput(channel.mInputId)) {
+ if (CommonUtils.isBundledInput(channel.mInputId)) {
channel.mRecordingProhibited = cursor.getInt(index++) != 0;
}
return channel;
}
- /**
- * Replaces the channel number separator with dash('-').
- */
+ /** Replaces the channel number separator with dash('-'). */
public static String normalizeDisplayNumber(String string) {
if (!TextUtils.isEmpty(string)) {
int length = string.length();
for (int i = 0; i < length; i++) {
char c = string.charAt(i);
- if (c == '.' || Character.isWhitespace(c)
+ if (c == '.'
+ || Character.isWhitespace(c)
|| Character.getType(c) == Character.DASH_PUNCTUATION) {
StringBuilder sb = new StringBuilder(string);
sb.setCharAt(i, CHANNEL_NUMBER_DELIMITER);
@@ -183,14 +152,16 @@ public final class Channel {
private boolean mChannelLogoExist;
- private Channel() {
+ private ChannelImpl() {
// Do nothing.
}
+ @Override
public long getId() {
return mId;
}
+ @Override
public Uri getUri() {
if (isPassthrough()) {
return TvContract.buildChannelUriForPassthroughInput(mInputId);
@@ -199,97 +170,110 @@ public final class Channel {
}
}
+ @Override
public String getPackageName() {
return mPackageName;
}
+ @Override
public String getInputId() {
return mInputId;
}
+ @Override
public String getType() {
return mType;
}
+ @Override
public String getDisplayNumber() {
return mDisplayNumber;
}
+ @Override
@Nullable
public String getDisplayName() {
return mDisplayName;
}
+ @Override
public String getDescription() {
return mDescription;
}
+ @Override
public String getVideoFormat() {
return mVideoFormat;
}
+ @Override
public boolean isPassthrough() {
return mIsPassthrough;
}
/**
- * Gets identification text for displaying or debugging.
- * It's made from Channels' display number plus their display name.
+ * Gets identification text for displaying or debugging. It's made from Channels' display number
+ * plus their display name.
*/
+ @Override
public String getDisplayText() {
- return TextUtils.isEmpty(mDisplayName) ? mDisplayNumber
+ return TextUtils.isEmpty(mDisplayName)
+ ? mDisplayNumber
: mDisplayNumber + " " + mDisplayName;
}
+ @Override
public String getAppLinkText() {
return mAppLinkText;
}
+ @Override
public int getAppLinkColor() {
return mAppLinkColor;
}
+ @Override
public String getAppLinkIconUri() {
return mAppLinkIconUri;
}
+ @Override
public String getAppLinkPosterArtUri() {
return mAppLinkPosterArtUri;
}
+ @Override
public String getAppLinkIntentUri() {
return mAppLinkIntentUri;
}
- /**
- * Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher.
- */
+ /** Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher. */
+ @Override
public String getLogoUri() {
return mLogoUri;
}
+ @Override
public boolean isRecordingProhibited() {
return mRecordingProhibited;
}
- /**
- * Checks whether this channel is physical tuner channel or not.
- */
+ /** Checks whether this channel is physical tuner channel or not. */
+ @Override
public boolean isPhysicalTunerChannel() {
return !TextUtils.isEmpty(mType) && !TvContract.Channels.TYPE_OTHER.equals(mType);
}
- /**
- * Checks if two channels equal by checking ids.
- */
+ /** Checks if two channels equal by checking ids. */
@Override
public boolean equals(Object o) {
- if (!(o instanceof Channel)) {
+ if (!(o instanceof ChannelImpl)) {
return false;
}
- Channel other = (Channel) o;
+ ChannelImpl other = (ChannelImpl) o;
// All pass-through TV channels have INVALID_ID value for mId.
- return mId == other.mId && TextUtils.equals(mInputId, other.mInputId)
+ return mId == other.mId
+ && TextUtils.equals(mInputId, other.mInputId)
&& mIsPassthrough == other.mIsPassthrough;
}
@@ -298,15 +282,18 @@ public final class Channel {
return Objects.hash(mId, mInputId, mIsPassthrough);
}
+ @Override
public boolean isBrowsable() {
return mBrowsable;
}
/** Checks whether this channel is searchable or not. */
+ @Override
public boolean isSearchable() {
return mSearchable;
}
+ @Override
public boolean isLocked() {
return mLocked;
}
@@ -319,9 +306,7 @@ public final class Channel {
mLocked = locked;
}
- /**
- * Sets channel logo uri which is got from cloud.
- */
+ /** Sets channel logo uri which is got from cloud. */
public void setLogoUri(String logoUri) {
mLogoUri = logoUri;
}
@@ -331,45 +316,91 @@ public final class Channel {
* channels have same logos. It also excludes browsable and locked, because two fields are
* changed by TV app.
*/
+ @Override
public boolean hasSameReadOnlyInfo(Channel other) {
return other != null
- && Objects.equals(mId, other.mId)
- && Objects.equals(mPackageName, other.mPackageName)
- && Objects.equals(mInputId, other.mInputId)
- && Objects.equals(mType, other.mType)
- && Objects.equals(mDisplayNumber, other.mDisplayNumber)
- && Objects.equals(mDisplayName, other.mDisplayName)
- && Objects.equals(mDescription, other.mDescription)
- && Objects.equals(mVideoFormat, other.mVideoFormat)
- && mIsPassthrough == other.mIsPassthrough
- && Objects.equals(mAppLinkText, other.mAppLinkText)
- && mAppLinkColor == other.mAppLinkColor
- && Objects.equals(mAppLinkIconUri, other.mAppLinkIconUri)
- && Objects.equals(mAppLinkPosterArtUri, other.mAppLinkPosterArtUri)
- && Objects.equals(mAppLinkIntentUri, other.mAppLinkIntentUri)
- && Objects.equals(mRecordingProhibited, other.mRecordingProhibited);
+ && Objects.equals(mId, other.getId())
+ && Objects.equals(mPackageName, other.getPackageName())
+ && Objects.equals(mInputId, other.getInputId())
+ && Objects.equals(mType, other.getType())
+ && Objects.equals(mDisplayNumber, other.getDisplayNumber())
+ && Objects.equals(mDisplayName, other.getDisplayName())
+ && Objects.equals(mDescription, other.getDescription())
+ && Objects.equals(mVideoFormat, other.getVideoFormat())
+ && mIsPassthrough == other.isPassthrough()
+ && Objects.equals(mAppLinkText, other.getAppLinkText())
+ && mAppLinkColor == other.getAppLinkColor()
+ && Objects.equals(mAppLinkIconUri, other.getAppLinkIconUri())
+ && Objects.equals(mAppLinkPosterArtUri, other.getAppLinkPosterArtUri())
+ && Objects.equals(mAppLinkIntentUri, other.getAppLinkIntentUri())
+ && Objects.equals(mRecordingProhibited, other.isRecordingProhibited());
}
@Override
public String toString() {
return "Channel{"
- + "id=" + mId
- + ", packageName=" + mPackageName
- + ", inputId=" + mInputId
- + ", type=" + mType
- + ", displayNumber=" + mDisplayNumber
- + ", displayName=" + mDisplayName
- + ", description=" + mDescription
- + ", videoFormat=" + mVideoFormat
- + ", isPassthrough=" + mIsPassthrough
- + ", browsable=" + mBrowsable
- + ", searchable=" + mSearchable
- + ", locked=" + mLocked
- + ", appLinkText=" + mAppLinkText
- + ", recordingProhibited=" + mRecordingProhibited + "}";
- }
-
- void copyFrom(Channel other) {
+ + "id="
+ + mId
+ + ", packageName="
+ + mPackageName
+ + ", inputId="
+ + mInputId
+ + ", type="
+ + mType
+ + ", displayNumber="
+ + mDisplayNumber
+ + ", displayName="
+ + mDisplayName
+ + ", description="
+ + mDescription
+ + ", videoFormat="
+ + mVideoFormat
+ + ", isPassthrough="
+ + mIsPassthrough
+ + ", browsable="
+ + mBrowsable
+ + ", searchable="
+ + mSearchable
+ + ", locked="
+ + mLocked
+ + ", appLinkText="
+ + mAppLinkText
+ + ", recordingProhibited="
+ + mRecordingProhibited
+ + "}";
+ }
+
+ @Override
+ public void copyFrom(Channel channel) {
+ if (channel instanceof ChannelImpl) {
+ copyFrom((ChannelImpl) channel);
+ } else {
+ // copy what we can
+ mId = channel.getId();
+ mPackageName = channel.getPackageName();
+ mInputId = channel.getInputId();
+ mType = channel.getType();
+ mDisplayNumber = channel.getDisplayNumber();
+ mDisplayName = channel.getDisplayName();
+ mDescription = channel.getDescription();
+ mVideoFormat = channel.getVideoFormat();
+ mIsPassthrough = channel.isPassthrough();
+ mBrowsable = channel.isBrowsable();
+ mSearchable = channel.isSearchable();
+ mLocked = channel.isLocked();
+ mAppLinkText = channel.getAppLinkText();
+ mAppLinkColor = channel.getAppLinkColor();
+ mAppLinkIconUri = channel.getAppLinkIconUri();
+ mAppLinkPosterArtUri = channel.getAppLinkPosterArtUri();
+ mAppLinkIntentUri = channel.getAppLinkIntentUri();
+ mRecordingProhibited = channel.isRecordingProhibited();
+ mChannelLogoExist = channel.channelLogoExists();
+ }
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ public void copyFrom(ChannelImpl channel) {
+ ChannelImpl other = (ChannelImpl) channel;
if (this == other) {
return;
}
@@ -396,10 +427,8 @@ public final class Channel {
mChannelLogoExist = other.mChannelLogoExist;
}
- /**
- * Creates a channel for a passthrough TV input.
- */
- public static Channel createPassthroughChannel(Uri uri) {
+ /** Creates a channel for a passthrough TV input. */
+ public static ChannelImpl createPassthroughChannel(Uri uri) {
if (!TvContract.isChannelUriForPassthroughInput(uri)) {
throw new IllegalArgumentException("URI is not a passthrough channel URI");
}
@@ -407,33 +436,25 @@ public final class Channel {
return createPassthroughChannel(inputId);
}
- /**
- * Creates a channel for a passthrough TV input with {@code inputId}.
- */
- public static Channel createPassthroughChannel(String inputId) {
- return new Builder()
- .setInputId(inputId)
- .setPassthrough(true)
- .build();
+ /** Creates a channel for a passthrough TV input with {@code inputId}. */
+ public static ChannelImpl createPassthroughChannel(String inputId) {
+ return new Builder().setInputId(inputId).setPassthrough(true).build();
}
- /**
- * Checks whether the channel is valid or not.
- */
+ /** Checks whether the channel is valid or not. */
public static boolean isValid(Channel channel) {
- return channel != null && (channel.mId != INVALID_ID || channel.mIsPassthrough);
+ return channel != null && (channel.getId() != INVALID_ID || channel.isPassthrough());
}
/**
- * Builder class for {@code Channel}.
- * Suppress using this outside of ChannelDataManager
- * so Channels could be managed by ChannelDataManager.
+ * Builder class for {@code ChannelImpl}. Suppress using this outside of ChannelDataManager so
+ * Channels could be managed by ChannelDataManager.
*/
public static final class Builder {
- private final Channel mChannel;
+ private final ChannelImpl mChannel;
public Builder() {
- mChannel = new Channel();
+ mChannel = new ChannelImpl();
// Fill initial data.
mChannel.mId = INVALID_ID;
mChannel.mPackageName = INVALID_PACKAGE_NAME;
@@ -447,7 +468,7 @@ public final class Channel {
}
public Builder(Channel other) {
- mChannel = new Channel();
+ mChannel = new ChannelImpl();
mChannel.copyFrom(other);
}
@@ -548,16 +569,14 @@ public final class Channel {
return this;
}
- public Channel build() {
- Channel channel = new Channel();
+ public ChannelImpl build() {
+ ChannelImpl channel = new ChannelImpl();
channel.copyFrom(mChannel);
return channel;
}
}
- /**
- * Prefetches the images for this channel.
- */
+ /** Prefetches the images for this channel. */
public void prefetchImage(Context context, int type, int maxWidth, int maxHeight) {
String uriString = getImageUriString(type);
if (!TextUtils.isEmpty(uriString)) {
@@ -566,46 +585,49 @@ public final class Channel {
}
/**
- * Loads the bitmap of this channel and returns it via {@code callback}.
- * The loaded bitmap will be cached and resized with given params.
- * <p>
- * Note that it may directly call {@code callback} if the bitmap is already loaded.
+ * Loads the bitmap of this channel and returns it via {@code callback}. The loaded bitmap will
+ * be cached and resized with given params.
+ *
+ * <p>Note that it may directly call {@code callback} if the bitmap is already loaded.
*
* @param context A context.
- * @param type The type of bitmap which will be loaded. It should be one of follows:
- * {@link #LOAD_IMAGE_TYPE_CHANNEL_LOGO}, {@link #LOAD_IMAGE_TYPE_APP_LINK_ICON}, or
- * {@link #LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART}.
+ * @param type The type of bitmap which will be loaded. It should be one of follows: {@link
+ * Channel#LOAD_IMAGE_TYPE_CHANNEL_LOGO}, {@link Channel#LOAD_IMAGE_TYPE_APP_LINK_ICON}, or
+ * {@link Channel#LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART}.
* @param maxWidth The max width of the loaded bitmap.
* @param maxHeight The max height of the loaded bitmap.
* @param callback A callback which will be called after the loading finished.
*/
@UiThread
- public void loadBitmap(Context context, final int type, int maxWidth, int maxHeight,
+ public void loadBitmap(
+ Context context,
+ final int type,
+ int maxWidth,
+ int maxHeight,
ImageLoader.ImageLoaderCallback callback) {
String uriString = getImageUriString(type);
ImageLoader.loadBitmap(context, uriString, maxWidth, maxHeight, callback);
}
/**
- * Sets if the channel logo exists. This method should be only called from
- * {@link ChannelDataManager}.
+ * Sets if the channel logo exists. This method should be only called from {@link
+ * ChannelDataManager}.
*/
- void setChannelLogoExist(boolean exist) {
+ @Override
+ public void setChannelLogoExist(boolean exist) {
mChannelLogoExist = exist;
}
- /**
- * Returns if channel logo exists.
- */
+ /** Returns if channel logo exists. */
public boolean channelLogoExists() {
return mChannelLogoExist;
}
/**
- * Returns the type of app link for this channel.
- * It returns {@link #APP_LINK_TYPE_CHANNEL} if the channel has a non null app link text and
- * a valid app link intent, it returns {@link #APP_LINK_TYPE_APP} if the input service which
- * holds the channel has leanback launch intent, and it returns {@link #APP_LINK_TYPE_NONE}
+ * Returns the type of app link for this channel. It returns {@link
+ * Channel#APP_LINK_TYPE_CHANNEL} if the channel has a non null app link text and a valid app
+ * link intent, it returns {@link Channel#APP_LINK_TYPE_APP} if the input service which holds
+ * the channel has leanback launch intent, and it returns {@link Channel#APP_LINK_TYPE_NONE}
* otherwise.
*/
public int getAppLinkType(Context context) {
@@ -616,8 +638,8 @@ public final class Channel {
}
/**
- * Returns the app link intent for this channel.
- * If the type of app link is {@link #APP_LINK_TYPE_NONE}, it returns {@code null}.
+ * Returns the app link intent for this channel. If the type of app link is {@link
+ * Channel#APP_LINK_TYPE_NONE}, it returns {@code null}.
*/
public Intent getAppLinkIntent(Context context) {
if (mAppLinkType == APP_LINK_TYPE_NOT_SET) {
@@ -635,8 +657,8 @@ public final class Channel {
Intent intent = Intent.parseUri(mAppLinkIntentUri, Intent.URI_INTENT_SCHEME);
if (intent.resolveActivityInfo(pm, 0) != null) {
mAppLinkIntent = intent;
- mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI,
- getUri().toString());
+ mAppLinkIntent.putExtra(
+ CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
mAppLinkType = APP_LINK_TYPE_CHANNEL;
return;
} else {
@@ -652,8 +674,8 @@ public final class Channel {
}
mAppLinkIntent = pm.getLeanbackLaunchIntentForPackage(mPackageName);
if (mAppLinkIntent != null) {
- mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI,
- getUri().toString());
+ mAppLinkIntent.putExtra(
+ CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
mAppLinkType = APP_LINK_TYPE_APP;
}
}
@@ -670,6 +692,17 @@ public final class Channel {
return null;
}
+ /**
+ * Default Channel ordering.
+ *
+ * <p>Ordering
+ * <li>{@link TvInputManagerHelper#isPartnerInput(String)}
+ * <li>{@link #getInputLabelForChannel(Channel)}
+ * <li>{@link #getInputId()}
+ * <li>{@link ChannelNumber#compare(String, String)}
+ * <li>
+ * </ol>
+ */
public static class DefaultComparator implements Comparator<Channel> {
private final Context mContext;
private final TvInputManagerHelper mInputManager;
@@ -685,6 +718,7 @@ public final class Channel {
mDetectDuplicatesEnabled = detectDuplicatesEnabled;
}
+ @SuppressWarnings("ReferenceEquality")
@Override
public int compare(Channel lhs, Channel rhs) {
if (lhs == rhs) {
@@ -699,8 +733,10 @@ public final class Channel {
// Compare the input labels.
String lhsLabel = getInputLabelForChannel(lhs);
String rhsLabel = getInputLabelForChannel(rhs);
- int result = lhsLabel == null ? (rhsLabel == null ? 0 : 1) : rhsLabel == null ? -1
- : lhsLabel.compareTo(rhsLabel);
+ int result =
+ lhsLabel == null
+ ? (rhsLabel == null ? 0 : 1)
+ : rhsLabel == null ? -1 : lhsLabel.compareTo(rhsLabel);
if (result != 0) {
return result;
}
@@ -712,8 +748,13 @@ public final class Channel {
// Compare the channel numbers if both channels belong to the same input.
result = ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
if (mDetectDuplicatesEnabled && result == 0) {
- Log.w(TAG, "Duplicate channels detected! - \""
- + lhs.getDisplayText() + "\" and \"" + rhs.getDisplayText() + "\"");
+ Log.w(
+ TAG,
+ "Duplicate channels detected! - \""
+ + lhs.getDisplayText()
+ + "\" and \""
+ + rhs.getDisplayText()
+ + "\"");
}
return result;
}
@@ -733,4 +774,4 @@ public final class Channel {
return label;
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java
index 132cab7a..89d1e36c 100644
--- a/src/com/android/tv/data/ChannelLogoFetcher.java
+++ b/src/com/android/tv/data/ChannelLogoFetcher.java
@@ -28,17 +28,16 @@ import android.os.RemoteException;
import android.support.annotation.MainThread;
import android.text.TextUtils;
import android.util.Log;
-
-import com.android.tv.common.SharedPreferencesUtils;
-import com.android.tv.util.BitmapUtils;
-import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
-import com.android.tv.util.PermissionUtils;
-
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.common.util.SharedPreferencesUtils;
+import com.android.tv.data.api.Channel;
+import com.android.tv.util.images.BitmapUtils;
+import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Map;
import java.util.List;
+import java.util.Map;
/**
* Fetches channel logos from the cloud into the database. It's for the channels which have no logos
@@ -54,12 +53,11 @@ public class ChannelLogoFetcher {
private static FetchLogoTask sFetchTask;
/**
- * Fetches the channel logos from the cloud data and insert them into TvProvider.
- * The previous task is canceled and a new task starts.
+ * Fetches the channel logos from the cloud data and insert them into TvProvider. The previous
+ * task is canceled and a new task starts.
*/
@MainThread
- public static void startFetchingChannelLogos(
- Context context, List<Channel> channels) {
+ public static void startFetchingChannelLogos(Context context, List<Channel> channels) {
if (!PermissionUtils.hasAccessAllEpg(context)) {
// TODO: support this feature for non-system LC app. b/23939816
return;
@@ -76,8 +74,7 @@ public class ChannelLogoFetcher {
sFetchTask.execute();
}
- private ChannelLogoFetcher() {
- }
+ private ChannelLogoFetcher() {}
private static final class FetchLogoTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
@@ -105,8 +102,8 @@ public class ChannelLogoFetcher {
Context.MODE_PRIVATE);
SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit();
Map<String, ?> uncheckedChannels = sharedPreferences.getAll();
- boolean isFirstTimeFetchChannelLogo = sharedPreferences.getBoolean(
- PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, true);
+ boolean isFirstTimeFetchChannelLogo =
+ sharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, true);
// Iterating channels.
for (Channel channel : mChannels) {
String channelIdString = Long.toString(channel.getId());
@@ -117,7 +114,7 @@ public class ChannelLogoFetcher {
sharedPreferencesEditor.putString(channelIdString, channel.getLogoUri());
} else if (TextUtils.isEmpty(channel.getLogoUri())
&& (!TextUtils.isEmpty(storedChannelLogoUri)
- || isFirstTimeFetchChannelLogo)) {
+ || isFirstTimeFetchChannelLogo)) {
channelsToRemove.add(channel);
sharedPreferencesEditor.remove(channelIdString);
}
@@ -136,11 +133,18 @@ public class ChannelLogoFetcher {
}
// Downloads the channel logo.
String logoUri = channel.getLogoUri();
- ScaledBitmapInfo bitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString(
- mContext, logoUri, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ ScaledBitmapInfo bitmapInfo =
+ BitmapUtils.decodeSampledBitmapFromUriString(
+ mContext, logoUri, Integer.MAX_VALUE, Integer.MAX_VALUE);
if (bitmapInfo == null) {
- Log.e(TAG, "Failed to load bitmap. {channelName=" + channel.getDisplayName()
- + ", " + "logoUri=" + logoUri + "}");
+ Log.e(
+ TAG,
+ "Failed to load bitmap. {channelName="
+ + channel.getDisplayName()
+ + ", "
+ + "logoUri="
+ + logoUri
+ + "}");
continue;
}
if (isCancelled()) {
@@ -160,8 +164,13 @@ public class ChannelLogoFetcher {
continue;
}
if (DEBUG) {
- Log.d(TAG, "Inserting logo file to DB succeeded. {from=" + logoUri + ", to="
- + dstLogoUri + "}");
+ Log.d(
+ TAG,
+ "Inserting logo file to DB succeeded. {from="
+ + logoUri
+ + ", to="
+ + dstLogoUri
+ + "}");
}
}
@@ -171,8 +180,10 @@ public class ChannelLogoFetcher {
if (!channelsToRemove.isEmpty()) {
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (Channel channel : channelsToRemove) {
- ops.add(ContentProviderOperation.newDelete(
- TvContract.buildChannelLogoUri(channel.getId())).build());
+ ops.add(
+ ContentProviderOperation.newDelete(
+ TvContract.buildChannelLogoUri(channel.getId()))
+ .build());
}
try {
mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java
index 29054aa5..afdcc580 100644
--- a/src/com/android/tv/data/ChannelNumber.java
+++ b/src/com/android/tv/data/ChannelNumber.java
@@ -19,18 +19,18 @@ package com.android.tv.data;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.KeyEvent;
-
-import com.android.tv.util.StringUtils;
-
+import com.android.tv.common.util.StringUtils;
+import com.android.tv.data.api.Channel;
import java.util.Objects;
-/**
- * A convenience class to handle channel number.
- */
+/** A convenience class to handle channel number. */
public final class ChannelNumber implements Comparable<ChannelNumber> {
private static final int[] CHANNEL_DELIMITER_KEYCODES = {
- KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_PERIOD,
- KeyEvent.KEYCODE_NUMPAD_DOT, KeyEvent.KEYCODE_SPACE
+ KeyEvent.KEYCODE_MINUS,
+ KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
+ KeyEvent.KEYCODE_PERIOD,
+ KeyEvent.KEYCODE_NUMPAD_DOT,
+ KeyEvent.KEYCODE_SPACE
};
/** The major part of the channel number. */
@@ -44,6 +44,23 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
reset();
}
+ /**
+ * {@code lhs} and {@code rhs} are equivalent if {@link ChannelNumber#compare(String, String)}
+ * is 0 or if only one has a delimiter and both {@link ChannelNumber#majorNumber} equals.
+ */
+ public static boolean equivalent(String lhs, String rhs) {
+ if (compare(lhs, rhs) == 0) {
+ return true;
+ }
+ // Match if only one has delimiter
+ ChannelNumber lhsNumber = parseChannelNumber(lhs);
+ ChannelNumber rhsNumber = parseChannelNumber(rhs);
+ return lhsNumber != null
+ && rhsNumber != null
+ && lhsNumber.hasDelimiter != rhsNumber.hasDelimiter
+ && lhsNumber.majorNumber.equals(rhsNumber.majorNumber);
+ }
+
public void reset() {
setChannelNumber("", false, "");
}
@@ -68,8 +85,7 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
int minor = hasDelimiter ? Integer.parseInt(minorNumber) : 0;
int opponentMajor = Integer.parseInt(another.majorNumber);
- int opponentMinor = another.hasDelimiter
- ? Integer.parseInt(another.minorNumber) : 0;
+ int opponentMinor = another.hasDelimiter ? Integer.parseInt(another.minorNumber) : 0;
if (major == opponentMajor) {
return minor - opponentMinor;
}
@@ -103,10 +119,10 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
/**
* Returns the ChannelNumber instance.
- * <p>
- * Note that all the channel number argument should be normalized by
- * {@link Channel#normalizeDisplayNumber}. The channels retrieved from
- * {@link ChannelDataManager} are already normalized.
+ *
+ * <p>Note that all the channel number argument should be normalized by {@link
+ * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager}
+ * are already normalized.
*/
public static ChannelNumber parseChannelNumber(String number) {
if (number == null) {
@@ -134,10 +150,10 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
/**
* Compares the channel numbers.
- * <p>
- * Note that all the channel number arguments should be normalized by
- * {@link Channel#normalizeDisplayNumber}. The channels retrieved from
- * {@link ChannelDataManager} are already normalized.
+ *
+ * <p>Note that all the channel number arguments should be normalized by {@link
+ * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager}
+ * are already normalized.
*/
public static int compare(String lhs, String rhs) {
ChannelNumber lhsNumber = parseChannelNumber(lhs);
@@ -156,7 +172,7 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
private static boolean isInteger(String string) {
try {
Integer.parseInt(string);
- } catch(NumberFormatException | NullPointerException e) {
+ } catch (NumberFormatException | NullPointerException e) {
return false;
}
return true;
diff --git a/src/com/android/tv/data/DisplayMode.java b/src/com/android/tv/data/DisplayMode.java
index ccba5480..dbcca010 100644
--- a/src/com/android/tv/data/DisplayMode.java
+++ b/src/com/android/tv/data/DisplayMode.java
@@ -17,7 +17,6 @@
package com.android.tv.data;
import android.content.Context;
-
import com.android.tv.R;
public class DisplayMode {
@@ -28,12 +27,10 @@ public class DisplayMode {
public static final int MODE_ZOOM = 2;
public static final int SIZE_OF_RATIO_TYPES = MODE_ZOOM + 1;
- /**
- * Constant to indicate that any mode is not set yet.
- */
+ /** Constant to indicate that any mode is not set yet. */
public static final int MODE_NOT_DEFINED = -1;
- private DisplayMode() { }
+ private DisplayMode() {}
public static String getLabel(int mode, Context context) {
return context.getResources().getStringArray(R.array.display_mode_labels)[mode];
diff --git a/src/com/android/tv/data/GenreItems.java b/src/com/android/tv/data/GenreItems.java
index b12fd1aa..dfecb63c 100644
--- a/src/com/android/tv/data/GenreItems.java
+++ b/src/com/android/tv/data/GenreItems.java
@@ -18,13 +18,10 @@ package com.android.tv.data;
import android.content.Context;
import android.media.tv.TvContract.Programs.Genres;
-
import com.android.tv.R;
public class GenreItems {
- /**
- * Genre ID indicating all channels.
- */
+ /** Genre ID indicating all channels. */
public static final int ID_ALL_CHANNELS = 0;
private static final String[] CANONICAL_GENRES = {
@@ -48,11 +45,9 @@ public class GenreItems {
Genres.TECH_SCIENCE
};
- private GenreItems() { }
+ private GenreItems() {}
- /**
- * Returns array of all genre labels.
- */
+ /** Returns array of all genre labels. */
public static String[] getLabels(Context context) {
String[] items = context.getResources().getStringArray(R.array.genre_labels);
if (items.length != CANONICAL_GENRES.length) {
@@ -61,16 +56,14 @@ public class GenreItems {
return items;
}
- /**
- * Returns the number of genres including all channels.
- */
+ /** Returns the number of genres including all channels. */
public static int getGenreCount() {
return CANONICAL_GENRES.length;
}
/**
- * Returns the canonical genre for the given id.
- * If the id is invalid, {@code null} will be returned instead.
+ * Returns the canonical genre for the given id. If the id is invalid, {@code null} will be
+ * returned instead.
*/
public static String getCanonicalGenre(int id) {
if (id < 0 || id >= CANONICAL_GENRES.length) {
@@ -80,8 +73,8 @@ public class GenreItems {
}
/**
- * Returns id for the given canonical genre.
- * If the genre is invalid, {@link #ID_ALL_CHANNELS} will be returned instead.
+ * Returns id for the given canonical genre. If the genre is invalid, {@link #ID_ALL_CHANNELS}
+ * will be returned instead.
*/
public static int getId(String canonicalGenre) {
if (canonicalGenre == null) {
diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java
index e33ca18f..4c30d395 100644
--- a/src/com/android/tv/data/InternalDataUtils.java
+++ b/src/com/android/tv/data/InternalDataUtils.java
@@ -19,10 +19,8 @@ package com.android.tv.data;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
-
import com.android.tv.data.Program.CriticScore;
import com.android.tv.dvr.data.RecordedProgram;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -31,22 +29,22 @@ import java.io.ObjectOutputStream;
import java.util.List;
/**
- * A utility class to parse and store data from the
- * {@link android.media.tv.TvContract.Programs#COLUMN_INTERNAL_PROVIDER_DATA} field in the
- * {@link android.media.tv.TvContract.Programs}.
+ * A utility class to parse and store data from the {@link
+ * android.media.tv.TvContract.Programs#COLUMN_INTERNAL_PROVIDER_DATA} field in the {@link
+ * android.media.tv.TvContract.Programs}.
*/
public final class InternalDataUtils {
private static final boolean DEBUG = false;
private static final String TAG = "InternalDataUtils";
private InternalDataUtils() {
- //do nothing
+ // do nothing
}
/**
* Deserializes a byte array into objects to be stored in the Program class.
*
- * <p> Series ID and critic scores are loaded from the bytes.
+ * <p>Series ID and critic scores are loaded from the bytes.
*
* @param bytes the bytes to be deserialized
* @param builder the builder for the Program class
@@ -70,6 +68,7 @@ public final class InternalDataUtils {
/**
* Convenience method for converting relevant data in Program class to a serialized blob type
* for storage in internal_provider_data field.
+ *
* @param program the program which contains the objects to be serialized
* @return serialized blob-type data
*/
@@ -83,8 +82,10 @@ public final class InternalDataUtils {
return bos.toByteArray();
}
} catch (IOException e) {
- Log.e(TAG, "Could not serialize internal provider contents for program: "
- + program.getTitle());
+ Log.e(
+ TAG,
+ "Could not serialize internal provider contents for program: "
+ + program.getTitle());
}
return null;
}
@@ -92,13 +93,13 @@ public final class InternalDataUtils {
/**
* Deserializes a byte array into objects to be stored in the RecordedProgram class.
*
- * <p> Series ID is loaded from the bytes.
+ * <p>Series ID is loaded from the bytes.
*
* @param bytes the bytes to be deserialized
* @param builder the builder for the RecordedProgram class
*/
- public static void deserializeInternalProviderData(byte[] bytes,
- RecordedProgram.Builder builder) {
+ public static void deserializeInternalProviderData(
+ byte[] bytes, RecordedProgram.Builder builder) {
if (bytes == null || bytes.length == 0) {
return;
}
@@ -115,6 +116,7 @@ public final class InternalDataUtils {
/**
* Serializes relevant objects in {@link android.media.tv.TvContract.Programs} to byte array.
+ *
* @return the serialized byte array
*/
public static byte[] serializeInternalProviderData(RecordedProgram program) {
@@ -125,9 +127,11 @@ public final class InternalDataUtils {
return bos.toByteArray();
}
} catch (IOException e) {
- Log.e(TAG, "Could not serialize internal provider contents for program: "
- + program.getTitle());
+ Log.e(
+ TAG,
+ "Could not serialize internal provider contents for program: "
+ + program.getTitle());
}
return null;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/data/Lineup.java b/src/com/android/tv/data/Lineup.java
index d0e9d7ba..4393cd3d 100644
--- a/src/com/android/tv/data/Lineup.java
+++ b/src/com/android/tv/data/Lineup.java
@@ -17,78 +17,94 @@
package com.android.tv.data;
import android.support.annotation.IntDef;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
-/**
- * A class that represents a lineup.
- */
+/** A class that represents a lineup. */
public class Lineup {
- /**
- * The ID of this lineup.
- */
- public final String id;
+ /** The ID of this lineup. */
+ public String getId() {
+ return id;
+ }
- /**
- * The type associated with this lineup.
- */
- public final int type;
+ /** The type associated with this lineup. */
+ public int getType() {
+ return type;
+ }
- /**
- * The human readable name associated with this lineup.
- */
- public final String name;
+ /** The human readable name associated with this lineup. */
+ public String getName() {
+ return name;
+ }
- /**
- * Location this lineup can be found.
- * This is a human readable description of a geographic location.
- */
- public final String location;
+ /** The human readable name associated with this lineup. */
+ public String getLocation() {
+ return location;
+ }
+
+ /** An unmodifiable list of channel numbers that this lineup has. */
+ public List<String> getChannels() {
+ return channels;
+ }
+
+ private final String id;
+
+ private final int type;
+
+ private final String name;
+
+ private final String location;
+
+ private final List<String> channels;
@Retention(RetentionPolicy.SOURCE)
- @IntDef({LINEUP_CABLE, LINEUP_SATELLITE, LINEUP_BROADCAST_DIGITAL, LINEUP_BROADCAST_ANALOG,
- LINEUP_IPTV, LINEUP_MVPD})
+ @IntDef({
+ LINEUP_CABLE,
+ LINEUP_SATELLITE,
+ LINEUP_BROADCAST_DIGITAL,
+ LINEUP_BROADCAST_ANALOG,
+ LINEUP_IPTV,
+ LINEUP_MVPD,
+ LINEUP_INTERNET,
+ LINEUP_OTHER
+ })
public @interface LineupType {}
- /**
- * Lineup type for cable.
- */
+ /** Lineup type for cable. */
public static final int LINEUP_CABLE = 0;
- /**
- * Lineup type for satelite.
- */
+ /** Lineup type for satelite. */
public static final int LINEUP_SATELLITE = 1;
- /**
- * Lineup type for broadcast digital.
- */
+ /** Lineup type for broadcast digital. */
public static final int LINEUP_BROADCAST_DIGITAL = 2;
- /**
- * Lineup type for broadcast analog.
- */
+ /** Lineup type for broadcast analog. */
public static final int LINEUP_BROADCAST_ANALOG = 3;
- /**
- * Lineup type for IPTV.
- */
+ /** Lineup type for IPTV. */
public static final int LINEUP_IPTV = 4;
/**
- * Indicates the lineup is either satelite, cable or IPTV but we are not sure which specific
+ * Indicates the lineup is either satellite, cable or IPTV but we are not sure which specific
* type.
- */
+ */
public static final int LINEUP_MVPD = 5;
- /**
- * Creates a lineup.
- */
- public Lineup(String id, int type, String name, String location) {
+ /** Lineup type for Internet. */
+ public static final int LINEUP_INTERNET = 6;
+
+ /** Lineup type for other. */
+ public static final int LINEUP_OTHER = 7;
+
+ /** Creates a lineup. */
+ public Lineup(String id, int type, String name, String location, List<String> channels) {
this.id = id;
this.type = type;
this.name = name;
this.location = location;
+ this.channels = Collections.unmodifiableList(channels);
}
}
diff --git a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
index 77b6c9b8..edb33556 100644
--- a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
+++ b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
@@ -17,8 +17,6 @@
package com.android.tv.data;
public interface OnCurrentProgramUpdatedListener {
- /**
- * Called when the current program is updated.
- */
+ /** Called when the current program is updated. */
void onCurrentProgramUpdated(long channelId, Program program);
}
diff --git a/src/com/android/tv/data/ParcelableList.java b/src/com/android/tv/data/ParcelableList.java
index 78f444e4..1c1f5f29 100644
--- a/src/com/android/tv/data/ParcelableList.java
+++ b/src/com/android/tv/data/ParcelableList.java
@@ -18,18 +18,13 @@ package com.android.tv.data;
import android.os.Parcel;
import android.os.Parcelable;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-/**
- * A convenience class for the list of {@link Parcelable}s.
- */
+/** A convenience class for the list of {@link Parcelable}s. */
public final class ParcelableList<T extends Parcelable> implements Parcelable {
- /**
- * Create instance from {@link Parcel}.
- */
+ /** Create instance from {@link Parcel}. */
public static ParcelableList fromParcel(Parcel in) {
ParcelableList list = new ParcelableList();
int length = in.readInt();
@@ -41,32 +36,29 @@ public final class ParcelableList<T extends Parcelable> implements Parcelable {
return list;
}
- /**
- * A creator for {@link ParcelableList}.
- */
- public static final Creator<ParcelableList> CREATOR = new Creator<ParcelableList>() {
- @Override
- public ParcelableList createFromParcel(Parcel in) {
- return ParcelableList.fromParcel(in);
- }
+ /** A creator for {@link ParcelableList}. */
+ public static final Creator<ParcelableList> CREATOR =
+ new Creator<ParcelableList>() {
+ @Override
+ public ParcelableList createFromParcel(Parcel in) {
+ return ParcelableList.fromParcel(in);
+ }
- @Override
- public ParcelableList[] newArray(int size) {
- return new ParcelableList[size];
- }
- };
+ @Override
+ public ParcelableList[] newArray(int size) {
+ return new ParcelableList[size];
+ }
+ };
private final List<T> mList = new ArrayList<>();
- private ParcelableList() { }
+ private ParcelableList() {}
public ParcelableList(Collection<T> initialList) {
mList.addAll(initialList);
}
- /**
- * Returns the list.
- */
+ /** Returns the list. */
public List<T> getList() {
return new ArrayList<T>(mList);
}
diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java
index 01a58520..44664dcf 100644
--- a/src/com/android/tv/data/PreviewDataManager.java
+++ b/src/com/android/tv/data/PreviewDataManager.java
@@ -35,10 +35,8 @@ import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.PreviewProgram;
import android.util.Log;
import android.util.Pair;
-
import com.android.tv.R;
-import com.android.tv.util.PermissionUtils;
-
+import com.android.tv.common.util.PermissionUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
@@ -46,32 +44,24 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
-/**
- * Class to manage the preview data.
- */
+/** Class to manage the preview data. */
@TargetApi(Build.VERSION_CODES.O)
@MainThread
public class PreviewDataManager {
private static final String TAG = "PreviewDataManager";
- // STOPSHIP: set it to false.
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
- /**
- * Invalid preview channel ID.
- */
+ /** Invalid preview channel ID. */
public static final long INVALID_PREVIEW_CHANNEL_ID = -1;
+
@IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL})
@Retention(RetentionPolicy.SOURCE)
- public @interface PreviewChannelType{}
+ public @interface PreviewChannelType {}
- /**
- * Type of default preview channel
- */
- public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1;
- /**
- * Type of recorded program channel
- */
- public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2;
+ /** Type of default preview channel */
+ public static final int TYPE_DEFAULT_PREVIEW_CHANNEL = 1;
+ /** Type of recorded program channel */
+ public static final int TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2;
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -80,8 +70,7 @@ public class PreviewDataManager {
private final Set<PreviewDataListener> mPreviewDataListeners = new CopyOnWriteArraySet<>();
private QueryPreviewDataTask mQueryPreviewTask;
- private final Map<Long, CreatePreviewChannelTask> mCreatePreviewChannelTasks =
- new HashMap<>();
+ private final Map<Long, CreatePreviewChannelTask> mCreatePreviewChannelTasks = new HashMap<>();
private final Map<Long, UpdatePreviewProgramTask> mUpdatePreviewProgramTasks = new HashMap<>();
private final int mPreviewChannelLogoWidth;
@@ -90,15 +79,13 @@ public class PreviewDataManager {
public PreviewDataManager(Context context) {
mContext = context.getApplicationContext();
mContentResolver = context.getContentResolver();
- mPreviewChannelLogoWidth = mContext.getResources().getDimensionPixelSize(
- R.dimen.preview_channel_logo_width);
- mPreviewChannelLogoHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.preview_channel_logo_height);
+ mPreviewChannelLogoWidth =
+ mContext.getResources().getDimensionPixelSize(R.dimen.preview_channel_logo_width);
+ mPreviewChannelLogoHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.preview_channel_logo_height);
}
- /**
- * Starts the preview data manager.
- */
+ /** Starts the preview data manager. */
public void start() {
if (mQueryPreviewTask == null) {
mQueryPreviewTask = new QueryPreviewDataTask();
@@ -106,19 +93,17 @@ public class PreviewDataManager {
}
}
- /**
- * Stops the preview data manager.
- */
+ /** Stops the preview data manager. */
public void stop() {
if (mQueryPreviewTask != null) {
mQueryPreviewTask.cancel(true);
}
- for (CreatePreviewChannelTask createPreviewChannelTask
- : mCreatePreviewChannelTasks.values()) {
+ for (CreatePreviewChannelTask createPreviewChannelTask :
+ mCreatePreviewChannelTasks.values()) {
createPreviewChannelTask.cancel(true);
}
- for (UpdatePreviewProgramTask updatePreviewProgramTask
- : mUpdatePreviewProgramTasks.values()) {
+ for (UpdatePreviewProgramTask updatePreviewProgramTask :
+ mUpdatePreviewProgramTasks.values()) {
updatePreviewProgramTask.cancel(true);
}
@@ -127,31 +112,26 @@ public class PreviewDataManager {
mUpdatePreviewProgramTasks.clear();
}
- /**
- * Gets preview channel ID from the preview channel type.
- */
+ /** Gets preview channel ID from the preview channel type. */
public @PreviewChannelType long getPreviewChannelId(long previewChannelType) {
return mPreviewData.getPreviewChannelId(previewChannelType);
}
- /**
- * Creates default preview channel.
- */
+ /** Creates default preview channel. */
public void createDefaultPreviewChannel(
OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
createPreviewChannel(TYPE_DEFAULT_PREVIEW_CHANNEL, onPreviewChannelCreationResultListener);
}
- /**
- * Creates a preview channel for specific channel type.
- */
- public void createPreviewChannel(@PreviewChannelType long previewChannelType,
+ /** Creates a preview channel for specific channel type. */
+ public void createPreviewChannel(
+ @PreviewChannelType long previewChannelType,
OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
CreatePreviewChannelTask currentRunningCreateTask =
mCreatePreviewChannelTasks.get(previewChannelType);
if (currentRunningCreateTask == null) {
- CreatePreviewChannelTask createPreviewChannelTask = new CreatePreviewChannelTask(
- previewChannelType);
+ CreatePreviewChannelTask createPreviewChannelTask =
+ new CreatePreviewChannelTask(previewChannelType);
createPreviewChannelTask.addOnPreviewChannelCreationResultListener(
onPreviewChannelCreationResultListener);
createPreviewChannelTask.execute();
@@ -162,32 +142,26 @@ public class PreviewDataManager {
}
}
- /**
- * Returns {@code true} if the preview data is loaded.
- */
+ /** Returns {@code true} if the preview data is loaded. */
public boolean isLoadFinished() {
return mLoadFinished;
}
- /**
- * Adds listener.
- */
+ /** Adds listener. */
public void addListener(PreviewDataListener previewDataListener) {
mPreviewDataListeners.add(previewDataListener);
}
- /**
- * Removes listener.
- */
+ /** Removes listener. */
public void removeListener(PreviewDataListener previewDataListener) {
mPreviewDataListeners.remove(previewDataListener);
}
- /**
- * Updates the preview programs table for a specific preview channel.
- */
- public void updatePreviewProgramsForChannel(long previewChannelId,
- Set<PreviewProgramContent> programs, PreviewDataListener previewDataListener) {
+ /** Updates the preview programs table for a specific preview channel. */
+ public void updatePreviewProgramsForChannel(
+ long previewChannelId,
+ Set<PreviewProgramContent> programs,
+ PreviewDataListener previewDataListener) {
UpdatePreviewProgramTask currentRunningUpdateTask =
mUpdatePreviewProgramTasks.get(previewChannelId);
if (currentRunningUpdateTask != null
@@ -215,22 +189,19 @@ public class PreviewDataManager {
}
public interface PreviewDataListener {
- /**
- * Called when the preview data is loaded.
- */
+ /** Called when the preview data is loaded. */
void onPreviewDataLoadFinished();
- /**
- * Called when the preview data is updated.
- */
+ /** Called when the preview data is updated. */
void onPreviewDataUpdateFinished();
}
public interface OnPreviewChannelCreationResultListener {
/**
* Called when the creation of preview channel is finished.
- * @param createdPreviewChannelId The preview channel ID if created successfully,
- * otherwise it's {@value #INVALID_PREVIEW_CHANNEL_ID}.
+ *
+ * @param createdPreviewChannelId The preview channel ID if created successfully, otherwise
+ * it's {@value #INVALID_PREVIEW_CHANNEL_ID}.
*/
void onPreviewChannelCreationResult(long createdPreviewChannelId);
}
@@ -283,7 +254,7 @@ public class PreviewDataManager {
android.support.media.tv.Channel previewChannel =
android.support.media.tv.Channel.fromCursor(cursor);
Long previewChannelType = previewChannel.getInternalProviderFlag1();
- if (previewChannel.getPackageName() == packageName
+ if (packageName.equals(previewChannel.getPackageName())
&& previewChannelType != null) {
previewData.addPreviewChannelId(
previewChannelType, previewChannel.getId());
@@ -352,9 +323,11 @@ public class PreviewDataManager {
if (DEBUG) Log.d(TAG, "CreatePreviewChannelTask.doInBackground");
long previewChannelId;
try {
- Uri channelUri = mContentResolver.insert(TvContract.Channels.CONTENT_URI,
- PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType)
- .toContentValues());
+ Uri channelUri =
+ mContentResolver.insert(
+ TvContract.Channels.CONTENT_URI,
+ PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType)
+ .toContentValues());
if (channelUri != null) {
previewChannelId = ContentUris.parseId(channelUri);
} else {
@@ -367,9 +340,14 @@ public class PreviewDataManager {
}
Drawable appIcon = mContext.getApplicationInfo().loadIcon(mContext.getPackageManager());
if (appIcon != null && appIcon instanceof BitmapDrawable) {
- ChannelLogoUtils.storeChannelLogo(mContext, previewChannelId,
- Bitmap.createScaledBitmap(((BitmapDrawable) appIcon).getBitmap(),
- mPreviewChannelLogoWidth, mPreviewChannelLogoHeight, false));
+ ChannelLogoUtils.storeChannelLogo(
+ mContext,
+ previewChannelId,
+ Bitmap.createScaledBitmap(
+ ((BitmapDrawable) appIcon).getBitmap(),
+ mPreviewChannelLogoWidth,
+ mPreviewChannelLogoHeight,
+ false));
}
return previewChannelId;
}
@@ -380,8 +358,8 @@ public class PreviewDataManager {
if (result != INVALID_PREVIEW_CHANNEL_ID) {
mPreviewData.addPreviewChannelId(mPreviewChannelType, result);
}
- for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener
- : mOnPreviewChannelCreationResultListeners) {
+ for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener :
+ mOnPreviewChannelCreationResultListeners) {
onPreviewChannelCreationResultListener.onPreviewChannelCreationResult(result);
}
mCreatePreviewChannelTasks.remove(mPreviewChannelType);
@@ -389,8 +367,8 @@ public class PreviewDataManager {
}
/**
- * Updates the whole data which belongs to the package in preview programs table for a
- * specific preview channel with a set of {@link PreviewProgramContent}.
+ * Updates the whole data which belongs to the package in preview programs table for a specific
+ * preview channel with a set of {@link PreviewProgramContent}.
*/
private final class UpdatePreviewProgramTask extends AsyncTask<Void, Void, Void> {
private long mPreviewChannelId;
@@ -398,15 +376,15 @@ public class PreviewDataManager {
private Map<Long, Long> mCurrentProgramId2PreviewProgramId;
private Set<PreviewDataListener> mPreviewDataListeners = new CopyOnWriteArraySet<>();
- public UpdatePreviewProgramTask(long previewChannelId,
- Set<PreviewProgramContent> programs) {
+ public UpdatePreviewProgramTask(
+ long previewChannelId, Set<PreviewProgramContent> programs) {
mPreviewChannelId = previewChannelId;
mPrograms = programs;
if (mPreviewData.getPreviewProgramIds(previewChannelId) == null) {
mCurrentProgramId2PreviewProgramId = new HashMap<>();
} else {
- mCurrentProgramId2PreviewProgramId = new HashMap<>(
- mPreviewData.getPreviewProgramIds(previewChannelId));
+ mCurrentProgramId2PreviewProgramId =
+ new HashMap<>(mPreviewData.getPreviewProgramIds(previewChannelId));
}
}
@@ -440,14 +418,22 @@ public class PreviewDataManager {
}
Long existingPreviewProgramId = uncheckedPrograms.remove(program.getId());
if (existingPreviewProgramId != null) {
- if (DEBUG) Log.d(TAG, "Preview program " + existingPreviewProgramId + " " +
- "already exists for program " + program.getId());
+ if (DEBUG)
+ Log.d(
+ TAG,
+ "Preview program "
+ + existingPreviewProgramId
+ + " "
+ + "already exists for program "
+ + program.getId());
continue;
}
try {
- Uri programUri = mContentResolver.insert(TvContract.PreviewPrograms.CONTENT_URI,
- PreviewDataUtils.createPreviewProgramFromContent(program)
- .toContentValues());
+ Uri programUri =
+ mContentResolver.insert(
+ TvContract.PreviewPrograms.CONTENT_URI,
+ PreviewDataUtils.createPreviewProgramFromContent(program)
+ .toContentValues());
if (programUri != null) {
long previewProgramId = ContentUris.parseId(programUri);
mCurrentProgramId2PreviewProgramId.put(program.getId(), previewProgramId);
@@ -466,8 +452,10 @@ public class PreviewDataManager {
}
try {
if (DEBUG) Log.d(TAG, "Remove preview program " + uncheckedPrograms.get(key));
- mContentResolver.delete(TvContract.buildPreviewProgramUri(
- uncheckedPrograms.get(key)), null, null);
+ mContentResolver.delete(
+ TvContract.buildPreviewProgramUri(uncheckedPrograms.get(key)),
+ null,
+ null);
mCurrentProgramId2PreviewProgramId.remove(key);
} catch (Exception e) {
Log.e(TAG, "Fail to remove preview program " + uncheckedPrograms.get(key));
@@ -493,9 +481,7 @@ public class PreviewDataManager {
}
}
- /**
- * Class to store the query result of preview data.
- */
+ /** Class to store the query result of preview data. */
private static final class PreviewData {
private Map<Long, Long> mPreviewChannelType2Id = new HashMap<>();
private Map<Long, Map<Long, Long>> mProgramId2PreviewProgramId = new HashMap<>();
@@ -565,13 +551,9 @@ public class PreviewDataManager {
}
}
- /**
- * A utils class for preview data.
- */
- public final static class PreviewDataUtils {
- /**
- * Creates a preview channel.
- */
+ /** A utils class for preview data. */
+ public static final class PreviewDataUtils {
+ /** Creates a preview channel. */
public static android.support.media.tv.Channel createPreviewChannel(
Context context, @PreviewChannelType long previewChannelType) {
if (previewChannelType == TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL) {
@@ -590,7 +572,7 @@ public class PreviewDataManager {
context.getApplicationInfo().loadDescription(context.getPackageManager());
builder.setType(TvContract.Channels.TYPE_PREVIEW)
.setDisplayName(appLabel == null ? null : appLabel.toString())
- .setDescription(appDescription == null ? null : appDescription.toString())
+ .setDescription(appDescription == null ? null : appDescription.toString())
.setAppLinkIntentUri(TvContract.Channels.CONTENT_URI)
.setInternalProviderFlag1(previewChannelType);
return builder.build();
@@ -601,16 +583,15 @@ public class PreviewDataManager {
android.support.media.tv.Channel.Builder builder =
new android.support.media.tv.Channel.Builder();
builder.setType(TvContract.Channels.TYPE_PREVIEW)
- .setDisplayName(context.getResources().getString(
- R.string.recorded_programs_preview_channel))
+ .setDisplayName(
+ context.getResources()
+ .getString(R.string.recorded_programs_preview_channel))
.setAppLinkIntentUri(TvContract.Channels.CONTENT_URI)
.setInternalProviderFlag1(previewChannelType);
return builder.build();
}
- /**
- * Creates a preview program.
- */
+ /** Creates a preview program. */
public static PreviewProgram createPreviewProgramFromContent(
PreviewProgramContent program) {
PreviewProgram.Builder builder = new PreviewProgram.Builder();
@@ -622,13 +603,12 @@ public class PreviewDataManager {
.setPosterArtUri(program.getPosterArtUri())
.setIntentUri(program.getIntentUri())
.setPreviewVideoUri(program.getPreviewVideoUri())
- .setInternalProviderId(Long.toString(program.getId()));
+ .setInternalProviderId(Long.toString(program.getId()))
+ .setContentId(program.getIntentUri().toString());
return builder.build();
}
- /**
- * Appends query parameters to a Uri.
- */
+ /** Appends query parameters to a Uri. */
public static Uri addQueryParamToUri(Uri uri, Pair<String, String> param) {
return uri.buildUpon().appendQueryParameter(param.first, param.second).build();
}
diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java
index 39f5051d..b5156408 100644
--- a/src/com/android/tv/data/PreviewProgramContent.java
+++ b/src/com/android/tv/data/PreviewProgramContent.java
@@ -17,21 +17,19 @@
package com.android.tv.data;
import android.content.Context;
-import android.media.tv.TvContract;
import android.net.Uri;
+import android.support.annotation.VisibleForTesting;
+import android.support.media.tv.TvContractCompat;
import android.text.TextUtils;
import android.util.Pair;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.data.RecordedProgram;
-
import java.util.Objects;
-/**
- * A class to store the content of preview programs.
- */
+/** A class to store the content of preview programs. */
public class PreviewProgramContent {
- private final static String PARAM_INPUT = "input";
+ @VisibleForTesting static final String PARAM_INPUT = "input";
private long mId;
private long mPreviewChannelId;
@@ -43,59 +41,71 @@ public class PreviewProgramContent {
private Uri mIntentUri;
private Uri mPreviewVideoUri;
- /**
- * Create preview program content from {@link Program}
- */
- public static PreviewProgramContent createFromProgram(Context context,
- long previewChannelId, Program program) {
- Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
- .getChannel(program.getChannelId());
- if (channel == null) {
- return null;
- }
+ /** Create preview program content from {@link Program} */
+ public static PreviewProgramContent createFromProgram(
+ Context context, long previewChannelId, Program program) {
+ Channel channel =
+ TvSingletons.getSingletons(context)
+ .getChannelDataManager()
+ .getChannel(program.getChannelId());
+ return channel == null ? null : createFromProgram(previewChannelId, program, channel);
+ }
+
+ /** Create preview program content from {@link RecordedProgram} */
+ public static PreviewProgramContent createFromRecordedProgram(
+ Context context, long previewChannelId, RecordedProgram recordedProgram) {
+ Channel channel =
+ TvSingletons.getSingletons(context)
+ .getChannelDataManager()
+ .getChannel(recordedProgram.getChannelId());
+ return createFromRecordedProgram(previewChannelId, recordedProgram, channel);
+ }
+
+ @VisibleForTesting
+ static PreviewProgramContent createFromProgram(
+ long previewChannelId, Program program, Channel channel) {
String channelDisplayName = channel.getDisplayName();
return new PreviewProgramContent.Builder()
.setId(program.getId())
.setPreviewChannelId(previewChannelId)
- .setType(TvContract.PreviewPrograms.TYPE_CHANNEL)
+ .setType(TvContractCompat.PreviewPrograms.TYPE_CHANNEL)
.setLive(true)
.setTitle(program.getTitle())
- .setDescription(!TextUtils.isEmpty(channelDisplayName)
- ? channelDisplayName : channel.getDisplayNumber())
+ .setDescription(
+ !TextUtils.isEmpty(channelDisplayName)
+ ? channelDisplayName
+ : channel.getDisplayNumber())
.setPosterArtUri(Uri.parse(program.getPosterArtUri()))
.setIntentUri(channel.getUri())
- .setPreviewVideoUri(PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
- channel.getUri(), new Pair<>(PARAM_INPUT, channel.getInputId())))
+ .setPreviewVideoUri(
+ PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
+ channel.getUri(), new Pair<>(PARAM_INPUT, channel.getInputId())))
.build();
}
- /**
- * Create preview program content from {@link RecordedProgram}
- */
- public static PreviewProgramContent createFromRecordedProgram(
- Context context, long previewChannelId, RecordedProgram recordedProgram) {
- Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
- .getChannel(recordedProgram.getChannelId());
- String channelDisplayName = null;
- if (channel != null) {
- channelDisplayName = channel.getDisplayName();
- }
- Uri recordedProgramUri = TvContract.buildRecordedProgramUri(recordedProgram.getId());
+ @VisibleForTesting
+ static PreviewProgramContent createFromRecordedProgram(
+ long previewChannelId, RecordedProgram recordedProgram, Channel channel) {
+ String channelDisplayName = channel == null ? null : channel.getDisplayName();
+ Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(recordedProgram.getId());
return new PreviewProgramContent.Builder()
.setId(recordedProgram.getId())
.setPreviewChannelId(previewChannelId)
- .setType(TvContract.PreviewPrograms.TYPE_CLIP)
+ .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
.setTitle(recordedProgram.getTitle())
.setDescription(channelDisplayName != null ? channelDisplayName : "")
.setPosterArtUri(Uri.parse(recordedProgram.getPosterArtUri()))
.setIntentUri(recordedProgramUri)
- .setPreviewVideoUri(PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
- recordedProgramUri, new Pair<>(PARAM_INPUT, recordedProgram.getInputId())))
+ .setPreviewVideoUri(
+ PreviewDataManager.PreviewDataUtils.addQueryParamToUri(
+ recordedProgramUri,
+ new Pair<>(PARAM_INPUT, recordedProgram.getInputId())))
.build();
}
- private PreviewProgramContent() { }
+ private PreviewProgramContent() {}
+ @SuppressWarnings("ReferenceEquality")
public void copyFrom(PreviewProgramContent other) {
if (this == other) {
return;
@@ -119,58 +129,42 @@ public class PreviewProgramContent {
return mId;
}
- /**
- * Returns the preview channel id which the preview program belongs to.
- */
+ /** Returns the preview channel id which the preview program belongs to. */
public long getPreviewChannelId() {
return mPreviewChannelId;
}
- /**
- * Returns the type of the preview program.
- */
+ /** Returns the type of the preview program. */
public int getType() {
return mType;
}
- /**
- * Returns whether the preview program is live or not.
- */
+ /** Returns whether the preview program is live or not. */
public boolean getLive() {
return mLive;
}
- /**
- * Returns the title of the preview program.
- */
+ /** Returns the title of the preview program. */
public String getTitle() {
return mTitle;
}
- /**
- * Returns the description of the preview program.
- */
+ /** Returns the description of the preview program. */
public String getDescription() {
return mDescription;
}
- /**
- * Returns the poster art uri of the preview program.
- */
+ /** Returns the poster art uri of the preview program. */
public Uri getPosterArtUri() {
return mPosterArtUri;
}
- /**
- * Returns the intent uri of the preview program.
- */
+ /** Returns the intent uri of the preview program. */
public Uri getIntentUri() {
return mIntentUri;
}
- /**
- * Returns the preview video uri of the preview program.
- */
+ /** Returns the preview video uri of the preview program. */
public Uri getPreviewVideoUri() {
return mPreviewVideoUri;
}
@@ -194,8 +188,16 @@ public class PreviewProgramContent {
@Override
public int hashCode() {
- return Objects.hash(mId, mPreviewChannelId, mType, mLive, mTitle, mDescription,
- mPosterArtUri, mIntentUri, mPreviewVideoUri);
+ return Objects.hash(
+ mId,
+ mPreviewChannelId,
+ mType,
+ mLive,
+ mTitle,
+ mDescription,
+ mPosterArtUri,
+ mIntentUri,
+ mPreviewVideoUri);
}
public static final class Builder {
diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java
index 071c7024..2c64cdbb 100644
--- a/src/com/android/tv/data/Program.java
+++ b/src/com/android/tv/data/Program.java
@@ -32,59 +32,56 @@ import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
-
import com.android.tv.common.BuildConfig;
-import com.android.tv.common.CollectionUtils;
import com.android.tv.common.TvContentRatingCache;
-import com.android.tv.util.ImageLoader;
+import com.android.tv.common.util.CollectionUtils;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.Utils;
-
+import com.android.tv.util.images.ImageLoader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-/**
- * A convenience class to create and insert program information entries into the database.
- */
+/** A convenience class to create and insert program information entries into the database. */
public final class Program extends BaseProgram implements Comparable<Program>, Parcelable {
private static final boolean DEBUG = false;
private static final boolean DEBUG_DUMP_DESCRIPTION = false;
private static final String TAG = "Program";
private static final String[] PROJECTION_BASE = {
- // Columns must match what is read in Program.fromCursor()
- TvContract.Programs._ID,
- TvContract.Programs.COLUMN_PACKAGE_NAME,
- TvContract.Programs.COLUMN_CHANNEL_ID,
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_EPISODE_TITLE,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_LONG_DESCRIPTION,
- TvContract.Programs.COLUMN_POSTER_ART_URI,
- TvContract.Programs.COLUMN_THUMBNAIL_URI,
- TvContract.Programs.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_VIDEO_WIDTH,
- TvContract.Programs.COLUMN_VIDEO_HEIGHT,
- TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
+ // Columns must match what is read in Program.fromCursor()
+ TvContract.Programs._ID,
+ TvContract.Programs.COLUMN_PACKAGE_NAME,
+ TvContract.Programs.COLUMN_CHANNEL_ID,
+ TvContract.Programs.COLUMN_TITLE,
+ TvContract.Programs.COLUMN_EPISODE_TITLE,
+ TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
+ TvContract.Programs.COLUMN_LONG_DESCRIPTION,
+ TvContract.Programs.COLUMN_POSTER_ART_URI,
+ TvContract.Programs.COLUMN_THUMBNAIL_URI,
+ TvContract.Programs.COLUMN_CANONICAL_GENRE,
+ TvContract.Programs.COLUMN_CONTENT_RATING,
+ TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_VIDEO_WIDTH,
+ TvContract.Programs.COLUMN_VIDEO_HEIGHT,
+ TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
};
// Columns which is deprecated in NYC
@SuppressWarnings("deprecation")
private static final String[] PROJECTION_DEPRECATED_IN_NYC = {
- TvContract.Programs.COLUMN_SEASON_NUMBER,
- TvContract.Programs.COLUMN_EPISODE_NUMBER
+ TvContract.Programs.COLUMN_SEASON_NUMBER, TvContract.Programs.COLUMN_EPISODE_NUMBER
};
private static final String[] PROJECTION_ADDED_IN_NYC = {
- TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_SEASON_TITLE,
- TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_RECORDING_PROHIBITED
+ TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+ TvContract.Programs.COLUMN_SEASON_TITLE,
+ TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+ TvContract.Programs.COLUMN_RECORDING_PROHIBITED
};
public static final String[] PROJECTION = createProjection();
@@ -97,9 +94,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
: PROJECTION_DEPRECATED_IN_NYC);
}
- /**
- * Returns the column index for {@code column}, -1 if the column doesn't exist.
- */
+ /** Returns the column index for {@code column}, -1 if the column doesn't exist. */
public static int getColumnIndex(String column) {
for (int i = 0; i < PROJECTION.length; ++i) {
if (PROJECTION[i].equals(column)) {
@@ -135,7 +130,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
builder.setEndTimeUtcMillis(cursor.getLong(index++));
builder.setVideoWidth((int) cursor.getLong(index++));
builder.setVideoHeight((int) cursor.getLong(index++));
- if (Utils.isInBundledPackageSet(packageName)) {
+ if (CommonUtils.isInBundledPackageSet(packageName)) {
InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
}
index++;
@@ -183,17 +178,18 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return program;
}
- public static final Parcelable.Creator<Program> CREATOR = new Parcelable.Creator<Program>() {
- @Override
- public Program createFromParcel(Parcel in) {
- return Program.fromParcel(in);
- }
+ public static final Parcelable.Creator<Program> CREATOR =
+ new Parcelable.Creator<Program>() {
+ @Override
+ public Program createFromParcel(Parcel in) {
+ return Program.fromParcel(in);
+ }
- @Override
- public Program[] newArray(int size) {
- return new Program[size];
- }
- };
+ @Override
+ public Program[] newArray(int size) {
+ return new Program[size];
+ }
+ };
private long mId;
private String mPackageName;
@@ -225,9 +221,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return mId;
}
- /**
- * Returns the package name of this program.
- */
+ /** Returns the package name of this program. */
public String getPackageName() {
return mPackageName;
}
@@ -236,18 +230,14 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return mChannelId;
}
- /**
- * Returns {@code true} if this program is valid or {@code false} otherwise.
- */
+ /** Returns {@code true} if this program is valid or {@code false} otherwise. */
@Override
public boolean isValid() {
return mChannelId >= 0;
}
- /**
- * Returns {@code true} if the program is valid and {@code false} otherwise.
- */
- public static boolean isValid(Program program) {
+ /** Returns {@code true} if the program is valid and {@code false} otherwise. */
+ public static boolean isProgramValid(Program program) {
return program != null && program.isValid();
}
@@ -256,17 +246,13 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return mTitle;
}
- /**
- * Returns the series ID.
- */
+ /** Returns the series ID. */
@Override
public String getSeriesId() {
return mSeriesId;
}
- /**
- * Returns the episode title.
- */
+ /** Returns the episode title. */
@Override
public String getEpisodeTitle() {
return mEpisodeTitle;
@@ -292,9 +278,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return mEndTimeUtcMillis;
}
- /**
- * Returns the program duration.
- */
+ /** Returns the program duration. */
@Override
public long getDurationMillis() {
return mEndTimeUtcMillis - mStartTimeUtcMillis;
@@ -318,9 +302,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return mVideoHeight;
}
- /**
- * Returns the list of Critic Scores for this program
- */
+ /** Returns the list of Critic Scores for this program */
@Nullable
public List<CriticScore> getCriticScores() {
return mCriticScores;
@@ -342,17 +324,12 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return mThumbnailUri;
}
- /**
- * Returns {@code true} if the recording of this program is prohibited.
- */
+ /** Returns {@code true} if the recording of this program is prohibited. */
public boolean isRecordingProhibited() {
return mRecordingProhibited;
}
- /**
- * Returns array of canonical genres for this program.
- * This is expected to be called rarely.
- */
+ /** Returns array of canonical genres for this program. This is expected to be called rarely. */
@Nullable
public String[] getCanonicalGenres() {
if (mCanonicalGenreIds == null) {
@@ -365,17 +342,13 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return genres;
}
- /**
- * Returns array of canonical genre ID's for this program.
- */
+ /** Returns array of canonical genre ID's for this program. */
@Override
public int[] getCanonicalGenreIds() {
return mCanonicalGenreIds;
}
- /**
- * Returns if this program has the genre.
- */
+ /** Returns if this program has the genre. */
public boolean hasGenre(int genreId) {
if (genreId == GenreItems.ID_ALL_CHANNELS) {
return true;
@@ -393,10 +366,24 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
@Override
public int hashCode() {
// Hash with all the properties because program ID can be invalid for the dummy programs.
- return Objects.hash(mChannelId, mStartTimeUtcMillis, mEndTimeUtcMillis,
- mTitle, mSeriesId, mEpisodeTitle, mDescription, mLongDescription, mVideoWidth,
- mVideoHeight, mPosterArtUri, mThumbnailUri, Arrays.hashCode(mContentRatings),
- Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mSeasonTitle, mEpisodeNumber,
+ return Objects.hash(
+ mChannelId,
+ mStartTimeUtcMillis,
+ mEndTimeUtcMillis,
+ mTitle,
+ mSeriesId,
+ mEpisodeTitle,
+ mDescription,
+ mLongDescription,
+ mVideoWidth,
+ mVideoHeight,
+ mPosterArtUri,
+ mThumbnailUri,
+ Arrays.hashCode(mContentRatings),
+ Arrays.hashCode(mCanonicalGenreIds),
+ mSeasonNumber,
+ mSeasonTitle,
+ mEpisodeNumber,
mRecordingProhibited);
}
@@ -436,28 +423,47 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append("Program[").append(mId)
- .append("]{channelId=").append(mChannelId)
- .append(", packageName=").append(mPackageName)
- .append(", title=").append(mTitle)
- .append(", seriesId=").append(mSeriesId)
- .append(", episodeTitle=").append(mEpisodeTitle)
- .append(", seasonNumber=").append(mSeasonNumber)
- .append(", seasonTitle=").append(mSeasonTitle)
- .append(", episodeNumber=").append(mEpisodeNumber)
- .append(", startTimeUtcSec=").append(Utils.toTimeString(mStartTimeUtcMillis))
- .append(", endTimeUtcSec=").append(Utils.toTimeString(mEndTimeUtcMillis))
- .append(", videoWidth=").append(mVideoWidth)
- .append(", videoHeight=").append(mVideoHeight)
+ builder.append("Program[")
+ .append(mId)
+ .append("]{channelId=")
+ .append(mChannelId)
+ .append(", packageName=")
+ .append(mPackageName)
+ .append(", title=")
+ .append(mTitle)
+ .append(", seriesId=")
+ .append(mSeriesId)
+ .append(", episodeTitle=")
+ .append(mEpisodeTitle)
+ .append(", seasonNumber=")
+ .append(mSeasonNumber)
+ .append(", seasonTitle=")
+ .append(mSeasonTitle)
+ .append(", episodeNumber=")
+ .append(mEpisodeNumber)
+ .append(", startTimeUtcSec=")
+ .append(Utils.toTimeString(mStartTimeUtcMillis))
+ .append(", endTimeUtcSec=")
+ .append(Utils.toTimeString(mEndTimeUtcMillis))
+ .append(", videoWidth=")
+ .append(mVideoWidth)
+ .append(", videoHeight=")
+ .append(mVideoHeight)
.append(", contentRatings=")
.append(TvContentRatingCache.contentRatingsToString(mContentRatings))
- .append(", posterArtUri=").append(mPosterArtUri)
- .append(", thumbnailUri=").append(mThumbnailUri)
- .append(", canonicalGenres=").append(Arrays.toString(mCanonicalGenreIds))
- .append(", recordingProhibited=").append(mRecordingProhibited);
+ .append(", posterArtUri=")
+ .append(mPosterArtUri)
+ .append(", thumbnailUri=")
+ .append(mThumbnailUri)
+ .append(", canonicalGenres=")
+ .append(Arrays.toString(mCanonicalGenreIds))
+ .append(", recordingProhibited=")
+ .append(mRecordingProhibited);
if (DEBUG_DUMP_DESCRIPTION) {
- builder.append(", description=").append(mDescription)
- .append(", longDescription=").append(mLongDescription);
+ builder.append(", description=")
+ .append(mDescription)
+ .append(", longDescription=")
+ .append(mLongDescription);
}
return builder.append("}").toString();
}
@@ -471,12 +477,19 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
public static ContentValues toContentValues(Program program) {
ContentValues values = new ContentValues();
values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId());
+ if (!TextUtils.isEmpty(program.getPackageName())) {
+ values.put(Programs.COLUMN_PACKAGE_NAME, program.getPackageName());
+ }
putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle());
putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- putValue(values, TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+ putValue(
+ values,
+ TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
program.getSeasonNumber());
- putValue(values, TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+ putValue(
+ values,
+ TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
program.getEpisodeNumber());
} else {
putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber());
@@ -488,17 +501,23 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
putValue(values, TvContract.Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri());
String[] canonicalGenres = program.getCanonicalGenres();
if (canonicalGenres != null && canonicalGenres.length > 0) {
- putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE,
+ putValue(
+ values,
+ TvContract.Programs.COLUMN_CANONICAL_GENRE,
TvContract.Programs.Genres.encode(canonicalGenres));
} else {
putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, "");
}
- putValue(values, Programs.COLUMN_CONTENT_RATING,
+ putValue(
+ values,
+ Programs.COLUMN_CONTENT_RATING,
TvContentRatingCache.contentRatingsToString(program.getContentRatings()));
- values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- program.getStartTimeUtcMillis());
+ values.put(
+ TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, program.getStartTimeUtcMillis());
values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis());
- putValue(values, TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA,
+ putValue(
+ values,
+ TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA,
InternalDataUtils.serializeInternalProviderData(program));
return values;
}
@@ -547,15 +566,11 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
mRecordingProhibited = other.mRecordingProhibited;
}
- /**
- * A Builder for the Program class
- */
+ /** A Builder for the Program class */
public static final class Builder {
private final Program mProgram;
- /**
- * Creates a Builder for this Program class
- */
+ /** Creates a Builder for this Program class */
public Builder() {
mProgram = new Program();
// Fill initial data.
@@ -574,8 +589,9 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
}
/**
- * Creates a builder for this Program class
- * by setting default values equivalent to another Program
+ * Creates a builder for this Program class by setting default values equivalent to another
+ * Program
+ *
* @param other the program to be copied
*/
@VisibleForTesting
@@ -586,6 +602,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the ID of this program
+ *
* @param id the ID
* @return a reference to this object
*/
@@ -596,16 +613,18 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the package name for this program
+ *
* @param packageName the package name
* @return a reference to this object
*/
- public Builder setPackageName(String packageName){
+ public Builder setPackageName(String packageName) {
mProgram.mPackageName = packageName;
return this;
}
/**
* Sets the channel ID for this program
+ *
* @param channelId the channel ID
* @return a reference to this object
*/
@@ -616,6 +635,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the program title
+ *
* @param title the title
* @return a reference to this object
*/
@@ -626,6 +646,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the series ID.
+ *
* @param seriesId the series ID
* @return a reference to this object
*/
@@ -636,6 +657,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the episode title if this is a series program
+ *
* @param episodeTitle the episode title
* @return a reference to this object
*/
@@ -646,6 +668,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the season number if this is a series program
+ *
* @param seasonNumber the season number
* @return a reference to this object
*/
@@ -654,9 +677,9 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return this;
}
-
/**
* Sets the season title if this is a series program
+ *
* @param seasonTitle the season title
* @return a reference to this object
*/
@@ -667,6 +690,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the episode number if this is a series program
+ *
* @param episodeNumber the episode number
* @return a reference to this object
*/
@@ -677,6 +701,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the start time of this program
+ *
* @param startTimeUtcMillis the start time in UTC milliseconds
* @return a reference to this object
*/
@@ -687,6 +712,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the end time of this program
+ *
* @param endTimeUtcMillis the end time in UTC milliseconds
* @return a reference to this object
*/
@@ -697,6 +723,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets a description
+ *
* @param description the description
* @return a reference to this object
*/
@@ -707,6 +734,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets a long description
+ *
* @param longDescription the long description
* @return a reference to this object
*/
@@ -717,6 +745,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Defines the video width of this program
+ *
* @param width
* @return a reference to this object
*/
@@ -727,6 +756,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Defines the video height of this program
+ *
* @param height
* @return a reference to this object
*/
@@ -737,6 +767,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the content ratings for this program
+ *
* @param contentRatings the content ratings
* @return a reference to this object
*/
@@ -747,6 +778,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the poster art URI
+ *
* @param posterArtUri the poster art URI
* @return a reference to this object
*/
@@ -757,6 +789,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the thumbnail URI
+ *
* @param thumbnailUri the thumbnail URI
* @return a reference to this object
*/
@@ -767,6 +800,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the canonical genres by id
+ *
* @param genres the genres
* @return a reference to this object
*/
@@ -777,6 +811,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the recording prohibited flag
+ *
* @param recordingProhibited recording prohibited flag
* @return a reference to this object
*/
@@ -787,6 +822,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Adds a critic score
+ *
* @param criticScore the critic score
* @return a reference to this object
*/
@@ -802,6 +838,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Sets the critic scores
+ *
* @param criticScores the critic scores
* @return a reference to this objects
*/
@@ -812,6 +849,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Returns a reference to the Program object being constructed
+ *
* @return the Program object constructed
*/
public Program build() {
@@ -831,7 +869,9 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
}
/**
- * Prefetches the program poster art.<p>
+ * Prefetches the program poster art.
+ *
+ * <p>
*/
public void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight) {
if (mPosterArtUri == null) {
@@ -842,13 +882,17 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Loads the program poster art and returns it via {@code callback}.
- * <p>
- * Note that it may directly call {@code callback} if the program poster art already is loaded.
+ *
+ * <p>Note that it may directly call {@code callback} if the program poster art already is
+ * loaded.
*
* @return {@code true} if the load is complete and the callback is executed.
*/
@UiThread
- public boolean loadPosterArt(Context context, int posterArtWidth, int posterArtHeight,
+ public boolean loadPosterArt(
+ Context context,
+ int posterArtWidth,
+ int posterArtHeight,
ImageLoader.ImageLoaderCallback callback) {
if (mPosterArtUri == null) {
return false;
@@ -861,12 +905,18 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
if (p1 == null || p2 == null) {
return false;
}
- boolean isDuplicate = p1.getChannelId() == p2.getChannelId()
- && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis()
- && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis();
+ boolean isDuplicate =
+ p1.getChannelId() == p2.getChannelId()
+ && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis()
+ && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis();
if (DEBUG && BuildConfig.ENG && isDuplicate) {
- Log.w(TAG, "Duplicate programs detected! - \"" + p1.getTitle() + "\" and \""
- + p2.getTitle() + "\"");
+ Log.w(
+ TAG,
+ "Duplicate programs detected! - \""
+ + p1.getTitle()
+ + "\" and \""
+ + p2.getTitle()
+ + "\"");
}
return isDuplicate;
}
@@ -906,21 +956,13 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
out.writeByte((byte) (mRecordingProhibited ? 1 : 0));
}
- /**
- * Holds one type of critic score and its source.
- */
+ /** Holds one type of critic score and its source. */
public static final class CriticScore implements Serializable, Parcelable {
- /**
- * The source of the rating.
- */
+ /** The source of the rating. */
public final String source;
- /**
- * The score.
- */
+ /** The score. */
public final String score;
- /**
- * The url of the logo image
- */
+ /** The url of the logo image */
public final String logoUrl;
public static final Parcelable.Creator<CriticScore> CREATOR =
@@ -929,7 +971,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
public CriticScore createFromParcel(Parcel in) {
String source = in.readString();
String score = in.readString();
- String logoUri = in.readString();
+ String logoUri = in.readString();
return new CriticScore(source, score, logoUri);
}
@@ -941,6 +983,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
/**
* Constructor for this class.
+ *
* @param source the source of the rating
* @param score the score
*/
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index 8cb5e74a..4631806c 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -33,14 +33,16 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
-
-import com.android.tv.common.MemoryManageable;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.config.api.RemoteConfig;
+import com.android.tv.common.config.api.RemoteConfigValue;
+import com.android.tv.common.memory.MemoryManageable;
+import com.android.tv.common.util.Clock;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.AsyncDbTask;
-import com.android.tv.util.Clock;
import com.android.tv.util.MultiLongSparseArray;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -51,6 +53,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@MainThread
@@ -60,23 +63,28 @@ public class ProgramDataManager implements MemoryManageable {
// To prevent from too many program update operations at the same time, we give random interval
// between PERIODIC_PROGRAM_UPDATE_MIN_MS and PERIODIC_PROGRAM_UPDATE_MAX_MS.
- private static final long PERIODIC_PROGRAM_UPDATE_MIN_MS = TimeUnit.MINUTES.toMillis(5);
+ @VisibleForTesting
+ static final long PERIODIC_PROGRAM_UPDATE_MIN_MS = TimeUnit.MINUTES.toMillis(5);
+
private static final long PERIODIC_PROGRAM_UPDATE_MAX_MS = TimeUnit.MINUTES.toMillis(10);
private static final long PROGRAM_PREFETCH_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5);
// TODO: need to optimize consecutive DB updates.
private static final long CURRENT_PROGRAM_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5);
- @VisibleForTesting
- static final long PROGRAM_GUIDE_SNAP_TIME_MS = TimeUnit.MINUTES.toMillis(30);
- @VisibleForTesting
- static final long PROGRAM_GUIDE_MAX_TIME_RANGE = TimeUnit.DAYS.toMillis(2);
+ @VisibleForTesting static final long PROGRAM_GUIDE_SNAP_TIME_MS = TimeUnit.MINUTES.toMillis(30);
+ private static final RemoteConfigValue<Long> PROGRAM_GUIDE_MAX_HOURS =
+ RemoteConfigValue.create("live_channels_program_guide_max_hours", 48);
// TODO: Use TvContract constants, once they become public.
private static final String PARAM_START_TIME = "start_time";
private static final String PARAM_END_TIME = "end_time";
// COLUMN_CHANNEL_ID, COLUMN_END_TIME_UTC_MILLIS are added to detect duplicated programs.
// Duplicated programs are always consecutive by the sorting order.
- private static final String SORT_BY_TIME = Programs.COLUMN_START_TIME_UTC_MILLIS + ", "
- + Programs.COLUMN_CHANNEL_ID + ", " + Programs.COLUMN_END_TIME_UTC_MILLIS;
+ private static final String SORT_BY_TIME =
+ Programs.COLUMN_START_TIME_UTC_MILLIS
+ + ", "
+ + Programs.COLUMN_CHANNEL_ID
+ + ", "
+ + Programs.COLUMN_END_TIME_UTC_MILLIS;
private static final int MSG_UPDATE_CURRENT_PROGRAMS = 1000;
private static final int MSG_UPDATE_ONE_CURRENT_PROGRAM = 1001;
@@ -84,6 +92,8 @@ public class ProgramDataManager implements MemoryManageable {
private final Clock mClock;
private final ContentResolver mContentResolver;
+ private final Executor mDbExecutor;
+ private final RemoteConfig mRemoteConfig;
private boolean mStarted;
// Updated only on the main thread.
private volatile boolean mCurrentProgramsLoadFinished;
@@ -114,32 +124,47 @@ public class ProgramDataManager implements MemoryManageable {
@MainThread
public ProgramDataManager(Context context) {
- this(context.getContentResolver(), Clock.SYSTEM, Looper.myLooper());
+ this(
+ TvSingletons.getSingletons(context).getDbExecutor(),
+ context.getContentResolver(),
+ Clock.SYSTEM,
+ Looper.myLooper(),
+ TvSingletons.getSingletons(context).getRemoteConfig());
}
@VisibleForTesting
- ProgramDataManager(ContentResolver contentResolver, Clock time, Looper looper) {
+ ProgramDataManager(
+ Executor executor,
+ ContentResolver contentResolver,
+ Clock time,
+ Looper looper,
+ RemoteConfig remoteConfig) {
+ mDbExecutor = executor;
mClock = time;
mContentResolver = contentResolver;
mHandler = new MyHandler(looper);
- mProgramObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) {
- mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS);
- }
- if (isProgramUpdatePaused()) {
- return;
- }
- if (mPrefetchEnabled) {
- // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be quite long
- // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing message
- // and send MSG_UPDATE_PREFETCH_PROGRAM again.
- mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
- mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
- }
- }
- };
+ mRemoteConfig = remoteConfig;
+ mProgramObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS);
+ }
+ if (isProgramUpdatePaused()) {
+ return;
+ }
+ if (mPrefetchEnabled) {
+ // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be
+ // quite long
+ // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing
+ // message
+ // and send MSG_UPDATE_PREFETCH_PROGRAM again.
+ mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
+ }
+ };
mProgramPrefetchUpdateWaitMs = PROGRAM_PREFETCH_UPDATE_WAIT_MS;
}
@@ -149,18 +174,16 @@ public class ProgramDataManager implements MemoryManageable {
}
/**
- * Set the program prefetch update wait which gives the delay to query all programs from DB
- * to prevent from too frequent DB queries.
- * Default value is {@link #PROGRAM_PREFETCH_UPDATE_WAIT_MS}
+ * Set the program prefetch update wait which gives the delay to query all programs from DB to
+ * prevent from too frequent DB queries. Default value is {@link
+ * #PROGRAM_PREFETCH_UPDATE_WAIT_MS}
*/
@VisibleForTesting
void setProgramPrefetchUpdateWait(long programPrefetchUpdateWaitMs) {
mProgramPrefetchUpdateWaitMs = programPrefetchUpdateWaitMs;
}
- /**
- * Starts the manager.
- */
+ /** Starts the manager. */
public void start() {
if (mStarted) {
return;
@@ -172,8 +195,7 @@ public class ProgramDataManager implements MemoryManageable {
if (mPrefetchEnabled) {
mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
}
- mContentResolver.registerContentObserver(Programs.CONTENT_URI,
- true, mProgramObserver);
+ mContentResolver.registerContentObserver(Programs.CONTENT_URI, true, mProgramObserver);
}
/**
@@ -214,9 +236,7 @@ public class ProgramDataManager implements MemoryManageable {
return new ArrayList<>(mChannelIdCurrentProgramMap.values());
}
- /**
- * Reloads program data.
- */
+ /** Reloads program data. */
public void reload() {
if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) {
mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS);
@@ -226,35 +246,27 @@ public class ProgramDataManager implements MemoryManageable {
}
}
- /**
- * A listener interface to receive notification on program data retrieval from DB.
- */
+ /** A listener interface to receive notification on program data retrieval from DB. */
public interface Listener {
/**
- * Called when a Program data is now available through getProgram()
- * after the DB operation is done which wasn't before.
- * This would be called only if fetched data is around the selected program.
- **/
+ * Called when a Program data is now available through getProgram() after the DB operation
+ * is done which wasn't before. This would be called only if fetched data is around the
+ * selected program.
+ */
void onProgramUpdated();
}
- /**
- * Adds the {@link Listener}.
- */
+ /** Adds the {@link Listener}. */
public void addListener(Listener listener) {
mListeners.add(listener);
}
- /**
- * Removes the {@link Listener}.
- */
+ /** Removes the {@link Listener}. */
public void removeListener(Listener listener) {
mListeners.remove(listener);
}
- /**
- * Enables or Disables program prefetch.
- */
+ /** Enables or Disables program prefetch. */
public void setPrefetchEnabled(boolean enable) {
if (mPrefetchEnabled == enable) {
return;
@@ -276,10 +288,10 @@ public class ProgramDataManager implements MemoryManageable {
/**
* Returns the programs for the given channel which ends after the given start time.
*
- * <p> Prefetch should be enabled to call it.
+ * <p>Prefetch should be enabled to call it.
*
* @return {@link List} with Programs. It may includes dummy program if the entry needs DB
- * operations to get.
+ * operations to get.
*/
public List<Program> getPrograms(long channelId, long startTime) {
SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
@@ -292,9 +304,12 @@ public class ProgramDataManager implements MemoryManageable {
cachedPrograms.subList(startIndex, cachedPrograms.size()));
}
- // Returns the index of program that is played at the specified time.
- // If there isn't, return the first program among programs that starts after the given time
- // if returnNextProgram is {@code true}.
+ /**
+ * Returns the index of program that is played at the specified time.
+ *
+ * <p>If there isn't, return the first program among programs that starts after the given time
+ * if returnNextProgram is {@code true}.
+ */
private int getProgramIndexAt(List<Program> programs, long time) {
Program key = mZeroLengthProgramCache.get(time);
if (key == null) {
@@ -321,38 +336,38 @@ public class ProgramDataManager implements MemoryManageable {
* Adds the listener to be notified if current program is updated for a channel.
*
* @param channelId A channel ID to get notified. If it's {@link Channel#INVALID_ID}, the
- * listener would be called whenever a current program is updated.
+ * listener would be called whenever a current program is updated.
*/
public void addOnCurrentProgramUpdatedListener(
long channelId, OnCurrentProgramUpdatedListener listener) {
- mChannelId2ProgramUpdatedListeners
- .put(channelId, listener);
+ mChannelId2ProgramUpdatedListeners.put(channelId, listener);
}
/**
- * Removes the listener previously added by
- * {@link #addOnCurrentProgramUpdatedListener(long, OnCurrentProgramUpdatedListener)}.
+ * Removes the listener previously added by {@link #addOnCurrentProgramUpdatedListener(long,
+ * OnCurrentProgramUpdatedListener)}.
*/
public void removeOnCurrentProgramUpdatedListener(
long channelId, OnCurrentProgramUpdatedListener listener) {
- mChannelId2ProgramUpdatedListeners
- .remove(channelId, listener);
+ mChannelId2ProgramUpdatedListeners.remove(channelId, listener);
}
private void notifyCurrentProgramUpdate(long channelId, Program program) {
- for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners
- .get(channelId)) {
+ for (OnCurrentProgramUpdatedListener listener :
+ mChannelId2ProgramUpdatedListeners.get(channelId)) {
listener.onCurrentProgramUpdated(channelId, program);
}
- for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners
- .get(Channel.INVALID_ID)) {
+ for (OnCurrentProgramUpdatedListener listener :
+ mChannelId2ProgramUpdatedListeners.get(Channel.INVALID_ID)) {
listener.onCurrentProgramUpdated(channelId, program);
}
}
private void updateCurrentProgram(long channelId, Program program) {
- Program previousProgram = program == null ? mChannelIdCurrentProgramMap.remove(channelId)
- : mChannelIdCurrentProgramMap.put(channelId, program);
+ Program previousProgram =
+ program == null
+ ? mChannelIdCurrentProgramMap.remove(channelId)
+ : mChannelIdCurrentProgramMap.put(channelId, program);
if (!Objects.equals(program, previousProgram)) {
if (mPrefetchEnabled) {
removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program);
@@ -362,20 +377,23 @@ public class ProgramDataManager implements MemoryManageable {
long delayedTime;
if (program == null) {
- delayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS
- + (long) (Math.random() * (PERIODIC_PROGRAM_UPDATE_MAX_MS
- - PERIODIC_PROGRAM_UPDATE_MIN_MS));
+ delayedTime =
+ PERIODIC_PROGRAM_UPDATE_MIN_MS
+ + (long)
+ (Math.random()
+ * (PERIODIC_PROGRAM_UPDATE_MAX_MS
+ - PERIODIC_PROGRAM_UPDATE_MIN_MS));
} else {
delayedTime = program.getEndTimeUtcMillis() - mClock.currentTimeMillis();
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_UPDATE_ONE_CURRENT_PROGRAM, channelId), delayedTime);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_UPDATE_ONE_CURRENT_PROGRAM, channelId), delayedTime);
}
private void removePreviousProgramsAndUpdateCurrentProgramInCache(
long channelId, Program currentProgram) {
SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
- if (!Program.isValid(currentProgram)) {
+ if (!Program.isProgramValid(currentProgram)) {
return;
}
ArrayList<Program> cachedPrograms = mChannelIdProgramCache.remove(channelId);
@@ -391,27 +409,29 @@ public class ProgramDataManager implements MemoryManageable {
continue;
}
- if (cachedProgram.getEndTimeUtcMillis() <= currentProgram
- .getStartTimeUtcMillis()) {
+ if (cachedProgram.getEndTimeUtcMillis() <= currentProgram.getStartTimeUtcMillis()) {
// Keep the programs that ends earlier than current program
// but later than mPrefetchTimeRangeStartMs.
continue;
}
// Update dummy program around current program if any.
- if (cachedProgram.getStartTimeUtcMillis() < currentProgram
- .getStartTimeUtcMillis()) {
+ if (cachedProgram.getStartTimeUtcMillis() < currentProgram.getStartTimeUtcMillis()) {
// The dummy program starts earlier than the current program. Adjust its end time.
- i.set(createDummyProgram(cachedProgram.getStartTimeUtcMillis(),
- currentProgram.getStartTimeUtcMillis()));
+ i.set(
+ createDummyProgram(
+ cachedProgram.getStartTimeUtcMillis(),
+ currentProgram.getStartTimeUtcMillis()));
i.add(currentProgram);
} else {
i.set(currentProgram);
}
if (currentProgram.getEndTimeUtcMillis() < cachedProgram.getEndTimeUtcMillis()) {
// The dummy program ends later than the current program. Adjust its start time.
- i.add(createDummyProgram(currentProgram.getEndTimeUtcMillis(),
- cachedProgram.getEndTimeUtcMillis()));
+ i.add(
+ createDummyProgram(
+ currentProgram.getEndTimeUtcMillis(),
+ cachedProgram.getEndTimeUtcMillis()));
}
break;
}
@@ -425,8 +445,8 @@ public class ProgramDataManager implements MemoryManageable {
private void handleUpdateCurrentPrograms() {
if (mProgramsUpdateTask != null) {
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_CURRENT_PROGRAMS,
- CURRENT_PROGRAM_UPDATE_WAIT_MS);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_UPDATE_CURRENT_PROGRAMS, CURRENT_PROGRAM_UPDATE_WAIT_MS);
return;
}
clearTask(mProgramUpdateTaskMap);
@@ -443,10 +463,13 @@ public class ProgramDataManager implements MemoryManageable {
private boolean mSuccess;
public ProgramsPrefetchTask() {
+ super(mDbExecutor);
long time = mClock.currentTimeMillis();
- mStartTimeMs = Utils
- .floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS);
- mEndTimeMs = mStartTimeMs + PROGRAM_GUIDE_MAX_TIME_RANGE;
+ mStartTimeMs =
+ Utils.floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS);
+ mEndTimeMs =
+ mStartTimeMs
+ + TimeUnit.HOURS.toMillis(PROGRAM_GUIDE_MAX_HOURS.get(mRemoteConfig));
mSuccess = false;
}
@@ -454,12 +477,19 @@ public class ProgramDataManager implements MemoryManageable {
protected Map<Long, ArrayList<Program>> doInBackground(Void... params) {
Map<Long, ArrayList<Program>> programMap = new HashMap<>();
if (DEBUG) {
- Log.d(TAG, "Starts programs prefetch. " + Utils.toTimeString(mStartTimeMs) + "-"
- + Utils.toTimeString(mEndTimeMs));
+ Log.d(
+ TAG,
+ "Starts programs prefetch. "
+ + Utils.toTimeString(mStartTimeMs)
+ + "-"
+ + Utils.toTimeString(mEndTimeMs));
}
- Uri uri = Programs.CONTENT_URI.buildUpon()
- .appendQueryParameter(PARAM_START_TIME, String.valueOf(mStartTimeMs))
- .appendQueryParameter(PARAM_END_TIME, String.valueOf(mEndTimeMs)).build();
+ Uri uri =
+ Programs.CONTENT_URI
+ .buildUpon()
+ .appendQueryParameter(PARAM_START_TIME, String.valueOf(mStartTimeMs))
+ .appendQueryParameter(PARAM_END_TIME, String.valueOf(mEndTimeMs))
+ .build();
final int RETRY_COUNT = 3;
Program lastReadProgram = null;
for (int retryCount = RETRY_COUNT; retryCount > 0; retryCount--) {
@@ -467,8 +497,8 @@ public class ProgramDataManager implements MemoryManageable {
return null;
}
programMap.clear();
- try (Cursor c = mContentResolver.query(uri, Program.PROJECTION, null, null,
- SORT_BY_TIME)) {
+ try (Cursor c =
+ mContentResolver.query(uri, Program.PROJECTION, null, null, SORT_BY_TIME)) {
if (c == null) {
continue;
}
@@ -527,14 +557,16 @@ public class ProgramDataManager implements MemoryManageable {
long currentTime = mClock.currentTimeMillis();
mLastPrefetchTaskRunMs = currentTime;
nextMessageDelayedTime =
- Utils.floorTime(mLastPrefetchTaskRunMs + PROGRAM_GUIDE_SNAP_TIME_MS,
- PROGRAM_GUIDE_SNAP_TIME_MS) - currentTime;
+ Utils.floorTime(
+ mLastPrefetchTaskRunMs + PROGRAM_GUIDE_SNAP_TIME_MS,
+ PROGRAM_GUIDE_SNAP_TIME_MS)
+ - currentTime;
} else {
nextMessageDelayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS;
}
if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM,
- nextMessageDelayedTime);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_UPDATE_PREFETCH_PROGRAM, nextMessageDelayedTime);
}
}
}
@@ -547,10 +579,18 @@ public class ProgramDataManager implements MemoryManageable {
private class ProgramsUpdateTask extends AsyncDbTask.AsyncQueryTask<List<Program>> {
public ProgramsUpdateTask(ContentResolver contentResolver, long time) {
- super(contentResolver, Programs.CONTENT_URI.buildUpon()
+ super(
+ mDbExecutor,
+ contentResolver,
+ Programs.CONTENT_URI
+ .buildUpon()
.appendQueryParameter(PARAM_START_TIME, String.valueOf(time))
- .appendQueryParameter(PARAM_END_TIME, String.valueOf(time)).build(),
- Program.PROJECTION, null, null, SORT_BY_TIME);
+ .appendQueryParameter(PARAM_END_TIME, String.valueOf(time))
+ .build(),
+ Program.PROJECTION,
+ null,
+ null,
+ SORT_BY_TIME);
}
@Override
@@ -604,10 +644,17 @@ public class ProgramDataManager implements MemoryManageable {
private class UpdateCurrentProgramForChannelTask extends AsyncDbTask.AsyncQueryTask<Program> {
private final long mChannelId;
- private UpdateCurrentProgramForChannelTask(ContentResolver contentResolver, long channelId,
- long time) {
- super(contentResolver, TvContract.buildProgramsUriForChannel(channelId, time, time),
- Program.PROJECTION, null, null, SORT_BY_TIME);
+
+ private UpdateCurrentProgramForChannelTask(
+ ContentResolver contentResolver, long channelId, long time) {
+ super(
+ mDbExecutor,
+ contentResolver,
+ TvContract.buildProgramsUriForChannel(channelId, time, time),
+ Program.PROJECTION,
+ null,
+ null,
+ SORT_BY_TIME);
mChannelId = channelId;
}
@@ -638,48 +685,55 @@ public class ProgramDataManager implements MemoryManageable {
case MSG_UPDATE_CURRENT_PROGRAMS:
handleUpdateCurrentPrograms();
break;
- case MSG_UPDATE_ONE_CURRENT_PROGRAM: {
- long channelId = (Long) msg.obj;
- UpdateCurrentProgramForChannelTask oldTask = mProgramUpdateTaskMap
- .get(channelId);
- if (oldTask != null) {
- oldTask.cancel(true);
- }
- UpdateCurrentProgramForChannelTask
- task = new UpdateCurrentProgramForChannelTask(
- mContentResolver, channelId, mClock.currentTimeMillis());
- mProgramUpdateTaskMap.put(channelId, task);
- task.executeOnDbThread();
- break;
- }
- case MSG_UPDATE_PREFETCH_PROGRAM: {
- if (isProgramUpdatePaused()) {
- return;
- }
- if (mProgramsPrefetchTask != null) {
- mHandler.sendEmptyMessageDelayed(msg.what, mProgramPrefetchUpdateWaitMs);
- return;
+ case MSG_UPDATE_ONE_CURRENT_PROGRAM:
+ {
+ long channelId = (Long) msg.obj;
+ UpdateCurrentProgramForChannelTask oldTask =
+ mProgramUpdateTaskMap.get(channelId);
+ if (oldTask != null) {
+ oldTask.cancel(true);
+ }
+ UpdateCurrentProgramForChannelTask task =
+ new UpdateCurrentProgramForChannelTask(
+ mContentResolver, channelId, mClock.currentTimeMillis());
+ mProgramUpdateTaskMap.put(channelId, task);
+ task.executeOnDbThread();
+ break;
}
- long delayMillis = mLastPrefetchTaskRunMs + mProgramPrefetchUpdateWaitMs
- - mClock.currentTimeMillis();
- if (delayMillis > 0) {
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM, delayMillis);
- } else {
- mProgramsPrefetchTask = new ProgramsPrefetchTask();
- mProgramsPrefetchTask.executeOnDbThread();
+ case MSG_UPDATE_PREFETCH_PROGRAM:
+ {
+ if (isProgramUpdatePaused()) {
+ return;
+ }
+ if (mProgramsPrefetchTask != null) {
+ mHandler.sendEmptyMessageDelayed(
+ msg.what, mProgramPrefetchUpdateWaitMs);
+ return;
+ }
+ long delayMillis =
+ mLastPrefetchTaskRunMs
+ + mProgramPrefetchUpdateWaitMs
+ - mClock.currentTimeMillis();
+ if (delayMillis > 0) {
+ mHandler.sendEmptyMessageDelayed(
+ MSG_UPDATE_PREFETCH_PROGRAM, delayMillis);
+ } else {
+ mProgramsPrefetchTask = new ProgramsPrefetchTask();
+ mProgramsPrefetchTask.executeOnDbThread();
+ }
+ break;
}
- break;
- }
+ default:
+ // Do nothing
}
}
}
/**
- * Pause program update.
- * Updating program data will result in UI refresh,
- * but UI is fragile to handle it so we'd better disable it for a while.
+ * Pause program update. Updating program data will result in UI refresh, but UI is fragile to
+ * handle it so we'd better disable it for a while.
*
- * <p> Prefetch should be enabled to call it.
+ * <p>Prefetch should be enabled to call it.
*/
public void setPauseProgramUpdate(boolean pauseProgramUpdate) {
SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
@@ -700,11 +754,10 @@ public class ProgramDataManager implements MemoryManageable {
}
/**
- * Sets program data prefetch time range.
- * Any program data that ends before the start time will be removed from the cache later.
- * Note that there's no limit for end time.
+ * Sets program data prefetch time range. Any program data that ends before the start time will
+ * be removed from the cache later. Note that there's no limit for end time.
*
- * <p> Prefetch should be enabled to call it.
+ * <p>Prefetch should be enabled to call it.
*/
public void setPrefetchTimeRange(long startTimeMs) {
SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
@@ -736,7 +789,8 @@ public class ProgramDataManager implements MemoryManageable {
return new Program.Builder()
.setChannelId(Channel.INVALID_ID)
.setStartTimeUtcMillis(startTimeMs)
- .setEndTimeUtcMillis(endTimeMs).build();
+ .setEndTimeUtcMillis(endTimeMs)
+ .build();
}
@Override
diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java
index 709863cf..e4237bf4 100644
--- a/src/com/android/tv/data/StreamInfo.java
+++ b/src/com/android/tv/data/StreamInfo.java
@@ -17,6 +17,7 @@
package com.android.tv.data;
import android.media.tv.TvContentRating;
+import com.android.tv.data.api.Channel;
public interface StreamInfo {
int VIDEO_DEFINITION_LEVEL_UNKNOWN = 0;
@@ -28,19 +29,26 @@ public interface StreamInfo {
int AUDIO_CHANNEL_COUNT_UNKNOWN = 0;
Channel getCurrentChannel();
+
TvContentRating getBlockedContentRating();
int getVideoWidth();
+
int getVideoHeight();
+
float getVideoFrameRate();
+
float getVideoDisplayAspectRatio();
+
int getVideoDefinitionLevel();
+
int getAudioChannelCount();
+
boolean hasClosedCaption();
+
boolean isVideoAvailable();
- /**
- * Returns true, if video or audio is available.
- */
+ /** Returns true, if video or audio is available. */
boolean isVideoOrAudioAvailable();
+
int getVideoUnavailableReason();
}
diff --git a/src/com/android/tv/data/TvInputNewComparator.java b/src/com/android/tv/data/TvInputNewComparator.java
index acc3e38a..effca970 100644
--- a/src/com/android/tv/data/TvInputNewComparator.java
+++ b/src/com/android/tv/data/TvInputNewComparator.java
@@ -17,15 +17,11 @@
package com.android.tv.data;
import android.media.tv.TvInputInfo;
-
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.Comparator;
-/**
- * Compares TV input such that the new input comes first.
- */
+/** Compares TV input such that the new input comes first. */
public class TvInputNewComparator implements Comparator<TvInputInfo> {
private final SetupUtils mSetupUtils;
private final TvInputManagerHelper mInputManager;
diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java
index 3edd7b1a..7187efd1 100644
--- a/src/com/android/tv/data/WatchedHistoryManager.java
+++ b/src/com/android/tv/data/WatchedHistoryManager.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.tv.data;
import android.content.Context;
@@ -12,9 +27,8 @@ import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.util.Log;
-
-import com.android.tv.common.SharedPreferencesUtils;
-
+import com.android.tv.common.util.SharedPreferencesUtils;
+import com.android.tv.data.api.Channel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -25,13 +39,14 @@ import java.util.concurrent.TimeUnit;
/**
* A class to manage watched history.
*
- * <p>When there is no access to watched table of TvProvider,
- * this class is used to build up watched history and to compute recent channels.
+ * <p>When there is no access to watched table of TvProvider, this class is used to build up watched
+ * history and to compute recent channels.
+ *
* <p>Note that this class is not thread safe. Please use this on one thread.
*/
public class WatchedHistoryManager {
- private final static String TAG = "WatchedHistoryManager";
- private final static boolean DEBUG = false;
+ private static final String TAG = "WatchedHistoryManager";
+ private static final boolean DEBUG = false;
private static final int MAX_HISTORY_SIZE = 10000;
private static final String PREF_KEY_LAST_INDEX = "last_index";
@@ -47,8 +62,8 @@ public class WatchedHistoryManager {
new OnSharedPreferenceChangeListener() {
@Override
@MainThread
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
if (key.equals(PREF_KEY_LAST_INDEX)) {
final long lastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1);
if (lastIndex <= mLastIndex) {
@@ -57,23 +72,26 @@ public class WatchedHistoryManager {
// onSharedPreferenceChanged is always called in a main thread.
// onNewRecordAdded will be called in the same thread as the thread
// which created this instance.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (long i = mLastIndex + 1; i <= lastIndex; ++i) {
- WatchedRecord record = decode(
- mSharedPreferences.getString(getSharedPreferencesKey(i),
- null));
- if (record != null) {
- mWatchedHistory.add(record);
- if (mListener != null) {
- mListener.onNewRecordAdded(record);
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ for (long i = mLastIndex + 1; i <= lastIndex; ++i) {
+ WatchedRecord record =
+ decode(
+ mSharedPreferences.getString(
+ getSharedPreferencesKey(i),
+ null));
+ if (record != null) {
+ mWatchedHistory.add(record);
+ if (mListener != null) {
+ mListener.onNewRecordAdded(record);
+ }
+ }
}
+ mLastIndex = lastIndex;
}
- }
- mLastIndex = lastIndex;
- }
- });
+ });
}
}
};
@@ -94,9 +112,7 @@ public class WatchedHistoryManager {
mHandler = new Handler();
}
- /**
- * Starts the manager. It loads history data from {@link SharedPreferences}.
- */
+ /** Starts the manager. It loads history data from {@link SharedPreferences}. */
public void start() {
if (mStarted) {
return;
@@ -123,22 +139,22 @@ public class WatchedHistoryManager {
@WorkerThread
private void loadWatchedHistory() {
- mSharedPreferences = mContext.getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE);
+ mSharedPreferences =
+ mContext.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE);
mLastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1);
if (mLastIndex >= 0 && mLastIndex < mMaxHistorySize) {
for (int i = 0; i <= mLastIndex; ++i) {
WatchedRecord record =
- decode(mSharedPreferences.getString(getSharedPreferencesKey(i),
- null));
+ decode(mSharedPreferences.getString(getSharedPreferencesKey(i), null));
if (record != null) {
mWatchedHistory.add(record);
}
}
} else if (mLastIndex >= mMaxHistorySize) {
for (long i = mLastIndex - mMaxHistorySize + 1; i <= mLastIndex; ++i) {
- WatchedRecord record = decode(mSharedPreferences.getString(
- getSharedPreferencesKey(i), null));
+ WatchedRecord record =
+ decode(mSharedPreferences.getString(getSharedPreferencesKey(i), null));
if (record != null) {
mWatchedHistory.add(record);
}
@@ -173,9 +189,7 @@ public class WatchedHistoryManager {
return mLoaded;
}
- /**
- * Logs the record of the watched channel.
- */
+ /** Logs the record of the watched channel. */
public void logChannelViewStop(Channel channel, long endTime, long duration) {
if (duration < MIN_DURATION_MS) {
return;
@@ -185,7 +199,8 @@ public class WatchedHistoryManager {
if (DEBUG) Log.d(TAG, "Log a watched record. " + record);
mWatchedHistory.add(record);
++mLastIndex;
- mSharedPreferences.edit()
+ mSharedPreferences
+ .edit()
.putString(getSharedPreferencesKey(mLastIndex), encode(record))
.putLong(PREF_KEY_LAST_INDEX, mLastIndex)
.apply();
@@ -197,16 +212,14 @@ public class WatchedHistoryManager {
}
}
- /**
- * Sets {@link Listener}.
- */
+ /** Sets {@link Listener}. */
public void setListener(Listener listener) {
mListener = listener;
}
/**
- * Returns watched history in the ascending order of time. In other words, the first element
- * is the oldest and the last element is the latest record.
+ * Returns watched history in the ascending order of time. In other words, the first element is
+ * the oldest and the last element is the latest record.
*/
@NonNull
public List<WatchedRecord> getWatchedHistory() {
@@ -242,8 +255,12 @@ public class WatchedHistoryManager {
@Override
public String toString() {
- return "WatchedRecord: id=" + channelId + ",watchedStartTime=" + watchedStartTime
- + ",duration=" + duration;
+ return "WatchedRecord: id="
+ + channelId
+ + ",watchedStartTime="
+ + watchedStartTime
+ + ",duration="
+ + duration;
}
@Override
@@ -281,10 +298,9 @@ public class WatchedHistoryManager {
}
public interface Listener {
- /**
- * Called when history is loaded.
- */
+ /** Called when history is loaded. */
void onLoadFinished();
+
void onNewRecordAdded(WatchedRecord watchedRecord);
}
}
diff --git a/src/com/android/tv/data/api/Channel.java b/src/com/android/tv/data/api/Channel.java
new file mode 100644
index 00000000..496331cf
--- /dev/null
+++ b/src/com/android/tv/data/api/Channel.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tv.data.api;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import com.android.tv.util.images.ImageLoader.ImageLoaderCallback;
+
+/**
+ * Interface for {@link com.android.tv.data.ChannelImpl}.
+ *
+ * <p><b>NOTE</b> Normally you should not use an interface for a data object like {@code
+ * ChannelImpl}, however there are many circular dependencies. An interface is the easiest way to
+ * break the cycles.
+ */
+public interface Channel {
+
+ long INVALID_ID = -1;
+ int LOAD_IMAGE_TYPE_CHANNEL_LOGO = 1;
+ int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2;
+ int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3;
+ /**
+ * When a TIS doesn't provide any information about app link, and it doesn't have a leanback
+ * launch intent, there will be no app link card for the TIS.
+ */
+ int APP_LINK_TYPE_NONE = -1;
+ /**
+ * When a TIS provide a specific app link information, the app link card will be {@code
+ * APP_LINK_TYPE_CHANNEL} which contains all the provided information.
+ */
+ int APP_LINK_TYPE_CHANNEL = 1;
+ /**
+ * When a TIS doesn't provide a specific app link information, but the app has a leanback launch
+ * intent, the app link card will be {@code APP_LINK_TYPE_APP} which launches the application.
+ */
+ int APP_LINK_TYPE_APP = 2;
+ /** Channel number delimiter between major and minor parts. */
+ char CHANNEL_NUMBER_DELIMITER = '-';
+
+ long getId();
+
+ Uri getUri();
+
+ String getPackageName();
+
+ String getInputId();
+
+ String getType();
+
+ String getDisplayNumber();
+
+ @Nullable
+ String getDisplayName();
+
+ String getDescription();
+
+ String getVideoFormat();
+
+ boolean isPassthrough();
+
+ String getDisplayText();
+
+ String getAppLinkText();
+
+ int getAppLinkColor();
+
+ String getAppLinkIconUri();
+
+ String getAppLinkPosterArtUri();
+
+ String getAppLinkIntentUri();
+
+ String getLogoUri();
+
+ boolean isRecordingProhibited();
+
+ boolean isPhysicalTunerChannel();
+
+ boolean isBrowsable();
+
+ boolean isSearchable();
+
+ boolean isLocked();
+
+ boolean hasSameReadOnlyInfo(Channel mCurrentChannel);
+
+ void setChannelLogoExist(boolean result);
+
+ void setBrowsable(boolean browsable);
+
+ void setLocked(boolean locked);
+
+ void copyFrom(Channel channel);
+
+ void setLogoUri(String logoUri);
+
+ boolean channelLogoExists();
+
+ void loadBitmap(
+ Context context,
+ int loadImageTypeChannelLogo,
+ int mChannelLogoImageViewWidth,
+ int mChannelLogoImageViewHeight,
+ ImageLoaderCallback<?> channelLogoCallback);
+
+ int getAppLinkType(Context context);
+
+ Intent getAppLinkIntent(Context context);
+
+ void prefetchImage(
+ Context mContext,
+ int loadImageTypeChannelLogo,
+ int mPosterArtWidth,
+ int mPosterArtHeight);
+}
diff --git a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java
new file mode 100644
index 00000000..795ad5c4
--- /dev/null
+++ b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.data.epg;
+
+import com.android.tv.data.api.Channel;
+
+/**
+ * Hand copy of generated Autovalue class.
+ *
+ * TODO get autovalue working
+ */
+final class AutoValue_EpgReader_EpgChannel extends EpgReader.EpgChannel {
+
+ private final Channel channel;
+ private final String epgChannelId;
+
+ AutoValue_EpgReader_EpgChannel(
+ Channel channel,
+ String epgChannelId) {
+ if (channel == null) {
+ throw new NullPointerException("Null channel");
+ }
+ this.channel = channel;
+ if (epgChannelId == null) {
+ throw new NullPointerException("Null epgChannelId");
+ }
+ this.epgChannelId = epgChannelId;
+ }
+
+ @Override
+ public Channel getChannel() {
+ return channel;
+ }
+
+ @Override
+ public String getEpgChannelId() {
+ return epgChannelId;
+ }
+
+ @Override
+ public String toString() {
+ return "EpgChannel{"
+ + "channel=" + channel + ", "
+ + "epgChannelId=" + epgChannelId
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof EpgReader.EpgChannel) {
+ EpgReader.EpgChannel that = (EpgReader.EpgChannel) o;
+ return (this.channel.equals(that.getChannel()))
+ && (this.epgChannelId.equals(that.getEpgChannelId()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int h = 1;
+ h *= 1000003;
+ h ^= this.channel.hashCode();
+ h *= 1000003;
+ h ^= this.epgChannelId.hashCode();
+ return h;
+ }
+
+}
+
diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java
index 5693c877..3c7112ec 100644
--- a/src/com/android/tv/data/epg/EpgFetchHelper.java
+++ b/src/com/android/tv/data/epg/EpgFetchHelper.java
@@ -27,15 +27,15 @@ import android.preference.PreferenceManager;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
-
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.util.Clock;
import com.android.tv.data.Program;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
-/** The helper class for {@link com.android.tv.data.epg.EpgFetcher} */
+/** The helper class for {@link EpgFetcher} */
class EpgFetchHelper {
private static final String TAG = "EpgFetchHelper";
private static final boolean DEBUG = false;
@@ -45,15 +45,15 @@ class EpgFetchHelper {
// Value: Long
private static final String KEY_LAST_UPDATED_EPG_TIMESTAMP =
- "com.android.tv.data.epg.EpgFetcher.LastUpdatedEpgTimestamp";
+ CommonConstants.BASE_PACKAGE + ".data.epg.EpgFetcher.LastUpdatedEpgTimestamp";
// Value: String
private static final String KEY_LAST_LINEUP_ID =
- "com.android.tv.data.epg.EpgFetcher.LastLineupId";
+ CommonConstants.BASE_PACKAGE + ".data.epg.EpgFetcher.LastLineupId";
private static long sLastEpgUpdatedTimestamp = -1;
private static String sLastLineupId;
- private EpgFetchHelper() { }
+ private EpgFetchHelper() {}
/**
* Updates newly fetched EPG data for the given channel to local providers. The method will
@@ -61,18 +61,19 @@ class EpgFetchHelper {
* of that channel in the database one by one. It will update the matched old program, or insert
* the new program if there is no matching program can be found in the database and at the same
* time remove those old programs which conflicts with the inserted one.
-
+ *
* @param channelId the target channel ID.
* @param fetchedPrograms the newly fetched program data.
* @return {@code true} if new program data are successfully updated. Otherwise {@code false}.
*/
- static boolean updateEpgData(Context context, long channelId, List<Program> fetchedPrograms) {
+ static boolean updateEpgData(
+ Context context, Clock clock, long channelId, List<Program> fetchedPrograms) {
final int fetchedProgramsCount = fetchedPrograms.size();
if (fetchedProgramsCount == 0) {
return false;
}
boolean updated = false;
- long startTimeMs = System.currentTimeMillis();
+ long startTimeMs = clock.currentTimeMillis();
long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION_MS;
List<Program> oldPrograms = queryPrograms(context, channelId, startTimeMs, endTimeMs);
int oldProgramsIndex = 0;
@@ -82,8 +83,10 @@ class EpgFetchHelper {
// or insert new program if there is no matching program in the database.
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
while (newProgramsIndex < fetchedProgramsCount) {
- Program oldProgram = oldProgramsIndex < oldPrograms.size()
- ? oldPrograms.get(oldProgramsIndex) : null;
+ Program oldProgram =
+ oldProgramsIndex < oldPrograms.size()
+ ? oldPrograms.get(oldProgramsIndex)
+ : null;
Program newProgram = fetchedPrograms.get(newProgramsIndex);
boolean addNewProgram = false;
if (oldProgram != null) {
@@ -95,18 +98,20 @@ class EpgFetchHelper {
// Partial match. Update the old program with the new one.
// NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There
// could be application specific settings which belong to the old program.
- ops.add(ContentProviderOperation.newUpdate(
- TvContract.buildProgramUri(oldProgram.getId()))
- .withValues(Program.toContentValues(newProgram))
- .build());
+ ops.add(
+ ContentProviderOperation.newUpdate(
+ TvContract.buildProgramUri(oldProgram.getId()))
+ .withValues(Program.toContentValues(newProgram))
+ .build());
oldProgramsIndex++;
newProgramsIndex++;
} else if (oldProgram.getEndTimeUtcMillis() < newProgram.getEndTimeUtcMillis()) {
// No match. Remove the old program first to see if the next program in
// {@code oldPrograms} partially matches the new program.
- ops.add(ContentProviderOperation.newDelete(
- TvContract.buildProgramUri(oldProgram.getId()))
- .build());
+ ops.add(
+ ContentProviderOperation.newDelete(
+ TvContract.buildProgramUri(oldProgram.getId()))
+ .build());
oldProgramsIndex++;
} else {
// No match. The new program does not match any of the old programs. Insert
@@ -120,10 +125,10 @@ class EpgFetchHelper {
newProgramsIndex++;
}
if (addNewProgram) {
- ops.add(ContentProviderOperation
- .newInsert(Programs.CONTENT_URI)
- .withValues(Program.toContentValues(newProgram))
- .build());
+ ops.add(
+ ContentProviderOperation.newInsert(Programs.CONTENT_URI)
+ .withValues(Program.toContentValues(newProgram))
+ .build());
}
// Throttle the batch operation not to cause TransactionTooLargeException.
if (ops.size() > BATCH_OPERATION_COUNT || newProgramsIndex >= fetchedProgramsCount) {
@@ -150,11 +155,17 @@ class EpgFetchHelper {
return updated;
}
- private static List<Program> queryPrograms(Context context, long channelId,
- long startTimeMs, long endTimeMs) {
- try (Cursor c = context.getContentResolver().query(
- TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs),
- Program.PROJECTION, null, null, Programs.COLUMN_START_TIME_UTC_MILLIS)) {
+ private static List<Program> queryPrograms(
+ Context context, long channelId, long startTimeMs, long endTimeMs) {
+ try (Cursor c =
+ context.getContentResolver()
+ .query(
+ TvContract.buildProgramsUriForChannel(
+ channelId, startTimeMs, endTimeMs),
+ Program.PROJECTION,
+ null,
+ null,
+ Programs.COLUMN_START_TIME_UTC_MILLIS)) {
if (c == null) {
return Collections.emptyList();
}
@@ -167,8 +178,8 @@ class EpgFetchHelper {
}
/**
- * Returns {@code true} if the {@code oldProgram} needs to be updated with the
- * {@code newProgram}.
+ * Returns {@code true} if the {@code oldProgram} needs to be updated with the {@code
+ * newProgram}.
*/
private static boolean hasSameTitleAndOverlap(Program oldProgram, Program newProgram) {
// NOTE: Here, we update the old program if it has the same title and overlaps with the
@@ -186,24 +197,25 @@ class EpgFetchHelper {
* every time when it needs to fetch EPG data.
*/
@WorkerThread
- synchronized static void setLastLineupId(Context context, String lineupId) {
+ static synchronized void setLastLineupId(Context context, String lineupId) {
if (DEBUG) {
if (lineupId == null) {
Log.d(TAG, "Clear stored lineup id: " + sLastLineupId);
}
}
sLastLineupId = lineupId;
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putString(KEY_LAST_LINEUP_ID, lineupId).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putString(KEY_LAST_LINEUP_ID, lineupId)
+ .apply();
}
- /**
- * Gets the last known lineup ID from shared preferences.
- */
- synchronized static String getLastLineupId(Context context) {
+ /** Gets the last known lineup ID from shared preferences. */
+ static synchronized String getLastLineupId(Context context) {
if (sLastLineupId == null) {
- sLastLineupId = PreferenceManager.getDefaultSharedPreferences(context)
- .getString(KEY_LAST_LINEUP_ID, null);
+ sLastLineupId =
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .getString(KEY_LAST_LINEUP_ID, null);
}
if (DEBUG) Log.d(TAG, "Last lineup is " + sLastLineupId);
return sLastLineupId;
@@ -214,20 +226,21 @@ class EpgFetchHelper {
* out-dated, it's not necessary for EPG fetcher to fetch EPG again.
*/
@WorkerThread
- synchronized static void setLastEpgUpdatedTimestamp(Context context, long timestamp) {
+ static synchronized void setLastEpgUpdatedTimestamp(Context context, long timestamp) {
sLastEpgUpdatedTimestamp = timestamp;
- PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(
- KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp)
+ .apply();
}
- /**
- * Gets the last updated timestamp of EPG data.
- */
- synchronized static long getLastEpgUpdatedTimestamp(Context context) {
+ /** Gets the last updated timestamp of EPG data. */
+ static synchronized long getLastEpgUpdatedTimestamp(Context context) {
if (sLastEpgUpdatedTimestamp < 0) {
- sLastEpgUpdatedTimestamp = PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, 0);
+ sLastEpgUpdatedTimestamp =
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, 0);
}
return sLastEpgUpdatedTimestamp;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/data/epg/EpgFetchService.java b/src/com/android/tv/data/epg/EpgFetchService.java
new file mode 100644
index 00000000..aa4f3588
--- /dev/null
+++ b/src/com/android/tv/data/epg/EpgFetchService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.data.epg;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import com.android.tv.Starter;
+import com.android.tv.TvSingletons;
+import com.android.tv.data.ChannelDataManager;
+
+/** JobService to Fetch EPG data. */
+public class EpgFetchService extends JobService {
+ private EpgFetcher mEpgFetcher;
+ private ChannelDataManager mChannelDataManager;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Starter.start(this);
+ TvSingletons tvSingletons = TvSingletons.getSingletons(getApplicationContext());
+ mEpgFetcher = tvSingletons.getEpgFetcher();
+ mChannelDataManager = tvSingletons.getChannelDataManager();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (!mChannelDataManager.isDbLoadFinished()) {
+ mChannelDataManager.addListener(
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ mChannelDataManager.removeListener(this);
+ if (!mEpgFetcher.executeFetchTaskIfPossible(
+ EpgFetchService.this, params)) {
+ jobFinished(params, false);
+ }
+ }
+
+ @Override
+ public void onChannelListUpdated() {}
+
+ @Override
+ public void onChannelBrowsableChanged() {}
+ });
+ return true;
+ } else {
+ return mEpgFetcher.executeFetchTaskIfPossible(this, params);
+ }
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ mEpgFetcher.stopFetchingJob();
+ return false;
+ }
+}
diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java
index 24f8b826..9c24613d 100644
--- a/src/com/android/tv/data/epg/EpgFetcher.java
+++ b/src/com/android/tv/data/epg/EpgFetcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,720 +16,44 @@
package com.android.tv.data.epg;
-import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.tv.TvInputInfo;
-import android.net.TrafficStats;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.Features;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
-import com.android.tv.config.RemoteConfigUtils;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.ChannelLogoFetcher;
-import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
-import com.android.tv.perf.EventNames;
-import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.TimerEvent;
-import com.android.tv.tuner.util.PostalCodeUtils;
-import com.android.tv.util.LocationUtils;
-import com.android.tv.util.NetworkTrafficTags;
-import com.android.tv.util.Utils;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The service class to fetch EPG routinely or on-demand during channel scanning
- *
- * <p>Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one
- * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on
- * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}.
- */
-public class EpgFetcher {
- private static final String TAG = "EpgFetcher";
- private static final boolean DEBUG = false;
-
- private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101;
-
- private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10);
-
- private static final int REASON_EPG_READER_NOT_READY = 1;
- private static final int REASON_LOCATION_INFO_UNAVAILABLE = 2;
- private static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3;
- private static final int REASON_NO_EPG_DATA_RETURNED = 4;
- private static final int REASON_NO_NEW_EPG = 5;
-
- private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
-
- private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3);
- private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2);
-
- private static final int DEFAULT_ROUTINE_INTERVAL_HOUR = 4;
- private static final String KEY_ROUTINE_INTERVAL = "live_channels_epg_fetcher_interval_hour";
-
- private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1;
- private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2;
- private static final int MSG_FINISH_FETCH_DURING_SCAN = 3;
- private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4;
-
- private static final int QUERY_CHANNEL_COUNT = 50;
- private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3;
-
- private static EpgFetcher sInstance;
-
- private final Context mContext;
- private final ChannelDataManager mChannelDataManager;
- private final EpgReader mEpgReader;
- private final PerformanceMonitor mPerformanceMonitor;
- private FetchAsyncTask mFetchTask;
- private FetchDuringScanHandler mFetchDuringScanHandler;
- private long mEpgTimeStamp;
- private List<Lineup> mPossibleLineups;
- private final Object mPossibleLineupsLock = new Object();
- private final Object mFetchDuringScanHandlerLock = new Object();
- // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished.
- private boolean mScanStarted;
-
- private final long mRoutineIntervalMs;
- private final long mEpgDataExpiredTimeLimitMs;
- private final long mFastFetchDurationSec;
-
- public static EpgFetcher getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new EpgFetcher(context);
- }
- return sInstance;
- }
-
- /** Creates and returns {@link EpgReader}. */
- public static EpgReader createEpgReader(Context context, String region) {
- return new StubEpgReader(context);
- }
-
- private EpgFetcher(Context context) {
- mContext = context.getApplicationContext();
- ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext);
- mChannelDataManager = applicationSingletons.getChannelDataManager();
- mPerformanceMonitor = applicationSingletons.getPerformanceMonitor();
- mEpgReader = createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext));
-
- int remoteInteval =
- (int) RemoteConfigUtils.getRemoteConfig(
- context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR);
- mRoutineIntervalMs =
- remoteInteval < 0
- ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR)
- : TimeUnit.HOURS.toMillis(remoteInteval);
- mEpgDataExpiredTimeLimitMs = mRoutineIntervalMs * 2;
- mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + mRoutineIntervalMs / 1000;
- }
+/** Fetch EPG routinely or on-demand during channel scanning */
+public interface EpgFetcher {
/**
* Starts the routine service of EPG fetching. It use {@link JobScheduler} to schedule the EPG
- * fetching routine. The EPG fetching routine will be started roughly every 4 hours, unless
- * the channel scanning of tuner input is started.
- */
- @MainThread
- public void startRoutineService() {
- JobScheduler jobScheduler =
- (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- for (JobInfo job : jobScheduler.getAllPendingJobs()) {
- if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) {
- return;
- }
- }
- JobInfo job =
- new JobInfo.Builder(
- EPG_ROUTINELY_FETCHING_JOB_ID,
- new ComponentName(mContext, EpgFetchService.class))
- .setPeriodic(mRoutineIntervalMs)
- .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
- .setPersisted(true)
- .build();
- jobScheduler.schedule(job);
- Log.i(TAG, "EPG fetching routine service started.");
- }
-
- /**
- * Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated
- * by routine fetching service due to various reasons.
+ * fetching routine. The EPG fetching routine will be started roughly every 4 hours, unless the
+ * channel scanning of tuner input is started.
*/
@MainThread
- public void fetchImmediatelyIfNeeded() {
- if (TvCommonUtils.isRunningInTest()) {
- // Do not run EpgFetcher in test.
- return;
- }
- new AsyncTask<Void, Void, Long>() {
- @Override
- protected Long doInBackground(Void... args) {
- return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext);
- }
-
- @Override
- protected void onPostExecute(Long result) {
- if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
- > mEpgDataExpiredTimeLimitMs) {
- Log.i(TAG, "EPG data expired. Start fetching immediately.");
- fetchImmediately();
- }
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- /**
- * Fetches EPG immediately.
- */
- @MainThread
- public void fetchImmediately() {
- if (!mChannelDataManager.isDbLoadFinished()) {
- mChannelDataManager.addListener(new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- mChannelDataManager.removeListener(this);
- executeFetchTaskIfPossible(null, null);
- }
-
- @Override
- public void onChannelListUpdated() { }
-
- @Override
- public void onChannelBrowsableChanged() { }
- });
- } else {
- executeFetchTaskIfPossible(null, null);
- }
- }
+ void startRoutineService();
/**
- * Notifies EPG fetch service that channel scanning is started.
+ * Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated by
+ * routine fetching service due to various reasons.
*/
@MainThread
- public void onChannelScanStarted() {
- if (mScanStarted || !Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
- return;
- }
- mScanStarted = true;
- stopFetchingJob();
- synchronized (mFetchDuringScanHandlerLock) {
- if (mFetchDuringScanHandler == null) {
- HandlerThread thread = new HandlerThread("EpgFetchDuringScan");
- thread.start();
- mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper());
- }
- mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN);
- }
- Log.i(TAG, "EPG fetching on channel scanning started.");
- }
+ void fetchImmediatelyIfNeeded();
- /**
- * Notifies EPG fetch service that channel scanning is finished.
- */
+ /** Fetches EPG immediately. */
@MainThread
- public void onChannelScanFinished() {
- if (!mScanStarted) {
- return;
- }
- mScanStarted = false;
- mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
- }
+ void fetchImmediately();
+ /** Notifies EPG fetch service that channel scanning is started. */
@MainThread
- private void stopFetchingJob() {
- if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job...");
- if (mFetchTask != null) {
- mFetchTask.cancel(true);
- mFetchTask = null;
- Log.i(TAG, "EPG routinely fetching job stopped.");
- }
- }
+ void onChannelScanStarted();
+ /** Notifies EPG fetch service that channel scanning is finished. */
@MainThread
- private boolean executeFetchTaskIfPossible(JobService service, JobParameters params) {
- SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished());
- if (!TvCommonUtils.isRunningInTest() && checkFetchPrerequisite()) {
- mFetchTask = new FetchAsyncTask(service, params);
- mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- return true;
- }
- return false;
- }
+ void onChannelScanFinished();
@MainThread
- private boolean checkFetchPrerequisite() {
- if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job.");
- if (!Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
- Log.i(TAG, "Cannot start routine service: country not supported: "
- + LocationUtils.getCurrentCountry(mContext));
- return false;
- }
- if (mFetchTask != null) {
- // Fetching job is already running or ready to run, no need to start again.
- return false;
- }
- if (mFetchDuringScanHandler != null) {
- if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels.");
- return false;
- }
- if (getTunerChannelCount() == 0) {
- if (DEBUG) Log.d(TAG, "Cannot start routine service: no internal tuner channels.");
- return false;
- }
- if (!TextUtils.isEmpty(EpgFetchHelper.getLastLineupId(mContext))) {
- return true;
- }
- if (!TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- return true;
- }
- return true;
- }
+ boolean executeFetchTaskIfPossible(JobService jobService, JobParameters params);
@MainThread
- private int getTunerChannelCount() {
- for (TvInputInfo input : TvApplication.getSingletons(mContext)
- .getTvInputManagerHelper().getTvInputInfos(true, true)) {
- String inputId = input.getId();
- if (Utils.isInternalTvInput(mContext, inputId)) {
- return mChannelDataManager.getChannelCountForInput(inputId);
- }
- }
- return 0;
- }
-
- @AnyThread
- private void clearUnusedLineups(@Nullable String lineupId) {
- synchronized (mPossibleLineupsLock) {
- if (mPossibleLineups == null) {
- return;
- }
- for (Lineup lineup : mPossibleLineups) {
- if (!TextUtils.equals(lineupId, lineup.id)) {
- mEpgReader.clearCachedChannels(lineup.id);
- }
- }
- mPossibleLineups = null;
- }
- }
-
- @WorkerThread
- private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) {
- if (!mEpgReader.isAvailable()) {
- Log.i(TAG, "EPG reader is temporarily unavailable.");
- return REASON_EPG_READER_NOT_READY;
- }
- // Checks the EPG Timestamp.
- mEpgTimeStamp = mEpgReader.getEpgTimestamp();
- if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) {
- if (DEBUG) Log.d(TAG, "No new EPG.");
- return REASON_NO_NEW_EPG;
- }
- // Updates postal code.
- boolean postalCodeChanged = false;
- try {
- postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext);
- } catch (IOException e) {
- if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e);
- if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- return REASON_LOCATION_INFO_UNAVAILABLE;
- }
- } catch (SecurityException e) {
- Log.w(TAG, "No permission to get the current location.");
- if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- return REASON_LOCATION_PERMISSION_NOT_GRANTED;
- }
- } catch (PostalCodeUtils.NoPostalCodeException e) {
- Log.i(TAG, "Cannot get address or postal code.");
- return REASON_LOCATION_INFO_UNAVAILABLE;
- }
- // Updates possible lineups if necessary.
- SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset.");
- if (postalCodeChanged || forceUpdatePossibleLineups
- || EpgFetchHelper.getLastLineupId(mContext) == null) {
- // To prevent main thread being blocked, though theoretically it should not happen.
- List<Lineup> possibleLineups =
- mEpgReader.getLineups(PostalCodeUtils.getLastPostalCode(mContext));
- if (possibleLineups.isEmpty()) {
- return REASON_NO_EPG_DATA_RETURNED;
- }
- for (Lineup lineup : possibleLineups) {
- mEpgReader.preloadChannels(lineup.id);
- }
- synchronized (mPossibleLineupsLock) {
- mPossibleLineups = possibleLineups;
- }
- EpgFetchHelper.setLastLineupId(mContext, null);
- }
- return null;
- }
-
- @WorkerThread
- private void batchFetchEpg(List<Channel> channels, long durationSec) {
- Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + channels.size());
- if (channels.size() == 0) {
- return;
- }
- List<Long> queryChannelIds = new ArrayList<>(QUERY_CHANNEL_COUNT);
- for (Channel channel : channels) {
- queryChannelIds.add(channel.getId());
- if (queryChannelIds.size() >= QUERY_CHANNEL_COUNT) {
- batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec));
- queryChannelIds.clear();
- }
- }
- if (!queryChannelIds.isEmpty()) {
- batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec));
- }
- }
-
- @WorkerThread
- private void batchUpdateEpg(Map<Long, List<Program>> allPrograms) {
- for (Map.Entry<Long, List<Program>> entry : allPrograms.entrySet()) {
- List<Program> programs = entry.getValue();
- if (programs == null) {
- continue;
- }
- Collections.sort(programs);
- Log.i(TAG, "Batch fetched " + programs.size() + " programs for channel "
- + entry.getKey());
- EpgFetchHelper.updateEpgData(mContext, entry.getKey(), programs);
- }
- }
-
- @Nullable
- @WorkerThread
- private String pickBestLineupId(List<Channel> currentChannelList) {
- String maxLineupId = null;
- synchronized (mPossibleLineupsLock) {
- if (mPossibleLineups == null) {
- return null;
- }
- int maxCount = 0;
- for (Lineup lineup : mPossibleLineups) {
- int count = getMatchedChannelCount(lineup.id, currentChannelList);
- Log.i(TAG, lineup.name + " (" + lineup.id + ") - " + count + " matches");
- if (count > maxCount) {
- maxCount = count;
- maxLineupId = lineup.id;
- }
- }
- }
- return maxLineupId;
- }
-
- @WorkerThread
- private int getMatchedChannelCount(String lineupId, List<Channel> currentChannelList) {
- // Construct a list of display numbers for existing channels.
- if (currentChannelList.isEmpty()) {
- if (DEBUG) Log.d(TAG, "No existing channel to compare");
- return 0;
- }
- List<String> numbers = new ArrayList<>(currentChannelList.size());
- for (Channel channel : currentChannelList) {
- // We only support channels from internal tuner inputs.
- if (Utils.isInternalTvInput(mContext, channel.getInputId())) {
- numbers.add(channel.getDisplayNumber());
- }
- }
- numbers.retainAll(mEpgReader.getChannelNumbers(lineupId));
- return numbers.size();
- }
-
- public static class EpgFetchService extends JobService {
- private EpgFetcher mEpgFetcher;
-
- @Override
- public void onCreate() {
- super.onCreate();
- TvApplication.setCurrentRunningProcess(this, true);
- mEpgFetcher = EpgFetcher.getInstance(this);
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- if (!mEpgFetcher.mChannelDataManager.isDbLoadFinished()) {
- mEpgFetcher.mChannelDataManager.addListener(new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- mEpgFetcher.mChannelDataManager.removeListener(this);
- if (!mEpgFetcher.executeFetchTaskIfPossible(EpgFetchService.this, params)) {
- jobFinished(params, false);
- }
- }
-
- @Override
- public void onChannelListUpdated() { }
-
- @Override
- public void onChannelBrowsableChanged() { }
- });
- return true;
- } else {
- return mEpgFetcher.executeFetchTaskIfPossible(this, params);
- }
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- mEpgFetcher.stopFetchingJob();
- return false;
- }
- }
-
- private class FetchAsyncTask extends AsyncTask<Void, Void, Integer> {
- private final JobService mService;
- private final JobParameters mParams;
- private List<Channel> mCurrentChannelList;
- private TimerEvent mTimerEvent;
-
- private FetchAsyncTask(JobService service, JobParameters params) {
- mService = service;
- mParams = params;
- }
-
- @Override
- protected void onPreExecute() {
- mTimerEvent = mPerformanceMonitor.startTimer();
- mCurrentChannelList = mChannelDataManager.getChannelList();
- }
-
- @Override
- protected Integer doInBackground(Void... args) {
- final int oldTag = TrafficStats.getThreadStatsTag();
- TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH);
- try {
- if (DEBUG) Log.d(TAG, "Start EPG routinely fetching.");
- Integer failureReason = prepareFetchEpg(false);
- // InterruptedException might be caught by RPC, we should check it here.
- if (failureReason != null || this.isCancelled()) {
- return failureReason;
- }
- String lineupId = EpgFetchHelper.getLastLineupId(mContext);
- lineupId = lineupId == null ? pickBestLineupId(mCurrentChannelList) : lineupId;
- if (lineupId != null) {
- Log.i(TAG, "Selecting the lineup " + lineupId);
- // During normal fetching process, the lineup ID should be confirmed since all
- // channels are known, clear up possible lineups to save resources.
- EpgFetchHelper.setLastLineupId(mContext, lineupId);
- clearUnusedLineups(lineupId);
- } else {
- Log.i(TAG, "Failed to get lineup id");
- return REASON_NO_EPG_DATA_RETURNED;
- }
- final List<Channel> channels = mEpgReader.getChannels(lineupId);
- // InterruptedException might be caught by RPC, we should check it here.
- if (this.isCancelled()) {
- return null;
- }
- if (channels.isEmpty()) {
- Log.i(TAG, "Failed to get EPG channels.");
- return REASON_NO_EPG_DATA_RETURNED;
- }
- if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
- > mEpgDataExpiredTimeLimitMs) {
- batchFetchEpg(channels, mFastFetchDurationSec);
- }
- new Handler(mContext.getMainLooper())
- .post(
- new Runnable() {
- @Override
- public void run() {
- ChannelLogoFetcher.startFetchingChannelLogos(
- mContext, channels);
- }
- });
- for (Channel channel : channels) {
- if (this.isCancelled()) {
- return null;
- }
- long channelId = channel.getId();
- List<Program> programs = new ArrayList<>(mEpgReader.getPrograms(channelId));
- // InterruptedException might be caught by RPC, we should check it here.
- Collections.sort(programs);
- Log.i(TAG, "Fetched " + programs.size() + " programs for channel " + channelId);
- EpgFetchHelper.updateEpgData(mContext, channelId, programs);
- }
- EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp);
- if (DEBUG) Log.d(TAG, "Fetching EPG is finished.");
- return null;
- } finally {
- TrafficStats.setThreadStatsTag(oldTag);
- }
- }
-
- @Override
- protected void onPostExecute(Integer failureReason) {
- mFetchTask = null;
- if (failureReason == null || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED
- || failureReason == REASON_NO_NEW_EPG) {
- jobFinished(false);
- } else {
- // Applies back-off policy
- jobFinished(true);
- }
- mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK);
- mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK);
- }
-
- @Override
- protected void onCancelled(Integer failureReason) {
- clearUnusedLineups(null);
- jobFinished(false);
- }
-
- private void jobFinished(boolean reschedule) {
- if (mService != null && mParams != null) {
- // Task is executed from JobService, need to report jobFinished.
- mService.jobFinished(mParams, reschedule);
- }
- }
- }
-
- @WorkerThread
- private class FetchDuringScanHandler extends Handler {
- private final Set<Long> mFetchedChannelIdsDuringScan = new HashSet<>();
- private String mPossibleLineupId;
-
- private final ChannelDataManager.Listener mDuringScanChannelListener =
- new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()");
- if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
- && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- Message.obtain(FetchDuringScanHandler.this,
- MSG_CHANNEL_UPDATED_DURING_SCAN, new ArrayList<>(
- mChannelDataManager.getChannelList())).sendToTarget();
- }
- }
-
- @Override
- public void onChannelListUpdated() {
- if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()");
- if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
- && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- Message.obtain(FetchDuringScanHandler.this,
- MSG_CHANNEL_UPDATED_DURING_SCAN,
- mChannelDataManager.getChannelList()).sendToTarget();
- }
- }
-
- @Override
- public void onChannelBrowsableChanged() {
- // Do nothing
- }
- };
-
- @AnyThread
- private FetchDuringScanHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_PREPARE_FETCH_DURING_SCAN:
- case MSG_RETRY_PREPARE_FETCH_DURING_SCAN:
- onPrepareFetchDuringScan();
- break;
- case MSG_CHANNEL_UPDATED_DURING_SCAN:
- if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- onChannelUpdatedDuringScan((List<Channel>) msg.obj);
- }
- break;
- case MSG_FINISH_FETCH_DURING_SCAN:
- removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN);
- if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
- } else {
- onFinishFetchDuringScan();
- }
- break;
- }
- }
-
- private void onPrepareFetchDuringScan() {
- Integer failureReason = prepareFetchEpg(true);
- if (failureReason != null) {
- sendEmptyMessageDelayed(
- MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS);
- return;
- }
- mChannelDataManager.addListener(mDuringScanChannelListener);
- }
-
- private void onChannelUpdatedDuringScan(List<Channel> currentChannelList) {
- String lineupId = pickBestLineupId(currentChannelList);
- Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId);
- if (TextUtils.isEmpty(lineupId)) {
- if (TextUtils.isEmpty(mPossibleLineupId)) {
- return;
- }
- } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) {
- mFetchedChannelIdsDuringScan.clear();
- mPossibleLineupId = lineupId;
- }
- List<Long> currentChannelIds = new ArrayList<>();
- for (Channel channel : currentChannelList) {
- currentChannelIds.add(channel.getId());
- }
- mFetchedChannelIdsDuringScan.retainAll(currentChannelIds);
- List<Channel> newChannels = new ArrayList<>();
- for (Channel channel : mEpgReader.getChannels(mPossibleLineupId)) {
- if (!mFetchedChannelIdsDuringScan.contains(channel.getId())) {
- newChannels.add(channel);
- mFetchedChannelIdsDuringScan.add(channel.getId());
- }
- }
- batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC);
- }
-
- private void onFinishFetchDuringScan() {
- mChannelDataManager.removeListener(mDuringScanChannelListener);
- EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId);
- clearUnusedLineups(null);
- mFetchedChannelIdsDuringScan.clear();
- synchronized (mFetchDuringScanHandlerLock) {
- if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) {
- removeCallbacksAndMessages(null);
- getLooper().quit();
- mFetchDuringScanHandler = null;
- }
- }
- // Clear timestamp to make routine service start right away.
- EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0);
- Log.i(TAG, "EPG Fetching during channel scanning finished.");
- new Handler(Looper.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- fetchImmediately();
- }
- });
- }
- }
+ void stopFetchingJob();
}
diff --git a/src/com/android/tv/data/epg/EpgFetcherImpl.java b/src/com/android/tv/data/epg/EpgFetcherImpl.java
new file mode 100644
index 00000000..2aaaa5b2
--- /dev/null
+++ b/src/com/android/tv/data/epg/EpgFetcherImpl.java
@@ -0,0 +1,811 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.data.epg;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.TrafficStats;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.TvFeatures;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.BuildConfig;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.config.api.RemoteConfigValue;
+import com.android.tv.common.util.Clock;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.LocationUtils;
+import com.android.tv.common.util.NetworkTrafficTags;
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.common.util.PostalCodeUtils;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.ChannelLogoFetcher;
+import com.android.tv.data.Lineup;
+import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
+import com.android.tv.perf.EventNames;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.TimerEvent;
+import com.android.tv.util.Utils;
+import com.google.android.tv.partner.support.EpgInput;
+import com.google.android.tv.partner.support.EpgInputs;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The service class to fetch EPG routinely or on-demand during channel scanning
+ *
+ * <p>Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one
+ * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on
+ * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+ */
+public class EpgFetcherImpl implements EpgFetcher {
+ private static final String TAG = "EpgFetcherImpl";
+ private static final boolean DEBUG = false;
+
+ private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101;
+
+ private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10);
+
+ @VisibleForTesting static final int REASON_EPG_READER_NOT_READY = 1;
+ @VisibleForTesting static final int REASON_LOCATION_INFO_UNAVAILABLE = 2;
+ @VisibleForTesting static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3;
+ @VisibleForTesting static final int REASON_NO_EPG_DATA_RETURNED = 4;
+ @VisibleForTesting static final int REASON_NO_NEW_EPG = 5;
+ @VisibleForTesting static final int REASON_ERROR = 6;
+ @VisibleForTesting static final int REASON_CLOUD_EPG_FAILURE = 7;
+ @VisibleForTesting static final int REASON_NO_BUILT_IN_CHANNELS = 8;
+
+ private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3);
+ private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2);
+
+ private static final RemoteConfigValue<Long> ROUTINE_INTERVAL_HOUR =
+ RemoteConfigValue.create("live_channels_epg_fetcher_interval_hour", 4);
+
+ private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1;
+ private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2;
+ private static final int MSG_FINISH_FETCH_DURING_SCAN = 3;
+ private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4;
+
+ private static final int QUERY_CHANNEL_COUNT = 50;
+ private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3;
+
+ private final Context mContext;
+ private final ChannelDataManager mChannelDataManager;
+ private final EpgReader mEpgReader;
+ private final PerformanceMonitor mPerformanceMonitor;
+ private FetchAsyncTask mFetchTask;
+ private FetchDuringScanHandler mFetchDuringScanHandler;
+ private long mEpgTimeStamp;
+ private List<Lineup> mPossibleLineups;
+ private final Object mPossibleLineupsLock = new Object();
+ private final Object mFetchDuringScanHandlerLock = new Object();
+ // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished.
+ private boolean mScanStarted;
+
+ private final long mRoutineIntervalMs;
+ private final long mEpgDataExpiredTimeLimitMs;
+ private final long mFastFetchDurationSec;
+ private Clock mClock;
+
+ public static EpgFetcher create(Context context) {
+ context = context.getApplicationContext();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ ChannelDataManager channelDataManager = tvSingletons.getChannelDataManager();
+ PerformanceMonitor performanceMonitor = tvSingletons.getPerformanceMonitor();
+ EpgReader epgReader = tvSingletons.providesEpgReader().get();
+ Clock clock = tvSingletons.getClock();
+ long routineIntervalMs = ROUTINE_INTERVAL_HOUR.get(tvSingletons.getRemoteConfig());
+
+ return new EpgFetcherImpl(
+ context,
+ channelDataManager,
+ epgReader,
+ performanceMonitor,
+ clock,
+ routineIntervalMs);
+ }
+
+ @VisibleForTesting
+ EpgFetcherImpl(
+ Context context,
+ ChannelDataManager channelDataManager,
+ EpgReader epgReader,
+ PerformanceMonitor performanceMonitor,
+ Clock clock,
+ long routineIntervalMs) {
+ mContext = context;
+ mChannelDataManager = channelDataManager;
+ mEpgReader = epgReader;
+ mPerformanceMonitor = performanceMonitor;
+ mClock = clock;
+ mRoutineIntervalMs =
+ routineIntervalMs <= 0
+ ? TimeUnit.HOURS.toMillis(ROUTINE_INTERVAL_HOUR.getDefaultValue())
+ : TimeUnit.HOURS.toMillis(routineIntervalMs);
+ mEpgDataExpiredTimeLimitMs = routineIntervalMs * 2;
+ mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + routineIntervalMs / 1000;
+ }
+
+ private static Set<Channel> getExistingChannelsForMyPackage(Context context) {
+ HashSet<Channel> channels = new HashSet<>();
+ String selection = null;
+ String[] selectionArgs = null;
+ String myPackageName = context.getPackageName();
+ if (PermissionUtils.hasAccessAllEpg(context)) {
+ selection = "package_name=?";
+ selectionArgs = new String[] {myPackageName};
+ }
+ try (Cursor c =
+ context.getContentResolver()
+ .query(
+ TvContract.Channels.CONTENT_URI,
+ ChannelImpl.PROJECTION,
+ selection,
+ selectionArgs,
+ null)) {
+ if (c != null) {
+ while (c.moveToNext()) {
+ Channel channel = ChannelImpl.fromCursor(c);
+ if (DEBUG) Log.d(TAG, "Found " + channel);
+ if (myPackageName.equals(channel.getPackageName())) {
+ channels.add(channel);
+ }
+ }
+ }
+ }
+ if (DEBUG)
+ Log.d(TAG, "Found " + channels.size() + " channels for package " + myPackageName);
+ return channels;
+ }
+
+ @Override
+ @MainThread
+ public void startRoutineService() {
+ JobScheduler jobScheduler =
+ (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ for (JobInfo job : jobScheduler.getAllPendingJobs()) {
+ if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) {
+ return;
+ }
+ }
+ JobInfo job =
+ new JobInfo.Builder(
+ EPG_ROUTINELY_FETCHING_JOB_ID,
+ new ComponentName(mContext, EpgFetchService.class))
+ .setPeriodic(mRoutineIntervalMs)
+ .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
+ .setPersisted(true)
+ .build();
+ jobScheduler.schedule(job);
+ Log.i(TAG, "EPG fetching routine service started.");
+ }
+
+ @Override
+ @MainThread
+ public void fetchImmediatelyIfNeeded() {
+ if (CommonUtils.isRunningInTest()) {
+ // Do not run EpgFetcher in test.
+ return;
+ }
+ new AsyncTask<Void, Void, Long>() {
+ @Override
+ protected Long doInBackground(Void... args) {
+ return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext);
+ }
+
+ @Override
+ protected void onPostExecute(Long result) {
+ if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
+ > mEpgDataExpiredTimeLimitMs) {
+ Log.i(TAG, "EPG data expired. Start fetching immediately.");
+ fetchImmediately();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @Override
+ @MainThread
+ public void fetchImmediately() {
+ if (DEBUG) Log.d(TAG, "fetchImmediately");
+ if (!mChannelDataManager.isDbLoadFinished()) {
+ mChannelDataManager.addListener(
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ mChannelDataManager.removeListener(this);
+ executeFetchTaskIfPossible(null, null);
+ }
+
+ @Override
+ public void onChannelListUpdated() {}
+
+ @Override
+ public void onChannelBrowsableChanged() {}
+ });
+ } else {
+ executeFetchTaskIfPossible(null, null);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onChannelScanStarted() {
+ if (mScanStarted || !TvFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
+ return;
+ }
+ mScanStarted = true;
+ stopFetchingJob();
+ synchronized (mFetchDuringScanHandlerLock) {
+ if (mFetchDuringScanHandler == null) {
+ HandlerThread thread = new HandlerThread("EpgFetchDuringScan");
+ thread.start();
+ mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper());
+ }
+ mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN);
+ }
+ Log.i(TAG, "EPG fetching on channel scanning started.");
+ }
+
+ @Override
+ @MainThread
+ public void onChannelScanFinished() {
+ if (!mScanStarted) {
+ return;
+ }
+ mScanStarted = false;
+ mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
+ }
+
+ @MainThread
+ @Override
+ public void stopFetchingJob() {
+ if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job...");
+ if (mFetchTask != null) {
+ mFetchTask.cancel(true);
+ mFetchTask = null;
+ Log.i(TAG, "EPG routinely fetching job stopped.");
+ }
+ }
+
+ @MainThread
+ @Override
+ public boolean executeFetchTaskIfPossible(JobService service, JobParameters params) {
+ if (DEBUG) Log.d(TAG, "executeFetchTaskIfPossible");
+ SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished());
+ if (!CommonUtils.isRunningInTest() && checkFetchPrerequisite()) {
+ mFetchTask = createFetchTask(service, params);
+ mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ FetchAsyncTask createFetchTask(JobService service, JobParameters params) {
+ return new FetchAsyncTask(service, params);
+ }
+
+ @MainThread
+ private boolean checkFetchPrerequisite() {
+ if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job.");
+ if (!TvFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
+ Log.i(
+ TAG,
+ "Cannot start routine service: country not supported: "
+ + LocationUtils.getCurrentCountry(mContext));
+ return false;
+ }
+ if (mFetchTask != null) {
+ // Fetching job is already running or ready to run, no need to start again.
+ return false;
+ }
+ if (mFetchDuringScanHandler != null) {
+ if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels.");
+ return false;
+ }
+ return true;
+ }
+
+ @MainThread
+ private int getTunerChannelCount() {
+ for (TvInputInfo input :
+ TvSingletons.getSingletons(mContext)
+ .getTvInputManagerHelper()
+ .getTvInputInfos(true, true)) {
+ String inputId = input.getId();
+ if (Utils.isInternalTvInput(mContext, inputId)) {
+ return mChannelDataManager.getChannelCountForInput(inputId);
+ }
+ }
+ return 0;
+ }
+
+ @AnyThread
+ private void clearUnusedLineups(@Nullable String lineupId) {
+ synchronized (mPossibleLineupsLock) {
+ if (mPossibleLineups == null) {
+ return;
+ }
+ for (Lineup lineup : mPossibleLineups) {
+ if (!TextUtils.equals(lineupId, lineup.getId())) {
+ mEpgReader.clearCachedChannels(lineup.getId());
+ }
+ }
+ mPossibleLineups = null;
+ }
+ }
+
+ @WorkerThread
+ private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) {
+ if (!mEpgReader.isAvailable()) {
+ Log.i(TAG, "EPG reader is temporarily unavailable.");
+ return REASON_EPG_READER_NOT_READY;
+ }
+ // Checks the EPG Timestamp.
+ mEpgTimeStamp = mEpgReader.getEpgTimestamp();
+ if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) {
+ if (DEBUG) Log.d(TAG, "No new EPG.");
+ return REASON_NO_NEW_EPG;
+ }
+ // Updates postal code.
+ boolean postalCodeChanged = false;
+ try {
+ postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext);
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e);
+ if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
+ return REASON_LOCATION_INFO_UNAVAILABLE;
+ }
+ } catch (SecurityException e) {
+ Log.w(TAG, "No permission to get the current location.");
+ if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
+ return REASON_LOCATION_PERMISSION_NOT_GRANTED;
+ }
+ } catch (PostalCodeUtils.NoPostalCodeException e) {
+ Log.i(TAG, "Cannot get address or postal code.");
+ return REASON_LOCATION_INFO_UNAVAILABLE;
+ }
+ // Updates possible lineups if necessary.
+ SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset.");
+ if (postalCodeChanged
+ || forceUpdatePossibleLineups
+ || EpgFetchHelper.getLastLineupId(mContext) == null) {
+ // To prevent main thread being blocked, though theoretically it should not happen.
+ String lastPostalCode = PostalCodeUtils.getLastPostalCode(mContext);
+ List<Lineup> possibleLineups = mEpgReader.getLineups(lastPostalCode);
+ if (possibleLineups.isEmpty()) {
+ Log.i(TAG, "No lineups found for " + lastPostalCode);
+ return REASON_NO_EPG_DATA_RETURNED;
+ }
+ for (Lineup lineup : possibleLineups) {
+ mEpgReader.preloadChannels(lineup.getId());
+ }
+ synchronized (mPossibleLineupsLock) {
+ mPossibleLineups = possibleLineups;
+ }
+ EpgFetchHelper.setLastLineupId(mContext, null);
+ }
+ return null;
+ }
+
+ @WorkerThread
+ private void batchFetchEpg(Set<EpgReader.EpgChannel> epgChannels, long durationSec) {
+ Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + epgChannels.size());
+ if (epgChannels.size() == 0) {
+ return;
+ }
+ Set<EpgReader.EpgChannel> batch = new HashSet<>(QUERY_CHANNEL_COUNT);
+ for (EpgReader.EpgChannel epgChannel : epgChannels) {
+ batch.add(epgChannel);
+ if (batch.size() >= QUERY_CHANNEL_COUNT) {
+ batchUpdateEpg(mEpgReader.getPrograms(batch, durationSec));
+ batch.clear();
+ }
+ }
+ if (!batch.isEmpty()) {
+ batchUpdateEpg(mEpgReader.getPrograms(batch, durationSec));
+ }
+ }
+
+ @WorkerThread
+ private void batchUpdateEpg(Map<EpgReader.EpgChannel, Collection<Program>> allPrograms) {
+ for (Map.Entry<EpgReader.EpgChannel, Collection<Program>> entry : allPrograms.entrySet()) {
+ List<Program> programs = new ArrayList(entry.getValue());
+ if (programs == null) {
+ continue;
+ }
+ Collections.sort(programs);
+ Log.i(
+ TAG,
+ "Batch fetched " + programs.size() + " programs for channel " + entry.getKey());
+ EpgFetchHelper.updateEpgData(
+ mContext, mClock, entry.getKey().getChannel().getId(), programs);
+ }
+ }
+
+ @Nullable
+ @WorkerThread
+ private String pickBestLineupId(Set<Channel> currentChannels) {
+ String maxLineupId = null;
+ synchronized (mPossibleLineupsLock) {
+ if (mPossibleLineups == null) {
+ return null;
+ }
+ int maxCount = 0;
+ for (Lineup lineup : mPossibleLineups) {
+ int count = getMatchedChannelCount(lineup.getId(), currentChannels);
+ Log.i(TAG, lineup.getName() + " (" + lineup.getId() + ") - " + count + " matches");
+ if (count > maxCount) {
+ maxCount = count;
+ maxLineupId = lineup.getId();
+ }
+ }
+ }
+ return maxLineupId;
+ }
+
+ @WorkerThread
+ private int getMatchedChannelCount(String lineupId, Set<Channel> currentChannels) {
+ // Construct a list of display numbers for existing channels.
+ if (currentChannels.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No existing channel to compare");
+ return 0;
+ }
+ List<String> numbers = new ArrayList<>(currentChannels.size());
+ for (Channel channel : currentChannels) {
+ // We only support channels from internal tuner inputs.
+ if (Utils.isInternalTvInput(mContext, channel.getInputId())) {
+ numbers.add(channel.getDisplayNumber());
+ }
+ }
+ numbers.retainAll(mEpgReader.getChannelNumbers(lineupId));
+ return numbers.size();
+ }
+
+ @VisibleForTesting
+ class FetchAsyncTask extends AsyncTask<Void, Void, Integer> {
+ private final JobService mService;
+ private final JobParameters mParams;
+ private Set<Channel> mCurrentChannels;
+ private TimerEvent mTimerEvent;
+
+ private FetchAsyncTask(JobService service, JobParameters params) {
+ mService = service;
+ mParams = params;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mTimerEvent = mPerformanceMonitor.startTimer();
+ mCurrentChannels = new HashSet<>(mChannelDataManager.getChannelList());
+ }
+
+ @Override
+ protected Integer doInBackground(Void... args) {
+ final int oldTag = TrafficStats.getThreadStatsTag();
+ TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH);
+ try {
+ if (DEBUG) Log.d(TAG, "Start EPG routinely fetching.");
+ Integer builtInResult = fetchEpgForBuiltInTuner();
+ boolean anyCloudEpgFailure = false;
+ boolean anyCloudEpgSuccess = false;
+ return builtInResult;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+ }
+
+ private Set<Channel> getExistingChannelsFor(String inputId) {
+ Set<Channel> result = new HashSet<>();
+ try (Cursor cursor =
+ mContext.getContentResolver()
+ .query(
+ TvContract.buildChannelsUriForInput(inputId),
+ ChannelImpl.PROJECTION,
+ null,
+ null,
+ null)) {
+ while (cursor.moveToNext()) {
+ result.add(ChannelImpl.fromCursor(cursor));
+ }
+ return result;
+ }
+ }
+
+ private Integer fetchEpgForBuiltInTuner() {
+ try {
+ Integer failureReason = prepareFetchEpg(false);
+ // InterruptedException might be caught by RPC, we should check it here.
+ if (failureReason != null || this.isCancelled()) {
+ return failureReason;
+ }
+ String lineupId = EpgFetchHelper.getLastLineupId(mContext);
+ lineupId = lineupId == null ? pickBestLineupId(mCurrentChannels) : lineupId;
+ if (lineupId != null) {
+ Log.i(TAG, "Selecting the lineup " + lineupId);
+ // During normal fetching process, the lineup ID should be confirmed since all
+ // channels are known, clear up possible lineups to save resources.
+ EpgFetchHelper.setLastLineupId(mContext, lineupId);
+ clearUnusedLineups(lineupId);
+ } else {
+ Log.i(TAG, "Failed to get lineup id");
+ return REASON_NO_EPG_DATA_RETURNED;
+ }
+ Set<Channel> existingChannelsForMyPackage =
+ getExistingChannelsForMyPackage(mContext);
+ if (existingChannelsForMyPackage.isEmpty()) {
+ return REASON_NO_BUILT_IN_CHANNELS;
+ }
+ return fetchEpgFor(lineupId, existingChannelsForMyPackage);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to update EPG for builtin tuner", e);
+ return REASON_ERROR;
+ }
+ }
+
+ @Nullable
+ private Integer fetchEpgFor(String lineupId, Set<Channel> existingChannels) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Starting Fetching EPG is for "
+ + lineupId
+ + " with channelCount "
+ + existingChannels.size());
+ }
+ final Set<EpgReader.EpgChannel> channels =
+ mEpgReader.getChannels(existingChannels, lineupId);
+ // InterruptedException might be caught by RPC, we should check it here.
+ if (this.isCancelled()) {
+ return null;
+ }
+ if (channels.isEmpty()) {
+ Log.i(TAG, "Failed to get EPG channels for " + lineupId);
+ return REASON_NO_EPG_DATA_RETURNED;
+ }
+ if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
+ > mEpgDataExpiredTimeLimitMs) {
+ batchFetchEpg(channels, mFastFetchDurationSec);
+ }
+ new Handler(mContext.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ ChannelLogoFetcher.startFetchingChannelLogos(
+ mContext, asChannelList(channels));
+ }
+ });
+ for (EpgReader.EpgChannel epgChannel : channels) {
+ if (this.isCancelled()) {
+ return null;
+ }
+ List<Program> programs = new ArrayList<>(mEpgReader.getPrograms(epgChannel));
+ // InterruptedException might be caught by RPC, we should check it here.
+ Collections.sort(programs);
+ Log.i(
+ TAG,
+ "Fetched "
+ + programs.size()
+ + " programs for channel "
+ + epgChannel.getChannel());
+ EpgFetchHelper.updateEpgData(
+ mContext, mClock, epgChannel.getChannel().getId(), programs);
+ }
+ EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp);
+ if (DEBUG) Log.d(TAG, "Fetching EPG is for " + lineupId);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Integer failureReason) {
+ mFetchTask = null;
+ if (failureReason == null
+ || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED
+ || failureReason == REASON_NO_NEW_EPG) {
+ jobFinished(false);
+ } else {
+ // Applies back-off policy
+ jobFinished(true);
+ }
+ mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK);
+ mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK);
+ }
+
+ @Override
+ protected void onCancelled(Integer failureReason) {
+ clearUnusedLineups(null);
+ jobFinished(false);
+ }
+
+ private void jobFinished(boolean reschedule) {
+ if (mService != null && mParams != null) {
+ // Task is executed from JobService, need to report jobFinished.
+ mService.jobFinished(mParams, reschedule);
+ }
+ }
+ }
+
+ private List<Channel> asChannelList(Set<EpgReader.EpgChannel> epgChannels) {
+ List<Channel> result = new ArrayList<>(epgChannels.size());
+ for (EpgReader.EpgChannel epgChannel : epgChannels) {
+ result.add(epgChannel.getChannel());
+ }
+ return result;
+ }
+
+ @WorkerThread
+ private class FetchDuringScanHandler extends Handler {
+ private final Set<Long> mFetchedChannelIdsDuringScan = new HashSet<>();
+ private String mPossibleLineupId;
+
+ private final ChannelDataManager.Listener mDuringScanChannelListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()");
+ if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
+ && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ Message.obtain(
+ FetchDuringScanHandler.this,
+ MSG_CHANNEL_UPDATED_DURING_SCAN,
+ getExistingChannelsForMyPackage(mContext))
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void onChannelListUpdated() {
+ if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()");
+ if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
+ && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ Message.obtain(
+ FetchDuringScanHandler.this,
+ MSG_CHANNEL_UPDATED_DURING_SCAN,
+ getExistingChannelsForMyPackage(mContext))
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ // Do nothing
+ }
+ };
+
+ @AnyThread
+ private FetchDuringScanHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PREPARE_FETCH_DURING_SCAN:
+ case MSG_RETRY_PREPARE_FETCH_DURING_SCAN:
+ onPrepareFetchDuringScan();
+ break;
+ case MSG_CHANNEL_UPDATED_DURING_SCAN:
+ if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ onChannelUpdatedDuringScan((Set<Channel>) msg.obj);
+ }
+ break;
+ case MSG_FINISH_FETCH_DURING_SCAN:
+ removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN);
+ if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
+ } else {
+ onFinishFetchDuringScan();
+ }
+ break;
+ default:
+ // do nothing
+ }
+ }
+
+ private void onPrepareFetchDuringScan() {
+ Integer failureReason = prepareFetchEpg(true);
+ if (failureReason != null) {
+ sendEmptyMessageDelayed(
+ MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS);
+ return;
+ }
+ mChannelDataManager.addListener(mDuringScanChannelListener);
+ }
+
+ private void onChannelUpdatedDuringScan(Set<Channel> currentChannels) {
+ String lineupId = pickBestLineupId(currentChannels);
+ Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId);
+ if (TextUtils.isEmpty(lineupId)) {
+ if (TextUtils.isEmpty(mPossibleLineupId)) {
+ return;
+ }
+ } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) {
+ mFetchedChannelIdsDuringScan.clear();
+ mPossibleLineupId = lineupId;
+ }
+ List<Long> currentChannelIds = new ArrayList<>();
+ for (Channel channel : currentChannels) {
+ currentChannelIds.add(channel.getId());
+ }
+ mFetchedChannelIdsDuringScan.retainAll(currentChannelIds);
+ Set<EpgReader.EpgChannel> newChannels = new HashSet<>();
+ for (EpgReader.EpgChannel epgChannel :
+ mEpgReader.getChannels(currentChannels, mPossibleLineupId)) {
+ if (!mFetchedChannelIdsDuringScan.contains(epgChannel.getChannel().getId())) {
+ newChannels.add(epgChannel);
+ mFetchedChannelIdsDuringScan.add(epgChannel.getChannel().getId());
+ }
+ }
+ batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC);
+ }
+
+ private void onFinishFetchDuringScan() {
+ mChannelDataManager.removeListener(mDuringScanChannelListener);
+ EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId);
+ clearUnusedLineups(null);
+ mFetchedChannelIdsDuringScan.clear();
+ synchronized (mFetchDuringScanHandlerLock) {
+ if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) {
+ removeCallbacksAndMessages(null);
+ getLooper().quit();
+ mFetchDuringScanHandler = null;
+ }
+ }
+ // Clear timestamp to make routine service start right away.
+ EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0);
+ Log.i(TAG, "EPG Fetching during channel scanning finished.");
+ new Handler(Looper.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ fetchImmediately();
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/tv/data/epg/EpgInputWhiteList.java b/src/com/android/tv/data/epg/EpgInputWhiteList.java
new file mode 100644
index 00000000..eada8b24
--- /dev/null
+++ b/src/com/android/tv/data/epg/EpgInputWhiteList.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.data.epg;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.common.BuildConfig;
+import com.android.tv.common.config.api.RemoteConfig;
+import com.android.tv.common.experiments.Experiments;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Checks if a package or a input is white listed. */
+public final class EpgInputWhiteList {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "EpgInputWhiteList";
+ @VisibleForTesting public static final String KEY = "live_channels_3rd_party_epg_inputs";
+ private static final String QA_DEV_INPUTS =
+ "com.example.partnersupportsampletvinput/.SampleTvInputService,"
+ + "com.android.tv.tuner.sample.dvb/.tvinput.SampleDvbTunerTvInputService";
+
+ /** Returns the package portion of a inputId */
+ @Nullable
+ public static String getPackageFromInput(@Nullable String inputId) {
+ return inputId == null ? null : inputId.substring(0, inputId.indexOf("/"));
+ }
+
+ private final RemoteConfig remoteConfig;
+
+ public EpgInputWhiteList(RemoteConfig remoteConfig) {
+ this.remoteConfig = remoteConfig;
+ }
+
+ public boolean isInputWhiteListed(String inputId) {
+ return getWhiteListedInputs().contains(inputId);
+ }
+
+ public boolean isPackageWhiteListed(String packageName) {
+ if (DEBUG) Log.d(TAG, "isPackageWhiteListed " + packageName);
+ Set<String> whiteList = getWhiteListedInputs();
+ for (String good : whiteList) {
+ try {
+ String goodPackage = getPackageFromInput(good);
+ if (goodPackage.equals(packageName)) {
+ return true;
+ }
+ } catch (Exception e) {
+ if (DEBUG) Log.d(TAG, "Error parsing package name of " + good, e);
+ continue;
+ }
+ }
+ return false;
+ }
+
+ private Set<String> getWhiteListedInputs() {
+ Set<String> result = toInputSet(remoteConfig.getString(KEY));
+ if (BuildConfig.ENG || Experiments.ENABLE_QA_FEATURES.get()) {
+ HashSet<String> moreInputs = new HashSet<>(toInputSet(QA_DEV_INPUTS));
+ if (result.isEmpty()) {
+ result = moreInputs;
+ } else {
+ result.addAll(moreInputs);
+ }
+ }
+ if (DEBUG) Log.d(TAG, "getWhiteListedInputs " + result);
+ return result;
+ }
+
+ @VisibleForTesting
+ static Set<String> toInputSet(String value) {
+ if (TextUtils.isEmpty(value)) {
+ return Collections.emptySet();
+ }
+ List<String> strings = Arrays.asList(value.split(","));
+ Set<String> result = new HashSet<>(strings.size());
+ for (String s : strings) {
+ String trimmed = s.trim();
+ if (!TextUtils.isEmpty(trimmed)) {
+ result.add(trimmed);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java
index c5aeca27..7147905a 100644
--- a/src/com/android/tv/data/epg/EpgReader.java
+++ b/src/com/android/tv/data/epg/EpgReader.java
@@ -19,28 +19,37 @@ package com.android.tv.data.epg;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
-
-import com.android.tv.data.Channel;
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.data.SeriesInfo;
-
+import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
-/**
- * An interface used to retrieve the EPG data. This class should be used in worker thread.
- */
+/** An interface used to retrieve the EPG data. This class should be used in worker thread. */
@WorkerThread
public interface EpgReader {
- /**
- * Checks if the reader is available.
- */
+
+ /** Value class that holds a EpgChannelId and its corresponding {@link Channel} */
+ // TODO(b/72052568): Get autovalue to work in aosp master
+ abstract class EpgChannel {
+ public static EpgChannel createEpgChannel(Channel channel, String epgChannelId) {
+ return new AutoValue_EpgReader_EpgChannel(channel, epgChannelId);
+ }
+
+ public abstract Channel getChannel();
+
+ public abstract String getEpgChannelId();
+ }
+
+ /** Checks if the reader is available. */
boolean isAvailable();
/**
- * Returns the timestamp of the current EPG.
- * The format should be YYYYMMDDHHmmSS as a long value. ex) 20160308141500
+ * Returns the timestamp of the current EPG. The format should be YYYYMMDDHHmmSS as a long
+ * value. ex) 20160308141500
*/
long getEpgTimestamp();
@@ -61,31 +70,30 @@ public interface EpgReader {
* Returns the list of channels for the given lineup. The returned channels should map into the
* existing channels on the device. This method is usually called after selecting the lineup.
*/
- List<Channel> getChannels(@NonNull String lineupId);
+ Set<EpgChannel> getChannels(Set<Channel> inputChannels, @NonNull String lineupId);
/** Pre-loads and caches channels for a given lineup. */
void preloadChannels(@NonNull String lineupId);
- /**
- * Clears cached channels for a given lineup.
- */
+ /** Clears cached channels for a given lineup. */
@AnyThread
void clearCachedChannels(@NonNull String lineupId);
/**
- * Returns the programs for the given channel. Must call {@link #getChannels(String)}
+ * Returns the programs for the given channel. Must call {@link #getChannels(Set, String)}
* beforehand. Note that the {@code Program} doesn't have valid program ID because it's not
* retrieved from TvProvider.
*/
- List<Program> getPrograms(long channelId);
+ List<Program> getPrograms(EpgChannel epgChannel);
/**
* Returns the programs for the given channels. Note that the {@code Program} doesn't have valid
* program ID because it's not retrieved from TvProvider. This method is only used to get
* programs for a short duration typically.
*/
- Map<Long, List<Program>> getPrograms(@NonNull List<Long> channelIds, long duration);
+ Map<EpgChannel, Collection<Program>> getPrograms(
+ @NonNull Set<EpgChannel> epgChannels, long duration);
/** Returns the series information for the given series ID. */
SeriesInfo getSeriesInfo(@NonNull String seriesId);
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java
index ab6935ad..3b001481 100644
--- a/src/com/android/tv/data/epg/StubEpgReader.java
+++ b/src/com/android/tv/data/epg/StubEpgReader.java
@@ -17,23 +17,20 @@
package com.android.tv.data.epg;
import android.content.Context;
-
import android.support.annotation.NonNull;
-import com.android.tv.data.Channel;
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.data.SeriesInfo;
-
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
-/**
- * A stub class to read EPG.
- */
-public class StubEpgReader implements EpgReader{
- public StubEpgReader(@SuppressWarnings("unused") Context context) {
- }
+/** A stub class to read EPG. */
+public class StubEpgReader implements EpgReader {
+ public StubEpgReader(@SuppressWarnings("unused") Context context) {}
@Override
public boolean isAvailable() {
@@ -61,8 +58,8 @@ public class StubEpgReader implements EpgReader{
}
@Override
- public List<Channel> getChannels(@NonNull String lineupId) {
- return Collections.emptyList();
+ public Set<EpgChannel> getChannels(Set<Channel> inputChannels, @NonNull String lineupId) {
+ return Collections.emptySet();
}
@Override
@@ -76,12 +73,13 @@ public class StubEpgReader implements EpgReader{
}
@Override
- public List<Program> getPrograms(long channelId) {
+ public List<Program> getPrograms(EpgChannel epgChannel) {
return Collections.emptyList();
}
@Override
- public Map<Long, List<Program>> getPrograms(@NonNull List<Long> channelIds, long duration) {
+ public Map<EpgChannel, Collection<Program>> getPrograms(
+ @NonNull Set<EpgChannel> channels, long duration) {
return Collections.emptyMap();
}
@@ -89,4 +87,4 @@ public class StubEpgReader implements EpgReader{
public SeriesInfo getSeriesInfo(@NonNull String seriesId) {
return null;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java
index d686e6e6..7e36591f 100644
--- a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java
+++ b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java
@@ -30,25 +30,21 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
+import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
-/**
- * Displays the DVR history.
- */
+/** Displays the DVR history. */
@TargetApi(VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
public class DvrHistoryDialogFragment extends SafeDismissDialogFragment {
public static final String DIALOG_TAG = DvrHistoryDialogFragment.class.getSimpleName();
@@ -57,7 +53,7 @@ public class DvrHistoryDialogFragment extends SafeDismissDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
DvrDataManager dataManager = singletons.getDvrDataManager();
ChannelDataManager channelDataManager = singletons.getChannelDataManager();
for (ScheduledRecording schedule : dataManager.getAllScheduledRecordings()) {
@@ -67,60 +63,78 @@ public class DvrHistoryDialogFragment extends SafeDismissDialogFragment {
}
mSchedules.sort(ScheduledRecording.START_TIME_COMPARATOR.reversed());
LayoutInflater inflater = LayoutInflater.from(getContext());
- ArrayAdapter adapter = new ArrayAdapter<ScheduledRecording>(getContext(),
- R.layout.list_item_dvr_history, ScheduledRecording.toArray(mSchedules)) {
- @NonNull
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = inflater.inflate(R.layout.list_item_dvr_history, parent, false);
- ScheduledRecording schedule = mSchedules.get(position);
- setText(view, R.id.state, getStateString(schedule.getState()));
- setText(view, R.id.schedule_time, getRecordingTimeText(schedule));
- setText(view, R.id.program_title, DvrUiHelper.getStyledTitleWithEpisodeNumber(
- getContext(), schedule, 0));
- setText(view, R.id.channel_name, getChannelNameText(schedule));
- return view;
- }
+ ArrayAdapter adapter =
+ new ArrayAdapter<ScheduledRecording>(
+ getContext(),
+ R.layout.list_item_dvr_history,
+ ScheduledRecording.toArray(mSchedules)) {
+ @NonNull
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = inflater.inflate(R.layout.list_item_dvr_history, parent, false);
+ ScheduledRecording schedule = mSchedules.get(position);
+ setText(view, R.id.state, getStateString(schedule.getState()));
+ setText(view, R.id.schedule_time, getRecordingTimeText(schedule));
+ setText(
+ view,
+ R.id.program_title,
+ DvrUiHelper.getStyledTitleWithEpisodeNumber(
+ getContext(), schedule, 0));
+ setText(view, R.id.channel_name, getChannelNameText(schedule));
+ return view;
+ }
- private void setText(View view, int id, CharSequence text) {
- ((TextView) view.findViewById(id)).setText(text);
- }
+ private void setText(View view, int id, CharSequence text) {
+ ((TextView) view.findViewById(id)).setText(text);
+ }
- private void setText(View view, int id, int text) {
- ((TextView) view.findViewById(id)).setText(text);
- }
+ private void setText(View view, int id, int text) {
+ ((TextView) view.findViewById(id)).setText(text);
+ }
- @SuppressLint("SwitchIntDef")
- private int getStateString(@RecordingState int state) {
- switch (state) {
- case ScheduledRecording.STATE_RECORDING_CLIPPED:
- return R.string.dvr_history_dialog_state_clip;
- case ScheduledRecording.STATE_RECORDING_FAILED:
- return R.string.dvr_history_dialog_state_fail;
- case ScheduledRecording.STATE_RECORDING_FINISHED:
- return R.string.dvr_history_dialog_state_success;
- default:
- break;
- }
- return 0;
- }
+ @SuppressLint("SwitchIntDef")
+ private int getStateString(@RecordingState int state) {
+ switch (state) {
+ case ScheduledRecording.STATE_RECORDING_CLIPPED:
+ return R.string.dvr_history_dialog_state_clip;
+ case ScheduledRecording.STATE_RECORDING_FAILED:
+ return R.string.dvr_history_dialog_state_fail;
+ case ScheduledRecording.STATE_RECORDING_FINISHED:
+ return R.string.dvr_history_dialog_state_success;
+ default:
+ break;
+ }
+ return 0;
+ }
- private String getChannelNameText(ScheduledRecording schedule) {
- Channel channel = channelDataManager.getChannel(schedule.getChannelId());
- return channel == null ? null :
- TextUtils.isEmpty(channel.getDisplayName()) ? channel.getDisplayNumber() :
- channel.getDisplayName().trim() + " " + channel.getDisplayNumber();
- }
+ private String getChannelNameText(ScheduledRecording schedule) {
+ Channel channel = channelDataManager.getChannel(schedule.getChannelId());
+ return channel == null
+ ? null
+ : TextUtils.isEmpty(channel.getDisplayName())
+ ? channel.getDisplayNumber()
+ : channel.getDisplayName().trim()
+ + " "
+ + channel.getDisplayNumber();
+ }
- private String getRecordingTimeText(ScheduledRecording schedule) {
- return Utils.getDurationString(getContext(), schedule.getStartTimeMs(),
- schedule.getEndTimeMs(), true, true, true, 0);
- }
- };
+ private String getRecordingTimeText(ScheduledRecording schedule) {
+ return Utils.getDurationString(
+ getContext(),
+ schedule.getStartTimeMs(),
+ schedule.getEndTimeMs(),
+ true,
+ true,
+ true,
+ 0);
+ }
+ };
ListView listView = new ListView(getActivity());
listView.setAdapter(adapter);
- return new AlertDialog.Builder(getActivity()).setTitle(R.string.dvr_history_dialog_title)
- .setView(listView).create();
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.dvr_history_dialog_title)
+ .setView(listView)
+ .create();
}
@Override
diff --git a/src/com/android/tv/dialog/FullscreenDialogFragment.java b/src/com/android/tv/dialog/FullscreenDialogFragment.java
index d00422a7..53adb308 100644
--- a/src/com/android/tv/dialog/FullscreenDialogFragment.java
+++ b/src/com/android/tv/dialog/FullscreenDialogFragment.java
@@ -23,21 +23,18 @@ import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-/**
- * Dialog fragment with full screen.
- */
+/** Dialog fragment with full screen. */
public class FullscreenDialogFragment extends SafeDismissDialogFragment {
public static final String DIALOG_TAG = FullscreenDialogFragment.class.getSimpleName();
public static final String VIEW_LAYOUT_ID = "viewLayoutId";
public static final String TRACKER_LABEL = "trackerLabel";
/**
- * Creates a FullscreenDialogFragment. View class of viewLayoutResId should
- * implement {@link DialogView}.
+ * Creates a FullscreenDialogFragment. View class of viewLayoutResId should implement {@link
+ * DialogView}.
*/
public static FullscreenDialogFragment newInstance(int viewLayoutResId, String trackerLabel) {
FullscreenDialogFragment f = new FullscreenDialogFragment();
@@ -100,21 +97,13 @@ public class FullscreenDialogFragment extends SafeDismissDialogFragment {
}
}
- /**
- * Interface for the view of {@link FullscreenDialogFragment}.
- */
+ /** Interface for the view of {@link FullscreenDialogFragment}. */
public interface DialogView {
- /**
- * Called after the view is inflated and attached to the dialog.
- */
+ /** Called after the view is inflated and attached to the dialog. */
void initialize(MainActivity activity, Dialog dialog);
- /**
- * Called when a back key is pressed.
- */
+ /** Called when a back key is pressed. */
void onBackPressed();
- /**
- * Called when {@link DialogFragment#onDestroy} is called.
- */
+ /** Called when {@link DialogFragment#onDestroy} is called. */
void onDestroy();
}
}
diff --git a/src/com/android/tv/dialog/HalfSizedDialogFragment.java b/src/com/android/tv/dialog/HalfSizedDialogFragment.java
index 315c6a93..aed75655 100644
--- a/src/com/android/tv/dialog/HalfSizedDialogFragment.java
+++ b/src/com/android/tv/dialog/HalfSizedDialogFragment.java
@@ -24,9 +24,7 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
-
import java.util.concurrent.TimeUnit;
public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
@@ -38,16 +36,17 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
private OnActionClickListener mOnActionClickListener;
private Handler mHandler = new Handler();
- private Runnable mAutoDismisser = new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- };
+ private Runnable mAutoDismisser =
+ new Runnable() {
+ @Override
+ public void run() {
+ dismiss();
+ }
+ };
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.halfsized_dialog, container, false);
}
@@ -76,13 +75,14 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
- dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
- public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) {
- mHandler.removeCallbacks(mAutoDismisser);
- mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
- return false;
- }
- });
+ dialog.setOnKeyListener(
+ new DialogInterface.OnKeyListener() {
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) {
+ mHandler.removeCallbacks(mAutoDismisser);
+ mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
+ return false;
+ }
+ });
return dialog;
}
@@ -98,26 +98,24 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
/**
* Sets {@link OnActionClickListener} for the dialog fragment. If listener is set, the dialog
- * will be automatically closed when it's paused to prevent the fragment being re-created by
- * the framework, which will result the listener being forgotten.
+ * will be automatically closed when it's paused to prevent the fragment being re-created by the
+ * framework, which will result the listener being forgotten.
*/
public void setOnActionClickListener(OnActionClickListener listener) {
mOnActionClickListener = listener;
}
- /**
- * Returns {@link OnActionClickListener} for sub-classes or any inner fragments.
- */
+ /** Returns {@link OnActionClickListener} for sub-classes or any inner fragments. */
protected OnActionClickListener getOnActionClickListener() {
return mOnActionClickListener;
}
/**
* An interface to provide callbacks for half-sized dialogs. Subclasses or inner fragments
- * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier
- * of the action user clicked.
+ * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier of
+ * the action user clicked.
*/
public interface OnActionClickListener {
void onActionClick(long actionId);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java
index d5c154da..71f45fbe 100644
--- a/src/com/android/tv/dialog/PinDialogFragment.java
+++ b/src/com/android/tv/dialog/PinDialogFragment.java
@@ -44,43 +44,34 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.util.TvSettings;
public class PinDialogFragment extends SafeDismissDialogFragment {
private static final String TAG = "PinDialogFragment";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
- /**
- * PIN code dialog for unlock channel
- */
+ /** PIN code dialog for unlock channel */
public static final int PIN_DIALOG_TYPE_UNLOCK_CHANNEL = 0;
/**
- * PIN code dialog for unlock content.
- * Only difference between {@code PIN_DIALOG_TYPE_UNLOCK_CHANNEL} is it's title.
+ * PIN code dialog for unlock content. Only difference between {@code
+ * PIN_DIALOG_TYPE_UNLOCK_CHANNEL} is it's title.
*/
public static final int PIN_DIALOG_TYPE_UNLOCK_PROGRAM = 1;
- /**
- * PIN code dialog for change parental control settings
- */
+ /** PIN code dialog for change parental control settings */
public static final int PIN_DIALOG_TYPE_ENTER_PIN = 2;
- /**
- * PIN code dialog for set new PIN
- */
+ /** PIN code dialog for set new PIN */
public static final int PIN_DIALOG_TYPE_NEW_PIN = 3;
- // PIN code dialog for checking old PIN. This is internal only.
+ // PIN code dialog for checking old PIN. Only used in this class.
private static final int PIN_DIALOG_TYPE_OLD_PIN = 4;
- /**
- * PIN code dialog for unlocking DVR playback
- */
+ /** PIN code dialog for unlocking DVR playback */
public static final int PIN_DIALOG_TYPE_UNLOCK_DVR = 5;
private static final int MAX_WRONG_PIN_COUNT = 5;
@@ -94,7 +85,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
public static final String DIALOG_TAG = PinDialogFragment.class.getName();
private static final int NUMBER_PICKERS_RES_ID[] = {
- R.id.first, R.id.second, R.id.third, R.id.fourth };
+ R.id.first, R.id.second, R.id.third, R.id.fourth
+ };
private int mType;
private int mRequestType;
@@ -164,15 +156,16 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
// So apply view size to window after the DialogFragment.onStart() where dialog is shown.
Dialog dlg = getDialog();
if (dlg != null) {
- dlg.getWindow().setLayout(
- getResources().getDimensionPixelSize(R.dimen.pin_dialog_width),
- LayoutParams.WRAP_CONTENT);
+ dlg.getWindow()
+ .setLayout(
+ getResources().getDimensionPixelSize(R.dimen.pin_dialog_width),
+ LayoutParams.WRAP_CONTENT);
}
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.pin_dialog, container, false);
mWrongPinView = (TextView) v.findViewById(R.id.wrong_pin);
@@ -199,7 +192,7 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
mTitleView.setText(
getString(
R.string.pin_enter_unlock_dvr,
- TvApplication.getSingletons(getContext())
+ TvSingletons.getSingletons(getContext())
.getTvInputManagerHelper()
.getContentRatingsManager()
.getDisplayNameForRating(tvContentRating)));
@@ -234,12 +227,13 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
return v;
}
- private final Runnable mUpdateEnterPinRunnable = new Runnable() {
- @Override
- public void run() {
- updateWrongPin();
- }
- };
+ private final Runnable mUpdateEnterPinRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ updateWrongPin();
+ }
+ };
private void updateWrongPin() {
if (getActivity() == null) {
@@ -257,8 +251,12 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
} else {
mEnterPinView.setVisibility(View.INVISIBLE);
mWrongPinView.setVisibility(View.VISIBLE);
- mWrongPinView.setText(getResources().getQuantityString(R.plurals.pin_enter_countdown,
- remainingSeconds, remainingSeconds));
+ mWrongPinView.setText(
+ getResources()
+ .getQuantityString(
+ R.plurals.pin_enter_countdown,
+ remainingSeconds,
+ remainingSeconds));
mHandler.postDelayed(mUpdateEnterPinRunnable, 1000);
}
}
@@ -280,8 +278,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
if (DEBUG) Log.d(TAG, "onDismiss: mPinChecked=" + mPinChecked);
SoftPreconditions.checkState(getActivity() instanceof OnPinCheckedListener);
if (!mDismissSilently && getActivity() instanceof OnPinCheckedListener) {
- ((OnPinCheckedListener) getActivity()).onPinChecked(
- mPinChecked, mRequestType, mRatingString);
+ ((OnPinCheckedListener) getActivity())
+ .onPinChecked(mPinChecked, mRequestType, mRatingString);
}
mDismissSilently = false;
}
@@ -391,7 +389,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
R.id.previous_number,
R.id.current_number,
R.id.next_number,
- R.id.next2_number };
+ R.id.next2_number
+ };
private static final int CURRENT_NUMBER_VIEW_INDEX = 2;
private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
@@ -436,8 +435,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
this(context, attrs, defStyleAttr, 0);
}
- public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
+ public PinNumberPicker(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
View view = inflate(context, R.layout.pin_number_picker, this);
mNumberViewHolder = view.findViewById(R.id.number_view_holder);
@@ -447,118 +446,149 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
mNumberViews[i] = (TextView) view.findViewById(NUMBER_VIEWS_RES_ID[i]);
}
Resources resources = context.getResources();
- mNumberViewHeight = resources.getDimensionPixelSize(
- R.dimen.pin_number_picker_text_view_height);
-
- mNumberViewHolder.setOnFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- updateFocus(true);
- }
- });
-
- mNumberViewHolder.setOnKeyListener(new OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN: {
- if (mCancelAnimation) {
- mScrollAnimatorSet.end();
+ mNumberViewHeight =
+ resources.getDimensionPixelSize(R.dimen.pin_number_picker_text_view_height);
+
+ mNumberViewHolder.setOnFocusChangeListener(
+ new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ updateFocus(true);
+ }
+ });
+
+ mNumberViewHolder.setOnKeyListener(
+ new OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ {
+ if (mCancelAnimation) {
+ mScrollAnimatorSet.end();
+ }
+ if (!mScrollAnimatorSet.isRunning()) {
+ mCancelAnimation = false;
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ mNextValue =
+ adjustValueInValidRange(
+ mCurrentValue + 1);
+ startScrollAnimation(true);
+ } else {
+ mNextValue =
+ adjustValueInValidRange(
+ mCurrentValue - 1);
+ startScrollAnimation(false);
+ }
+ }
+ return true;
+ }
}
- if (!mScrollAnimatorSet.isRunning()) {
- mCancelAnimation = false;
- if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
- mNextValue = adjustValueInValidRange(mCurrentValue + 1);
- startScrollAnimation(true);
- } else {
- mNextValue = adjustValueInValidRange(mCurrentValue - 1);
- startScrollAnimation(false);
- }
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ {
+ mCancelAnimation = true;
+ return true;
+ }
}
- return true;
- }
- }
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN: {
- mCancelAnimation = true;
- return true;
}
+ return false;
}
- }
- return false;
- }
- });
+ });
mNumberViewHolder.setScrollY(mNumberViewHeight);
mFocusGainAnimator = new AnimatorSet();
mFocusGainAnimator.playTogether(
- ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1],
- "alpha", 0f, sAlphaForAdjacentNumber),
- ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX],
- "alpha", sAlphaForFocusedNumber, 0f),
- ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1],
- "alpha", 0f, sAlphaForAdjacentNumber),
+ ObjectAnimator.ofFloat(
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1],
+ "alpha",
+ 0f,
+ sAlphaForAdjacentNumber),
+ ObjectAnimator.ofFloat(
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX],
+ "alpha",
+ sAlphaForFocusedNumber,
+ 0f),
+ ObjectAnimator.ofFloat(
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1],
+ "alpha",
+ 0f,
+ sAlphaForAdjacentNumber),
ObjectAnimator.ofFloat(mBackgroundView, "alpha", 0f, 1f));
- mFocusGainAnimator.setDuration(context.getResources().getInteger(
- android.R.integer.config_shortAnimTime));
- mFocusGainAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText(mBackgroundView.getText());
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(sAlphaForFocusedNumber);
- mBackgroundView.setText("");
- }
- });
+ mFocusGainAnimator.setDuration(
+ context.getResources().getInteger(android.R.integer.config_shortAnimTime));
+ mFocusGainAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText(
+ mBackgroundView.getText());
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(
+ sAlphaForFocusedNumber);
+ mBackgroundView.setText("");
+ }
+ });
mFocusLossAnimator = new AnimatorSet();
mFocusLossAnimator.playTogether(
- ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1],
- "alpha", sAlphaForAdjacentNumber, 0f),
- ObjectAnimator.ofFloat(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1],
- "alpha", sAlphaForAdjacentNumber, 0f),
+ ObjectAnimator.ofFloat(
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1],
+ "alpha",
+ sAlphaForAdjacentNumber,
+ 0f),
+ ObjectAnimator.ofFloat(
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1],
+ "alpha",
+ sAlphaForAdjacentNumber,
+ 0f),
ObjectAnimator.ofFloat(mBackgroundView, "alpha", 1f, 0f));
- mFocusLossAnimator.setDuration(context.getResources().getInteger(
- android.R.integer.config_shortAnimTime));
+ mFocusLossAnimator.setDuration(
+ context.getResources().getInteger(android.R.integer.config_shortAnimTime));
mScrollAnimatorSet = new AnimatorSet();
- mScrollAnimatorSet.setDuration(context.getResources().getInteger(
- R.integer.pin_number_scroll_duration));
- mScrollAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Set mCurrent value when scroll animation is finished.
- mCurrentValue = mNextValue;
- updateText();
- mNumberViewHolder.setScrollY(mNumberViewHeight);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(sAlphaForAdjacentNumber);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(sAlphaForFocusedNumber);
- mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(sAlphaForAdjacentNumber);
- }
- });
+ mScrollAnimatorSet.setDuration(
+ context.getResources().getInteger(R.integer.pin_number_scroll_duration));
+ mScrollAnimatorSet.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Set mCurrent value when scroll animation is finished.
+ mCurrentValue = mNextValue;
+ updateText();
+ mNumberViewHolder.setScrollY(mNumberViewHeight);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(
+ sAlphaForAdjacentNumber);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setAlpha(
+ sAlphaForFocusedNumber);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(
+ sAlphaForAdjacentNumber);
+ }
+ });
}
static void loadResources(Context context) {
if (sFocusedNumberEnterAnimator == null) {
TypedValue outValue = new TypedValue();
- context.getResources().getValue(
- R.dimen.pin_alpha_for_focused_number, outValue, true);
+ context.getResources()
+ .getValue(R.dimen.pin_alpha_for_focused_number, outValue, true);
sAlphaForFocusedNumber = outValue.getFloat();
- context.getResources().getValue(
- R.dimen.pin_alpha_for_adjacent_number, outValue, true);
+ context.getResources()
+ .getValue(R.dimen.pin_alpha_for_adjacent_number, outValue, true);
sAlphaForAdjacentNumber = outValue.getFloat();
- sFocusedNumberEnterAnimator = AnimatorInflater.loadAnimator(context,
- R.animator.pin_focused_number_enter);
- sFocusedNumberExitAnimator = AnimatorInflater.loadAnimator(context,
- R.animator.pin_focused_number_exit);
- sAdjacentNumberEnterAnimator = AnimatorInflater.loadAnimator(context,
- R.animator.pin_adjacent_number_enter);
- sAdjacentNumberExitAnimator = AnimatorInflater.loadAnimator(context,
- R.animator.pin_adjacent_number_exit);
+ sFocusedNumberEnterAnimator =
+ AnimatorInflater.loadAnimator(context, R.animator.pin_focused_number_enter);
+ sFocusedNumberExitAnimator =
+ AnimatorInflater.loadAnimator(context, R.animator.pin_focused_number_exit);
+ sAdjacentNumberEnterAnimator =
+ AnimatorInflater.loadAnimator(
+ context, R.animator.pin_adjacent_number_enter);
+ sAdjacentNumberExitAnimator =
+ AnimatorInflater.loadAnimator(context, R.animator.pin_adjacent_number_exit);
}
}
@@ -588,15 +618,16 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
void startScrollAnimation(boolean scrollUp) {
mFocusGainAnimator.end();
mFocusLossAnimator.end();
- final ValueAnimator scrollAnimator = ValueAnimator.ofInt(
- 0, scrollUp ? mNumberViewHeight : -mNumberViewHeight);
- scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int value = (Integer) animation.getAnimatedValue();
- mNumberViewHolder.setScrollY(value + mNumberViewHeight);
- }
- });
+ final ValueAnimator scrollAnimator =
+ ValueAnimator.ofInt(0, scrollUp ? mNumberViewHeight : -mNumberViewHeight);
+ scrollAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int value = (Integer) animation.getAnimatedValue();
+ mNumberViewHolder.setScrollY(value + mNumberViewHeight);
+ }
+ });
scrollAnimator.setDuration(
getResources().getInteger(R.integer.pin_number_scroll_duration));
@@ -612,9 +643,12 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
sAdjacentNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1]);
}
- mScrollAnimatorSet.playTogether(scrollAnimator,
- sAdjacentNumberExitAnimator, sFocusedNumberExitAnimator,
- sFocusedNumberEnterAnimator, sAdjacentNumberEnterAnimator);
+ mScrollAnimatorSet.playTogether(
+ scrollAnimator,
+ sAdjacentNumberExitAnimator,
+ sFocusedNumberExitAnimator,
+ sFocusedNumberEnterAnimator,
+ sAdjacentNumberEnterAnimator);
mScrollAnimatorSet.start();
}
@@ -688,8 +722,10 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
// In order to show the text change animation, keep the text of
// mNumberViews[CURRENT_NUMBER_VIEW_INDEX].
} else {
- mNumberViews[i].setText(String.valueOf(adjustValueInValidRange(
- mCurrentValue - CURRENT_NUMBER_VIEW_INDEX + i)));
+ mNumberViews[i].setText(
+ String.valueOf(
+ adjustValueInValidRange(
+ mCurrentValue - CURRENT_NUMBER_VIEW_INDEX + i)));
}
}
}
@@ -698,10 +734,11 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
private int adjustValueInValidRange(int value) {
int interval = mMaxValue - mMinValue + 1;
if (value < mMinValue - interval || value > mMaxValue + interval) {
- throw new IllegalArgumentException("The value( " + value
- + ") is too small or too big to adjust");
+ throw new IllegalArgumentException(
+ "The value( " + value + ") is too small or too big to adjust");
}
- return (value < mMinValue) ? value + interval
+ return (value < mMinValue)
+ ? value + interval
: (value > mMaxValue) ? value - interval : value;
}
}
@@ -714,8 +751,8 @@ public class PinDialogFragment extends SafeDismissDialogFragment {
/**
* Called when {@link PinDialogFragment} is dismissed.
*
- * @param checked {@code true} if the pin code entered is checked to be correct,
- * otherwise {@code false}.
+ * @param checked {@code true} if the pin code entered is checked to be correct, otherwise
+ * {@code false}.
* @param type The dialog type regarding to what pin entering is for.
* @param rating The target rating to unblock for.
*/
diff --git a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java
index 1875f411..eb6940fb 100644
--- a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java
+++ b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java
@@ -29,17 +29,14 @@ import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
-/**
- * Displays the watch history
- */
-public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment implements
- LoaderManager.LoaderCallbacks<Cursor> {
+/** Displays the watch history */
+public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment
+ implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String DIALOG_TAG = RecentlyWatchedDialogFragment.class.getSimpleName();
private static final String EMPTY_STRING = "";
@@ -55,47 +52,57 @@ public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment imp
((MainActivity) getActivity()).getChannelDataManager();
String[] from = {
- TvContract.WatchedPrograms._ID,
- TvContract.WatchedPrograms.COLUMN_CHANNEL_ID,
- TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
- TvContract.WatchedPrograms.COLUMN_TITLE };
+ TvContract.WatchedPrograms._ID,
+ TvContract.WatchedPrograms.COLUMN_CHANNEL_ID,
+ TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.COLUMN_TITLE
+ };
int[] to = {
- R.id.watched_program_id,
- R.id.watched_program_channel_id,
- R.id.watched_program_watch_time,
- R.id.watched_program_title};
- mAdapter = new SimpleCursorAdapter(getActivity(), R.layout.list_item_watched_program, null,
- from, to, 0);
- mAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
- @Override
- public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
- String name = cursor.getColumnName(columnIndex);
- if (TvContract.WatchedPrograms.COLUMN_CHANNEL_ID.equals(name)) {
- long channelId = cursor.getLong(columnIndex);
- ((TextView) view).setText(String.valueOf(channelId));
- // Update display number
- String displayNumber;
- Channel channel = dataChannelManager.getChannel(channelId);
- if (channel == null) {
- displayNumber = EMPTY_STRING;
- } else {
- displayNumber = channel.getDisplayNumber();
+ R.id.watched_program_id,
+ R.id.watched_program_channel_id,
+ R.id.watched_program_watch_time,
+ R.id.watched_program_title
+ };
+ mAdapter =
+ new SimpleCursorAdapter(
+ getActivity(), R.layout.list_item_watched_program, null, from, to, 0);
+ mAdapter.setViewBinder(
+ new SimpleCursorAdapter.ViewBinder() {
+ @Override
+ public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
+ String name = cursor.getColumnName(columnIndex);
+ if (TvContract.WatchedPrograms.COLUMN_CHANNEL_ID.equals(name)) {
+ long channelId = cursor.getLong(columnIndex);
+ ((TextView) view).setText(String.valueOf(channelId));
+ // Update display number
+ String displayNumber;
+ Channel channel = dataChannelManager.getChannel(channelId);
+ if (channel == null) {
+ displayNumber = EMPTY_STRING;
+ } else {
+ displayNumber = channel.getDisplayNumber();
+ }
+ TextView displayNumberView =
+ ((TextView)
+ ((View) view.getParent())
+ .findViewById(
+ R.id.watched_program_channel_display_number));
+ displayNumberView.setText(displayNumber);
+ return true;
+ } else if (TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS
+ .equals(name)) {
+ long time = cursor.getLong(columnIndex);
+ CharSequence timeString =
+ DateUtils.getRelativeTimeSpanString(
+ time,
+ System.currentTimeMillis(),
+ DateUtils.SECOND_IN_MILLIS);
+ ((TextView) view).setText(String.valueOf(timeString));
+ return true;
+ }
+ return false;
}
- TextView displayNumberView = ((TextView) ((View) view.getParent())
- .findViewById(R.id.watched_program_channel_display_number));
- displayNumberView.setText(displayNumber);
- return true;
- } else if (TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS.equals(
- name)) {
- long time = cursor.getLong(columnIndex);
- CharSequence timeString = DateUtils.getRelativeTimeSpanString(time,
- System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS);
- ((TextView) view).setText(String.valueOf(timeString));
- return true;
- }
- return false;
- }
- });
+ });
ListView listView = new ListView(getActivity());
listView.setAdapter(mAdapter);
@@ -121,12 +128,18 @@ public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment imp
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = {
- TvContract.WatchedPrograms._ID,
- TvContract.WatchedPrograms.COLUMN_CHANNEL_ID,
- TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
- TvContract.WatchedPrograms.COLUMN_TITLE };
- return new CursorLoader(getActivity(), TvContract.WatchedPrograms.CONTENT_URI, projection,
- null, null, TvContract.WatchedPrograms._ID + " DESC");
+ TvContract.WatchedPrograms._ID,
+ TvContract.WatchedPrograms.COLUMN_CHANNEL_ID,
+ TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
+ TvContract.WatchedPrograms.COLUMN_TITLE
+ };
+ return new CursorLoader(
+ getActivity(),
+ TvContract.WatchedPrograms.CONTENT_URI,
+ projection,
+ null,
+ null,
+ TvContract.WatchedPrograms._ID + " DESC");
}
@Override
diff --git a/src/com/android/tv/dialog/SafeDismissDialogFragment.java b/src/com/android/tv/dialog/SafeDismissDialogFragment.java
index e3390b0a..6eb67dfd 100644
--- a/src/com/android/tv/dialog/SafeDismissDialogFragment.java
+++ b/src/com/android/tv/dialog/SafeDismissDialogFragment.java
@@ -18,17 +18,13 @@ package com.android.tv.dialog;
import android.app.Activity;
import android.app.DialogFragment;
-
import com.android.tv.MainActivity;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.HasTrackerLabel;
import com.android.tv.analytics.Tracker;
-/**
- * Provides the safe dismiss feature regardless of the DialogFragment's life cycle.
- */
-public abstract class SafeDismissDialogFragment extends DialogFragment
- implements HasTrackerLabel {
+/** Provides the safe dismiss feature regardless of the DialogFragment's life cycle. */
+public abstract class SafeDismissDialogFragment extends DialogFragment implements HasTrackerLabel {
private MainActivity mActivity;
private boolean mAttached = false;
private boolean mDismissPending = false;
@@ -41,7 +37,7 @@ public abstract class SafeDismissDialogFragment extends DialogFragment
if (activity instanceof MainActivity) {
mActivity = (MainActivity) activity;
}
- mTracker = TvApplication.getSingletons(activity).getTracker();
+ mTracker = TvSingletons.getSingletons(activity).getTracker();
if (mDismissPending) {
mDismissPending = false;
dismiss();
@@ -69,9 +65,7 @@ public abstract class SafeDismissDialogFragment extends DialogFragment
mTracker = null;
}
- /**
- * Dismiss safely regardless of the DialogFragment's life cycle.
- */
+ /** Dismiss safely regardless of the DialogFragment's life cycle. */
@Override
public void dismiss() {
if (!mAttached) {
diff --git a/src/com/android/tv/dialog/WebDialogFragment.java b/src/com/android/tv/dialog/WebDialogFragment.java
index 171a256b..5266f760 100644
--- a/src/com/android/tv/dialog/WebDialogFragment.java
+++ b/src/com/android/tv/dialog/WebDialogFragment.java
@@ -28,9 +28,7 @@ import android.view.ViewGroup.LayoutParams;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-/**
- * A DialogFragment that shows a web view.
- */
+/** A DialogFragment that shows a web view. */
public class WebDialogFragment extends SafeDismissDialogFragment {
private static final String TAG = "WebDialogFragment";
private static final String URL = "URL";
@@ -43,11 +41,11 @@ public class WebDialogFragment extends SafeDismissDialogFragment {
/**
* Create a new WebDialogFragment to show a particular web page.
*
- * @param url The URL of the content to show.
+ * @param url The URL of the content to show.
* @param title Optional title for the dialog.
*/
- public static WebDialogFragment newInstance(String url, @Nullable String title,
- String trackerLabel) {
+ public static WebDialogFragment newInstance(
+ String url, @Nullable String title, String trackerLabel) {
WebDialogFragment f = new WebDialogFragment();
Bundle args = new Bundle();
args.putString(URL, url);
@@ -62,15 +60,17 @@ public class WebDialogFragment extends SafeDismissDialogFragment {
super.onCreate(savedInstanceState);
String title = getArguments().getString(TITLE);
mTrackerLabel = getArguments().getString(TRACKER_LABEL);
- int style = TextUtils.isEmpty(title) ? DialogFragment.STYLE_NO_TITLE
- : DialogFragment.STYLE_NORMAL;
+ int style =
+ TextUtils.isEmpty(title)
+ ? DialogFragment.STYLE_NO_TITLE
+ : DialogFragment.STYLE_NORMAL;
setStyle(style, 0);
}
@Nullable
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
String title = getArguments().getString(TITLE);
getDialog().setTitle(title);
diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java
index a8637449..b8bffa18 100644
--- a/src/com/android/tv/dvr/BaseDvrDataManager.java
+++ b/src/com/android/tv/dvr/BaseDvrDataManager.java
@@ -23,15 +23,13 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.util.ArraySet;
import android.util.Log;
-
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.util.Clock;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
import com.android.tv.dvr.data.SeriesRecording;
-import com.android.tv.util.Clock;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -42,14 +40,12 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
-/**
- * Base implementation of @{link DataManagerInternal}.
- */
+/** Base implementation of @{link DataManagerInternal}. */
@MainThread
@TargetApi(Build.VERSION_CODES.N)
public abstract class BaseDvrDataManager implements WritableDvrDataManager {
- private final static String TAG = "BaseDvrDataManager";
- private final static boolean DEBUG = false;
+ private static final String TAG = "BaseDvrDataManager";
+ private static final boolean DEBUG = false;
protected final Clock mClock;
private final Set<OnDvrScheduleLoadFinishedListener> mOnDvrScheduleLoadFinishedListeners =
@@ -61,7 +57,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
private final Set<RecordedProgramListener> mRecordedProgramListeners = new ArraySet<>();
private final HashMap<Long, ScheduledRecording> mDeletedScheduleMap = new HashMap<>();
- BaseDvrDataManager(Context context, Clock clock) {
+ public BaseDvrDataManager(Context context, Clock clock) {
SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG);
mClock = clock;
}
@@ -129,8 +125,8 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
/**
- * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()}
- * for each listener.
+ * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()} for each
+ * listener.
*/
protected final void notifyRecordedProgramLoadFinished() {
for (OnRecordedProgramLoadFinishedListener l : mOnRecordedProgramLoadFinishedListeners) {
@@ -139,10 +135,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link RecordedProgramListener#onRecordedProgramsAdded}
- * for each listener.
- */
+ /** Calls {@link RecordedProgramListener#onRecordedProgramsAdded} for each listener. */
protected final void notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
for (RecordedProgramListener l : mRecordedProgramListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(recordedPrograms));
@@ -150,10 +143,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link RecordedProgramListener#onRecordedProgramsChanged}
- * for each listener.
- */
+ /** Calls {@link RecordedProgramListener#onRecordedProgramsChanged} for each listener. */
protected final void notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
for (RecordedProgramListener l : mRecordedProgramListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(recordedPrograms));
@@ -161,10 +151,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link RecordedProgramListener#onRecordedProgramsRemoved}
- * for each listener.
- */
+ /** Calls {@link RecordedProgramListener#onRecordedProgramsRemoved} for each listener. */
protected final void notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
for (RecordedProgramListener l : mRecordedProgramListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(recordedPrograms));
@@ -172,10 +159,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link SeriesRecordingListener#onSeriesRecordingAdded}
- * for each listener.
- */
+ /** Calls {@link SeriesRecordingListener#onSeriesRecordingAdded} for each listener. */
protected final void notifySeriesRecordingAdded(SeriesRecording... seriesRecordings) {
for (SeriesRecordingListener l : mSeriesRecordingListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(seriesRecordings));
@@ -183,10 +167,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved}
- * for each listener.
- */
+ /** Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved} for each listener. */
protected final void notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
for (SeriesRecordingListener l : mSeriesRecordingListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(seriesRecordings));
@@ -194,11 +175,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls
- * {@link SeriesRecordingListener#onSeriesRecordingChanged}
- * for each listener.
- */
+ /** Calls {@link SeriesRecordingListener#onSeriesRecordingChanged} for each listener. */
protected final void notifySeriesRecordingChanged(SeriesRecording... seriesRecordings) {
for (SeriesRecordingListener l : mSeriesRecordingListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(seriesRecordings));
@@ -206,10 +183,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded}
- * for each listener.
- */
+ /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */
protected final void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording) {
for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(scheduledRecording));
@@ -217,10 +191,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
}
- /**
- * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved}
- * for each listener.
- */
+ /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */
protected final void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording) {
for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(scheduledRecording));
@@ -229,9 +200,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
/**
- * Calls
- * {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged}
- * for each listener.
+ * Calls {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} for each listener.
*/
protected final void notifyScheduledRecordingStatusChanged(
ScheduledRecording... scheduledRecording) {
@@ -257,28 +226,47 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
@Override
public List<ScheduledRecording> getAvailableScheduledRecordings() {
- return filterEndTimeIsPast(getRecordingsWithState(
- ScheduledRecording.STATE_RECORDING_IN_PROGRESS,
- ScheduledRecording.STATE_RECORDING_NOT_STARTED));
+ return filterEndTimeIsPast(
+ getRecordingsWithState(
+ ScheduledRecording.STATE_RECORDING_IN_PROGRESS,
+ ScheduledRecording.STATE_RECORDING_NOT_STARTED));
}
@Override
public List<ScheduledRecording> getStartedRecordings() {
- return filterEndTimeIsPast(getRecordingsWithState(
- ScheduledRecording.STATE_RECORDING_IN_PROGRESS));
+ return filterEndTimeIsPast(
+ getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS));
}
@Override
public List<ScheduledRecording> getNonStartedScheduledRecordings() {
- return filterEndTimeIsPast(getRecordingsWithState(
- ScheduledRecording.STATE_RECORDING_NOT_STARTED));
+ return filterEndTimeIsPast(
+ getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED));
+ }
+
+ @Override
+ public List<ScheduledRecording> getFailedScheduledRecordings() {
+ return getRecordingsWithState(ScheduledRecording.STATE_RECORDING_FAILED);
}
@Override
public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) {
if (scheduledRecording.getState() != newState) {
- updateScheduledRecording(ScheduledRecording.buildFrom(scheduledRecording)
- .setState(newState).build());
+ updateScheduledRecording(
+ ScheduledRecording.buildFrom(scheduledRecording).setState(newState).build());
+ }
+ }
+
+ @Override
+ public void changeState(
+ ScheduledRecording scheduledRecording, @RecordingState int newState, int reason) {
+ if (scheduledRecording.getState() != newState) {
+ ScheduledRecording.Builder builder =
+ ScheduledRecording.buildFrom(scheduledRecording).setState(newState);
+ if (newState == ScheduledRecording.STATE_RECORDING_FAILED) {
+ builder.setFailedReason(reason);
+ }
+ updateScheduledRecording(builder.build());
}
}
@@ -300,9 +288,7 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
return mDeletedScheduleMap;
}
- /**
- * Returns the schedules whose state is contained by states.
- */
+ /** Returns the schedules whose state is contained by states. */
protected abstract List<ScheduledRecording> getRecordingsWithState(int... states);
@Override
@@ -357,5 +343,5 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
@Override
- public void forgetStorage(String inputId) { }
+ public void forgetStorage(String inputId) {}
}
diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java
index 6d400b82..10dfc4c9 100644
--- a/src/com/android/tv/dvr/DvrDataManager.java
+++ b/src/com/android/tv/dvr/DvrDataManager.java
@@ -20,48 +20,36 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Range;
-
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
import com.android.tv.dvr.data.SeriesRecording;
-
import java.util.Collection;
import java.util.List;
-/**
- * Read only data manager.
- */
+/** Read only data manager. */
@MainThread
public interface DvrDataManager {
long NEXT_START_TIME_NOT_FOUND = -1;
boolean isInitialized();
- /**
- * Returns {@code true} if the schedules were loaded, otherwise {@code false}.
- */
+ /** Returns {@code true} if the schedules were loaded, otherwise {@code false}. */
boolean isDvrScheduleLoadFinished();
- /**
- * Returns {@code true} if the recorded programs were loaded, otherwise {@code false}.
- */
+ /** Returns {@code true} if the recorded programs were loaded, otherwise {@code false}. */
boolean isRecordedProgramLoadFinished();
- /**
- * Returns past recordings.
- */
+ /** Returns past recordings. */
List<RecordedProgram> getRecordedPrograms();
- /**
- * Returns past recorded programs in the given series.
- */
+ /** Returns past recorded programs in the given series. */
List<RecordedProgram> getRecordedPrograms(long seriesRecordingId);
/**
* Returns all {@link ScheduledRecording} regardless of state.
- * <p>
- * The result doesn't contain the deleted schedules.
+ *
+ * <p>The result doesn't contain the deleted schedules.
*/
List<ScheduledRecording> getAllScheduledRecordings();
@@ -71,29 +59,24 @@ public interface DvrDataManager {
*/
List<ScheduledRecording> getAvailableScheduledRecordings();
- /**
- * Returns started recordings that expired.
- */
+ /** Returns started recordings that expired. */
List<ScheduledRecording> getStartedRecordings();
- /**
- * Returns scheduled but not started recordings that have not expired.
- */
+ /** Returns scheduled but not started recordings that have not expired. */
List<ScheduledRecording> getNonStartedScheduledRecordings();
- /**
- * Returns series recordings.
- */
+ /** Returns failed recordings. */
+ List<ScheduledRecording> getFailedScheduledRecordings();
+
+ /** Returns series recordings. */
List<SeriesRecording> getSeriesRecordings();
- /**
- * Returns series recordings from the given input.
- */
+ /** Returns series recordings from the given input. */
List<SeriesRecording> getSeriesRecordings(String inputId);
/**
- * Returns the next start time after {@code time} or {@link #NEXT_START_TIME_NOT_FOUND}
- * if none is found.
+ * Returns the next start time after {@code time} or {@link #NEXT_START_TIME_NOT_FOUND} if none
+ * is found.
*
* @param time time milliseconds
*/
@@ -103,73 +86,48 @@ public interface DvrDataManager {
* Returns a list of the schedules with a overlap with the given time period inclusive and with
* the given state.
*
- * <p> A recording overlaps with a period when
- * {@code recording.getStartTime() <= period.getUpper() &&
- * recording.getEndTime() >= period.getLower()}.
+ * <p>A recording overlaps with a period when {@code recording.getStartTime() <=
+ * period.getUpper() && recording.getEndTime() >= period.getLower()}.
*
* @param period a time period in milliseconds.
* @param state the state of the schedule.
*/
List<ScheduledRecording> getScheduledRecordings(Range<Long> period, @RecordingState int state);
- /**
- * Returns a list of the schedules in the given series.
- */
+ /** Returns a list of the schedules in the given series. */
List<ScheduledRecording> getScheduledRecordings(long seriesRecordingId);
- /**
- * Returns a list of the schedules from the given input.
- */
+ /** Returns a list of the schedules from the given input. */
List<ScheduledRecording> getScheduledRecordings(String inputId);
- /**
- * Add a {@link OnDvrScheduleLoadFinishedListener}.
- */
+ /** Add a {@link OnDvrScheduleLoadFinishedListener}. */
void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener);
- /**
- * Remove a {@link OnDvrScheduleLoadFinishedListener}.
- */
+ /** Remove a {@link OnDvrScheduleLoadFinishedListener}. */
void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener);
- /**
- * Add a {@link OnRecordedProgramLoadFinishedListener}.
- */
+ /** Add a {@link OnRecordedProgramLoadFinishedListener}. */
void addRecordedProgramLoadFinishedListener(OnRecordedProgramLoadFinishedListener listener);
- /**
- * Remove a {@link OnRecordedProgramLoadFinishedListener}.
- */
+ /** Remove a {@link OnRecordedProgramLoadFinishedListener}. */
void removeRecordedProgramLoadFinishedListener(OnRecordedProgramLoadFinishedListener listener);
- /**
- * Add a {@link ScheduledRecordingListener}.
- */
+ /** Add a {@link ScheduledRecordingListener}. */
void addScheduledRecordingListener(ScheduledRecordingListener scheduledRecordingListener);
- /**
- * Remove a {@link ScheduledRecordingListener}.
- */
+ /** Remove a {@link ScheduledRecordingListener}. */
void removeScheduledRecordingListener(ScheduledRecordingListener scheduledRecordingListener);
- /**
- * Add a {@link RecordedProgramListener}.
- */
+ /** Add a {@link RecordedProgramListener}. */
void addRecordedProgramListener(RecordedProgramListener listener);
- /**
- * Remove a {@link RecordedProgramListener}.
- */
+ /** Remove a {@link RecordedProgramListener}. */
void removeRecordedProgramListener(RecordedProgramListener listener);
- /**
- * Add a {@link ScheduledRecordingListener}.
- */
+ /** Add a {@link ScheduledRecordingListener}. */
void addSeriesRecordingListener(SeriesRecordingListener seriesRecordingListener);
- /**
- * Remove a {@link ScheduledRecordingListener}.
- */
+ /** Remove a {@link ScheduledRecordingListener}. */
void removeSeriesRecordingListener(SeriesRecordingListener seriesRecordingListener);
/**
@@ -178,65 +136,47 @@ public interface DvrDataManager {
@Nullable
ScheduledRecording getScheduledRecording(long recordingId);
- /**
- * Returns the scheduled recording program with the given programId or null if is not found.
- */
+ /** Returns the scheduled recording program with the given programId or null if is not found. */
@Nullable
ScheduledRecording getScheduledRecordingForProgramId(long programId);
- /**
- * Returns the recorded program with the given recordingId or null if is not found.
- */
+ /** Returns the recorded program with the given recordingId or null if is not found. */
@Nullable
RecordedProgram getRecordedProgram(long recordingId);
- /**
- * Returns the series recording with the given seriesId or null if is not found.
- */
+ /** Returns the series recording with the given seriesId or null if is not found. */
@Nullable
SeriesRecording getSeriesRecording(long seriesRecordingId);
- /**
- * Returns the series recording with the given series ID or {@code null} if not found.
- */
+ /** Returns the series recording with the given series ID or {@code null} if not found. */
@Nullable
SeriesRecording getSeriesRecording(String seriesId);
- /**
- * Returns the schedules which are marked deleted.
- */
+ /** Returns the schedules which are marked deleted. */
Collection<ScheduledRecording> getDeletedSchedules();
- /**
- * Returns the program IDs which is not allowed to make a schedule automatically.
- */
+ /** Returns the program IDs which is not allowed to make a schedule automatically. */
@NonNull
Collection<Long> getDisallowedProgramIds();
/**
- * Checks each of the give series recordings to see if it's empty, i.e., it doesn't contains
- * any available schedules or recorded programs, and it's status is
- * {@link SeriesRecording#STATE_SERIES_STOPPED}; and removes those empty series recordings.
+ * Checks each of the give series recordings to see if it's empty, i.e., it doesn't contains any
+ * available schedules or recorded programs, and it's status is {@link
+ * SeriesRecording#STATE_SERIES_STOPPED}; and removes those empty series recordings.
*/
void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds);
- /**
- * Listens for the DVR schedules loading finished.
- */
+ /** Listens for the DVR schedules loading finished. */
interface OnDvrScheduleLoadFinishedListener {
void onDvrScheduleLoadFinished();
}
- /**
- * Listens for the recorded program loading finished.
- */
+ /** Listens for the recorded program loading finished. */
interface OnRecordedProgramLoadFinishedListener {
void onRecordedProgramLoadFinished();
}
- /**
- * Listens for changes to {@link ScheduledRecording}s.
- */
+ /** Listens for changes to {@link ScheduledRecording}s. */
interface ScheduledRecordingListener {
void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings);
@@ -250,9 +190,7 @@ public interface DvrDataManager {
void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings);
}
- /**
- * Listens for changes to {@link SeriesRecording}s.
- */
+ /** Listens for changes to {@link SeriesRecording}s. */
interface SeriesRecordingListener {
void onSeriesRecordingAdded(SeriesRecording... seriesRecordings);
@@ -261,9 +199,7 @@ public interface DvrDataManager {
void onSeriesRecordingChanged(SeriesRecording... seriesRecordings);
}
- /**
- * Listens for changes to {@link RecordedProgram}s.
- */
+ /** Listens for changes to {@link RecordedProgram}s. */
interface RecordedProgramListener {
void onRecordedProgramsAdded(RecordedProgram... recordedPrograms);
diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java
index 6094ca72..2b4ecbf5 100644
--- a/src/com/android/tv/dvr/DvrDataManagerImpl.java
+++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java
@@ -38,10 +38,12 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Range;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.DvrStorageStatusManager.OnStorageMountChangedListener;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
+import com.android.tv.common.recording.RecordingStorageStatusManager.OnStorageMountChangedListener;
+import com.android.tv.common.util.Clock;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.dvr.data.IdGenerator;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
@@ -59,12 +61,9 @@ import com.android.tv.dvr.provider.DvrDbSync;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.AsyncDbTask.AsyncRecordedProgramQueryTask;
-import com.android.tv.util.Clock;
import com.android.tv.util.Filter;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.TvUriMatcher;
-import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -73,10 +72,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.Executor;
-/**
- * DVR Data manager to handle recordings and schedules.
- */
+/** DVR Data manager to handle recordings and schedules. */
@MainThread
@TargetApi(Build.VERSION_CODES.N)
public class DvrDataManagerImpl extends BaseDvrDataManager {
@@ -98,52 +96,54 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
private final HashMap<Long, SeriesRecording> mSeriesRecordingsForRemovedInput = new HashMap<>();
private final Context mContext;
- private final ContentObserver mContentObserver = new ContentObserver(new Handler(
- Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- onChange(selfChange, null);
- }
+ private Executor mDbExecutor;
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
- @Override
- public void onChange(boolean selfChange, final @Nullable Uri uri) {
- RecordedProgramsQueryTask task = new RecordedProgramsQueryTask(
- mContext.getContentResolver(), uri);
- task.executeOnDbThread();
- mPendingTasks.add(task);
- }
- };
+ @Override
+ public void onChange(boolean selfChange, final @Nullable Uri uri) {
+ RecordedProgramsQueryTask task =
+ new RecordedProgramsQueryTask(mContext.getContentResolver(), uri);
+ task.executeOnDbThread();
+ mPendingTasks.add(task);
+ }
+ };
private boolean mDvrLoadFinished;
private boolean mRecordedProgramLoadFinished;
private final Set<AsyncTask> mPendingTasks = new ArraySet<>();
private DvrDbSync mDbSync;
- private DvrStorageStatusManager mStorageStatusManager;
+ private RecordingStorageStatusManager mStorageStatusManager;
- private final TvInputCallback mInputCallback = new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
- if (!isInputAvailable(inputId)) {
- if (DEBUG) Log.d(TAG, "Not available for recording");
- return;
- }
- unhideInput(inputId);
- }
+ private final TvInputCallback mInputCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
+ if (!isInputAvailable(inputId)) {
+ if (DEBUG) Log.d(TAG, "Not available for recording");
+ return;
+ }
+ unhideInput(inputId);
+ }
- @Override
- public void onInputRemoved(String inputId) {
- if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
- hideInput(inputId);
- }
- };
+ @Override
+ public void onInputRemoved(String inputId) {
+ if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
+ hideInput(inputId);
+ }
+ };
private final OnStorageMountChangedListener mStorageMountChangedListener =
new OnStorageMountChangedListener() {
@Override
public void onStorageMountChanged(boolean storageMounted) {
for (TvInputInfo input : mInputManager.getTvInputInfos(true, true)) {
- if (Utils.isBundledInput(input.getId())) {
+ if (CommonUtils.isBundledInput(input.getId())) {
if (storageMounted) {
unhideInput(input.getId());
} else {
@@ -154,8 +154,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
};
- private static <T> List<T> moveElements(HashMap<Long, T> from, HashMap<Long, T> to,
- Filter<T> filter) {
+ private static <T> List<T> moveElements(
+ HashMap<Long, T> from, HashMap<Long, T> to, Filter<T> filter) {
List<T> moved = new ArrayList<>();
Iterator<Entry<Long, T>> iter = from.entrySet().iterator();
while (iter.hasNext()) {
@@ -172,119 +172,139 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
public DvrDataManagerImpl(Context context, Clock clock) {
super(context, clock);
mContext = context;
- mInputManager = TvApplication.getSingletons(context).getTvInputManagerHelper();
- mStorageStatusManager = TvApplication.getSingletons(context).getDvrStorageStatusManager();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mInputManager = tvSingletons.getTvInputManagerHelper();
+ mStorageStatusManager = tvSingletons.getRecordingStorageStatusManager();
+ mDbExecutor = tvSingletons.getDbExecutor();
}
public void start() {
mInputManager.addCallback(mInputCallback);
mStorageStatusManager.addListener(mStorageMountChangedListener);
- AsyncDvrQuerySeriesRecordingTask dvrQuerySeriesRecordingTask
- = new AsyncDvrQuerySeriesRecordingTask(mContext) {
- @Override
- protected void onCancelled(List<SeriesRecording> seriesRecordings) {
- mPendingTasks.remove(this);
- }
+ AsyncDvrQuerySeriesRecordingTask dvrQuerySeriesRecordingTask =
+ new AsyncDvrQuerySeriesRecordingTask(mContext) {
+ @Override
+ protected void onCancelled(List<SeriesRecording> seriesRecordings) {
+ mPendingTasks.remove(this);
+ }
- @Override
- protected void onPostExecute(List<SeriesRecording> seriesRecordings) {
- mPendingTasks.remove(this);
- long maxId = 0;
- HashSet<String> seriesIds = new HashSet<>();
- for (SeriesRecording r : seriesRecordings) {
- if (SoftPreconditions.checkState(!seriesIds.contains(r.getSeriesId()), TAG,
- "Skip loading series recording with duplicate series ID: " + r)) {
- seriesIds.add(r.getSeriesId());
- if (isInputAvailable(r.getInputId())) {
- mSeriesRecordings.put(r.getId(), r);
- mSeriesId2SeriesRecordings.put(r.getSeriesId(), r);
- } else {
- mSeriesRecordingsForRemovedInput.put(r.getId(), r);
+ @Override
+ protected void onPostExecute(List<SeriesRecording> seriesRecordings) {
+ mPendingTasks.remove(this);
+ long maxId = 0;
+ HashSet<String> seriesIds = new HashSet<>();
+ for (SeriesRecording r : seriesRecordings) {
+ if (SoftPreconditions.checkState(
+ !seriesIds.contains(r.getSeriesId()),
+ TAG,
+ "Skip loading series recording with duplicate series ID: "
+ + r)) {
+ seriesIds.add(r.getSeriesId());
+ if (isInputAvailable(r.getInputId())) {
+ mSeriesRecordings.put(r.getId(), r);
+ mSeriesId2SeriesRecordings.put(r.getSeriesId(), r);
+ } else {
+ mSeriesRecordingsForRemovedInput.put(r.getId(), r);
+ }
+ }
+ if (maxId < r.getId()) {
+ maxId = r.getId();
+ }
}
+ IdGenerator.SERIES_RECORDING.setMaxId(maxId);
}
- if (maxId < r.getId()) {
- maxId = r.getId();
- }
- }
- IdGenerator.SERIES_RECORDING.setMaxId(maxId);
- }
- };
+ };
dvrQuerySeriesRecordingTask.executeOnDbThread();
mPendingTasks.add(dvrQuerySeriesRecordingTask);
- AsyncDvrQueryScheduleTask dvrQueryScheduleTask
- = new AsyncDvrQueryScheduleTask(mContext) {
- @Override
- protected void onCancelled(List<ScheduledRecording> scheduledRecordings) {
- mPendingTasks.remove(this);
- }
+ AsyncDvrQueryScheduleTask dvrQueryScheduleTask =
+ new AsyncDvrQueryScheduleTask(mContext) {
+ @Override
+ protected void onCancelled(List<ScheduledRecording> scheduledRecordings) {
+ mPendingTasks.remove(this);
+ }
- @SuppressLint("SwitchIntDef")
- @Override
- protected void onPostExecute(List<ScheduledRecording> result) {
- mPendingTasks.remove(this);
- long maxId = 0;
- List<SeriesRecording> seriesRecordingsToAdd = new ArrayList<>();
- List<ScheduledRecording> toUpdate = new ArrayList<>();
- List<ScheduledRecording> toDelete = new ArrayList<>();
- for (ScheduledRecording r : result) {
- if (!isInputAvailable(r.getInputId())) {
- mScheduledRecordingsForRemovedInput.put(r.getId(), r);
- } else if (r.getState() == ScheduledRecording.STATE_RECORDING_DELETED) {
- getDeletedScheduleMap().put(r.getProgramId(), r);
- } else {
- mScheduledRecordings.put(r.getId(), r);
- if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) {
- mProgramId2ScheduledRecordings.put(r.getProgramId(), r);
- }
- // Adjust the state of the schedules before DB loading is finished.
- switch (r.getState()) {
- case ScheduledRecording.STATE_RECORDING_IN_PROGRESS:
- if (r.getEndTimeMs() <= mClock.currentTimeMillis()) {
- toUpdate.add(ScheduledRecording.buildFrom(r)
- .setState(ScheduledRecording.STATE_RECORDING_FAILED)
- .build());
- } else {
- toUpdate.add(ScheduledRecording.buildFrom(r)
- .setState(
- ScheduledRecording.STATE_RECORDING_NOT_STARTED)
- .build());
+ @SuppressLint("SwitchIntDef")
+ @Override
+ protected void onPostExecute(List<ScheduledRecording> result) {
+ mPendingTasks.remove(this);
+ long maxId = 0;
+ int reasonNotStarted =
+ ScheduledRecording
+ .FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED;
+ List<ScheduledRecording> toUpdate = new ArrayList<>();
+ List<ScheduledRecording> toDelete = new ArrayList<>();
+ for (ScheduledRecording r : result) {
+ if (!isInputAvailable(r.getInputId())) {
+ mScheduledRecordingsForRemovedInput.put(r.getId(), r);
+ } else if (r.getState() == ScheduledRecording.STATE_RECORDING_DELETED) {
+ getDeletedScheduleMap().put(r.getProgramId(), r);
+ } else {
+ mScheduledRecordings.put(r.getId(), r);
+ if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) {
+ mProgramId2ScheduledRecordings.put(r.getProgramId(), r);
}
- break;
- case ScheduledRecording.STATE_RECORDING_NOT_STARTED:
- if (r.getEndTimeMs() <= mClock.currentTimeMillis()) {
- toUpdate.add(ScheduledRecording.buildFrom(r)
- .setState(ScheduledRecording.STATE_RECORDING_FAILED)
- .build());
+ // Adjust the state of the schedules before DB loading is finished.
+ switch (r.getState()) {
+ case ScheduledRecording.STATE_RECORDING_IN_PROGRESS:
+ if (r.getEndTimeMs() <= mClock.currentTimeMillis()) {
+ int reason =
+ ScheduledRecording.FAILED_REASON_NOT_FINISHED;
+ toUpdate.add(
+ ScheduledRecording.buildFrom(r)
+ .setState(
+ ScheduledRecording
+ .STATE_RECORDING_FAILED)
+ .setFailedReason(reason)
+ .build());
+ } else {
+ toUpdate.add(
+ ScheduledRecording.buildFrom(r)
+ .setState(
+ ScheduledRecording
+ .STATE_RECORDING_NOT_STARTED)
+ .build());
+ }
+ break;
+ case ScheduledRecording.STATE_RECORDING_NOT_STARTED:
+ if (r.getEndTimeMs() <= mClock.currentTimeMillis()) {
+ toUpdate.add(
+ ScheduledRecording.buildFrom(r)
+ .setState(
+ ScheduledRecording
+ .STATE_RECORDING_FAILED)
+ .setFailedReason(reasonNotStarted)
+ .build());
+ }
+ break;
+ case ScheduledRecording.STATE_RECORDING_CANCELED:
+ toDelete.add(r);
+ break;
+ default: // fall out
}
- break;
- case ScheduledRecording.STATE_RECORDING_CANCELED:
- toDelete.add(r);
- break;
+ }
+ if (maxId < r.getId()) {
+ maxId = r.getId();
+ }
+ }
+ if (!toUpdate.isEmpty()) {
+ updateScheduledRecording(ScheduledRecording.toArray(toUpdate));
+ }
+ if (!toDelete.isEmpty()) {
+ removeScheduledRecording(ScheduledRecording.toArray(toDelete));
+ }
+ IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId);
+ if (mRecordedProgramLoadFinished) {
+ validateSeriesRecordings();
+ }
+ mDvrLoadFinished = true;
+ notifyDvrScheduleLoadFinished();
+ if (isInitialized()) {
+ mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
+ mDbSync.start();
+ SeriesRecordingScheduler.getInstance(mContext).start();
}
}
- if (maxId < r.getId()) {
- maxId = r.getId();
- }
- }
- if (!toUpdate.isEmpty()) {
- updateScheduledRecording(ScheduledRecording.toArray(toUpdate));
- }
- if (!toDelete.isEmpty()) {
- removeScheduledRecording(ScheduledRecording.toArray(toDelete));
- }
- IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId);
- if (mRecordedProgramLoadFinished) {
- validateSeriesRecordings();
- }
- mDvrLoadFinished = true;
- notifyDvrScheduleLoadFinished();
- if (isInitialized()) {
- mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
- mDbSync.start();
- SeriesRecordingScheduler.getInstance(mContext).start();
- }
- }
- };
+ };
dvrQueryScheduleTask.executeOnDbThread();
mPendingTasks.add(dvrQueryScheduleTask);
RecordedProgramsQueryTask mRecordedProgramQueryTask =
@@ -341,8 +361,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
mRecordedProgramsForRemovedInput.clear();
notifyRecordedProgramsRemoved(RecordedProgram.toArray(oldRecordedPrograms));
} else {
- HashMap<Long, RecordedProgram> oldRecordedPrograms
- = new HashMap<>(mRecordedPrograms);
+ HashMap<Long, RecordedProgram> oldRecordedPrograms =
+ new HashMap<>(mRecordedPrograms);
mRecordedPrograms.clear();
mRecordedProgramsForRemovedInput.clear();
List<RecordedProgram> addedRecordedPrograms = new ArrayList<>();
@@ -492,7 +512,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
@VisibleForTesting
- static long getNextStartTimeAfter(List<ScheduledRecording> scheduledRecordings, long startTime) {
+ static long getNextStartTimeAfter(
+ List<ScheduledRecording> scheduledRecordings, long startTime) {
int start = 0;
int end = scheduledRecordings.size() - 1;
while (start <= end) {
@@ -503,13 +524,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
end = mid - 1;
}
}
- return start < scheduledRecordings.size() ? scheduledRecordings.get(start).getStartTimeMs()
+ return start < scheduledRecordings.size()
+ ? scheduledRecordings.get(start).getStartTimeMs()
: NEXT_START_TIME_NOT_FOUND;
}
@Override
- public List<ScheduledRecording> getScheduledRecordings(Range<Long> period,
- @RecordingState int state) {
+ public List<ScheduledRecording> getScheduledRecordings(
+ Range<Long> period, @RecordingState int state) {
List<ScheduledRecording> result = new ArrayList<>();
for (ScheduledRecording r : mScheduledRecordings.values()) {
if (r.isOverLapping(period) && r.getState() == state) {
@@ -595,8 +617,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
r.setId(IdGenerator.SERIES_RECORDING.newId());
mSeriesRecordings.put(r.getId(), r);
SeriesRecording previousSeries = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r);
- SoftPreconditions.checkArgument(previousSeries == null, TAG, "Attempt to add series"
- + " recording with the duplicate series ID: " + r.getSeriesId());
+ SoftPreconditions.checkArgument(
+ previousSeries == null,
+ TAG,
+ "Attempt to add series" + " recording with the duplicate series ID: %s",
+ r.getSeriesId());
}
if (mDvrLoadFinished) {
notifySeriesRecordingAdded(seriesRecordings);
@@ -620,20 +645,23 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
mProgramId2ScheduledRecordings.remove(r.getProgramId());
if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET
&& (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || r.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ || r.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
seriesRecordingIdsToCheck.add(r.getSeriesRecordingId());
}
boolean isScheduleForRemovedInput =
mScheduledRecordingsForRemovedInput.remove(r.getProgramId()) != null;
// If it belongs to the series recording and it's not started yet, just mark delete
// instead of deleting it.
- if (!isScheduleForRemovedInput && !forceRemove
+ if (!isScheduleForRemovedInput
+ && !forceRemove
&& r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET
&& (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || r.getState() == ScheduledRecording.STATE_RECORDING_CANCELED)) {
+ || r.getState() == ScheduledRecording.STATE_RECORDING_CANCELED)) {
SoftPreconditions.checkState(r.getProgramId() != ScheduledRecording.ID_NOT_SET);
- ScheduledRecording deleted = ScheduledRecording.buildFrom(r)
- .setState(ScheduledRecording.STATE_RECORDING_DELETED).build();
+ ScheduledRecording deleted =
+ ScheduledRecording.buildFrom(r)
+ .setState(ScheduledRecording.STATE_RECORDING_DELETED)
+ .build();
getDeletedScheduleMap().put(deleted.getProgramId(), deleted);
schedulesNotToDelete.add(deleted);
} else {
@@ -655,12 +683,12 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (!schedulesToDelete.isEmpty()) {
- new AsyncDeleteScheduleTask(mContext).executeOnDbThread(
- ScheduledRecording.toArray(schedulesToDelete));
+ new AsyncDeleteScheduleTask(mContext)
+ .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete));
}
if (!schedulesNotToDelete.isEmpty()) {
- new AsyncUpdateScheduleTask(mContext).executeOnDbThread(
- ScheduledRecording.toArray(schedulesNotToDelete));
+ new AsyncUpdateScheduleTask(mContext)
+ .executeOnDbThread(ScheduledRecording.toArray(schedulesNotToDelete));
}
}
@@ -680,8 +708,10 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
toDelete.add(r);
} else {
- toUpdate.add(ScheduledRecording.buildFrom(r)
- .setSeriesRecordingId(SeriesRecording.ID_NOT_SET).build());
+ toUpdate.add(
+ ScheduledRecording.buildFrom(r)
+ .setSeriesRecordingId(SeriesRecording.ID_NOT_SET)
+ .build());
}
}
}
@@ -709,7 +739,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
List<ScheduledRecording> toUpdate = new ArrayList<>();
Set<Long> seriesRecordingIdsToCheck = new HashSet<>();
for (ScheduledRecording r : schedules) {
- if (!SoftPreconditions.checkState(mScheduledRecordings.containsKey(r.getId()), TAG,
+ if (!SoftPreconditions.checkState(
+ mScheduledRecordings.containsKey(r.getId()),
+ TAG,
"Recording not found for: " + r)) {
continue;
}
@@ -720,8 +752,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
long programId = r.getProgramId();
if (oldScheduledRecording.getProgramId() != programId
&& oldScheduledRecording.getProgramId() != ScheduledRecording.ID_NOT_SET) {
- ScheduledRecording oldValueForProgramId = mProgramId2ScheduledRecordings
- .get(oldScheduledRecording.getProgramId());
+ ScheduledRecording oldValueForProgramId =
+ mProgramId2ScheduledRecordings.get(oldScheduledRecording.getProgramId());
if (oldValueForProgramId.getId() == r.getId()) {
// Only remove the old ScheduledRecording if it has the same ID as the new one.
mProgramId2ScheduledRecordings.remove(oldScheduledRecording.getProgramId());
@@ -755,14 +787,17 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
@Override
public void updateSeriesRecording(final SeriesRecording... seriesRecordings) {
for (SeriesRecording r : seriesRecordings) {
- if (!SoftPreconditions.checkArgument(mSeriesRecordings.containsKey(r.getId()), TAG,
- "Non Existing Series ID: " + r)) {
+ if (!SoftPreconditions.checkArgument(
+ mSeriesRecordings.containsKey(r.getId()),
+ TAG,
+ "Non Existing Series ID: %s",
+ r)) {
continue;
}
SeriesRecording old1 = mSeriesRecordings.put(r.getId(), r);
SeriesRecording old2 = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r);
- SoftPreconditions.checkArgument(old1.equals(old2), TAG, "Series ID cannot be"
- + " updated: " + r);
+ SoftPreconditions.checkArgument(
+ old1.equals(old2), TAG, "Series ID cannot be updated: %s", r);
}
if (mDvrLoadFinished) {
notifySeriesRecordingChanged(seriesRecordings);
@@ -772,7 +807,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
private boolean isInputAvailable(String inputId) {
return mInputManager.hasTvInputInfo(inputId)
- && (!Utils.isBundledInput(inputId) || mStorageStatusManager.isStorageMounted());
+ && (!CommonUtils.isBundledInput(inputId)
+ || mStorageStatusManager.isStorageMounted());
}
private void removeDeletedSchedules(ScheduledRecording... addedSchedules) {
@@ -784,8 +820,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (!schedulesToDelete.isEmpty()) {
- new AsyncDeleteScheduleTask(mContext).executeOnDbThread(
- ScheduledRecording.toArray(schedulesToDelete));
+ new AsyncDeleteScheduleTask(mContext)
+ .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete));
}
}
@@ -805,15 +841,17 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (!schedulesToDelete.isEmpty()) {
- new AsyncDeleteScheduleTask(mContext).executeOnDbThread(
- ScheduledRecording.toArray(schedulesToDelete));
+ new AsyncDeleteScheduleTask(mContext)
+ .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete));
}
}
private void unhideInput(String inputId) {
if (DEBUG) Log.d(TAG, "unhideInput " + inputId);
List<ScheduledRecording> movedSchedules =
- moveElements(mScheduledRecordingsForRemovedInput, mScheduledRecordings,
+ moveElements(
+ mScheduledRecordingsForRemovedInput,
+ mScheduledRecordings,
new Filter<ScheduledRecording>() {
@Override
public boolean filter(ScheduledRecording r) {
@@ -821,7 +859,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
});
List<RecordedProgram> movedRecordedPrograms =
- moveElements(mRecordedProgramsForRemovedInput, mRecordedPrograms,
+ moveElements(
+ mRecordedProgramsForRemovedInput,
+ mRecordedPrograms,
new Filter<RecordedProgram>() {
@Override
public boolean filter(RecordedProgram r) {
@@ -830,7 +870,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
});
List<SeriesRecording> removedSeriesRecordings = new ArrayList<>();
List<SeriesRecording> movedSeriesRecordings =
- moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings,
+ moveElements(
+ mSeriesRecordingsForRemovedInput,
+ mSeriesRecordings,
new Filter<SeriesRecording>() {
@Override
public boolean filter(SeriesRecording r) {
@@ -856,8 +898,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
for (SeriesRecording r : removedSeriesRecordings) {
mSeriesRecordingsForRemovedInput.remove(r.getId());
}
- new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(
- SeriesRecording.toArray(removedSeriesRecordings));
+ new AsyncDeleteSeriesRecordingTask(mContext)
+ .executeOnDbThread(SeriesRecording.toArray(removedSeriesRecordings));
// Notify after all the data are moved.
if (!movedSchedules.isEmpty()) {
notifyScheduledRecordingAdded(ScheduledRecording.toArray(movedSchedules));
@@ -873,7 +915,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
private void hideInput(String inputId) {
if (DEBUG) Log.d(TAG, "hideInput " + inputId);
List<ScheduledRecording> movedSchedules =
- moveElements(mScheduledRecordings, mScheduledRecordingsForRemovedInput,
+ moveElements(
+ mScheduledRecordings,
+ mScheduledRecordingsForRemovedInput,
new Filter<ScheduledRecording>() {
@Override
public boolean filter(ScheduledRecording r) {
@@ -881,7 +925,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
});
List<SeriesRecording> movedSeriesRecordings =
- moveElements(mSeriesRecordings, mSeriesRecordingsForRemovedInput,
+ moveElements(
+ mSeriesRecordings,
+ mSeriesRecordingsForRemovedInput,
new Filter<SeriesRecording>() {
@Override
public boolean filter(SeriesRecording r) {
@@ -889,7 +935,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
});
List<RecordedProgram> movedRecordedPrograms =
- moveElements(mRecordedPrograms, mRecordedProgramsForRemovedInput,
+ moveElements(
+ mRecordedPrograms,
+ mRecordedProgramsForRemovedInput,
new Filter<RecordedProgram>() {
@Override
public boolean filter(RecordedProgram r) {
@@ -931,7 +979,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
public void forgetStorage(String inputId) {
List<ScheduledRecording> schedulesToDelete = new ArrayList<>();
for (Iterator<ScheduledRecording> i =
- mScheduledRecordingsForRemovedInput.values().iterator(); i.hasNext(); ) {
+ mScheduledRecordingsForRemovedInput.values().iterator();
+ i.hasNext(); ) {
ScheduledRecording r = i.next();
if (inputId.equals(r.getInputId())) {
schedulesToDelete.add(r);
@@ -939,32 +988,34 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
List<SeriesRecording> seriesRecordingsToDelete = new ArrayList<>();
- for (Iterator<SeriesRecording> i =
- mSeriesRecordingsForRemovedInput.values().iterator(); i.hasNext(); ) {
+ for (Iterator<SeriesRecording> i = mSeriesRecordingsForRemovedInput.values().iterator();
+ i.hasNext(); ) {
SeriesRecording r = i.next();
if (inputId.equals(r.getInputId())) {
seriesRecordingsToDelete.add(r);
i.remove();
}
}
- for (Iterator<RecordedProgram> i =
- mRecordedProgramsForRemovedInput.values().iterator(); i.hasNext(); ) {
+ for (Iterator<RecordedProgram> i = mRecordedProgramsForRemovedInput.values().iterator();
+ i.hasNext(); ) {
if (inputId.equals(i.next().getInputId())) {
i.remove();
}
}
- new AsyncDeleteScheduleTask(mContext).executeOnDbThread(
- ScheduledRecording.toArray(schedulesToDelete));
- new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(
- SeriesRecording.toArray(seriesRecordingsToDelete));
- new AsyncDbTask<Void, Void, Void>() {
+ new AsyncDeleteScheduleTask(mContext)
+ .executeOnDbThread(ScheduledRecording.toArray(schedulesToDelete));
+ new AsyncDeleteSeriesRecordingTask(mContext)
+ .executeOnDbThread(SeriesRecording.toArray(seriesRecordingsToDelete));
+ new AsyncDbTask<Void, Void, Void>(mDbExecutor) {
@Override
protected Void doInBackground(Void... params) {
ContentResolver resolver = mContext.getContentResolver();
- String args[] = { inputId };
+ String[] args = {inputId};
try {
- resolver.delete(RecordedPrograms.CONTENT_URI,
- RecordedPrograms.COLUMN_INPUT_ID + " = ?", args);
+ resolver.delete(
+ RecordedPrograms.CONTENT_URI,
+ RecordedPrograms.COLUMN_INPUT_ID + " = ?",
+ args);
} catch (SQLiteException e) {
Log.e(TAG, "Failed to delete recorded programs for inputId: " + inputId, e);
}
@@ -996,7 +1047,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
private final Uri mUri;
public RecordedProgramsQueryTask(ContentResolver contentResolver, Uri uri) {
- super(contentResolver, uri == null ? RecordedPrograms.CONTENT_URI : uri);
+ super(mDbExecutor, contentResolver, uri == null ? RecordedPrograms.CONTENT_URI : uri);
mUri = uri;
}
diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java
index d222003d..63a245a3 100644
--- a/src/com/android/tv/dvr/DvrManager.java
+++ b/src/com/android/tv/dvr/DvrManager.java
@@ -36,13 +36,12 @@ import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Range;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrScheduleManager.OnInitializeListener;
@@ -51,7 +50,6 @@ import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.Utils;
-
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -60,6 +58,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.Executor;
/**
* DVR manager class to add and remove recordings. UI can modify recording list through this class,
@@ -76,13 +75,15 @@ public class DvrManager {
// @GuardedBy("mListener")
private final Map<Listener, Handler> mListener = new HashMap<>();
private final Context mAppContext;
+ private final Executor mDbExecutor;
public DvrManager(Context context) {
SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG);
mAppContext = context.getApplicationContext();
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager();
- mScheduleManager = appSingletons.getDvrScheduleManager();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mDbExecutor = tvSingletons.getDbExecutor();
+ mDataManager = (WritableDvrDataManager) tvSingletons.getDvrDataManager();
+ mScheduleManager = tvSingletons.getDvrScheduleManager();
if (mDataManager.isInitialized() && mScheduleManager.isInitialized()) {
createSeriesRecordingsForRecordedProgramsIfNeeded(mDataManager.getRecordedPrograms());
} else {
@@ -103,37 +104,41 @@ public class DvrManager {
});
}
if (!mScheduleManager.isInitialized()) {
- mScheduleManager.addOnInitializeListener(new OnInitializeListener() {
+ mScheduleManager.addOnInitializeListener(
+ new OnInitializeListener() {
+ @Override
+ public void onInitialize() {
+ mScheduleManager.removeOnInitializeListener(this);
+ if (mDataManager.isInitialized()
+ && mScheduleManager.isInitialized()) {
+ createSeriesRecordingsForRecordedProgramsIfNeeded(
+ mDataManager.getRecordedPrograms());
+ }
+ }
+ });
+ }
+ }
+ mDataManager.addRecordedProgramListener(
+ new RecordedProgramListener() {
@Override
- public void onInitialize() {
- mScheduleManager.removeOnInitializeListener(this);
- if (mDataManager.isInitialized() && mScheduleManager.isInitialized()) {
- createSeriesRecordingsForRecordedProgramsIfNeeded(
- mDataManager.getRecordedPrograms());
+ public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
+ if (!mDataManager.isInitialized() || !mScheduleManager.isInitialized()) {
+ return;
+ }
+ for (RecordedProgram recordedProgram : recordedPrograms) {
+ createSeriesRecordingForRecordedProgramIfNeeded(recordedProgram);
}
}
- });
- }
- }
- mDataManager.addRecordedProgramListener(new RecordedProgramListener() {
- @Override
- public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
- if (!mDataManager.isInitialized() || !mScheduleManager.isInitialized()) {
- return;
- }
- for (RecordedProgram recordedProgram : recordedPrograms) {
- createSeriesRecordingForRecordedProgramIfNeeded(recordedProgram);
- }
- }
- @Override
- public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { }
+ @Override
+ public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {}
- @Override
- public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
- // Removing series recording is handled in the SeriesRecordingDetailsFragment.
- }
- });
+ @Override
+ public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
+ // Removing series recording is handled in the
+ // SeriesRecordingDetailsFragment.
+ }
+ });
}
private void createSeriesRecordingsForRecordedProgramsIfNeeded(
@@ -153,33 +158,38 @@ public class DvrManager {
}
}
- /**
- * Schedules a recording for {@code program}.
- */
+ /** Schedules a recording for {@code program}. */
public ScheduledRecording addSchedule(Program program) {
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return null;
}
SeriesRecording seriesRecording = getSeriesRecording(program);
- return addSchedule(program, seriesRecording == null
- ? mScheduleManager.suggestNewPriority()
- : seriesRecording.getPriority());
+ return addSchedule(
+ program,
+ seriesRecording == null
+ ? mScheduleManager.suggestNewPriority()
+ : seriesRecording.getPriority());
}
/**
- * Schedules a recording for {@code program} with the highest priority so that the schedule
- * can be recorded.
+ * Schedules a recording for {@code program} with the highest priority so that the schedule can
+ * be recorded.
*/
public ScheduledRecording addScheduleWithHighestPriority(Program program) {
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return null;
}
SeriesRecording seriesRecording = getSeriesRecording(program);
- return addSchedule(program, seriesRecording == null
- ? mScheduleManager.suggestNewPriority()
- : mScheduleManager.suggestHighestPriority(seriesRecording.getInputId(),
- new Range(program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()),
- seriesRecording.getPriority()));
+ return addSchedule(
+ program,
+ seriesRecording == null
+ ? mScheduleManager.suggestNewPriority()
+ : mScheduleManager.suggestHighestPriority(
+ seriesRecording.getInputId(),
+ new Range(
+ program.getStartTimeUtcMillis(),
+ program.getEndTimeUtcMillis()),
+ seriesRecording.getPriority()));
}
private ScheduledRecording addSchedule(Program program, long priority) {
@@ -190,21 +200,28 @@ public class DvrManager {
}
ScheduledRecording schedule;
SeriesRecording seriesRecording = getSeriesRecording(program);
- schedule = createScheduledRecordingBuilder(input.getId(), program)
- .setPriority(priority)
- .setSeriesRecordingId(seriesRecording == null ? SeriesRecording.ID_NOT_SET
- : seriesRecording.getId())
- .build();
+ schedule =
+ createScheduledRecordingBuilder(input.getId(), program)
+ .setPriority(priority)
+ .setSeriesRecordingId(
+ seriesRecording == null
+ ? SeriesRecording.ID_NOT_SET
+ : seriesRecording.getId())
+ .build();
mDataManager.addScheduledRecording(schedule);
return schedule;
}
- /**
- * Adds a recording schedule with a time range.
- */
+ /** Adds a recording schedule with a time range. */
public void addSchedule(Channel channel, long startTime, long endTime) {
- Log.i(TAG, "Adding scheduled recording of channel " + channel + " starting at " +
- Utils.toTimeString(startTime) + " and ending at " + Utils.toTimeString(endTime));
+ Log.i(
+ TAG,
+ "Adding scheduled recording of channel "
+ + channel
+ + " starting at "
+ + Utils.toTimeString(startTime)
+ + " and ending at "
+ + Utils.toTimeString(endTime));
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return;
}
@@ -216,9 +233,7 @@ public class DvrManager {
addScheduleInternal(input.getId(), channel.getId(), startTime, endTime);
}
- /**
- * Adds the schedule.
- */
+ /** Adds the schedule. */
public void addSchedule(ScheduledRecording schedule) {
if (mDataManager.isDvrScheduleLoadFinished()) {
mDataManager.addScheduledRecording(schedule);
@@ -226,19 +241,23 @@ public class DvrManager {
}
private void addScheduleInternal(String inputId, long channelId, long startTime, long endTime) {
- mDataManager.addScheduledRecording(ScheduledRecording
- .builder(inputId, channelId, startTime, endTime)
- .setPriority(mScheduleManager.suggestNewPriority())
- .build());
+ mDataManager.addScheduledRecording(
+ ScheduledRecording.builder(inputId, channelId, startTime, endTime)
+ .setPriority(mScheduleManager.suggestNewPriority())
+ .build());
}
- /**
- * Adds a new series recording and schedules for the programs with the initial state.
- */
- public SeriesRecording addSeriesRecording(Program selectedProgram,
- List<Program> programsToSchedule, @SeriesRecording.SeriesState int initialState) {
- Log.i(TAG, "Adding series recording for program " + selectedProgram + ", and schedules: "
- + programsToSchedule);
+ /** Adds a new series recording and schedules for the programs with the initial state. */
+ public SeriesRecording addSeriesRecording(
+ Program selectedProgram,
+ List<Program> programsToSchedule,
+ @SeriesRecording.SeriesState int initialState) {
+ Log.i(
+ TAG,
+ "Adding series recording for program "
+ + selectedProgram
+ + ", and schedules: "
+ + programsToSchedule);
if (!SoftPreconditions.checkState(mDataManager.isInitialized())) {
return null;
}
@@ -247,10 +266,11 @@ public class DvrManager {
Log.e(TAG, "Can't find input for program: " + selectedProgram);
return null;
}
- SeriesRecording seriesRecording = SeriesRecording.builder(input.getId(), selectedProgram)
- .setPriority(mScheduleManager.suggestNewSeriesPriority())
- .setState(initialState)
- .build();
+ SeriesRecording seriesRecording =
+ SeriesRecording.builder(input.getId(), selectedProgram)
+ .setPriority(mScheduleManager.suggestNewSeriesPriority())
+ .setState(initialState)
+ .build();
mDataManager.addSeriesRecording(seriesRecording);
// The schedules for the recorded programs should be added not to create the schedule the
// duplicate episodes.
@@ -279,9 +299,11 @@ public class DvrManager {
// Duplicate schedules can exist, but they will be deleted in a few days. And it's
// also guaranteed that the schedules don't belong to any series recordings because
// there are no more than one series recordings which have the same program title.
- toAdd.add(ScheduledRecording.builder(recordedProgram)
- .setPriority(series.getPriority())
- .setSeriesRecordingId(series.getId()).build());
+ toAdd.add(
+ ScheduledRecording.builder(recordedProgram)
+ .setPriority(series.getPriority())
+ .setSeriesRecordingId(series.getId())
+ .build());
}
}
if (!toAdd.isEmpty()) {
@@ -291,11 +313,11 @@ public class DvrManager {
/**
* Adds {@link ScheduledRecording}s for the series recording.
- * <p>
- * This method doesn't add the series recording.
+ *
+ * <p>This method doesn't add the series recording.
*/
- public void addScheduleToSeriesRecording(SeriesRecording series,
- List<Program> programsToSchedule) {
+ public void addScheduleToSeriesRecording(
+ SeriesRecording series, List<Program> programsToSchedule) {
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return;
}
@@ -311,18 +333,20 @@ public class DvrManager {
mDataManager.getScheduledRecordingForProgramId(program.getId());
if (scheduleWithSameProgram != null) {
if (scheduleWithSameProgram.isNotStarted()) {
- ScheduledRecording r = ScheduledRecording.buildFrom(scheduleWithSameProgram)
- .setSeriesRecordingId(series.getId())
- .build();
+ ScheduledRecording r =
+ ScheduledRecording.buildFrom(scheduleWithSameProgram)
+ .setSeriesRecordingId(series.getId())
+ .build();
if (!r.equals(scheduleWithSameProgram)) {
toUpdate.add(r);
}
}
} else {
- toAdd.add(createScheduledRecordingBuilder(input.getId(), program)
- .setPriority(series.getPriority())
- .setSeriesRecordingId(series.getId())
- .build());
+ toAdd.add(
+ createScheduledRecordingBuilder(input.getId(), program)
+ .setPriority(series.getPriority())
+ .setSeriesRecordingId(series.getId())
+ .build());
}
}
if (!toAdd.isEmpty()) {
@@ -333,9 +357,7 @@ public class DvrManager {
}
}
- /**
- * Updates the series recording.
- */
+ /** Updates the series recording. */
public void updateSeriesRecording(SeriesRecording series) {
if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
SeriesRecording previousSeries = mDataManager.getSeriesRecording(series.getId());
@@ -344,7 +366,7 @@ public class DvrManager {
// schedules will be added by SeriesRecordingScheduler or by SeriesSettingsFragment.
if (previousSeries.getChannelOption() != series.getChannelOption()
|| (previousSeries.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE
- && previousSeries.getChannelId() != series.getChannelId())) {
+ && previousSeries.getChannelId() != series.getChannelId())) {
List<ScheduledRecording> schedules =
mDataManager.getScheduledRecordings(series.getId());
List<ScheduledRecording> schedulesToRemove = new ArrayList<>();
@@ -365,20 +387,21 @@ public class DvrManager {
schedulesToRemove.add(deletedSchedule);
}
}
- mDataManager.removeScheduledRecording(true,
- ScheduledRecording.toArray(schedulesToRemove));
+ mDataManager.removeScheduledRecording(
+ true, ScheduledRecording.toArray(schedulesToRemove));
}
}
mDataManager.updateSeriesRecording(series);
- if (previousSeries == null
- || previousSeries.getPriority() != series.getPriority()) {
+ if (previousSeries == null || previousSeries.getPriority() != series.getPriority()) {
long priority = series.getPriority();
List<ScheduledRecording> schedulesToUpdate = new ArrayList<>();
- for (ScheduledRecording schedule
- : mDataManager.getScheduledRecordings(series.getId())) {
+ for (ScheduledRecording schedule :
+ mDataManager.getScheduledRecordings(series.getId())) {
if (schedule.isNotStarted() || schedule.isInProgress()) {
- schedulesToUpdate.add(ScheduledRecording.buildFrom(schedule)
- .setPriority(priority).build());
+ schedulesToUpdate.add(
+ ScheduledRecording.buildFrom(schedule)
+ .setPriority(priority)
+ .build());
}
}
if (!schedulesToUpdate.isEmpty()) {
@@ -411,28 +434,26 @@ public class DvrManager {
mDataManager.removeSeriesRecording(series);
}
- /**
- * Stops the currently recorded program
- */
+ /** Stops the currently recorded program */
public void stopRecording(final ScheduledRecording recording) {
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return;
}
synchronized (mListener) {
for (final Entry<Listener, Handler> entry : mListener.entrySet()) {
- entry.getValue().post(new Runnable() {
- @Override
- public void run() {
- entry.getKey().onStopRecordingRequested(recording);
- }
- });
+ entry.getValue()
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onStopRecordingRequested(recording);
+ }
+ });
}
}
}
- /**
- * Removes scheduled recordings or an existing recordings.
- */
+ /** Removes scheduled recordings or an existing recordings. */
public void removeScheduledRecording(ScheduledRecording... schedules) {
Log.i(TAG, "Removing " + Arrays.asList(schedules));
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
@@ -447,9 +468,7 @@ public class DvrManager {
}
}
- /**
- * Removes scheduled recordings without changing to the DELETED state.
- */
+ /** Removes scheduled recordings without changing to the DELETED state. */
public void forceRemoveScheduledRecording(ScheduledRecording... schedules) {
Log.i(TAG, "Force removing " + Arrays.asList(schedules));
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
@@ -464,9 +483,7 @@ public class DvrManager {
}
}
- /**
- * Removes the recorded program. It deletes the file if possible.
- */
+ /** Removes the recorded program. It deletes the file if possible. */
public void removeRecordedProgram(Uri recordedProgramUri) {
if (!SoftPreconditions.checkState(mDataManager.isInitialized())) {
return;
@@ -474,9 +491,7 @@ public class DvrManager {
removeRecordedProgram(ContentUris.parseId(recordedProgramUri));
}
- /**
- * Removes the recorded program. It deletes the file if possible.
- */
+ /** Removes the recorded program. It deletes the file if possible. */
public void removeRecordedProgram(long recordedProgramId) {
if (!SoftPreconditions.checkState(mDataManager.isInitialized())) {
return;
@@ -487,14 +502,12 @@ public class DvrManager {
}
}
- /**
- * Removes the recorded program. It deletes the file if possible.
- */
+ /** Removes the recorded program. It deletes the file if possible. */
public void removeRecordedProgram(final RecordedProgram recordedProgram) {
if (!SoftPreconditions.checkState(mDataManager.isInitialized())) {
return;
}
- new AsyncDbTask<Void, Void, Integer>() {
+ new AsyncDbTask<Void, Void, Integer>(mDbExecutor) {
@Override
protected Integer doInBackground(Void... params) {
ContentResolver resolver = mAppContext.getContentResolver();
@@ -526,7 +539,7 @@ public class DvrManager {
dbOperations.add(ContentProviderOperation.newDelete(r.getUri()).build());
}
}
- new AsyncDbTask<Void, Void, Boolean>() {
+ new AsyncDbTask<Void, Void, Boolean>(mDbExecutor) {
@Override
protected Boolean doInBackground(Void... params) {
ContentResolver resolver = mAppContext.getContentResolver();
@@ -556,9 +569,7 @@ public class DvrManager {
}.executeOnDbThread();
}
- /**
- * Updates the scheduled recording.
- */
+ /** Updates the scheduled recording. */
public void updateScheduledRecording(ScheduledRecording recording) {
if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
mDataManager.updateScheduledRecording(recording);
@@ -566,8 +577,8 @@ public class DvrManager {
}
/**
- * Returns priority ordered list of all scheduled recordings that will not be recorded if
- * this program is.
+ * Returns priority ordered list of all scheduled recordings that will not be recorded if this
+ * program is.
*
* @see DvrScheduleManager#getConflictingSchedules(Program)
*/
@@ -579,13 +590,13 @@ public class DvrManager {
}
/**
- * Returns priority ordered list of all scheduled recordings that will not be recorded if
- * this channel is.
+ * Returns priority ordered list of all scheduled recordings that will not be recorded if this
+ * channel is.
*
* @see DvrScheduleManager#getConflictingSchedules(long, long, long)
*/
- public List<ScheduledRecording> getConflictingSchedules(long channelId, long startTimeMs,
- long endTimeMs) {
+ public List<ScheduledRecording> getConflictingSchedules(
+ long channelId, long startTimeMs, long endTimeMs) {
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return Collections.emptyList();
}
@@ -595,8 +606,8 @@ public class DvrManager {
/**
* Checks if the schedule is conflicting.
*
- * <p>Note that the {@code schedule} should be the existing one. If not, this returns
- * {@code false}.
+ * <p>Note that the {@code schedule} should be the existing one. If not, this returns {@code
+ * false}.
*/
public boolean isConflicting(ScheduledRecording schedule) {
return schedule != null
@@ -605,8 +616,8 @@ public class DvrManager {
}
/**
- * Returns priority ordered list of all scheduled recording that will not be recorded if
- * this channel is tuned to.
+ * Returns priority ordered list of all scheduled recording that will not be recorded if this
+ * channel is tuned to.
*
* @see DvrScheduleManager#getConflictingSchedulesForTune
*/
@@ -617,22 +628,18 @@ public class DvrManager {
return mScheduleManager.getConflictingSchedulesForTune(channelId);
}
- /**
- * Sets the highest priority to the schedule.
- */
+ /** Sets the highest priority to the schedule. */
public void setHighestPriority(ScheduledRecording schedule) {
if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
long newPriority = mScheduleManager.suggestHighestPriority(schedule);
if (newPriority != schedule.getPriority()) {
- mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule)
- .setPriority(newPriority).build());
+ mDataManager.updateScheduledRecording(
+ ScheduledRecording.buildFrom(schedule).setPriority(newPriority).build());
}
}
}
- /**
- * Suggests the higher priority than the schedules which overlap with {@code schedule}.
- */
+ /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */
public long suggestHighestPriority(ScheduledRecording schedule) {
if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
return mScheduleManager.suggestHighestPriority(schedule);
@@ -642,9 +649,9 @@ public class DvrManager {
/**
* Returns {@code true} if the channel can be recorded.
- * <p>
- * Note that this method doesn't check the conflict of the schedule or available tuners.
- * This can be called from the UI before the schedules are loaded.
+ *
+ * <p>Note that this method doesn't check the conflict of the schedule or available tuners. This
+ * can be called from the UI before the schedules are loaded.
*/
public boolean isChannelRecordable(Channel channel) {
if (!mDataManager.isDvrScheduleLoadFinished() || channel == null) {
@@ -661,23 +668,27 @@ public class DvrManager {
if (!info.canRecord()) {
return false;
}
- Program program = TvApplication.getSingletons(mAppContext).getProgramDataManager()
- .getCurrentProgram(channel.getId());
+ Program program =
+ TvSingletons.getSingletons(mAppContext)
+ .getProgramDataManager()
+ .getCurrentProgram(channel.getId());
return program == null || !program.isRecordingProhibited();
}
/**
* Returns {@code true} if the program can be recorded.
- * <p>
- * Note that this method doesn't check the conflict of the schedule or available tuners.
- * This can be called from the UI before the schedules are loaded.
+ *
+ * <p>Note that this method doesn't check the conflict of the schedule or available tuners. This
+ * can be called from the UI before the schedules are loaded.
*/
public boolean isProgramRecordable(Program program) {
if (!mDataManager.isInitialized()) {
return false;
}
- Channel channel = TvApplication.getSingletons(mAppContext).getChannelDataManager()
- .getChannel(program.getChannelId());
+ Channel channel =
+ TvSingletons.getSingletons(mAppContext)
+ .getChannelDataManager()
+ .getChannel(program.getChannelId());
if (channel == null || channel.isRecordingProhibited()) {
return false;
}
@@ -691,8 +702,8 @@ public class DvrManager {
/**
* Returns the current recording for the channel.
- * <p>
- * This can be called from the UI before the schedules are loaded.
+ *
+ * <p>This can be called from the UI before the schedules are loaded.
*/
public ScheduledRecording getCurrentRecording(long channelId) {
if (!mDataManager.isDvrScheduleLoadFinished()) {
@@ -707,8 +718,8 @@ public class DvrManager {
}
/**
- * Returns schedules which is available (i.e., isNotStarted or isInProgress) and belongs to
- * the series recording {@code seriesRecordingId}.
+ * Returns schedules which is available (i.e., isNotStarted or isInProgress) and belongs to the
+ * series recording {@code seriesRecordingId}.
*/
public List<ScheduledRecording> getAvailableScheduledRecording(long seriesRecordingId) {
if (!mDataManager.isDvrScheduleLoadFinished()) {
@@ -723,9 +734,7 @@ public class DvrManager {
return schedules;
}
- /**
- * Returns the series recording related to the program.
- */
+ /** Returns the series recording related to the program. */
@Nullable
public SeriesRecording getSeriesRecording(Program program) {
if (!SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
@@ -735,8 +744,8 @@ public class DvrManager {
}
/**
- * Returns if there are valid items. Valid item contains {@link RecordedProgram},
- * available {@link ScheduledRecording} and {@link SeriesRecording}.
+ * Returns if there are valid items. Valid item contains {@link RecordedProgram}, available
+ * {@link ScheduledRecording} and {@link SeriesRecording}.
*/
public boolean hasValidItems() {
return !(mDataManager.getRecordedPrograms().isEmpty()
@@ -768,8 +777,8 @@ public class DvrManager {
* Returns ScheduledRecording.builder based on {@code program}. If program is already started,
* recording started time is clipped to the current time.
*/
- private ScheduledRecording.Builder createScheduledRecordingBuilder(String inputId,
- Program program) {
+ private ScheduledRecording.Builder createScheduledRecordingBuilder(
+ String inputId, Program program) {
ScheduledRecording.Builder builder = ScheduledRecording.builder(inputId, program);
long time = System.currentTimeMillis();
if (program.getStartTimeUtcMillis() < time && time < program.getEndTimeUtcMillis()) {
@@ -778,13 +787,13 @@ public class DvrManager {
return builder;
}
- /**
- * Returns a schedule which matches to the given episode.
- */
- public ScheduledRecording getScheduledRecording(String title, String seasonNumber,
- String episodeNumber) {
- if (!SoftPreconditions.checkState(mDataManager.isInitialized()) || title == null
- || seasonNumber == null || episodeNumber == null) {
+ /** Returns a schedule which matches to the given episode. */
+ public ScheduledRecording getScheduledRecording(
+ String title, String seasonNumber, String episodeNumber) {
+ if (!SoftPreconditions.checkState(mDataManager.isInitialized())
+ || title == null
+ || seasonNumber == null
+ || episodeNumber == null) {
return null;
}
for (ScheduledRecording r : mDataManager.getAllScheduledRecordings()) {
@@ -797,13 +806,13 @@ public class DvrManager {
return null;
}
- /**
- * Returns a recorded program which is the same episode as the given {@code program}.
- */
- public RecordedProgram getRecordedProgram(String title, String seasonNumber,
- String episodeNumber) {
- if (!SoftPreconditions.checkState(mDataManager.isInitialized()) || title == null
- || seasonNumber == null || episodeNumber == null) {
+ /** Returns a recorded program which is the same episode as the given {@code program}. */
+ public RecordedProgram getRecordedProgram(
+ String title, String seasonNumber, String episodeNumber) {
+ if (!SoftPreconditions.checkState(mDataManager.isInitialized())
+ || title == null
+ || seasonNumber == null
+ || episodeNumber == null) {
return null;
}
for (RecordedProgram r : mDataManager.getRecordedPrograms()) {
@@ -820,13 +829,14 @@ public class DvrManager {
@WorkerThread
private void removeRecordedData(Uri dataUri) {
try {
- if (dataUri != null && ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())
+ if (dataUri != null
+ && ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())
&& dataUri.getPath() != null) {
File recordedProgramPath = new File(dataUri.getPath());
if (!recordedProgramPath.exists()) {
if (DEBUG) Log.d(TAG, "File to delete not exist: " + recordedProgramPath);
} else {
- Utils.deleteDirOrFile(recordedProgramPath);
+ CommonUtils.deleteDirOrFile(recordedProgramPath);
if (DEBUG) {
Log.d(TAG, "Sucessfully deleted files of the recorded program: " + dataUri);
}
@@ -834,16 +844,19 @@ public class DvrManager {
}
} catch (SecurityException e) {
if (DEBUG) {
- Log.d(TAG, "To delete this recorded program, please manually delete video data at"
- + "\nadb shell rm -rf " + dataUri);
+ Log.d(
+ TAG,
+ "To delete this recorded program, please manually delete video data at"
+ + "\nadb shell rm -rf "
+ + dataUri);
}
}
}
/**
* Remove all the records related to the input.
- * <p>
- * Note that this should be called after the input was removed.
+ *
+ * <p>Note that this should be called after the input was removed.
*/
public void forgetStorage(String inputId) {
if (mDataManager.isInitialized()) {
diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java
index b72117aa..d5126b12 100644
--- a/src/com/android/tv/dvr/DvrScheduleManager.java
+++ b/src/com/android/tv/dvr/DvrScheduleManager.java
@@ -25,21 +25,18 @@ import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Range;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
-import com.android.tv.dvr.recorder.InputTaskScheduler;
-import com.android.tv.util.CompositeComparator;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.recorder.InputTaskScheduler;
+import com.android.tv.util.CompositeComparator;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -50,21 +47,16 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
-/**
- * A class to manage the schedules.
- */
+/** A class to manage the schedules. */
@TargetApi(Build.VERSION_CODES.N)
@MainThread
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
public class DvrScheduleManager {
private static final String TAG = "DvrScheduleManager";
- /**
- * The default priority of scheduled recording.
- */
+ /** The default priority of scheduled recording. */
public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
- /**
- * The default priority of series recording.
- */
+ /** The default priority of series recording. */
public static final long DEFAULT_SERIES_PRIORITY = DEFAULT_PRIORITY >> 1;
// The new priority will have the offset from the existing one.
private static final long PRIORITY_OFFSET = 1024;
@@ -102,9 +94,9 @@ public class DvrScheduleManager {
public DvrScheduleManager(Context context) {
mContext = context;
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mDataManager = (DvrDataManagerImpl) appSingletons.getDvrDataManager();
- mChannelDataManager = appSingletons.getChannelDataManager();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mDataManager = (DvrDataManagerImpl) tvSingletons.getDvrDataManager();
+ mChannelDataManager = tvSingletons.getChannelDataManager();
if (mDataManager.isDvrScheduleLoadFinished() && mChannelDataManager.isDbLoadFinished()) {
buildData();
} else {
@@ -119,146 +111,151 @@ public class DvrScheduleManager {
}
});
}
- ScheduledRecordingListener scheduledRecordingListener = new ScheduledRecordingListener() {
- @Override
- public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
- if (!mInitialized) {
- return;
- }
- for (ScheduledRecording schedule : scheduledRecordings) {
- if (!schedule.isNotStarted() && !schedule.isInProgress()) {
- continue;
- }
- TvInputInfo input = Utils
- .getTvInputInfoForInputId(mContext, schedule.getInputId());
- if (!SoftPreconditions.checkArgument(input != null, TAG,
- "Input was removed for : " + schedule)) {
- // Input removed.
- mInputScheduleMap.remove(schedule.getInputId());
- mInputConflictInfoMap.remove(schedule.getInputId());
- continue;
- }
- String inputId = input.getId();
- List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
- if (schedules == null) {
- schedules = new ArrayList<>();
- mInputScheduleMap.put(inputId, schedules);
+ ScheduledRecordingListener scheduledRecordingListener =
+ new ScheduledRecordingListener() {
+ @Override
+ public void onScheduledRecordingAdded(
+ ScheduledRecording... scheduledRecordings) {
+ if (!mInitialized) {
+ return;
+ }
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ if (!schedule.isNotStarted() && !schedule.isInProgress()) {
+ continue;
+ }
+ TvInputInfo input =
+ Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
+ if (!SoftPreconditions.checkArgument(
+ input != null, TAG, "Input was removed for : %s", schedule)) {
+ // Input removed.
+ mInputScheduleMap.remove(schedule.getInputId());
+ mInputConflictInfoMap.remove(schedule.getInputId());
+ continue;
+ }
+ String inputId = input.getId();
+ List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
+ if (schedules == null) {
+ schedules = new ArrayList<>();
+ mInputScheduleMap.put(inputId, schedules);
+ }
+ schedules.add(schedule);
+ }
+ onSchedulesChanged();
+ notifyScheduledRecordingAdded(scheduledRecordings);
}
- schedules.add(schedule);
- }
- onSchedulesChanged();
- notifyScheduledRecordingAdded(scheduledRecordings);
- }
- @Override
- public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
- if (!mInitialized) {
- return;
- }
- for (ScheduledRecording schedule : scheduledRecordings) {
- TvInputInfo input = Utils
- .getTvInputInfoForInputId(mContext, schedule.getInputId());
- if (input == null) {
- // Input removed.
- mInputScheduleMap.remove(schedule.getInputId());
- mInputConflictInfoMap.remove(schedule.getInputId());
- continue;
- }
- String inputId = input.getId();
- List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
- if (schedules != null) {
- schedules.remove(schedule);
- if (schedules.isEmpty()) {
- mInputScheduleMap.remove(inputId);
+ @Override
+ public void onScheduledRecordingRemoved(
+ ScheduledRecording... scheduledRecordings) {
+ if (!mInitialized) {
+ return;
}
- }
- Map<Long, ConflictInfo> conflictInfo = mInputConflictInfoMap.get(inputId);
- if (conflictInfo != null) {
- conflictInfo.remove(schedule.getId());
- if (conflictInfo.isEmpty()) {
- mInputConflictInfoMap.remove(inputId);
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ TvInputInfo input =
+ Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
+ if (input == null) {
+ // Input removed.
+ mInputScheduleMap.remove(schedule.getInputId());
+ mInputConflictInfoMap.remove(schedule.getInputId());
+ continue;
+ }
+ String inputId = input.getId();
+ List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
+ if (schedules != null) {
+ schedules.remove(schedule);
+ if (schedules.isEmpty()) {
+ mInputScheduleMap.remove(inputId);
+ }
+ }
+ Map<Long, ConflictInfo> conflictInfo =
+ mInputConflictInfoMap.get(inputId);
+ if (conflictInfo != null) {
+ conflictInfo.remove(schedule.getId());
+ if (conflictInfo.isEmpty()) {
+ mInputConflictInfoMap.remove(inputId);
+ }
+ }
}
+ onSchedulesChanged();
+ notifyScheduledRecordingRemoved(scheduledRecordings);
}
- }
- onSchedulesChanged();
- notifyScheduledRecordingRemoved(scheduledRecordings);
- }
- @Override
- public void onScheduledRecordingStatusChanged(
- ScheduledRecording... scheduledRecordings) {
- if (!mInitialized) {
- return;
- }
- for (ScheduledRecording schedule : scheduledRecordings) {
- TvInputInfo input = Utils
- .getTvInputInfoForInputId(mContext, schedule.getInputId());
- if (!SoftPreconditions.checkArgument(input != null, TAG,
- "Input was removed for : " + schedule)) {
- // Input removed.
- mInputScheduleMap.remove(schedule.getInputId());
- mInputConflictInfoMap.remove(schedule.getInputId());
- continue;
- }
- String inputId = input.getId();
- List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
- if (schedules == null) {
- schedules = new ArrayList<>();
- mInputScheduleMap.put(inputId, schedules);
- }
- // Compare ID because ScheduledRecording.equals() doesn't work if the state
- // is changed.
- for (Iterator<ScheduledRecording> i = schedules.iterator(); i.hasNext(); ) {
- if (i.next().getId() == schedule.getId()) {
- i.remove();
- break;
+ @Override
+ public void onScheduledRecordingStatusChanged(
+ ScheduledRecording... scheduledRecordings) {
+ if (!mInitialized) {
+ return;
}
- }
- if (schedule.isNotStarted() || schedule.isInProgress()) {
- schedules.add(schedule);
- }
- if (schedules.isEmpty()) {
- mInputScheduleMap.remove(inputId);
- }
- // Update conflict list as well
- Map<Long, ConflictInfo> conflictInfo = mInputConflictInfoMap.get(inputId);
- if (conflictInfo != null) {
- ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId());
- if (oldConflictInfo != null) {
- oldConflictInfo.schedule = schedule;
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ TvInputInfo input =
+ Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
+ if (!SoftPreconditions.checkArgument(
+ input != null, TAG, "Input was removed for : %s", schedule)) {
+ // Input removed.
+ mInputScheduleMap.remove(schedule.getInputId());
+ mInputConflictInfoMap.remove(schedule.getInputId());
+ continue;
+ }
+ String inputId = input.getId();
+ List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
+ if (schedules == null) {
+ schedules = new ArrayList<>();
+ mInputScheduleMap.put(inputId, schedules);
+ }
+ // Compare ID because ScheduledRecording.equals() doesn't work if the
+ // state
+ // is changed.
+ for (Iterator<ScheduledRecording> i = schedules.iterator();
+ i.hasNext(); ) {
+ if (i.next().getId() == schedule.getId()) {
+ i.remove();
+ break;
+ }
+ }
+ if (schedule.isNotStarted() || schedule.isInProgress()) {
+ schedules.add(schedule);
+ }
+ if (schedules.isEmpty()) {
+ mInputScheduleMap.remove(inputId);
+ }
+ // Update conflict list as well
+ Map<Long, ConflictInfo> conflictInfo =
+ mInputConflictInfoMap.get(inputId);
+ if (conflictInfo != null) {
+ ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId());
+ if (oldConflictInfo != null) {
+ oldConflictInfo.schedule = schedule;
+ }
+ }
}
+ onSchedulesChanged();
+ notifyScheduledRecordingStatusChanged(scheduledRecordings);
}
- }
- onSchedulesChanged();
- notifyScheduledRecordingStatusChanged(scheduledRecordings);
- }
- };
+ };
mDataManager.addScheduledRecordingListener(scheduledRecordingListener);
- ChannelDataManager.Listener channelDataManagerListener = new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- if (mDataManager.isDvrScheduleLoadFinished() && !mInitialized) {
- buildData();
- }
- }
+ ChannelDataManager.Listener channelDataManagerListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ if (mDataManager.isDvrScheduleLoadFinished() && !mInitialized) {
+ buildData();
+ }
+ }
- @Override
- public void onChannelListUpdated() {
- if (mDataManager.isDvrScheduleLoadFinished()) {
- buildData();
- }
- }
+ @Override
+ public void onChannelListUpdated() {
+ if (mDataManager.isDvrScheduleLoadFinished()) {
+ buildData();
+ }
+ }
- @Override
- public void onChannelBrowsableChanged() {
- }
- };
+ @Override
+ public void onChannelBrowsableChanged() {}
+ };
mChannelDataManager.addListener(channelDataManagerListener);
}
- /**
- * Returns the started recordings for the given input.
- */
+ /** Returns the started recordings for the given input. */
private List<ScheduledRecording> getStartedRecordings(String inputId) {
if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) {
return Collections.emptyList();
@@ -337,39 +334,29 @@ public class DvrScheduleManager {
}
}
- /**
- * Returns {@code true} if this class has been initialized.
- */
+ /** Returns {@code true} if this class has been initialized. */
public boolean isInitialized() {
return mInitialized;
}
- /**
- * Adds a {@link ScheduledRecordingListener}.
- */
+ /** Adds a {@link ScheduledRecordingListener}. */
public final void addScheduledRecordingListener(ScheduledRecordingListener listener) {
mScheduledRecordingListeners.add(listener);
}
- /**
- * Removes a {@link ScheduledRecordingListener}.
- */
+ /** Removes a {@link ScheduledRecordingListener}. */
public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) {
mScheduledRecordingListeners.remove(listener);
}
- /**
- * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener.
- */
+ /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */
private void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
l.onScheduledRecordingAdded(scheduledRecordings);
}
}
- /**
- * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener.
- */
+ /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */
private void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
l.onScheduledRecordingRemoved(scheduledRecordings);
@@ -385,48 +372,36 @@ public class DvrScheduleManager {
}
}
- /**
- * Adds a {@link OnInitializeListener}.
- */
+ /** Adds a {@link OnInitializeListener}. */
public final void addOnInitializeListener(OnInitializeListener listener) {
mOnInitializeListeners.add(listener);
}
- /**
- * Removes a {@link OnInitializeListener}.
- */
+ /** Removes a {@link OnInitializeListener}. */
public final void removeOnInitializeListener(OnInitializeListener listener) {
mOnInitializeListeners.remove(listener);
}
- /**
- * Calls {@link OnInitializeListener#onInitialize} for each listener.
- */
+ /** Calls {@link OnInitializeListener#onInitialize} for each listener. */
private void notifyInitialize() {
for (OnInitializeListener l : mOnInitializeListeners) {
l.onInitialize();
}
}
- /**
- * Adds a {@link OnConflictStateChangeListener}.
- */
+ /** Adds a {@link OnConflictStateChangeListener}. */
public final void addOnConflictStateChangeListener(OnConflictStateChangeListener listener) {
mOnConflictStateChangeListeners.add(listener);
}
- /**
- * Removes a {@link OnConflictStateChangeListener}.
- */
+ /** Removes a {@link OnConflictStateChangeListener}. */
public final void removeOnConflictStateChangeListener(OnConflictStateChangeListener listener) {
mOnConflictStateChangeListeners.remove(listener);
}
- /**
- * Calls {@link OnConflictStateChangeListener#onConflictStateChange} for each listener.
- */
- private void notifyConflictStateChange(boolean conflict,
- ScheduledRecording... scheduledRecordings) {
+ /** Calls {@link OnConflictStateChangeListener#onConflictStateChange} for each listener. */
+ private void notifyConflictStateChange(
+ boolean conflict, ScheduledRecording... scheduledRecordings) {
for (OnConflictStateChangeListener l : mOnConflictStateChangeListeners) {
l.onConflictStateChange(conflict, scheduledRecordings);
}
@@ -434,8 +409,8 @@ public class DvrScheduleManager {
/**
* Returns the priority for the program if it is recorded.
- * <p>
- * The recording will have the higher priority than the existing ones.
+ *
+ * <p>The recording will have the higher priority than the existing ones.
*/
public long suggestNewPriority() {
if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) {
@@ -454,9 +429,7 @@ public class DvrScheduleManager {
return highestPriority + PRIORITY_OFFSET;
}
- /**
- * Suggests the higher priority than the schedules which overlap with {@code schedule}.
- */
+ /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */
public long suggestHighestPriority(ScheduledRecording schedule) {
List<ScheduledRecording> schedules = mInputScheduleMap.get(schedule.getInputId());
if (schedules == null) {
@@ -464,7 +437,8 @@ public class DvrScheduleManager {
}
long highestPriority = Long.MIN_VALUE;
for (ScheduledRecording r : schedules) {
- if (!r.equals(schedule) && r.isOverLapping(schedule)
+ if (!r.equals(schedule)
+ && r.isOverLapping(schedule)
&& r.getPriority() > highestPriority) {
highestPriority = r.getPriority();
}
@@ -475,9 +449,7 @@ public class DvrScheduleManager {
return highestPriority + PRIORITY_OFFSET;
}
- /**
- * Suggests the higher priority than the schedules which overlap with {@code schedule}.
- */
+ /** Suggests the higher priority than the schedules which overlap with {@code schedule}. */
public long suggestHighestPriority(String inputId, Range<Long> peroid, long basePriority) {
List<ScheduledRecording> schedules = mInputScheduleMap.get(inputId);
if (schedules == null) {
@@ -497,8 +469,8 @@ public class DvrScheduleManager {
/**
* Returns the priority for a series recording.
- * <p>
- * The recording will have the higher priority than the existing series.
+ *
+ * <p>The recording will have the higher priority than the existing series.
*/
public long suggestNewSeriesPriority() {
if (!SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet")) {
@@ -510,7 +482,7 @@ public class DvrScheduleManager {
/**
* Returns the priority for a series recording by order of series recording priority.
*
- * Higher order will have higher priority.
+ * <p>Higher order will have higher priority.
*/
public static long suggestSeriesPriority(int order) {
return DEFAULT_SERIES_PRIORITY + order * PRIORITY_OFFSET;
@@ -527,21 +499,23 @@ public class DvrScheduleManager {
}
/**
- * Returns a sorted list of all scheduled recordings that will not be recorded if
- * this program is going to be recorded, with their priorities in decending order.
- * <p>
- * An empty list means there is no conflicts. If there is conflict, a priority higher than
+ * Returns a sorted list of all scheduled recordings that will not be recorded if this program
+ * is going to be recorded, with their priorities in decending order.
+ *
+ * <p>An empty list means there is no conflicts. If there is conflict, a priority higher than
* the first recording in the returned list should be assigned to the new schedule of this
* program to guarantee the program would be completely recorded.
*/
public List<ScheduledRecording> getConflictingSchedules(Program program) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
- SoftPreconditions.checkState(Program.isValid(program), TAG,
- "Program is invalid: " + program);
SoftPreconditions.checkState(
- program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(), TAG,
+ Program.isProgramValid(program), TAG, "Program is invalid: " + program);
+ SoftPreconditions.checkState(
+ program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(),
+ TAG,
"Program duration is empty: " + program);
- if (!mInitialized || !Program.isValid(program)
+ if (!mInitialized
+ || !Program.isProgramValid(program)
|| program.getStartTimeUtcMillis() >= program.getEndTimeUtcMillis()) {
return Collections.emptyList();
}
@@ -549,17 +523,19 @@ public class DvrScheduleManager {
if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
return Collections.emptyList();
}
- return getConflictingSchedules(input, Collections.singletonList(
- ScheduledRecording.builder(input.getId(), program)
- .setPriority(suggestHighestPriority())
- .build()));
+ return getConflictingSchedules(
+ input,
+ Collections.singletonList(
+ ScheduledRecording.builder(input.getId(), program)
+ .setPriority(suggestHighestPriority())
+ .build()));
}
/**
* Returns list of all conflicting scheduled recordings for the given {@code seriesRecording}
* recording.
- * <p>
- * Any empty list means there is no conflicts.
+ *
+ * <p>Any empty list means there is no conflicts.
*/
public List<ScheduledRecording> getConflictingSchedules(SeriesRecording seriesRecording) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
@@ -571,8 +547,8 @@ public class DvrScheduleManager {
if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
return Collections.emptyList();
}
- List<ScheduledRecording> scheduledRecordingForSeries = mDataManager.getScheduledRecordings(
- seriesRecording.getId());
+ List<ScheduledRecording> scheduledRecordingForSeries =
+ mDataManager.getScheduledRecordings(seriesRecording.getId());
List<ScheduledRecording> availableScheduledRecordingForSeries = new ArrayList<>();
for (ScheduledRecording scheduledRecording : scheduledRecordingForSeries) {
if (scheduledRecording.isNotStarted() || scheduledRecording.isInProgress()) {
@@ -586,15 +562,15 @@ public class DvrScheduleManager {
}
/**
- * Returns a sorted list of all scheduled recordings that will not be recorded if
- * this channel is going to be recorded, with their priority in decending order.
- * <p>
- * An empty list means there is no conflicts. If there is conflict, a priority higher than
+ * Returns a sorted list of all scheduled recordings that will not be recorded if this channel
+ * is going to be recorded, with their priority in decending order.
+ *
+ * <p>An empty list means there is no conflicts. If there is conflict, a priority higher than
* the first recording in the returned list should be assigned to the new schedule of this
* channel to guarantee the channel would be completely recorded in the designated time range.
*/
- public List<ScheduledRecording> getConflictingSchedules(long channelId, long startTimeMs,
- long endTimeMs) {
+ public List<ScheduledRecording> getConflictingSchedules(
+ long channelId, long startTimeMs, long endTimeMs) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID");
SoftPreconditions.checkState(startTimeMs < endTimeMs, TAG, "Recording duration is empty.");
@@ -605,10 +581,12 @@ public class DvrScheduleManager {
if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
return Collections.emptyList();
}
- return getConflictingSchedules(input, Collections.singletonList(
- ScheduledRecording.builder(input.getId(), channelId, startTimeMs, endTimeMs)
- .setPriority(suggestHighestPriority())
- .build()));
+ return getConflictingSchedules(
+ input,
+ Collections.singletonList(
+ ScheduledRecording.builder(input.getId(), channelId, startTimeMs, endTimeMs)
+ .setPriority(suggestHighestPriority())
+ .build()));
}
/**
@@ -633,14 +611,14 @@ public class DvrScheduleManager {
/**
* Checks if the schedule is conflicting.
*
- * <p>Note that the {@code schedule} should be the existing one. If not, this returns
- * {@code false}.
+ * <p>Note that the {@code schedule} should be the existing one. If not, this returns {@code
+ * false}.
*/
public boolean isConflicting(ScheduledRecording schedule) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
- SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : "
- + schedule.getChannelId());
+ SoftPreconditions.checkState(
+ input != null, TAG, "Can't find input for channel ID : " + schedule.getChannelId());
if (!mInitialized || input == null) {
return false;
}
@@ -651,15 +629,15 @@ public class DvrScheduleManager {
/**
* Checks if the schedule is partially conflicting, i.e., part of the scheduled program might be
* recorded even if the priority of the schedule is not raised.
- * <p>
- * If the given schedule is not conflicting or is totally conflicting, i.e., cannot be recorded
- * at all, this method returns {@code false} in both cases.
+ *
+ * <p>If the given schedule is not conflicting or is totally conflicting, i.e., cannot be
+ * recorded at all, this method returns {@code false} in both cases.
*/
public boolean isPartiallyConflicting(@NonNull ScheduledRecording schedule) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
- SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID : "
- + schedule.getChannelId());
+ SoftPreconditions.checkState(
+ input != null, TAG, "Can't find input for channel ID : " + schedule.getChannelId());
if (!mInitialized || input == null) {
return false;
}
@@ -672,27 +650,35 @@ public class DvrScheduleManager {
}
/**
- * Returns priority ordered list of all scheduled recordings that will not be recorded if
- * this channel is tuned to.
+ * Returns priority ordered list of all scheduled recordings that will not be recorded if this
+ * channel is tuned to.
*/
public List<ScheduledRecording> getConflictingSchedulesForTune(long channelId) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID");
TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId);
- SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID: "
- + channelId);
+ SoftPreconditions.checkState(
+ input != null, TAG, "Can't find input for channel ID: " + channelId);
if (!mInitialized || channelId == Channel.INVALID_ID || input == null) {
return Collections.emptyList();
}
- return getConflictingSchedulesForTune(input.getId(), channelId, System.currentTimeMillis(),
- suggestHighestPriority(), getStartedRecordings(input.getId()),
+ return getConflictingSchedulesForTune(
+ input.getId(),
+ channelId,
+ System.currentTimeMillis(),
+ suggestHighestPriority(),
+ getStartedRecordings(input.getId()),
input.getTunerCount());
}
@VisibleForTesting
- public static List<ScheduledRecording> getConflictingSchedulesForTune(String inputId,
- long channelId, long currentTimeMs, long newPriority,
- List<ScheduledRecording> startedRecordings, int tunerCount) {
+ public static List<ScheduledRecording> getConflictingSchedulesForTune(
+ String inputId,
+ long channelId,
+ long currentTimeMs,
+ long newPriority,
+ List<ScheduledRecording> startedRecordings,
+ int tunerCount) {
boolean channelFound = false;
for (ScheduledRecording schedule : startedRecordings) {
if (schedule.getChannelId() == channelId) {
@@ -704,10 +690,10 @@ public class DvrScheduleManager {
if (!channelFound) {
// The current channel is not being recorded.
schedules = new ArrayList<>(startedRecordings);
- schedules.add(ScheduledRecording
- .builder(inputId, channelId, currentTimeMs, currentTimeMs + 1)
- .setPriority(newPriority)
- .build());
+ schedules.add(
+ ScheduledRecording.builder(inputId, channelId, currentTimeMs, currentTimeMs + 1)
+ .setPriority(newPriority)
+ .build());
} else {
schedules = startedRecordings;
}
@@ -715,17 +701,17 @@ public class DvrScheduleManager {
}
/**
- * Returns priority ordered list of all scheduled recordings that will not be recorded if
- * the user keeps watching this channel.
- * <p>
- * Note that if the user keeps watching the channel, the channel can be recorded.
+ * Returns priority ordered list of all scheduled recordings that will not be recorded if the
+ * user keeps watching this channel.
+ *
+ * <p>Note that if the user keeps watching the channel, the channel can be recorded.
*/
public List<ScheduledRecording> getConflictingSchedulesForWatching(long channelId) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
SoftPreconditions.checkState(channelId != Channel.INVALID_ID, TAG, "Invalid channel ID");
TvInputInfo input = Utils.getTvInputInfoForChannelId(mContext, channelId);
- SoftPreconditions.checkState(input != null, TAG, "Can't find input for channel ID: "
- + channelId);
+ SoftPreconditions.checkState(
+ input != null, TAG, "Can't find input for channel ID: " + channelId);
if (!mInitialized || channelId == Channel.INVALID_ID || input == null) {
return Collections.emptyList();
}
@@ -733,12 +719,17 @@ public class DvrScheduleManager {
if (schedules == null || schedules.isEmpty()) {
return Collections.emptyList();
}
- return getConflictingSchedulesForWatching(input.getId(), channelId,
- System.currentTimeMillis(), suggestNewPriority(), schedules, input.getTunerCount());
+ return getConflictingSchedulesForWatching(
+ input.getId(),
+ channelId,
+ System.currentTimeMillis(),
+ suggestNewPriority(),
+ schedules,
+ input.getTunerCount());
}
- private List<ScheduledRecording> getConflictingSchedules(TvInputInfo input,
- List<ScheduledRecording> schedulesToAdd) {
+ private List<ScheduledRecording> getConflictingSchedules(
+ TvInputInfo input, List<ScheduledRecording> schedulesToAdd) {
SoftPreconditions.checkNotNull(input);
if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
return Collections.emptyList();
@@ -751,9 +742,13 @@ public class DvrScheduleManager {
}
@VisibleForTesting
- static List<ScheduledRecording> getConflictingSchedulesForWatching(String inputId,
- long channelId, long currentTimeMs, long newPriority,
- @NonNull List<ScheduledRecording> schedules, int tunerCount) {
+ static List<ScheduledRecording> getConflictingSchedulesForWatching(
+ String inputId,
+ long channelId,
+ long currentTimeMs,
+ long newPriority,
+ @NonNull List<ScheduledRecording> schedules,
+ int tunerCount) {
List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules);
List<ScheduledRecording> schedulesSameChannel = new ArrayList<>();
for (ScheduledRecording schedule : schedules) {
@@ -763,10 +758,10 @@ public class DvrScheduleManager {
}
}
// Assume that the user will watch the current channel forever.
- schedulesToCheck.add(ScheduledRecording
- .builder(inputId, channelId, currentTimeMs, Long.MAX_VALUE)
- .setPriority(newPriority)
- .build());
+ schedulesToCheck.add(
+ ScheduledRecording.builder(inputId, channelId, currentTimeMs, Long.MAX_VALUE)
+ .setPriority(newPriority)
+ .build());
List<ScheduledRecording> result = new ArrayList<>();
result.addAll(getConflictingSchedules(schedulesSameChannel, 1));
result.addAll(getConflictingSchedules(schedulesToCheck, tunerCount));
@@ -775,8 +770,10 @@ public class DvrScheduleManager {
}
@VisibleForTesting
- static List<ScheduledRecording> getConflictingSchedules(List<ScheduledRecording> schedulesToAdd,
- List<ScheduledRecording> currentSchedules, int tunerCount) {
+ static List<ScheduledRecording> getConflictingSchedules(
+ List<ScheduledRecording> schedulesToAdd,
+ List<ScheduledRecording> currentSchedules,
+ int tunerCount) {
List<ScheduledRecording> schedulesToCheck = new ArrayList<>(currentSchedules);
// When the duplicate schedule is to be added, remove the current duplicate recording.
for (Iterator<ScheduledRecording> iter = schedulesToCheck.iterator(); iter.hasNext(); ) {
@@ -805,9 +802,7 @@ public class DvrScheduleManager {
return getConflictingSchedules(schedulesToCheck, tunerCount, ranges);
}
- /**
- * Returns all conflicting scheduled recordings for the given schedules and count of tuner.
- */
+ /** Returns all conflicting scheduled recordings for the given schedules and count of tuner. */
public static List<ScheduledRecording> getConflictingSchedules(
List<ScheduledRecording> schedules, int tunerCount) {
return getConflictingSchedules(schedules, tunerCount, null);
@@ -825,21 +820,21 @@ public class DvrScheduleManager {
}
@VisibleForTesting
- static List<ConflictInfo> getConflictingSchedulesInfo(List<ScheduledRecording> schedules,
- int tunerCount) {
+ static List<ConflictInfo> getConflictingSchedulesInfo(
+ List<ScheduledRecording> schedules, int tunerCount) {
return getConflictingSchedulesInfo(schedules, tunerCount, null);
}
/**
* This is the core method to calculate all the conflicting schedules (in given periods).
- * <p>
- * Note that this method will ignore duplicated schedules with a same hash code. (Please refer
- * to {@link ScheduledRecording#hashCode}.)
+ *
+ * <p>Note that this method will ignore duplicated schedules with a same hash code. (Please
+ * refer to {@link ScheduledRecording#hashCode}.)
*
* @return A {@link HashMap} from {@link ScheduledRecording} to {@link Boolean}. The boolean
- * value denotes if the scheduled recording is partially conflicting, i.e., is possible
- * to be partially recorded under the given schedules and tuner count {@code true},
- * or not {@code false}.
+ * value denotes if the scheduled recording is partially conflicting, i.e., is possible to
+ * be partially recorded under the given schedules and tuner count {@code true}, or not
+ * {@code false}.
*/
private static List<ConflictInfo> getConflictingSchedulesInfo(
List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
@@ -886,14 +881,19 @@ public class DvrScheduleManager {
if (earliestEndTime < schedule.getEndTimeMs()) {
// The schedule can starts when other recording ends even though it's
// clipped.
- ScheduledRecording modifiedSchedule = ScheduledRecording.buildFrom(schedule)
- .setStartTimeMs(earliestEndTime).build();
+ ScheduledRecording modifiedSchedule =
+ ScheduledRecording.buildFrom(schedule)
+ .setStartTimeMs(earliestEndTime)
+ .build();
ScheduledRecording originalSchedule =
modified2OriginalSchedules.getOrDefault(schedule, schedule);
modified2OriginalSchedules.put(modifiedSchedule, originalSchedule);
- int insertPosition = Collections.binarySearch(schedulesToCheck,
- modifiedSchedule,
- ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
+ int insertPosition =
+ Collections.binarySearch(
+ schedulesToCheck,
+ modifiedSchedule,
+ ScheduledRecording
+ .START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
if (insertPosition >= 0) {
schedulesToCheck.add(insertPosition, modifiedSchedule);
} else {
@@ -921,17 +921,19 @@ public class DvrScheduleManager {
}
}
List<ConflictInfo> result = new ArrayList<>(conflicts.values());
- Collections.sort(result, new Comparator<ConflictInfo>() {
- @Override
- public int compare(ConflictInfo lhs, ConflictInfo rhs) {
- return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule);
- }
- });
+ Collections.sort(
+ result,
+ new Comparator<ConflictInfo>() {
+ @Override
+ public int compare(ConflictInfo lhs, ConflictInfo rhs) {
+ return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule);
+ }
+ });
return result;
}
- private static void removeFinishedRecordings(List<ScheduledRecording> recordings,
- long currentTimeMs) {
+ private static void removeFinishedRecordings(
+ List<ScheduledRecording> recordings, long currentTimeMs) {
for (Iterator<ScheduledRecording> iter = recordings.iterator(); iter.hasNext(); ) {
if (iter.next().getEndTimeMs() <= currentTimeMs) {
iter.remove();
@@ -939,11 +941,9 @@ public class DvrScheduleManager {
}
}
- /**
- * @see InputTaskScheduler#getReplacableTask
- */
- private static ScheduledRecording findReplaceableRecording(List<ScheduledRecording> recordings,
- ScheduledRecording schedule) {
+ /** @see InputTaskScheduler#getReplacableTask */
+ private static ScheduledRecording findReplaceableRecording(
+ List<ScheduledRecording> recordings, ScheduledRecording schedule) {
// Returns the recording with the following priority.
// 1. The recording with the lowest priority is returned.
// 2. If the priorities are the same, the recording which finishes early is returned.
@@ -980,30 +980,24 @@ public class DvrScheduleManager {
}
}
- /**
- * A listener which is notified the initialization of schedule manager.
- */
+ /** A listener which is notified the initialization of schedule manager. */
public interface OnInitializeListener {
- /**
- * Called when the schedule manager has been initialized.
- */
+ /** Called when the schedule manager has been initialized. */
void onInitialize();
}
- /**
- * A listener which is notified the conflict state change of the schedules.
- */
+ /** A listener which is notified the conflict state change of the schedules. */
public interface OnConflictStateChangeListener {
/**
* Called when the conflicting schedules change.
- * <p>
- * Note that this can be called before
- * {@link ScheduledRecordingListener#onScheduledRecordingAdded} is called.
+ *
+ * <p>Note that this can be called before {@link
+ * ScheduledRecordingListener#onScheduledRecordingAdded} is called.
*
* @param conflict {@code true} if the {@code schedules} are the new conflicts, otherwise
- * {@code false}.
+ * {@code false}.
* @param schedules the schedules
*/
void onConflictStateChange(boolean conflict, ScheduledRecording... schedules);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java
index 2d41d732..ed8d6903 100644
--- a/src/com/android/tv/dvr/DvrStorageStatusManager.java
+++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,291 +11,55 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-
package com.android.tv.dvr;
-import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.OperationApplicationException;
import android.database.Cursor;
-import android.media.tv.TvContract;
import android.media.tv.TvInputInfo;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.Looper;
import android.os.RemoteException;
-import android.os.StatFs;
-import android.support.annotation.AnyThread;
-import android.support.annotation.IntDef;
-import android.support.annotation.WorkerThread;
+import android.support.media.tv.TvContractCompat;
import android.util.Log;
-
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.util.TvInputManagerHelper;
-import com.android.tv.util.Utils;
-
import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-/**
- * Signals DVR storage status change such as plugging/unplugging.
- */
-public class DvrStorageStatusManager {
+/** A class for extending TV app-specific function to {@link RecordingStorageStatusManager}. */
+public class DvrStorageStatusManager extends RecordingStorageStatusManager {
private static final String TAG = "DvrStorageStatusManager";
- private static final boolean DEBUG = false;
-
- /**
- * Minimum storage size to support DVR
- */
- public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB
- private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES
- = 10 * 1024 * 1024 * 1024L; // 10GB
- private static final String RECORDING_DATA_SUB_PATH = "/recording";
-
- private static final String[] PROJECTION = {
- TvContract.RecordedPrograms._ID,
- TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME,
- TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI
- };
- private final static int BATCH_OPERATION_COUNT = 100;
-
- @IntDef({STORAGE_STATUS_OK, STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL,
- STORAGE_STATUS_FREE_SPACE_INSUFFICIENT, STORAGE_STATUS_MISSING})
- @Retention(RetentionPolicy.SOURCE)
- public @interface StorageStatus {
- }
-
- /**
- * Current storage is OK to record a program.
- */
- public static final int STORAGE_STATUS_OK = 0;
-
- /**
- * Current storage's total capacity is smaller than DVR requirement.
- */
- public static final int STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL = 1;
-
- /**
- * Current storage's free space is insufficient to record programs.
- */
- public static final int STORAGE_STATUS_FREE_SPACE_INSUFFICIENT = 2;
-
- /**
- * Current storage is missing.
- */
- public static final int STORAGE_STATUS_MISSING = 3;
private final Context mContext;
- private final Set<OnStorageMountChangedListener> mOnStorageMountChangedListeners =
- new CopyOnWriteArraySet<>();
- private final boolean mRunningInMainProcess;
- private MountedStorageStatus mMountedStorageStatus;
- private boolean mStorageValid;
private CleanUpDbTask mCleanUpDbTask;
- private class MountedStorageStatus {
- private final boolean mStorageMounted;
- private final File mStorageMountedDir;
- private final long mStorageMountedCapacity;
-
- private MountedStorageStatus(boolean mounted, File mountedDir, long capacity) {
- mStorageMounted = mounted;
- mStorageMountedDir = mountedDir;
- mStorageMountedCapacity = capacity;
- }
-
- private boolean isValidForDvr() {
- return mStorageMounted && mStorageMountedCapacity >= MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof MountedStorageStatus)) {
- return false;
- }
- MountedStorageStatus status = (MountedStorageStatus) other;
- return mStorageMounted == status.mStorageMounted
- && Objects.equals(mStorageMountedDir, status.mStorageMountedDir)
- && mStorageMountedCapacity == status.mStorageMountedCapacity;
- }
- }
-
- public interface OnStorageMountChangedListener {
-
- /**
- * Listener for DVR storage status change.
- *
- * @param storageMounted {@code true} when DVR possible storage is mounted,
- * {@code false} otherwise.
- */
- void onStorageMountChanged(boolean storageMounted);
- }
-
- private final class StorageStatusBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- MountedStorageStatus result = getStorageStatusInternal();
- if (mMountedStorageStatus.equals(result)) {
- return;
- }
- mMountedStorageStatus = result;
- if (result.mStorageMounted && mRunningInMainProcess) {
- // Cleans up DB in LC process.
- // Tuner process is not always on.
- if (mCleanUpDbTask != null) {
- mCleanUpDbTask.cancel(true);
- }
- mCleanUpDbTask = new CleanUpDbTask();
- mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- boolean valid = result.isValidForDvr();
- if (valid == mStorageValid) {
- return;
- }
- mStorageValid = valid;
- for (OnStorageMountChangedListener l : mOnStorageMountChangedListeners) {
- l.onStorageMountChanged(valid);
- }
- }
- }
+ private static final String[] PROJECTION = {
+ TvContractCompat.RecordedPrograms._ID,
+ TvContractCompat.RecordedPrograms.COLUMN_PACKAGE_NAME,
+ TvContractCompat.RecordedPrograms.COLUMN_RECORDING_DATA_URI
+ };
+ private static final int BATCH_OPERATION_COUNT = 100;
- /**
- * Creates DvrStorageStatusManager.
- *
- * @param context {@link Context}
- */
- public DvrStorageStatusManager(final Context context, boolean runningInMainProcess) {
+ public DvrStorageStatusManager(Context context) {
+ super(context);
mContext = context;
- mRunningInMainProcess = runningInMainProcess;
- mMountedStorageStatus = getStorageStatusInternal();
- mStorageValid = mMountedStorageStatus.isValidForDvr();
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
- filter.addAction(Intent.ACTION_MEDIA_EJECT);
- filter.addAction(Intent.ACTION_MEDIA_REMOVED);
- filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
- filter.addDataScheme(ContentResolver.SCHEME_FILE);
- mContext.registerReceiver(new StorageStatusBroadcastReceiver(), filter);
}
- /**
- * Adds the listener for receiving storage status change.
- *
- * @param listener
- */
- public void addListener(OnStorageMountChangedListener listener) {
- mOnStorageMountChangedListeners.add(listener);
- }
-
- /**
- * Removes the current listener.
- */
- public void removeListener(OnStorageMountChangedListener listener) {
- mOnStorageMountChangedListeners.remove(listener);
- }
-
- /**
- * Returns true if a storage is mounted.
- */
- public boolean isStorageMounted() {
- return mMountedStorageStatus.mStorageMounted;
- }
-
- /**
- * Returns the path to DVR recording data directory.
- * This can take for a while sometimes.
- */
- @WorkerThread
- public File getRecordingRootDataDirectory() {
- SoftPreconditions.checkState(Looper.myLooper() != Looper.getMainLooper());
- if (mMountedStorageStatus.mStorageMountedDir == null) {
- return null;
- }
- File root = mContext.getExternalFilesDir(null);
- String rootPath;
- try {
- rootPath = root != null ? root.getCanonicalPath() : null;
- } catch (IOException | SecurityException e) {
- return null;
- }
- return rootPath == null ? null : new File(rootPath + RECORDING_DATA_SUB_PATH);
- }
-
- /**
- * Returns the current storage status for DVR recordings.
- *
- * @return {@link StorageStatus}
- */
- @AnyThread
- public @StorageStatus int getDvrStorageStatus() {
- MountedStorageStatus status = mMountedStorageStatus;
- if (status.mStorageMountedDir == null) {
- return STORAGE_STATUS_MISSING;
- }
- if (CommonFeatures.FORCE_RECORDING_UNTIL_NO_SPACE.isEnabled(mContext)) {
- return STORAGE_STATUS_OK;
- }
- if (status.mStorageMountedCapacity < MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES) {
- return STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL;
- }
- try {
- StatFs statFs = new StatFs(status.mStorageMountedDir.toString());
- if (statFs.getAvailableBytes() < MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES) {
- return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT;
- }
- } catch (IllegalArgumentException e) {
- // In rare cases, storage status change was not notified yet.
- SoftPreconditions.checkState(false);
- return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT;
- }
- return STORAGE_STATUS_OK;
- }
-
- /**
- * Returns whether the storage has sufficient storage.
- *
- * @return {@code true} when there is sufficient storage, {@code false} otherwise
- */
- public boolean isStorageSufficient() {
- return getDvrStorageStatus() == STORAGE_STATUS_OK;
- }
-
- private MountedStorageStatus getStorageStatusInternal() {
- boolean storageMounted =
- Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
- File storageMountedDir = storageMounted ? Environment.getExternalStorageDirectory() : null;
- storageMounted = storageMounted && storageMountedDir != null;
- long storageMountedCapacity = 0L;
- if (storageMounted) {
- try {
- StatFs statFs = new StatFs(storageMountedDir.toString());
- storageMountedCapacity = statFs.getTotalBytes();
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Storage mount status was changed.");
- storageMounted = false;
- storageMountedDir = null;
- }
+ @Override
+ protected void cleanUpDbIfNeeded() {
+ if (mCleanUpDbTask != null) {
+ mCleanUpDbTask.cancel(true);
}
- return new MountedStorageStatus(
- storageMounted, storageMountedDir, storageMountedCapacity);
+ mCleanUpDbTask = new CleanUpDbTask();
+ mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> {
@@ -307,26 +71,29 @@ public class DvrStorageStatusManager {
@Override
protected Boolean doInBackground(Void... params) {
- @DvrStorageStatusManager.StorageStatus int storageStatus = getDvrStorageStatus();
- if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
+ @StorageStatus int storageStatus = getDvrStorageStatus();
+ if (storageStatus == STORAGE_STATUS_MISSING) {
return null;
}
- if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
+ if (storageStatus == STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
return true;
}
List<ContentProviderOperation> ops = getDeleteOps();
if (ops == null || ops.isEmpty()) {
return null;
}
- Log.i(TAG, "New device storage mounted. # of recordings to be forgotten : "
- + ops.size());
- for (int i = 0 ; i < ops.size() && !isCancelled() ; i += BATCH_OPERATION_COUNT) {
- int toIndex = (i + BATCH_OPERATION_COUNT) > ops.size()
- ? ops.size() : (i + BATCH_OPERATION_COUNT);
+ Log.i(
+ TAG,
+ "New device storage mounted. # of recordings to be forgotten : " + ops.size());
+ for (int i = 0; i < ops.size() && !isCancelled(); i += BATCH_OPERATION_COUNT) {
+ int toIndex =
+ (i + BATCH_OPERATION_COUNT) > ops.size()
+ ? ops.size()
+ : (i + BATCH_OPERATION_COUNT);
ArrayList<ContentProviderOperation> batchOps =
new ArrayList<>(ops.subList(i, toIndex));
try {
- mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, batchOps);
+ mContext.getContentResolver().applyBatch(TvContractCompat.AUTHORITY, batchOps);
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Failed to clean up RecordedPrograms.", e);
}
@@ -337,16 +104,16 @@ public class DvrStorageStatusManager {
@Override
protected void onPostExecute(Boolean forgetStorage) {
if (forgetStorage != null && forgetStorage == true) {
- DvrManager dvrManager = TvApplication.getSingletons(mContext).getDvrManager();
+ DvrManager dvrManager = TvSingletons.getSingletons(mContext).getDvrManager();
TvInputManagerHelper tvInputManagerHelper =
- TvApplication.getSingletons(mContext).getTvInputManagerHelper();
+ TvSingletons.getSingletons(mContext).getTvInputManagerHelper();
List<TvInputInfo> tvInputInfoList =
tvInputManagerHelper.getTvInputInfos(true, false);
if (tvInputInfoList == null || tvInputInfoList.isEmpty()) {
return;
}
for (TvInputInfo info : tvInputInfoList) {
- if (Utils.isBundledInput(info.getId())) {
+ if (CommonUtils.isBundledInput(info.getId())) {
dvrManager.forgetStorage(info.getId());
}
}
@@ -359,16 +126,19 @@ public class DvrStorageStatusManager {
private List<ContentProviderOperation> getDeleteOps() {
List<ContentProviderOperation> ops = new ArrayList<>();
- try (Cursor c = mContentResolver.query(
- TvContract.RecordedPrograms.CONTENT_URI, PROJECTION, null, null, null)) {
+ try (Cursor c =
+ mContentResolver.query(
+ TvContractCompat.RecordedPrograms.CONTENT_URI,
+ PROJECTION,
+ null,
+ null,
+ null)) {
if (c == null) {
return null;
}
while (c.moveToNext()) {
- @DvrStorageStatusManager.StorageStatus int storageStatus =
- getDvrStorageStatus();
- if (isCancelled()
- || storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
+ @StorageStatus int storageStatus = getDvrStorageStatus();
+ if (isCancelled() || storageStatus == STORAGE_STATUS_MISSING) {
ops.clear();
break;
}
@@ -379,15 +149,19 @@ public class DvrStorageStatusManager {
continue;
}
Uri dataUri = Uri.parse(dataUriString);
- if (!Utils.isInBundledPackageSet(packageName)
- || dataUri == null || dataUri.getPath() == null
+ if (!CommonUtils.isInBundledPackageSet(packageName)
+ || dataUri == null
+ || dataUri.getPath() == null
|| !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) {
continue;
}
File recordedProgramDir = new File(dataUri.getPath());
if (!recordedProgramDir.exists()) {
- ops.add(ContentProviderOperation.newDelete(
- TvContract.buildRecordedProgramUri(Long.parseLong(id))).build());
+ ops.add(
+ ContentProviderOperation.newDelete(
+ TvContractCompat.buildRecordedProgramUri(
+ Long.parseLong(id)))
+ .build());
}
}
return ops;
diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
index da6ddb1a..8616962f 100644
--- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java
+++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
@@ -20,10 +20,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.media.tv.TvInputManager;
import android.support.annotation.IntDef;
-
-import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.dvr.data.RecordedProgram;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -33,8 +31,8 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
- * A class to manage DVR watched state.
- * It will remember and provides previous watched position of DVR playback.
+ * A class to manage DVR watched state. It will remember and provides previous watched position of
+ * DVR playback.
*/
public class DvrWatchedPositionManager {
private SharedPreferences mWatchedPositions;
@@ -49,55 +47,46 @@ public class DvrWatchedPositionManager {
@Retention(RetentionPolicy.SOURCE)
@IntDef({DVR_WATCHED_STATUS_NEW, DVR_WATCHED_STATUS_WATCHING, DVR_WATCHED_STATUS_WATCHED})
public @interface DvrWatchedStatus {}
- /**
- * The status indicates the recorded program has not been watched at all.
- */
+ /** The status indicates the recorded program has not been watched at all. */
public static final int DVR_WATCHED_STATUS_NEW = 0;
- /**
- * The status indicates the recorded program is being watched.
- */
+ /** The status indicates the recorded program is being watched. */
public static final int DVR_WATCHED_STATUS_WATCHING = 1;
- /**
- * The status indicates the recorded program was completely watched.
- */
+ /** The status indicates the recorded program was completely watched. */
public static final int DVR_WATCHED_STATUS_WATCHED = 2;
public DvrWatchedPositionManager(Context context) {
- mWatchedPositions = context.getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE);
+ mWatchedPositions =
+ context.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_DVR_WATCHED_POSITION,
+ Context.MODE_PRIVATE);
}
- /**
- * Sets the watched position of the give program.
- */
+ /** Sets the watched position of the give program. */
public void setWatchedPosition(long recordedProgramId, long positionMs) {
mWatchedPositions.edit().putLong(Long.toString(recordedProgramId), positionMs).apply();
notifyWatchedPositionChanged(recordedProgramId, positionMs);
}
- /**
- * Gets the watched position of the give program.
- */
+ /** Gets the watched position of the give program. */
public long getWatchedPosition(long recordedProgramId) {
- return mWatchedPositions.getLong(Long.toString(recordedProgramId),
- TvInputManager.TIME_SHIFT_INVALID_TIME);
+ return mWatchedPositions.getLong(
+ Long.toString(recordedProgramId), TvInputManager.TIME_SHIFT_INVALID_TIME);
}
- @DvrWatchedStatus public int getWatchedStatus(RecordedProgram recordedProgram) {
+ @DvrWatchedStatus
+ public int getWatchedStatus(RecordedProgram recordedProgram) {
long watchedPosition = getWatchedPosition(recordedProgram.getId());
if (watchedPosition == TvInputManager.TIME_SHIFT_INVALID_TIME) {
return DVR_WATCHED_STATUS_NEW;
- } else if (watchedPosition > recordedProgram
- .getDurationMillis() * DVR_WATCHED_THRESHOLD_RATE) {
+ } else if (watchedPosition
+ > recordedProgram.getDurationMillis() * DVR_WATCHED_THRESHOLD_RATE) {
return DVR_WATCHED_STATUS_WATCHED;
} else {
return DVR_WATCHED_STATUS_WATCHING;
}
}
- /**
- * Adds {@link WatchedPositionChangedListener}.
- */
+ /** Adds {@link WatchedPositionChangedListener}. */
public void addListener(WatchedPositionChangedListener listener, long recordedProgramId) {
if (recordedProgramId == RecordedProgram.ID_NOT_SET) {
return;
@@ -110,18 +99,14 @@ public class DvrWatchedPositionManager {
listenerSet.add(listener);
}
- /**
- * Removes {@link WatchedPositionChangedListener}.
- */
+ /** Removes {@link WatchedPositionChangedListener}. */
public void removeListener(WatchedPositionChangedListener listener) {
for (long recordedProgramId : new ArrayList<>(mListeners.keySet())) {
removeListener(listener, recordedProgramId);
}
}
- /**
- * Removes {@link WatchedPositionChangedListener}.
- */
+ /** Removes {@link WatchedPositionChangedListener}. */
public void removeListener(WatchedPositionChangedListener listener, long recordedProgramId) {
Set<WatchedPositionChangedListener> listenerSet = mListeners.get(recordedProgramId);
if (listenerSet == null) {
@@ -144,9 +129,7 @@ public class DvrWatchedPositionManager {
}
public interface WatchedPositionChangedListener {
- /**
- * Called when the watched position of some program is changed.
- */
+ /** Called when the watched position of some program is changed. */
void onWatchedPositionChanged(long recordedProgramId, long positionMs);
}
}
diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java
index 129ba153..1b505e80 100644
--- a/src/com/android/tv/dvr/WritableDvrDataManager.java
+++ b/src/com/android/tv/dvr/WritableDvrDataManager.java
@@ -17,7 +17,6 @@
package com.android.tv.dvr;
import android.support.annotation.MainThread;
-
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
import com.android.tv.dvr.data.SeriesRecording;
@@ -30,19 +29,13 @@ import com.android.tv.dvr.data.SeriesRecording;
*/
@MainThread
public interface WritableDvrDataManager extends DvrDataManager {
- /**
- * Adds new recordings.
- */
+ /** Adds new recordings. */
void addScheduledRecording(ScheduledRecording... scheduledRecordings);
- /**
- * Adds new series recordings.
- */
+ /** Adds new series recordings. */
void addSeriesRecording(SeriesRecording... seriesRecordings);
- /**
- * Removes recordings.
- */
+ /** Removes recordings. */
void removeScheduledRecording(ScheduledRecording... scheduledRecordings);
/**
@@ -51,30 +44,30 @@ public interface WritableDvrDataManager extends DvrDataManager {
*/
void removeScheduledRecording(boolean forceRemove, ScheduledRecording... scheduledRecordings);
- /**
- * Removes series recordings.
- */
+ /** Removes series recordings. */
void removeSeriesRecording(SeriesRecording... seasonSchedules);
- /**
- * Updates existing recordings.
- */
+ /** Updates existing recordings. */
void updateScheduledRecording(ScheduledRecording... scheduledRecordings);
- /**
- * Updates existing series recordings.
- */
+ /** Updates existing series recordings. */
void updateSeriesRecording(SeriesRecording... seriesRecordings);
+ /** Changes the state of the recording. */
+ void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState);
+
/**
* Changes the state of the recording.
+ *
+ * @param reason the reason of this change
*/
- void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState);
+ void changeState(
+ ScheduledRecording scheduledRecording, @RecordingState int newState, int reason);
/**
* Remove all the records related to the input.
- * <p>
- * Note that this should be called after the input was removed.
+ *
+ * <p>Note that this should be called after the input was removed.
*/
void forgetStorage(String inputId);
}
diff --git a/src/com/android/tv/dvr/data/IdGenerator.java b/src/com/android/tv/dvr/data/IdGenerator.java
index 2ade1dad..496651ba 100644
--- a/src/com/android/tv/dvr/data/IdGenerator.java
+++ b/src/com/android/tv/dvr/data/IdGenerator.java
@@ -18,32 +18,22 @@ package com.android.tv.dvr.data;
import java.util.concurrent.atomic.AtomicLong;
-/**
- * A class which generate the ID which increases sequentially.
- */
+/** A class which generate the ID which increases sequentially. */
public class IdGenerator {
- /**
- * ID generator for the scheduled recording.
- */
+ /** ID generator for the scheduled recording. */
public static final IdGenerator SCHEDULED_RECORDING = new IdGenerator();
- /**
- * ID generator for the series recording.
- */
+ /** ID generator for the series recording. */
public static final IdGenerator SERIES_RECORDING = new IdGenerator();
private final AtomicLong mMaxId = new AtomicLong(0);
- /**
- * Sets the new maximum ID.
- */
+ /** Sets the new maximum ID. */
public void setMaxId(long maxId) {
mMaxId.set(maxId);
}
- /**
- * Returns the new ID which is greater than the existing maximum ID by 1.
- */
+ /** Returns the new ID which is greater than the existing maximum ID by 1. */
public long newId() {
return mMaxId.incrementAndGet();
}
diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java
index 2e953a52..e1fbca8c 100644
--- a/src/com/android/tv/dvr/data/RecordedProgram.java
+++ b/src/com/android/tv/dvr/data/RecordedProgram.java
@@ -28,99 +28,97 @@ import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
import android.text.TextUtils;
-
import com.android.tv.common.R;
import com.android.tv.common.TvContentRatingCache;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.BaseProgram;
import com.android.tv.data.GenreItems;
import com.android.tv.data.InternalDataUtils;
-import com.android.tv.util.Utils;
-
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-/**
- * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}.
- */
+/** Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. */
@TargetApi(Build.VERSION_CODES.N)
public class RecordedProgram extends BaseProgram {
public static final int ID_NOT_SET = -1;
- public final static String[] PROJECTION = {
- // These are in exactly the order listed in RecordedPrograms
- RecordedPrograms._ID,
- RecordedPrograms.COLUMN_PACKAGE_NAME,
- RecordedPrograms.COLUMN_INPUT_ID,
- RecordedPrograms.COLUMN_CHANNEL_ID,
- RecordedPrograms.COLUMN_TITLE,
- RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
- RecordedPrograms.COLUMN_SEASON_TITLE,
- RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
- RecordedPrograms.COLUMN_EPISODE_TITLE,
- RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
- RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
- RecordedPrograms.COLUMN_BROADCAST_GENRE,
- RecordedPrograms.COLUMN_CANONICAL_GENRE,
- RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
- RecordedPrograms.COLUMN_LONG_DESCRIPTION,
- RecordedPrograms.COLUMN_VIDEO_WIDTH,
- RecordedPrograms.COLUMN_VIDEO_HEIGHT,
- RecordedPrograms.COLUMN_AUDIO_LANGUAGE,
- RecordedPrograms.COLUMN_CONTENT_RATING,
- RecordedPrograms.COLUMN_POSTER_ART_URI,
- RecordedPrograms.COLUMN_THUMBNAIL_URI,
- RecordedPrograms.COLUMN_SEARCHABLE,
- RecordedPrograms.COLUMN_RECORDING_DATA_URI,
- RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
- RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
- RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
- RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
- RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
- RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
- RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
- RecordedPrograms.COLUMN_VERSION_NUMBER,
- RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ public static final String[] PROJECTION = {
+ // These are in exactly the order listed in RecordedPrograms
+ RecordedPrograms._ID,
+ RecordedPrograms.COLUMN_PACKAGE_NAME,
+ RecordedPrograms.COLUMN_INPUT_ID,
+ RecordedPrograms.COLUMN_CHANNEL_ID,
+ RecordedPrograms.COLUMN_TITLE,
+ RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_SEASON_TITLE,
+ RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
+ RecordedPrograms.COLUMN_EPISODE_TITLE,
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_BROADCAST_GENRE,
+ RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
+ RecordedPrograms.COLUMN_LONG_DESCRIPTION,
+ RecordedPrograms.COLUMN_VIDEO_WIDTH,
+ RecordedPrograms.COLUMN_VIDEO_HEIGHT,
+ RecordedPrograms.COLUMN_AUDIO_LANGUAGE,
+ RecordedPrograms.COLUMN_CONTENT_RATING,
+ RecordedPrograms.COLUMN_POSTER_ART_URI,
+ RecordedPrograms.COLUMN_THUMBNAIL_URI,
+ RecordedPrograms.COLUMN_SEARCHABLE,
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI,
+ RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ RecordedPrograms.COLUMN_VERSION_NUMBER,
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
};
public static RecordedProgram fromCursor(Cursor cursor) {
int index = 0;
- Builder builder = builder()
- .setId(cursor.getLong(index++))
- .setPackageName(cursor.getString(index++))
- .setInputId(cursor.getString(index++))
- .setChannelId(cursor.getLong(index++))
- .setTitle(cursor.getString(index++))
- .setSeasonNumber(cursor.getString(index++))
- .setSeasonTitle(cursor.getString(index++))
- .setEpisodeNumber(cursor.getString(index++))
- .setEpisodeTitle(cursor.getString(index++))
- .setStartTimeUtcMillis(cursor.getLong(index++))
- .setEndTimeUtcMillis(cursor.getLong(index++))
- .setBroadcastGenres(cursor.getString(index++))
- .setCanonicalGenres(cursor.getString(index++))
- .setShortDescription(cursor.getString(index++))
- .setLongDescription(cursor.getString(index++))
- .setVideoWidth(cursor.getInt(index++))
- .setVideoHeight(cursor.getInt(index++))
- .setAudioLanguage(cursor.getString(index++))
- .setContentRatings(
- TvContentRatingCache.getInstance().getRatings(cursor.getString(index++)))
- .setPosterArtUri(cursor.getString(index++))
- .setThumbnailUri(cursor.getString(index++))
- .setSearchable(cursor.getInt(index++) == 1)
- .setDataUri(cursor.getString(index++))
- .setDataBytes(cursor.getLong(index++))
- .setDurationMillis(cursor.getLong(index++))
- .setExpireTimeUtcMillis(cursor.getLong(index++))
- .setInternalProviderFlag1(cursor.getInt(index++))
- .setInternalProviderFlag2(cursor.getInt(index++))
- .setInternalProviderFlag3(cursor.getInt(index++))
- .setInternalProviderFlag4(cursor.getInt(index++))
- .setVersionNumber(cursor.getInt(index++));
- if (Utils.isInBundledPackageSet(builder.mPackageName)) {
+ Builder builder =
+ builder()
+ .setId(cursor.getLong(index++))
+ .setPackageName(cursor.getString(index++))
+ .setInputId(cursor.getString(index++))
+ .setChannelId(cursor.getLong(index++))
+ .setTitle(cursor.getString(index++))
+ .setSeasonNumber(cursor.getString(index++))
+ .setSeasonTitle(cursor.getString(index++))
+ .setEpisodeNumber(cursor.getString(index++))
+ .setEpisodeTitle(cursor.getString(index++))
+ .setStartTimeUtcMillis(cursor.getLong(index++))
+ .setEndTimeUtcMillis(cursor.getLong(index++))
+ .setBroadcastGenres(cursor.getString(index++))
+ .setCanonicalGenres(cursor.getString(index++))
+ .setShortDescription(cursor.getString(index++))
+ .setLongDescription(cursor.getString(index++))
+ .setVideoWidth(cursor.getInt(index++))
+ .setVideoHeight(cursor.getInt(index++))
+ .setAudioLanguage(cursor.getString(index++))
+ .setContentRatings(
+ TvContentRatingCache.getInstance()
+ .getRatings(cursor.getString(index++)))
+ .setPosterArtUri(cursor.getString(index++))
+ .setThumbnailUri(cursor.getString(index++))
+ .setSearchable(cursor.getInt(index++) == 1)
+ .setDataUri(cursor.getString(index++))
+ .setDataBytes(cursor.getLong(index++))
+ .setDurationMillis(cursor.getLong(index++))
+ .setExpireTimeUtcMillis(cursor.getLong(index++))
+ .setInternalProviderFlag1(cursor.getInt(index++))
+ .setInternalProviderFlag2(cursor.getInt(index++))
+ .setInternalProviderFlag3(cursor.getInt(index++))
+ .setInternalProviderFlag4(cursor.getInt(index++))
+ .setVersionNumber(cursor.getInt(index++));
+ if (CommonUtils.isInBundledPackageSet(builder.mPackageName)) {
InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
}
return builder.build();
@@ -138,12 +136,14 @@ public class RecordedProgram extends BaseProgram {
values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle);
values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber);
values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle);
- values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
- recordedProgram.mStartTimeUtcMillis);
+ values.put(
+ RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, recordedProgram.mStartTimeUtcMillis);
values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis);
- values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE,
+ values.put(
+ RecordedPrograms.COLUMN_BROADCAST_GENRE,
safeEncode(recordedProgram.mBroadcastGenres));
- values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ values.put(
+ RecordedPrograms.COLUMN_CANONICAL_GENRE,
safeEncode(recordedProgram.mCanonicalGenres));
values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription);
values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription);
@@ -158,33 +158,40 @@ public class RecordedProgram extends BaseProgram {
values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight);
}
values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage);
- values.put(RecordedPrograms.COLUMN_CONTENT_RATING,
+ values.put(
+ RecordedPrograms.COLUMN_CONTENT_RATING,
TvContentRatingCache.contentRatingsToString(recordedProgram.mContentRatings));
values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri);
values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri);
values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0);
- values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI,
- safeToString(recordedProgram.mDataUri));
+ values.put(
+ RecordedPrograms.COLUMN_RECORDING_DATA_URI, safeToString(recordedProgram.mDataUri));
values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes);
- values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
- recordedProgram.mDurationMillis);
- values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
+ values.put(
+ RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, recordedProgram.mDurationMillis);
+ values.put(
+ RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
recordedProgram.mExpireTimeUtcMillis);
- values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ values.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
InternalDataUtils.serializeInternalProviderData(recordedProgram));
- values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
+ values.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
recordedProgram.mInternalProviderFlag1);
- values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+ values.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
recordedProgram.mInternalProviderFlag2);
- values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
+ values.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
recordedProgram.mInternalProviderFlag3);
- values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
+ values.put(
+ RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
recordedProgram.mInternalProviderFlag4);
values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber);
return values;
}
- public static class Builder{
+ public static class Builder {
private long mId = ID_NOT_SET;
private String mPackageName;
private String mInputId;
@@ -414,18 +421,45 @@ public class RecordedProgram extends BaseProgram {
// If series ID is not set, generate it for the episodic program of other TV input.
setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle));
}
- return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId,
- mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis,
- mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription,
- mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRatings,
- mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes,
- mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1,
- mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4,
+ return new RecordedProgram(
+ mId,
+ mPackageName,
+ mInputId,
+ mChannelId,
+ mTitle,
+ mSeriesId,
+ mSeasonNumber,
+ mSeasonTitle,
+ mEpisodeNumber,
+ mEpisodeTitle,
+ mStartTimeUtcMillis,
+ mEndTimeUtcMillis,
+ mBroadcastGenres,
+ mCanonicalGenres,
+ mShortDescription,
+ mLongDescription,
+ mVideoWidth,
+ mVideoHeight,
+ mAudioLanguage,
+ mContentRatings,
+ mPosterArtUri,
+ mThumbnailUri,
+ mSearchable,
+ mDataUri,
+ mDataBytes,
+ mDurationMillis,
+ mExpireTimeUtcMillis,
+ mInternalProviderFlag1,
+ mInternalProviderFlag2,
+ mInternalProviderFlag3,
+ mInternalProviderFlag4,
mVersionNumber);
}
}
- public static Builder builder() { return new Builder(); }
+ public static Builder builder() {
+ return new Builder();
+ }
public static Builder buildFrom(RecordedProgram orig) {
return builder()
@@ -470,7 +504,7 @@ public class RecordedProgram extends BaseProgram {
}
return Long.compare(lhs.mId, rhs.mId);
}
- };
+ };
private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
@@ -507,15 +541,38 @@ public class RecordedProgram extends BaseProgram {
private final int mInternalProviderFlag4;
private final int mVersionNumber;
- private RecordedProgram(long id, String packageName, String inputId, long channelId,
- String title, String seriesId, String seasonNumber, String seasonTitle,
- String episodeNumber, String episodeTitle, long startTimeUtcMillis,
- long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres,
- String shortDescription, String longDescription, int videoWidth, int videoHeight,
- String audioLanguage, TvContentRating[] contentRatings, String posterArtUri,
- String thumbnailUri, boolean searchable, Uri dataUri, long dataBytes,
- long durationMillis, long expireTimeUtcMillis, int internalProviderFlag1,
- int internalProviderFlag2, int internalProviderFlag3, int internalProviderFlag4,
+ private RecordedProgram(
+ long id,
+ String packageName,
+ String inputId,
+ long channelId,
+ String title,
+ String seriesId,
+ String seasonNumber,
+ String seasonTitle,
+ String episodeNumber,
+ String episodeTitle,
+ long startTimeUtcMillis,
+ long endTimeUtcMillis,
+ String[] broadcastGenres,
+ String[] canonicalGenres,
+ String shortDescription,
+ String longDescription,
+ int videoWidth,
+ int videoHeight,
+ String audioLanguage,
+ TvContentRating[] contentRatings,
+ String posterArtUri,
+ String thumbnailUri,
+ boolean searchable,
+ Uri dataUri,
+ long dataBytes,
+ long durationMillis,
+ long expireTimeUtcMillis,
+ int internalProviderFlag1,
+ int internalProviderFlag2,
+ int internalProviderFlag3,
+ int internalProviderFlag4,
int versionNumber) {
mId = id;
mPackageName = packageName;
@@ -564,9 +621,7 @@ public class RecordedProgram extends BaseProgram {
return mCanonicalGenres;
}
- /**
- * Returns array of canonical genre ID's for this recorded program.
- */
+ /** Returns array of canonical genre ID's for this recorded program. */
@Override
public int[] getCanonicalGenreIds() {
if (mCanonicalGenres == null) {
@@ -623,11 +678,15 @@ public class RecordedProgram extends BaseProgram {
if (!TextUtils.isEmpty(mEpisodeNumber)) {
if (TextUtils.equals(mSeasonNumber, "0")) {
// Do not show "S0: ".
- return String.format(context.getResources().getString(
- R.string.display_episode_number_format_no_season_number), mEpisodeNumber);
+ return String.format(
+ context.getResources()
+ .getString(R.string.display_episode_number_format_no_season_number),
+ mEpisodeNumber);
} else {
- return String.format(context.getResources().getString(
- R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber);
+ return String.format(
+ context.getResources().getString(R.string.display_episode_number_format),
+ mSeasonNumber,
+ mEpisodeNumber);
}
}
return null;
@@ -734,9 +793,7 @@ public class RecordedProgram extends BaseProgram {
return mVideoWidth;
}
- /**
- * Checks whether the recording has been clipped or not.
- */
+ /** Checks whether the recording has been clipped or not. */
public boolean isClipped() {
return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS;
}
@@ -746,40 +803,38 @@ public class RecordedProgram extends BaseProgram {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecordedProgram that = (RecordedProgram) o;
- return Objects.equals(mId, that.mId) &&
- Objects.equals(mChannelId, that.mChannelId) &&
- Objects.equals(mSeriesId, that.mSeriesId) &&
- Objects.equals(mSeasonNumber, that.mSeasonNumber) &&
- Objects.equals(mSeasonTitle, that.mSeasonTitle) &&
- Objects.equals(mEpisodeNumber, that.mEpisodeNumber) &&
- Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) &&
- Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) &&
- Objects.equals(mVideoWidth, that.mVideoWidth) &&
- Objects.equals(mVideoHeight, that.mVideoHeight) &&
- Objects.equals(mSearchable, that.mSearchable) &&
- Objects.equals(mDataBytes, that.mDataBytes) &&
- Objects.equals(mDurationMillis, that.mDurationMillis) &&
- Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) &&
- Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) &&
- Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) &&
- Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) &&
- Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) &&
- Objects.equals(mVersionNumber, that.mVersionNumber) &&
- Objects.equals(mTitle, that.mTitle) &&
- Objects.equals(mEpisodeTitle, that.mEpisodeTitle) &&
- Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) &&
- Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) &&
- Objects.equals(mShortDescription, that.mShortDescription) &&
- Objects.equals(mLongDescription, that.mLongDescription) &&
- Objects.equals(mAudioLanguage, that.mAudioLanguage) &&
- Arrays.equals(mContentRatings, that.mContentRatings) &&
- Objects.equals(mPosterArtUri, that.mPosterArtUri) &&
- Objects.equals(mThumbnailUri, that.mThumbnailUri);
- }
-
- /**
- * Hashes based on the ID.
- */
+ return Objects.equals(mId, that.mId)
+ && Objects.equals(mChannelId, that.mChannelId)
+ && Objects.equals(mSeriesId, that.mSeriesId)
+ && Objects.equals(mSeasonNumber, that.mSeasonNumber)
+ && Objects.equals(mSeasonTitle, that.mSeasonTitle)
+ && Objects.equals(mEpisodeNumber, that.mEpisodeNumber)
+ && Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis)
+ && Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis)
+ && Objects.equals(mVideoWidth, that.mVideoWidth)
+ && Objects.equals(mVideoHeight, that.mVideoHeight)
+ && Objects.equals(mSearchable, that.mSearchable)
+ && Objects.equals(mDataBytes, that.mDataBytes)
+ && Objects.equals(mDurationMillis, that.mDurationMillis)
+ && Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis)
+ && Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1)
+ && Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2)
+ && Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3)
+ && Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4)
+ && Objects.equals(mVersionNumber, that.mVersionNumber)
+ && Objects.equals(mTitle, that.mTitle)
+ && Objects.equals(mEpisodeTitle, that.mEpisodeTitle)
+ && Arrays.equals(mBroadcastGenres, that.mBroadcastGenres)
+ && Arrays.equals(mCanonicalGenres, that.mCanonicalGenres)
+ && Objects.equals(mShortDescription, that.mShortDescription)
+ && Objects.equals(mLongDescription, that.mLongDescription)
+ && Objects.equals(mAudioLanguage, that.mAudioLanguage)
+ && Arrays.equals(mContentRatings, that.mContentRatings)
+ && Objects.equals(mPosterArtUri, that.mPosterArtUri)
+ && Objects.equals(mThumbnailUri, that.mThumbnailUri);
+ }
+
+ /** Hashes based on the ID. */
@Override
public int hashCode() {
return Objects.hash(mId);
@@ -788,42 +843,80 @@ public class RecordedProgram extends BaseProgram {
@Override
public String toString() {
return "RecordedProgram"
- + "[" + mId +
- "]{ mPackageName=" + mPackageName +
- ", mInputId='" + mInputId + '\'' +
- ", mChannelId='" + mChannelId + '\'' +
- ", mTitle='" + mTitle + '\'' +
- ", mSeriesId='" + mSeriesId + '\'' +
- ", mEpisodeNumber=" + mEpisodeNumber +
- ", mEpisodeTitle='" + mEpisodeTitle + '\'' +
- ", mStartTimeUtcMillis=" + mStartTimeUtcMillis +
- ", mEndTimeUtcMillis=" + mEndTimeUtcMillis +
- ", mBroadcastGenres=" +
- (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") +
- ", mCanonicalGenres=" +
- (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") +
- ", mShortDescription='" + mShortDescription + '\'' +
- ", mLongDescription='" + mLongDescription + '\'' +
- ", mVideoHeight=" + mVideoHeight +
- ", mVideoWidth=" + mVideoWidth +
- ", mAudioLanguage='" + mAudioLanguage + '\'' +
- ", mContentRatings='" +
- TvContentRatingCache.contentRatingsToString(mContentRatings) + '\'' +
- ", mPosterArtUri=" + mPosterArtUri +
- ", mThumbnailUri=" + mThumbnailUri +
- ", mSearchable=" + mSearchable +
- ", mDataUri=" + mDataUri +
- ", mDataBytes=" + mDataBytes +
- ", mDurationMillis=" + mDurationMillis +
- ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis +
- ", mInternalProviderFlag1=" + mInternalProviderFlag1 +
- ", mInternalProviderFlag2=" + mInternalProviderFlag2 +
- ", mInternalProviderFlag3=" + mInternalProviderFlag3 +
- ", mInternalProviderFlag4=" + mInternalProviderFlag4 +
- ", mSeasonNumber=" + mSeasonNumber +
- ", mSeasonTitle=" + mSeasonTitle +
- ", mVersionNumber=" + mVersionNumber +
- '}';
+ + "["
+ + mId
+ + "]{ mPackageName="
+ + mPackageName
+ + ", mInputId='"
+ + mInputId
+ + '\''
+ + ", mChannelId='"
+ + mChannelId
+ + '\''
+ + ", mTitle='"
+ + mTitle
+ + '\''
+ + ", mSeriesId='"
+ + mSeriesId
+ + '\''
+ + ", mEpisodeNumber="
+ + mEpisodeNumber
+ + ", mEpisodeTitle='"
+ + mEpisodeTitle
+ + '\''
+ + ", mStartTimeUtcMillis="
+ + mStartTimeUtcMillis
+ + ", mEndTimeUtcMillis="
+ + mEndTimeUtcMillis
+ + ", mBroadcastGenres="
+ + (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null")
+ + ", mCanonicalGenres="
+ + (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null")
+ + ", mShortDescription='"
+ + mShortDescription
+ + '\''
+ + ", mLongDescription='"
+ + mLongDescription
+ + '\''
+ + ", mVideoHeight="
+ + mVideoHeight
+ + ", mVideoWidth="
+ + mVideoWidth
+ + ", mAudioLanguage='"
+ + mAudioLanguage
+ + '\''
+ + ", mContentRatings='"
+ + TvContentRatingCache.contentRatingsToString(mContentRatings)
+ + '\''
+ + ", mPosterArtUri="
+ + mPosterArtUri
+ + ", mThumbnailUri="
+ + mThumbnailUri
+ + ", mSearchable="
+ + mSearchable
+ + ", mDataUri="
+ + mDataUri
+ + ", mDataBytes="
+ + mDataBytes
+ + ", mDurationMillis="
+ + mDurationMillis
+ + ", mExpireTimeUtcMillis="
+ + mExpireTimeUtcMillis
+ + ", mInternalProviderFlag1="
+ + mInternalProviderFlag1
+ + ", mInternalProviderFlag2="
+ + mInternalProviderFlag2
+ + ", mInternalProviderFlag3="
+ + mInternalProviderFlag3
+ + ", mInternalProviderFlag4="
+ + mInternalProviderFlag4
+ + ", mSeasonNumber="
+ + mSeasonNumber
+ + ", mSeasonTitle="
+ + mSeasonTitle
+ + ", mVersionNumber="
+ + mVersionNumber
+ + '}';
}
@Nullable
@@ -836,9 +929,7 @@ public class RecordedProgram extends BaseProgram {
return genres == null ? null : TvContract.Programs.Genres.encode(genres);
}
- /**
- * Returns an array containing all of the elements in the list.
- */
+ /** Returns an array containing all of the elements in the list. */
public static RecordedProgram[] toArray(Collection<RecordedProgram> recordedPrograms) {
return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]);
}
diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java
index 5d11c0f3..7c2d12d9 100644
--- a/src/com/android/tv/dvr/data/ScheduledRecording.java
+++ b/src/com/android/tv/dvr/data/ScheduledRecording.java
@@ -16,107 +16,97 @@
package com.android.tv.dvr.data;
+import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Range;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.util.CompositeComparator;
-import com.android.tv.util.Utils;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
-/**
- * A data class for one recording contents.
- */
+/** A data class for one recording contents. */
+@TargetApi(Build.VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
public final class ScheduledRecording implements Parcelable {
private static final String TAG = "ScheduledRecording";
- /**
- * Indicates that the ID is not assigned yet.
- */
+ /** Indicates that the ID is not assigned yet. */
public static final long ID_NOT_SET = 0;
- /**
- * The default priority of the recording.
- */
+ /** The default priority of the recording. */
public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
- /**
- * Compares the start time in ascending order.
- */
- public static final Comparator<ScheduledRecording> START_TIME_COMPARATOR
- = new Comparator<ScheduledRecording>() {
- @Override
- public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
- return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs);
- }
- };
-
- /**
- * Compares the end time in ascending order.
- */
- public static final Comparator<ScheduledRecording> END_TIME_COMPARATOR
- = new Comparator<ScheduledRecording>() {
- @Override
- public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
- return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs);
- }
- };
-
- /**
- * Compares ID in ascending order. The schedule with the larger ID was created later.
- */
- public static final Comparator<ScheduledRecording> ID_COMPARATOR
- = new Comparator<ScheduledRecording>() {
- @Override
- public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
- return Long.compare(lhs.mId, rhs.mId);
- }
- };
-
- /**
- * Compares the priority in ascending order.
- */
- public static final Comparator<ScheduledRecording> PRIORITY_COMPARATOR
- = new Comparator<ScheduledRecording>() {
- @Override
- public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
- return Long.compare(lhs.mPriority, rhs.mPriority);
- }
- };
+ /** Compares the start time in ascending order. */
+ public static final Comparator<ScheduledRecording> START_TIME_COMPARATOR =
+ new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs);
+ }
+ };
+
+ /** Compares the end time in ascending order. */
+ public static final Comparator<ScheduledRecording> END_TIME_COMPARATOR =
+ new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs);
+ }
+ };
+
+ /** Compares ID in ascending order. The schedule with the larger ID was created later. */
+ public static final Comparator<ScheduledRecording> ID_COMPARATOR =
+ new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mId, rhs.mId);
+ }
+ };
+
+ /** Compares the priority in ascending order. */
+ public static final Comparator<ScheduledRecording> PRIORITY_COMPARATOR =
+ new Comparator<ScheduledRecording>() {
+ @Override
+ public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
+ return Long.compare(lhs.mPriority, rhs.mPriority);
+ }
+ };
/**
* Compares start time in ascending order and then priority in descending order and then ID in
* descending order.
*/
- public static final Comparator<ScheduledRecording> START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
- = new CompositeComparator<>(START_TIME_COMPARATOR, PRIORITY_COMPARATOR.reversed(),
- ID_COMPARATOR.reversed());
+ public static final Comparator<ScheduledRecording> START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR =
+ new CompositeComparator<>(
+ START_TIME_COMPARATOR,
+ PRIORITY_COMPARATOR.reversed(),
+ ID_COMPARATOR.reversed());
- /**
- * Builds scheduled recordings from programs.
- */
+ /** Builds scheduled recordings from programs. */
public static Builder builder(String inputId, Program p) {
return new Builder()
.setInputId(inputId)
.setChannelId(p.getChannelId())
- .setStartTimeMs(p.getStartTimeUtcMillis()).setEndTimeMs(p.getEndTimeUtcMillis())
+ .setStartTimeMs(p.getStartTimeUtcMillis())
+ .setEndTimeMs(p.getEndTimeUtcMillis())
.setProgramId(p.getId())
.setProgramTitle(p.getTitle())
.setSeasonNumber(p.getSeasonNumber())
@@ -138,9 +128,7 @@ public final class ScheduledRecording implements Parcelable {
.setType(TYPE_TIMED);
}
- /**
- * Creates a new Builder with the values set from the {@link RecordedProgram}.
- */
+ /** Creates a new Builder with the values set from the {@link RecordedProgram}. */
public static Builder builder(RecordedProgram p) {
boolean isProgramRecording = !TextUtils.isEmpty(p.getTitle());
return new Builder()
@@ -157,7 +145,8 @@ public final class ScheduledRecording implements Parcelable {
.setProgramLongDescription(p.getLongDescription())
.setProgramPosterArtUri(p.getPosterArtUri())
.setProgramThumbnailUri(p.getThumbnailUri())
- .setState(STATE_RECORDING_FINISHED);
+ .setState(STATE_RECORDING_FINISHED)
+ .setRecordedProgramId(p.getId());
}
public static final class Builder {
@@ -179,8 +168,10 @@ public final class ScheduledRecording implements Parcelable {
private String mProgramThumbnailUri;
private @RecordingState int mState;
private long mSeriesRecordingId = ID_NOT_SET;
+ private Long mRecodedProgramId;
+ private Integer mFailedReason;
- private Builder() { }
+ private Builder() {}
public Builder setId(long id) {
mId = id;
@@ -272,17 +263,42 @@ public final class ScheduledRecording implements Parcelable {
return this;
}
+ public Builder setRecordedProgramId(Long recordedProgramId) {
+ mRecodedProgramId = recordedProgramId;
+ return this;
+ }
+
+ public Builder setFailedReason(Integer reason) {
+ mFailedReason = reason;
+ return this;
+ }
+
public ScheduledRecording build() {
- return new ScheduledRecording(mId, mPriority, mInputId, mChannelId, mProgramId,
- mProgramTitle, mType, mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber,
- mEpisodeTitle, mProgramDescription, mProgramLongDescription,
- mProgramPosterArtUri, mProgramThumbnailUri, mState, mSeriesRecordingId);
+ return new ScheduledRecording(
+ mId,
+ mPriority,
+ mInputId,
+ mChannelId,
+ mProgramId,
+ mProgramTitle,
+ mType,
+ mStartTimeMs,
+ mEndTimeMs,
+ mSeasonNumber,
+ mEpisodeNumber,
+ mEpisodeTitle,
+ mProgramDescription,
+ mProgramLongDescription,
+ mProgramPosterArtUri,
+ mProgramThumbnailUri,
+ mState,
+ mSeriesRecordingId,
+ mRecodedProgramId,
+ mFailedReason);
}
}
- /**
- * Creates {@link Builder} object from the given original {@code Recording}.
- */
+ /** Creates {@link Builder} object from the given original {@code Recording}. */
public static Builder buildFrom(ScheduledRecording orig) {
return new Builder()
.setId(orig.mId)
@@ -301,14 +317,23 @@ public final class ScheduledRecording implements Parcelable {
.setProgramLongDescription(orig.getProgramLongDescription())
.setProgramPosterArtUri(orig.getProgramPosterArtUri())
.setProgramThumbnailUri(orig.getProgramThumbnailUri())
- .setState(orig.mState).setType(orig.mType);
+ .setState(orig.mState)
+ .setFailedReason(orig.getFailedReason())
+ .setType(orig.mType);
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS, STATE_RECORDING_FINISHED,
- STATE_RECORDING_FAILED, STATE_RECORDING_CLIPPED, STATE_RECORDING_DELETED,
- STATE_RECORDING_CANCELED})
+ @IntDef({
+ STATE_RECORDING_NOT_STARTED,
+ STATE_RECORDING_IN_PROGRESS,
+ STATE_RECORDING_FINISHED,
+ STATE_RECORDING_FAILED,
+ STATE_RECORDING_CLIPPED,
+ STATE_RECORDING_DELETED,
+ STATE_RECORDING_CANCELED
+ })
public @interface RecordingState {}
+
public static final int STATE_RECORDING_NOT_STARTED = 0;
public static final int STATE_RECORDING_IN_PROGRESS = 1;
public static final int STATE_RECORDING_FINISHED = 2;
@@ -317,48 +342,74 @@ public final class ScheduledRecording implements Parcelable {
public static final int STATE_RECORDING_DELETED = 5;
public static final int STATE_RECORDING_CANCELED = 6;
+ /** The reasons of failed recordings */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ FAILED_REASON_OTHER,
+ FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED,
+ FAILED_REASON_NOT_FINISHED,
+ FAILED_REASON_SCHEDULER_STOPPED,
+ FAILED_REASON_INVALID_CHANNEL,
+ FAILED_REASON_MESSAGE_NOT_SENT,
+ FAILED_REASON_CONNECTION_FAILED,
+ FAILED_REASON_RESOURCE_BUSY,
+ FAILED_REASON_INPUT_UNAVAILABLE,
+ FAILED_REASON_INPUT_DVR_UNSUPPORTED,
+ FAILED_REASON_INSUFFICIENT_SPACE
+ })
+ public @interface RecordingFailedReason {}
+
+ public static final int FAILED_REASON_OTHER = 0;
+ public static final int FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED = 1;
+ public static final int FAILED_REASON_NOT_FINISHED = 2;
+ public static final int FAILED_REASON_SCHEDULER_STOPPED = 3;
+ public static final int FAILED_REASON_INVALID_CHANNEL = 4;
+ public static final int FAILED_REASON_MESSAGE_NOT_SENT = 5;
+ public static final int FAILED_REASON_CONNECTION_FAILED = 6;
+ public static final int FAILED_REASON_RESOURCE_BUSY = 7;
+ // For the following reasons, show advice to users
+ public static final int FAILED_REASON_INPUT_UNAVAILABLE = 8;
+ public static final int FAILED_REASON_INPUT_DVR_UNSUPPORTED = 9;
+ public static final int FAILED_REASON_INSUFFICIENT_SPACE = 10;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_TIMED, TYPE_PROGRAM})
public @interface RecordingType {}
- /**
- * Record with given time range.
- */
+ /** Record with given time range. */
public static final int TYPE_TIMED = 1;
- /**
- * Record with a given program.
- */
+ /** Record with a given program. */
public static final int TYPE_PROGRAM = 2;
@RecordingType private final int mType;
/**
- * Use this projection if you want to create {@link ScheduledRecording} object using
- * {@link #fromCursor}.
+ * Use this projection if you want to create {@link ScheduledRecording} object using {@link
+ * #fromCursor}.
*/
public static final String[] PROJECTION = {
- // Columns must match what is read in #fromCursor
- Schedules._ID,
- Schedules.COLUMN_PRIORITY,
- Schedules.COLUMN_TYPE,
- Schedules.COLUMN_INPUT_ID,
- Schedules.COLUMN_CHANNEL_ID,
- Schedules.COLUMN_PROGRAM_ID,
- Schedules.COLUMN_PROGRAM_TITLE,
- Schedules.COLUMN_START_TIME_UTC_MILLIS,
- Schedules.COLUMN_END_TIME_UTC_MILLIS,
- Schedules.COLUMN_SEASON_NUMBER,
- Schedules.COLUMN_EPISODE_NUMBER,
- Schedules.COLUMN_EPISODE_TITLE,
- Schedules.COLUMN_PROGRAM_DESCRIPTION,
- Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION,
- Schedules.COLUMN_PROGRAM_POST_ART_URI,
- Schedules.COLUMN_PROGRAM_THUMBNAIL_URI,
- Schedules.COLUMN_STATE,
- Schedules.COLUMN_SERIES_RECORDING_ID};
+ // Columns must match what is read in #fromCursor
+ Schedules._ID,
+ Schedules.COLUMN_PRIORITY,
+ Schedules.COLUMN_TYPE,
+ Schedules.COLUMN_INPUT_ID,
+ Schedules.COLUMN_CHANNEL_ID,
+ Schedules.COLUMN_PROGRAM_ID,
+ Schedules.COLUMN_PROGRAM_TITLE,
+ Schedules.COLUMN_START_TIME_UTC_MILLIS,
+ Schedules.COLUMN_END_TIME_UTC_MILLIS,
+ Schedules.COLUMN_SEASON_NUMBER,
+ Schedules.COLUMN_EPISODE_NUMBER,
+ Schedules.COLUMN_EPISODE_TITLE,
+ Schedules.COLUMN_PROGRAM_DESCRIPTION,
+ Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION,
+ Schedules.COLUMN_PROGRAM_POST_ART_URI,
+ Schedules.COLUMN_PROGRAM_THUMBNAIL_URI,
+ Schedules.COLUMN_STATE,
+ Schedules.COLUMN_FAILED_REASON,
+ Schedules.COLUMN_SERIES_RECORDING_ID
+ };
- /**
- * Creates {@link ScheduledRecording} object from the given {@link Cursor}.
- */
+ /** Creates {@link ScheduledRecording} object from the given {@link Cursor}. */
public static ScheduledRecording fromCursor(Cursor c) {
int index = -1;
return new Builder()
@@ -379,6 +430,7 @@ public final class ScheduledRecording implements Parcelable {
.setProgramPosterArtUri(c.getString(++index))
.setProgramThumbnailUri(c.getString(++index))
.setState(recordingState(c.getString(++index)))
+ .setFailedReason(recordingFailedReason(c.getString(++index)))
.setSeriesRecordingId(c.getLong(++index))
.build();
}
@@ -403,6 +455,7 @@ public final class ScheduledRecording implements Parcelable {
values.put(Schedules.COLUMN_PROGRAM_POST_ART_URI, r.getProgramPosterArtUri());
values.put(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, r.getProgramThumbnailUri());
values.put(Schedules.COLUMN_STATE, recordingState(r.getState()));
+ values.put(Schedules.COLUMN_FAILED_REASON, recordingFailedReason(r.getFailedReason()));
values.put(Schedules.COLUMN_TYPE, recordingType(r.getType()));
if (r.getSeriesRecordingId() != ID_NOT_SET) {
values.put(Schedules.COLUMN_SERIES_RECORDING_ID, r.getSeriesRecordingId());
@@ -431,42 +484,40 @@ public final class ScheduledRecording implements Parcelable {
.setProgramPosterArtUri(in.readString())
.setProgramThumbnailUri(in.readString())
.setState(in.readInt())
+ .setFailedReason(recordingFailedReason(in.readString()))
.setSeriesRecordingId(in.readLong())
.build();
}
public static final Parcelable.Creator<ScheduledRecording> CREATOR =
new Parcelable.Creator<ScheduledRecording>() {
- @Override
- public ScheduledRecording createFromParcel(Parcel in) {
- return ScheduledRecording.fromParcel(in);
- }
-
- @Override
- public ScheduledRecording[] newArray(int size) {
- return new ScheduledRecording[size];
- }
- };
-
- /**
- * The ID internal to Live TV
- */
+ @Override
+ public ScheduledRecording createFromParcel(Parcel in) {
+ return ScheduledRecording.fromParcel(in);
+ }
+
+ @Override
+ public ScheduledRecording[] newArray(int size) {
+ return new ScheduledRecording[size];
+ }
+ };
+
+ /** The ID internal to Live TV */
private long mId;
/**
* The priority of this recording.
*
- * <p> The highest number is recorded first. If there is a tie in priority then the higher id
+ * <p>The highest number is recorded first. If there is a tie in priority then the higher id
* wins.
*/
private final long mPriority;
private final String mInputId;
private final long mChannelId;
- /**
- * Optional id of the associated program.
- */
+ /** Optional id of the associated program. */
private final long mProgramId;
+
private final String mProgramTitle;
private final long mStartTimeMs;
@@ -480,12 +531,30 @@ public final class ScheduledRecording implements Parcelable {
private final String mProgramThumbnailUri;
@RecordingState private final int mState;
private final long mSeriesRecordingId;
-
- private ScheduledRecording(long id, long priority, String inputId, long channelId, long programId,
- String programTitle, @RecordingType int type, long startTime, long endTime,
- String seasonNumber, String episodeNumber, String episodeTitle,
- String programDescription, String programLongDescription, String programPosterArtUri,
- String programThumbnailUri, @RecordingState int state, long seriesRecordingId) {
+ private final Long mRecordedProgramId;
+ private final Integer mFailedReason;
+
+ private ScheduledRecording(
+ long id,
+ long priority,
+ String inputId,
+ long channelId,
+ long programId,
+ String programTitle,
+ @RecordingType int type,
+ long startTime,
+ long endTime,
+ String seasonNumber,
+ String episodeNumber,
+ String episodeTitle,
+ String programDescription,
+ String programLongDescription,
+ String programPosterArtUri,
+ String programThumbnailUri,
+ @RecordingState int state,
+ long seriesRecordingId,
+ Long recordedProgramId,
+ Integer failedReason) {
mId = id;
mPriority = priority;
mInputId = inputId;
@@ -504,139 +573,122 @@ public final class ScheduledRecording implements Parcelable {
mProgramThumbnailUri = programThumbnailUri;
mState = state;
mSeriesRecordingId = seriesRecordingId;
+ mRecordedProgramId = recordedProgramId;
+ mFailedReason = failedReason;
}
/**
- * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and
- * {@link #TYPE_TIMED}.
+ * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and {@link
+ * #TYPE_TIMED}.
*/
@RecordingType
public int getType() {
return mType;
}
- /**
- * Returns schedules' input id.
- */
+ /** Returns schedules' input id. */
public String getInputId() {
return mInputId;
}
- /**
- * Returns recorded {@link Channel}.
- */
+ /** Returns recorded {@link Channel}. */
public long getChannelId() {
return mChannelId;
}
- /**
- * Return the optional program id
- */
+ /** Return the optional program id */
public long getProgramId() {
return mProgramId;
}
- /**
- * Return the optional program Title
- */
+ /** Return the optional program Title */
public String getProgramTitle() {
return mProgramTitle;
}
- /**
- * Returns started time.
- */
+ /** Returns started time. */
public long getStartTimeMs() {
return mStartTimeMs;
}
- /**
- * Returns ended time.
- */
+ /** Returns ended time. */
public long getEndTimeMs() {
return mEndTimeMs;
}
- /**
- * Returns the season number.
- */
+ /** Returns the season number. */
public String getSeasonNumber() {
return mSeasonNumber;
}
- /**
- * Returns the episode number.
- */
+ /** Returns the episode number. */
public String getEpisodeNumber() {
return mEpisodeNumber;
}
- /**
- * Returns the episode title.
- */
+ /** Returns the episode title. */
public String getEpisodeTitle() {
return mEpisodeTitle;
}
- /**
- * Returns the description of program.
- */
+ /** Returns the description of program. */
public String getProgramDescription() {
return mProgramDescription;
}
- /**
- * Returns the long description of program.
- */
+ /** Returns the long description of program. */
public String getProgramLongDescription() {
return mProgramLongDescription;
}
- /**
- * Returns the poster uri of program.
- */
+ /** Returns the poster uri of program. */
public String getProgramPosterArtUri() {
return mProgramPosterArtUri;
}
- /**
- * Returns the thumb nail uri of program.
- */
+ /** Returns the thumb nail uri of program. */
public String getProgramThumbnailUri() {
return mProgramThumbnailUri;
}
- /**
- * Returns duration.
- */
+ /** Returns duration. */
public long getDuration() {
return mEndTimeMs - mStartTimeMs;
}
/**
- * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED},
- * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED},
- * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and
- * {@link #STATE_RECORDING_DELETED}.
+ * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED}, {@link
+ * #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, {@link
+ * #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and {@link
+ * #STATE_RECORDING_DELETED}.
*/
- @RecordingState public int getState() {
+ @RecordingState
+ public int getState() {
return mState;
}
- /**
- * Returns the ID of the {@link SeriesRecording} including this schedule.
- */
+ /** Returns the ID of the {@link SeriesRecording} including this schedule. */
public long getSeriesRecordingId() {
return mSeriesRecordingId;
}
+ /** Returns the ID of the corresponding {@link RecordedProgram}. */
+ @Nullable
+ public Long getRecordedProgramId() {
+ return mRecordedProgramId;
+ }
+
+ /** Returns the failed reason of the {@link ScheduledRecording}. */
+ @Nullable @RecordingFailedReason
+ public Integer getFailedReason() {
+ return mFailedReason;
+ }
+
public long getId() {
return mId;
}
- /**
- * Sets the ID;
- */
+ /** Sets the ID; */
public void setId(long id) {
mId = id;
}
@@ -645,21 +697,23 @@ public final class ScheduledRecording implements Parcelable {
return mPriority;
}
- /**
- * Returns season number, episode number and episode title for display.
- */
+ /** Returns season number, episode number and episode title for display. */
public String getEpisodeDisplayTitle(Context context) {
if (!TextUtils.isEmpty(mEpisodeNumber)) {
String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle;
if (TextUtils.equals(mSeasonNumber, "0")) {
// Do not show "S0: ".
- return String.format(context.getResources().getString(
- R.string.display_episode_title_format_no_season_number),
- mEpisodeNumber, episodeTitle);
+ return String.format(
+ context.getResources()
+ .getString(R.string.display_episode_title_format_no_season_number),
+ mEpisodeNumber,
+ episodeTitle);
} else {
- return String.format(context.getResources().getString(
- R.string.display_episode_title_format),
- mSeasonNumber, mEpisodeNumber, episodeTitle);
+ return String.format(
+ context.getResources().getString(R.string.display_episode_title_format),
+ mSeasonNumber,
+ mEpisodeNumber,
+ episodeTitle);
}
}
return mEpisodeTitle;
@@ -673,15 +727,14 @@ public final class ScheduledRecording implements Parcelable {
if (!TextUtils.isEmpty(mProgramTitle)) {
return mProgramTitle;
}
- Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
- .getChannel(mChannelId);
- return channel != null ? channel.getDisplayName()
+ Channel channel =
+ TvSingletons.getSingletons(context).getChannelDataManager().getChannel(mChannelId);
+ return channel != null
+ ? channel.getDisplayName()
: context.getString(R.string.no_program_information);
}
- /**
- * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}.
- */
+ /** Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. */
private static @RecordingType int recordingType(String type) {
switch (type) {
case Schedules.TYPE_TIMED:
@@ -689,14 +742,12 @@ public final class ScheduledRecording implements Parcelable {
case Schedules.TYPE_PROGRAM:
return TYPE_PROGRAM;
default:
- SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording type %s", type);
return TYPE_TIMED;
}
}
- /**
- * Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}.
- */
+ /** Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}. */
private static String recordingType(@RecordingType int type) {
switch (type) {
case TYPE_TIMED:
@@ -704,14 +755,14 @@ public final class ScheduledRecording implements Parcelable {
case TYPE_PROGRAM:
return Schedules.TYPE_PROGRAM;
default:
- SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording type %s", type);
return Schedules.TYPE_TIMED;
}
}
/**
- * Converts a string to a @RecordingState int, defaulting to
- * {@link #STATE_RECORDING_NOT_STARTED}.
+ * Converts a string to a @RecordingState int, defaulting to {@link
+ * #STATE_RECORDING_NOT_STARTED}.
*/
private static @RecordingState int recordingState(String state) {
switch (state) {
@@ -730,14 +781,14 @@ public final class ScheduledRecording implements Parcelable {
case Schedules.STATE_RECORDING_CANCELED:
return STATE_RECORDING_CANCELED;
default:
- SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording state %s", state);
return STATE_RECORDING_NOT_STARTED;
}
}
/**
- * Converts a @RecordingState int to string, defaulting to
- * {@link Schedules#STATE_RECORDING_NOT_STARTED}.
+ * Converts a @RecordingState int to string, defaulting to {@link
+ * Schedules#STATE_RECORDING_NOT_STARTED}.
*/
private static String recordingState(@RecordingState int state) {
switch (state) {
@@ -756,46 +807,138 @@ public final class ScheduledRecording implements Parcelable {
case STATE_RECORDING_CANCELED:
return Schedules.STATE_RECORDING_CANCELED;
default:
- SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
+ SoftPreconditions.checkArgument(false, TAG, "Unknown recording state %s", state);
return Schedules.STATE_RECORDING_NOT_STARTED;
}
}
/**
- * Checks if the {@code period} overlaps with the recording time.
+ * Converts a string to a failed reason integer, defaulting to {@link
+ * #FAILED_REASON_OTHER}.
*/
- public boolean isOverLapping(Range<Long> period) {
- return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower();
+ private static Integer recordingFailedReason(String reason) {
+ if (TextUtils.isEmpty(reason)) {
+ return null;
+ }
+ switch (reason) {
+ case Schedules.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED:
+ return FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED;
+ case Schedules.FAILED_REASON_NOT_FINISHED:
+ return FAILED_REASON_NOT_FINISHED;
+ case Schedules.FAILED_REASON_SCHEDULER_STOPPED:
+ return FAILED_REASON_SCHEDULER_STOPPED;
+ case Schedules.FAILED_REASON_INVALID_CHANNEL:
+ return FAILED_REASON_INVALID_CHANNEL;
+ case Schedules.FAILED_REASON_MESSAGE_NOT_SENT:
+ return FAILED_REASON_MESSAGE_NOT_SENT;
+ case Schedules.FAILED_REASON_CONNECTION_FAILED:
+ return FAILED_REASON_CONNECTION_FAILED;
+ case Schedules.FAILED_REASON_RESOURCE_BUSY:
+ return FAILED_REASON_RESOURCE_BUSY;
+ case Schedules.FAILED_REASON_INPUT_UNAVAILABLE:
+ return FAILED_REASON_INPUT_UNAVAILABLE;
+ case Schedules.FAILED_REASON_INPUT_DVR_UNSUPPORTED:
+ return FAILED_REASON_INPUT_DVR_UNSUPPORTED;
+ case Schedules.FAILED_REASON_INSUFFICIENT_SPACE:
+ return FAILED_REASON_INSUFFICIENT_SPACE;
+ case Schedules.FAILED_REASON_OTHER:
+ default:
+ return FAILED_REASON_OTHER;
+ }
}
/**
- * Checks if the {@code schedule} overlaps with this schedule.
+ * Converts a failed reason integer to string, defaulting to {@link
+ * Schedules#FAILED_REASON_OTHER}.
*/
+ private static String recordingFailedReason(Integer reason) {
+ if (reason == null) {
+ return null;
+ }
+ switch (reason) {
+ case FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED:
+ return Schedules.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED;
+ case FAILED_REASON_NOT_FINISHED:
+ return Schedules.FAILED_REASON_NOT_FINISHED;
+ case FAILED_REASON_SCHEDULER_STOPPED:
+ return Schedules.FAILED_REASON_SCHEDULER_STOPPED;
+ case FAILED_REASON_INVALID_CHANNEL:
+ return Schedules.FAILED_REASON_INVALID_CHANNEL;
+ case FAILED_REASON_MESSAGE_NOT_SENT:
+ return Schedules.FAILED_REASON_MESSAGE_NOT_SENT;
+ case FAILED_REASON_CONNECTION_FAILED:
+ return Schedules.FAILED_REASON_CONNECTION_FAILED;
+ case FAILED_REASON_RESOURCE_BUSY:
+ return Schedules.FAILED_REASON_RESOURCE_BUSY;
+ case FAILED_REASON_INPUT_UNAVAILABLE:
+ return Schedules.FAILED_REASON_INPUT_UNAVAILABLE;
+ case FAILED_REASON_INPUT_DVR_UNSUPPORTED:
+ return Schedules.FAILED_REASON_INPUT_DVR_UNSUPPORTED;
+ case FAILED_REASON_INSUFFICIENT_SPACE:
+ return Schedules.FAILED_REASON_INSUFFICIENT_SPACE;
+ case FAILED_REASON_OTHER: // fall through
+ default:
+ return Schedules.FAILED_REASON_OTHER;
+ }
+ }
+
+ /** Checks if the {@code period} overlaps with the recording time. */
+ public boolean isOverLapping(Range<Long> period) {
+ return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower();
+ }
+
+ /** Checks if the {@code schedule} overlaps with this schedule. */
public boolean isOverLapping(ScheduledRecording schedule) {
return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs();
}
@Override
public String toString() {
- return "ScheduledRecording[" + mId
+ return "ScheduledRecording["
+ + mId
+ "]"
- + "(inputId=" + mInputId
- + ",channelId=" + mChannelId
- + ",programId=" + mProgramId
- + ",programTitle=" + mProgramTitle
- + ",type=" + mType
- + ",startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")"
- + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")"
- + ",seasonNumber=" + mSeasonNumber
- + ",episodeNumber=" + mEpisodeNumber
- + ",episodeTitle=" + mEpisodeTitle
- + ",programDescription=" + mProgramDescription
- + ",programLongDescription=" + mProgramLongDescription
- + ",programPosterArtUri=" + mProgramPosterArtUri
- + ",programThumbnailUri=" + mProgramThumbnailUri
- + ",state=" + mState
- + ",priority=" + mPriority
- + ",seriesRecordingId=" + mSeriesRecordingId
+ + "(inputId="
+ + mInputId
+ + ",channelId="
+ + mChannelId
+ + ",programId="
+ + mProgramId
+ + ",programTitle="
+ + mProgramTitle
+ + ",type="
+ + mType
+ + ",startTime="
+ + CommonUtils.toIsoDateTimeString(mStartTimeMs)
+ + "("
+ + mStartTimeMs
+ + ")"
+ + ",endTime="
+ + CommonUtils.toIsoDateTimeString(mEndTimeMs)
+ + "("
+ + mEndTimeMs
+ + ")"
+ + ",seasonNumber="
+ + mSeasonNumber
+ + ",episodeNumber="
+ + mEpisodeNumber
+ + ",episodeTitle="
+ + mEpisodeTitle
+ + ",programDescription="
+ + mProgramDescription
+ + ",programLongDescription="
+ + mProgramLongDescription
+ + ",programPosterArtUri="
+ + mProgramPosterArtUri
+ + ",programThumbnailUri="
+ + mProgramThumbnailUri
+ + ",state="
+ + mState
+ + ",failedReason="
+ + mFailedReason
+ + ",priority="
+ + mPriority
+ + ",seriesRecordingId="
+ + mSeriesRecordingId
+ ")";
}
@@ -823,23 +966,25 @@ public final class ScheduledRecording implements Parcelable {
out.writeString(mProgramPosterArtUri);
out.writeString(mProgramThumbnailUri);
out.writeInt(mState);
+ out.writeString(recordingFailedReason(mFailedReason));
out.writeLong(mSeriesRecordingId);
}
- /**
- * Returns {@code true} if the recording is not started yet, otherwise @{code false}.
- */
+ /** Returns {@code true} if the recording is not started yet, otherwise @{code false}. */
public boolean isNotStarted() {
return mState == STATE_RECORDING_NOT_STARTED;
}
- /**
- * Returns {@code true} if the recording is in progress, otherwise @{code false}.
- */
+ /** Returns {@code true} if the recording is in progress, otherwise @{code false}. */
public boolean isInProgress() {
return mState == STATE_RECORDING_IN_PROGRESS;
}
+ /** Returns {@code true} if the recording is finished, otherwise @{code false}. */
+ public boolean isFinished() {
+ return mState == STATE_RECORDING_FINISHED;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ScheduledRecording)) {
@@ -862,20 +1007,34 @@ public final class ScheduledRecording implements Parcelable {
&& Objects.equals(mProgramPosterArtUri, r.getProgramPosterArtUri())
&& Objects.equals(mProgramThumbnailUri, r.getProgramThumbnailUri())
&& mState == r.mState
+ && Objects.equals(mFailedReason, r.mFailedReason)
&& mSeriesRecordingId == r.mSeriesRecordingId;
}
@Override
public int hashCode() {
- return Objects.hash(mId, mPriority, mChannelId, mProgramId, mProgramTitle, mType,
- mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, mEpisodeTitle,
- mProgramDescription, mProgramLongDescription, mProgramPosterArtUri,
- mProgramThumbnailUri, mState, mSeriesRecordingId);
+ return Objects.hash(
+ mId,
+ mPriority,
+ mChannelId,
+ mProgramId,
+ mProgramTitle,
+ mType,
+ mStartTimeMs,
+ mEndTimeMs,
+ mSeasonNumber,
+ mEpisodeNumber,
+ mEpisodeTitle,
+ mProgramDescription,
+ mProgramLongDescription,
+ mProgramPosterArtUri,
+ mProgramThumbnailUri,
+ mState,
+ mFailedReason,
+ mSeriesRecordingId);
}
- /**
- * Returns an array containing all of the elements in the list.
- */
+ /** Returns an array containing all of the elements in the list. */
public static ScheduledRecording[] toArray(Collection<ScheduledRecording> schedules) {
return schedules.toArray(new ScheduledRecording[schedules.size()]);
}
diff --git a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
index 89533dbb..c697451a 100644
--- a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
+++ b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
@@ -17,20 +17,15 @@
package com.android.tv.dvr.data;
import android.text.TextUtils;
-
import java.util.Objects;
-/**
- * A plain java object which includes the season/episode number for the series recording.
- */
+/** A plain java object which includes the season/episode number for the series recording. */
public class SeasonEpisodeNumber {
public final long seriesRecordingId;
public final String seasonNumber;
public final String episodeNumber;
- /**
- * Creates a new Builder with the values set from an existing {@link ScheduledRecording}.
- */
+ /** Creates a new Builder with the values set from an existing {@link ScheduledRecording}. */
public SeasonEpisodeNumber(ScheduledRecording r) {
this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber());
}
@@ -47,7 +42,8 @@ public class SeasonEpisodeNumber {
return true;
}
if (!(o instanceof SeasonEpisodeNumber)
- || TextUtils.isEmpty(seasonNumber) || TextUtils.isEmpty(episodeNumber)) {
+ || TextUtils.isEmpty(seasonNumber)
+ || TextUtils.isEmpty(episodeNumber)) {
return false;
}
SeasonEpisodeNumber that = (SeasonEpisodeNumber) o;
@@ -63,10 +59,13 @@ public class SeasonEpisodeNumber {
@Override
public String toString() {
- return "SeasonEpisodeNumber{" +
- "seriesRecordingId=" + seriesRecordingId +
- ", seasonNumber='" + seasonNumber +
- ", episodeNumber=" + episodeNumber +
- '}';
+ return "SeasonEpisodeNumber{"
+ + "seriesRecordingId="
+ + seriesRecordingId
+ + ", seasonNumber='"
+ + seasonNumber
+ + ", episodeNumber="
+ + episodeNumber
+ + '}';
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/data/SeriesInfo.java b/src/com/android/tv/dvr/data/SeriesInfo.java
index a0dec4a4..aa622c3d 100644
--- a/src/com/android/tv/dvr/data/SeriesInfo.java
+++ b/src/com/android/tv/dvr/data/SeriesInfo.java
@@ -16,9 +16,7 @@
package com.android.tv.dvr.data;
-/**
- * Series information.
- */
+/** Series information. */
public class SeriesInfo {
private final String mId;
private final String mTitle;
@@ -28,8 +26,14 @@ public class SeriesInfo {
private final String mPosterUri;
private final String mPhotoUri;
- public SeriesInfo(String id, String title, String description, String longDescription,
- int[] canonicalGenreIds, String posterUri, String photoUri) {
+ public SeriesInfo(
+ String id,
+ String title,
+ String description,
+ String longDescription,
+ int[] canonicalGenreIds,
+ String posterUri,
+ String photoUri) {
this.mId = id;
this.mTitle = title;
this.mDescription = description;
@@ -39,37 +43,37 @@ public class SeriesInfo {
this.mPhotoUri = photoUri;
}
- /** Returns the ID. **/
+ /** Returns the ID. * */
public String getId() {
return mId;
}
- /** Returns the title. **/
+ /** Returns the title. * */
public String getTitle() {
return mTitle;
}
- /** Returns the description. **/
+ /** Returns the description. * */
public String getDescription() {
return mDescription;
}
- /** Returns the description. **/
+ /** Returns the description. * */
public String getLongDescription() {
return mLongDescription;
}
- /** Returns the canonical genre IDs. **/
+ /** Returns the canonical genre IDs. * */
public int[] getCanonicalGenreIds() {
return mCanonicalGenreIds;
}
- /** Returns the poster URI. **/
+ /** Returns the poster URI. * */
public String getPosterUri() {
return mPosterUri;
}
- /** Returns the photo URI. **/
+ /** Returns the photo URI. * */
public String getPhotoUri() {
return mPhotoUri;
}
diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java
index 822e7320..96b3425a 100644
--- a/src/com/android/tv/dvr/data/SeriesRecording.java
+++ b/src/com/android/tv/dvr/data/SeriesRecording.java
@@ -21,15 +21,12 @@ import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
-
import com.android.tv.data.BaseProgram;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
import com.android.tv.util.Utils;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
@@ -40,68 +37,55 @@ import java.util.Objects;
/**
* Schedules the recording of a Series of Programs.
*
- * <p>
- * Contains the data needed to create new ScheduleRecordings as the programs become available in
+ * <p>Contains the data needed to create new ScheduleRecordings as the programs become available in
* the EPG.
*/
public class SeriesRecording implements Parcelable {
- /**
- * Indicates that the ID is not assigned yet.
- */
+ /** Indicates that the ID is not assigned yet. */
public static final long ID_NOT_SET = 0;
- /**
- * The default priority of this recording.
- */
+ /** The default priority of this recording. */
public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true,
- value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL})
+ @IntDef(
+ flag = true,
+ value = {OPTION_CHANNEL_ONE, OPTION_CHANNEL_ALL}
+ )
public @interface ChannelOption {}
- /**
- * An option which indicates that the episodes in one channel are recorded.
- */
+ /** An option which indicates that the episodes in one channel are recorded. */
public static final int OPTION_CHANNEL_ONE = 0;
- /**
- * An option which indicates that the episodes in all the channels are recorded.
- */
+ /** An option which indicates that the episodes in all the channels are recorded. */
public static final int OPTION_CHANNEL_ALL = 1;
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true,
- value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED})
+ @IntDef(
+ flag = true,
+ value = {STATE_SERIES_NORMAL, STATE_SERIES_STOPPED}
+ )
public @interface SeriesState {}
- /**
- * The state indicates that the series recording is a normal one.
- */
+ /** The state indicates that the series recording is a normal one. */
public static final int STATE_SERIES_NORMAL = 0;
- /**
- * The state indicates that the series recording is stopped.
- */
+ /** The state indicates that the series recording is stopped. */
public static final int STATE_SERIES_STOPPED = 1;
- /**
- * Compare priority in descending order.
- */
+ /** Compare priority in descending order. */
public static final Comparator<SeriesRecording> PRIORITY_COMPARATOR =
new Comparator<SeriesRecording>() {
- @Override
- public int compare(SeriesRecording lhs, SeriesRecording rhs) {
- int value = Long.compare(rhs.mPriority, lhs.mPriority);
- if (value == 0) {
- // New recording has the higher priority.
- value = Long.compare(rhs.mId, lhs.mId);
- }
- return value;
- }
- };
+ @Override
+ public int compare(SeriesRecording lhs, SeriesRecording rhs) {
+ int value = Long.compare(rhs.mPriority, lhs.mPriority);
+ if (value == 0) {
+ // New recording has the higher priority.
+ value = Long.compare(rhs.mId, lhs.mId);
+ }
+ return value;
+ }
+ };
- /**
- * Compare ID in ascending order.
- */
+ /** Compare ID in ascending order. */
public static final Comparator<SeriesRecording> ID_COMPARATOR =
new Comparator<SeriesRecording>() {
@Override
@@ -126,9 +110,7 @@ public class SeriesRecording implements Parcelable {
.setPhotoUri(p.getThumbnailUri());
}
- /**
- * Creates a new Builder with the values set from an existing {@link SeriesRecording}.
- */
+ /** Creates a new Builder with the values set from an existing {@link SeriesRecording}. */
public static Builder buildFrom(SeriesRecording r) {
return new Builder()
.setId(r.mId)
@@ -149,30 +131,28 @@ public class SeriesRecording implements Parcelable {
}
/**
- * Use this projection if you want to create {@link SeriesRecording} object using
- * {@link #fromCursor}.
+ * Use this projection if you want to create {@link SeriesRecording} object using {@link
+ * #fromCursor}.
*/
public static final String[] PROJECTION = {
- // Columns must match what is read in fromCursor()
- SeriesRecordings._ID,
- SeriesRecordings.COLUMN_INPUT_ID,
- SeriesRecordings.COLUMN_CHANNEL_ID,
- SeriesRecordings.COLUMN_PRIORITY,
- SeriesRecordings.COLUMN_TITLE,
- SeriesRecordings.COLUMN_SHORT_DESCRIPTION,
- SeriesRecordings.COLUMN_LONG_DESCRIPTION,
- SeriesRecordings.COLUMN_SERIES_ID,
- SeriesRecordings.COLUMN_START_FROM_EPISODE,
- SeriesRecordings.COLUMN_START_FROM_SEASON,
- SeriesRecordings.COLUMN_CHANNEL_OPTION,
- SeriesRecordings.COLUMN_CANONICAL_GENRE,
- SeriesRecordings.COLUMN_POSTER_URI,
- SeriesRecordings.COLUMN_PHOTO_URI,
- SeriesRecordings.COLUMN_STATE
+ // Columns must match what is read in fromCursor()
+ SeriesRecordings._ID,
+ SeriesRecordings.COLUMN_INPUT_ID,
+ SeriesRecordings.COLUMN_CHANNEL_ID,
+ SeriesRecordings.COLUMN_PRIORITY,
+ SeriesRecordings.COLUMN_TITLE,
+ SeriesRecordings.COLUMN_SHORT_DESCRIPTION,
+ SeriesRecordings.COLUMN_LONG_DESCRIPTION,
+ SeriesRecordings.COLUMN_SERIES_ID,
+ SeriesRecordings.COLUMN_START_FROM_EPISODE,
+ SeriesRecordings.COLUMN_START_FROM_SEASON,
+ SeriesRecordings.COLUMN_CHANNEL_OPTION,
+ SeriesRecordings.COLUMN_CANONICAL_GENRE,
+ SeriesRecordings.COLUMN_POSTER_URI,
+ SeriesRecordings.COLUMN_PHOTO_URI,
+ SeriesRecordings.COLUMN_STATE
};
- /**
- * Creates {@link SeriesRecording} object from the given {@link Cursor}.
- */
+ /** Creates {@link SeriesRecording} object from the given {@link Cursor}. */
public static SeriesRecording fromCursor(Cursor c) {
int index = -1;
return new Builder()
@@ -195,8 +175,8 @@ public class SeriesRecording implements Parcelable {
}
/**
- * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings}
- * and the values from {@code r}.
+ * Returns the ContentValues with keys as the columns specified in {@link SeriesRecordings} and
+ * the values from {@code r}.
*/
public static ContentValues toContentValues(SeriesRecording r) {
ContentValues values = new ContentValues();
@@ -214,9 +194,9 @@ public class SeriesRecording implements Parcelable {
values.put(SeriesRecordings.COLUMN_SERIES_ID, r.getSeriesId());
values.put(SeriesRecordings.COLUMN_START_FROM_EPISODE, r.getStartFromEpisode());
values.put(SeriesRecordings.COLUMN_START_FROM_SEASON, r.getStartFromSeason());
- values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION,
- channelOption(r.getChannelOption()));
- values.put(SeriesRecordings.COLUMN_CANONICAL_GENRE,
+ values.put(SeriesRecordings.COLUMN_CHANNEL_OPTION, channelOption(r.getChannelOption()));
+ values.put(
+ SeriesRecordings.COLUMN_CANONICAL_GENRE,
Utils.getCanonicalGenre(r.getCanonicalGenreIds()));
values.put(SeriesRecordings.COLUMN_POSTER_URI, r.getPosterUri());
values.put(SeriesRecordings.COLUMN_PHOTO_URI, r.getPhotoUri());
@@ -234,7 +214,8 @@ public class SeriesRecording implements Parcelable {
return SeriesRecordings.OPTION_CHANNEL_ONE;
}
- @ChannelOption private static int channelOption(String option) {
+ @ChannelOption
+ private static int channelOption(String option) {
switch (option) {
case SeriesRecordings.OPTION_CHANNEL_ONE:
return OPTION_CHANNEL_ONE;
@@ -254,7 +235,8 @@ public class SeriesRecording implements Parcelable {
return SeriesRecordings.STATE_SERIES_NORMAL;
}
- @SeriesState private static int seriesRecordingState(String state) {
+ @SeriesState
+ private static int seriesRecordingState(String state) {
switch (state) {
case SeriesRecordings.STATE_SERIES_NORMAL:
return STATE_SERIES_NORMAL;
@@ -264,9 +246,7 @@ public class SeriesRecording implements Parcelable {
return STATE_SERIES_NORMAL;
}
- /**
- * Builder for {@link SeriesRecording}.
- */
+ /** Builder for {@link SeriesRecording}. */
public static class Builder {
private long mId = ID_NOT_SET;
private long mPriority = DvrScheduleManager.DEFAULT_SERIES_PRIORITY;
@@ -284,141 +264,120 @@ public class SeriesRecording implements Parcelable {
private String mPhotoUri;
private int mState = SeriesRecording.STATE_SERIES_NORMAL;
- /**
- * @see #getId()
- */
+ /** @see #getId() */
public Builder setId(long id) {
mId = id;
return this;
}
- /**
- * @see #getPriority() ()
- */
+ /** @see #getPriority() () */
public Builder setPriority(long priority) {
mPriority = priority;
return this;
}
- /**
- * @see #getTitle()
- */
+ /** @see #getTitle() */
public Builder setTitle(String title) {
mTitle = title;
return this;
}
- /**
- * @see #getDescription()
- */
+ /** @see #getDescription() */
public Builder setDescription(String description) {
mDescription = description;
return this;
}
- /**
- * @see #getLongDescription()
- */
+ /** @see #getLongDescription() */
public Builder setLongDescription(String longDescription) {
mLongDescription = longDescription;
return this;
}
- /**
- * @see #getInputId()
- */
+ /** @see #getInputId() */
public Builder setInputId(String inputId) {
mInputId = inputId;
return this;
}
- /**
- * @see #getChannelId()
- */
+ /** @see #getChannelId() */
public Builder setChannelId(long channelId) {
mChannelId = channelId;
return this;
}
- /**
- * @see #getSeriesId()
- */
+ /** @see #getSeriesId() */
public Builder setSeriesId(String seriesId) {
mSeriesId = seriesId;
return this;
}
- /**
- * @see #getStartFromSeason()
- */
+ /** @see #getStartFromSeason() */
public Builder setStartFromSeason(int startFromSeason) {
mStartFromSeason = startFromSeason;
return this;
}
- /**
- * @see #getChannelOption()
- */
+ /** @see #getChannelOption() */
public Builder setChannelOption(@ChannelOption int option) {
mChannelOption = option;
return this;
}
- /**
- * @see #getStartFromEpisode()
- */
+ /** @see #getStartFromEpisode() */
public Builder setStartFromEpisode(int startFromEpisode) {
mStartFromEpisode = startFromEpisode;
return this;
}
- /**
- * @see #getCanonicalGenreIds()
- */
+ /** @see #getCanonicalGenreIds() */
public Builder setCanonicalGenreIds(String genres) {
mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres);
return this;
}
- /**
- * @see #getCanonicalGenreIds()
- */
+ /** @see #getCanonicalGenreIds() */
public Builder setCanonicalGenreIds(int[] canonicalGenreIds) {
mCanonicalGenreIds = canonicalGenreIds;
return this;
}
- /**
- * @see #getPosterUri()
- */
+ /** @see #getPosterUri() */
public Builder setPosterUri(String posterUri) {
mPosterUri = posterUri;
return this;
}
- /**
- * @see #getPhotoUri()
- */
+ /** @see #getPhotoUri() */
public Builder setPhotoUri(String photoUri) {
mPhotoUri = photoUri;
return this;
}
- /**
- * @see #getState()
- */
+ /** @see #getState() */
public Builder setState(@SeriesState int state) {
mState = state;
return this;
}
- /**
- * Creates a new {@link SeriesRecording}.
- */
+ /** Creates a new {@link SeriesRecording}. */
public SeriesRecording build() {
- return new SeriesRecording(mId, mPriority, mTitle, mDescription, mLongDescription,
- mInputId, mChannelId, mSeriesId, mStartFromSeason, mStartFromEpisode,
- mChannelOption, mCanonicalGenreIds, mPosterUri, mPhotoUri, mState);
+ return new SeriesRecording(
+ mId,
+ mPriority,
+ mTitle,
+ mDescription,
+ mLongDescription,
+ mInputId,
+ mChannelId,
+ mSeriesId,
+ mStartFromSeason,
+ mStartFromEpisode,
+ mChannelOption,
+ mCanonicalGenreIds,
+ mPosterUri,
+ mPhotoUri,
+ mState);
}
}
@@ -444,16 +403,16 @@ public class SeriesRecording implements Parcelable {
public static final Parcelable.Creator<SeriesRecording> CREATOR =
new Parcelable.Creator<SeriesRecording>() {
- @Override
- public SeriesRecording createFromParcel(Parcel in) {
- return SeriesRecording.fromParcel(in);
- }
+ @Override
+ public SeriesRecording createFromParcel(Parcel in) {
+ return SeriesRecording.fromParcel(in);
+ }
- @Override
- public SeriesRecording[] newArray(int size) {
- return new SeriesRecording[size];
- }
- };
+ @Override
+ public SeriesRecording[] newArray(int size) {
+ return new SeriesRecording[size];
+ }
+ };
private long mId;
private final long mPriority;
@@ -471,9 +430,7 @@ public class SeriesRecording implements Parcelable {
private final String mPhotoUri;
@SeriesState private int mState;
- /**
- * The input id of this SeriesRecording.
- */
+ /** The input id of this SeriesRecording. */
public String getInputId() {
return mInputId;
}
@@ -485,16 +442,12 @@ public class SeriesRecording implements Parcelable {
return mChannelId;
}
- /**
- * The id of this SeriesRecording.
- */
+ /** The id of this SeriesRecording. */
public long getId() {
return mId;
}
- /**
- * Sets the ID.
- */
+ /** Sets the ID. */
public void setId(long id) {
mId = id;
}
@@ -502,30 +455,24 @@ public class SeriesRecording implements Parcelable {
/**
* The priority of this recording.
*
- * <p> The highest number is recorded first. If there is a tie in mPriority then the higher mId
+ * <p>The highest number is recorded first. If there is a tie in mPriority then the higher mId
* wins.
*/
public long getPriority() {
return mPriority;
}
- /**
- * The series title.
- */
+ /** The series title. */
public String getTitle() {
return mTitle;
}
- /**
- * The series description.
- */
+ /** The series description. */
public String getDescription() {
return mDescription;
}
- /**
- * The long series description.
- */
+ /** The long series description. */
public String getLongDescription() {
return mLongDescription;
}
@@ -555,44 +502,34 @@ public class SeriesRecording implements Parcelable {
return mStartFromSeason;
}
- /**
- * Returns the channel recording option.
- */
- @ChannelOption public int getChannelOption() {
+ /** Returns the channel recording option. */
+ @ChannelOption
+ public int getChannelOption() {
return mChannelOption;
}
- /**
- * Returns the canonical genre ID's.
- */
+ /** Returns the canonical genre ID's. */
public int[] getCanonicalGenreIds() {
return mCanonicalGenreIds;
}
- /**
- * Returns the poster URI.
- */
+ /** Returns the poster URI. */
public String getPosterUri() {
return mPosterUri;
}
- /**
- * Returns the photo URI.
- */
+ /** Returns the photo URI. */
public String getPhotoUri() {
return mPhotoUri;
}
- /**
- * Returns the state of series recording.
- */
- @SeriesState public int getState() {
+ /** Returns the state of series recording. */
+ @SeriesState
+ public int getState() {
return mState;
}
- /**
- * Checks whether the series recording is stopped or not.
- */
+ /** Checks whether the series recording is stopped or not. */
public boolean isStopped() {
return mState == STATE_SERIES_STOPPED;
}
@@ -620,35 +557,77 @@ public class SeriesRecording implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mPriority, mChannelId, mStartFromSeason, mStartFromEpisode, mId,
- mTitle, mDescription, mLongDescription, mSeriesId, mChannelOption,
- mCanonicalGenreIds, mPosterUri, mPhotoUri, mState);
+ return Objects.hash(
+ mPriority,
+ mChannelId,
+ mStartFromSeason,
+ mStartFromEpisode,
+ mId,
+ mTitle,
+ mDescription,
+ mLongDescription,
+ mSeriesId,
+ mChannelOption,
+ Arrays.hashCode(mCanonicalGenreIds),
+ mPosterUri,
+ mPhotoUri,
+ mState);
}
@Override
public String toString() {
- return "SeriesRecording{" +
- "inputId=" + mInputId +
- ", channelId=" + mChannelId +
- ", id='" + mId + '\'' +
- ", priority=" + mPriority +
- ", title='" + mTitle + '\'' +
- ", description='" + mDescription + '\'' +
- ", longDescription='" + mLongDescription + '\'' +
- ", startFromSeason=" + mStartFromSeason +
- ", startFromEpisode=" + mStartFromEpisode +
- ", channelOption=" + mChannelOption +
- ", canonicalGenreIds=" + Arrays.toString(mCanonicalGenreIds) +
- ", posterUri=" + mPosterUri +
- ", photoUri=" + mPhotoUri +
- ", state=" + mState +
- '}';
- }
-
- private SeriesRecording(long id, long priority, String title, String description,
- String longDescription, String inputId, long channelId, String seriesId,
- int startFromSeason, int startFromEpisode, int channelOption, int[] canonicalGenreIds,
- String posterUri, String photoUri, int state) {
+ return "SeriesRecording{"
+ + "inputId="
+ + mInputId
+ + ", channelId="
+ + mChannelId
+ + ", id='"
+ + mId
+ + '\''
+ + ", priority="
+ + mPriority
+ + ", title='"
+ + mTitle
+ + '\''
+ + ", description='"
+ + mDescription
+ + '\''
+ + ", longDescription='"
+ + mLongDescription
+ + '\''
+ + ", startFromSeason="
+ + mStartFromSeason
+ + ", startFromEpisode="
+ + mStartFromEpisode
+ + ", channelOption="
+ + mChannelOption
+ + ", canonicalGenreIds="
+ + Arrays.toString(mCanonicalGenreIds)
+ + ", posterUri="
+ + mPosterUri
+ + ", photoUri="
+ + mPhotoUri
+ + ", state="
+ + mState
+ + '}';
+ }
+
+ private SeriesRecording(
+ long id,
+ long priority,
+ String title,
+ String description,
+ String longDescription,
+ String inputId,
+ long channelId,
+ String seriesId,
+ int startFromSeason,
+ int startFromEpisode,
+ int channelOption,
+ int[] canonicalGenreIds,
+ String posterUri,
+ String photoUri,
+ int state) {
this.mId = id;
this.mPriority = priority;
this.mTitle = title;
@@ -690,9 +669,7 @@ public class SeriesRecording implements Parcelable {
out.writeInt(mState);
}
- /**
- * Returns an array containing all of the elements in the list.
- */
+ /** Returns an array containing all of the elements in the list. */
public static SeriesRecording[] toArray(Collection<SeriesRecording> series) {
return series.toArray(new SeriesRecording[series.size()]);
}
@@ -715,16 +692,16 @@ public class SeriesRecording implements Parcelable {
long channelId = program.getChannelId();
String seasonNumber = program.getSeasonNumber();
String episodeNumber = program.getEpisodeNumber();
- if (!mSeriesId.equals(seriesId) || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE
- && mChannelId != channelId)) {
+ if (!mSeriesId.equals(seriesId)
+ || (channelOption == SeriesRecording.OPTION_CHANNEL_ONE
+ && mChannelId != channelId)) {
return false;
}
// Season number and episode number matches if
// start_season_number < program_season_number
// || (start_season_number == program_season_number
// && start_episode_number <= program_episode_number).
- if (mStartFromSeason == SeriesRecordings.THE_BEGINNING
- || TextUtils.isEmpty(seasonNumber)) {
+ if (mStartFromSeason == SeriesRecordings.THE_BEGINNING || TextUtils.isEmpty(seasonNumber)) {
return true;
} else {
int intSeasonNumber;
diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
index c5383d02..7d2af9c3 100644
--- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
+++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
@@ -20,27 +20,23 @@ import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
-
+import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
-import com.android.tv.util.NamedThreadFactory;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-/**
- * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
- */
+/** {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. */
public abstract class AsyncDvrDbTask<Params, Progress, Result>
extends AsyncTask<Params, Progress, Result> {
- private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory(
- AsyncDvrDbTask.class.getSimpleName());
- private static final ExecutorService DB_EXECUTOR = Executors
- .newSingleThreadExecutor(THREAD_FACTORY);
+ private static final NamedThreadFactory THREAD_FACTORY =
+ new NamedThreadFactory(AsyncDvrDbTask.class.getSimpleName());
+ private static final ExecutorService DB_EXECUTOR =
+ Executors.newSingleThreadExecutor(THREAD_FACTORY);
private static DvrDatabaseHelper sDbHelper;
@@ -57,9 +53,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
mContext = context;
}
- /**
- * Execute the task on the {@link #DB_EXECUTOR} thread.
- */
+ /** Execute the task on the {@link #DB_EXECUTOR} thread. */
@SafeVarargs
public final void executeOnDbThread(Params... params) {
executeOnExecutor(DB_EXECUTOR, params);
@@ -71,15 +65,11 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
return doInDvrBackground(params);
}
- /**
- * Executes in the background after {@link #initializeDbHelper(Context)}
- */
+ /** Executes in the background after {@link #initializeDbHelper(Context)} */
@Nullable
protected abstract Result doInDvrBackground(Params... params);
- /**
- * Inserts schedules.
- */
+ /** Inserts schedules. */
public static class AsyncAddScheduleTask
extends AsyncDvrDbTask<ScheduledRecording, Void, Void> {
public AsyncAddScheduleTask(Context context) {
@@ -93,9 +83,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Update schedules.
- */
+ /** Update schedules. */
public static class AsyncUpdateScheduleTask
extends AsyncDvrDbTask<ScheduledRecording, Void, Void> {
public AsyncUpdateScheduleTask(Context context) {
@@ -109,9 +97,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Delete schedules.
- */
+ /** Delete schedules. */
public static class AsyncDeleteScheduleTask
extends AsyncDvrDbTask<ScheduledRecording, Void, Void> {
public AsyncDeleteScheduleTask(Context context) {
@@ -125,9 +111,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Returns all {@link ScheduledRecording}s.
- */
+ /** Returns all {@link ScheduledRecording}s. */
public abstract static class AsyncDvrQueryScheduleTask
extends AsyncDvrDbTask<Void, Void, List<ScheduledRecording>> {
public AsyncDvrQueryScheduleTask(Context context) {
@@ -150,9 +134,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Inserts series recordings.
- */
+ /** Inserts series recordings. */
public static class AsyncAddSeriesRecordingTask
extends AsyncDvrDbTask<SeriesRecording, Void, Void> {
public AsyncAddSeriesRecordingTask(Context context) {
@@ -166,9 +148,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Update series recordings.
- */
+ /** Update series recordings. */
public static class AsyncUpdateSeriesRecordingTask
extends AsyncDvrDbTask<SeriesRecording, Void, Void> {
public AsyncUpdateSeriesRecordingTask(Context context) {
@@ -182,9 +162,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Delete series recordings.
- */
+ /** Delete series recordings. */
public static class AsyncDeleteSeriesRecordingTask
extends AsyncDvrDbTask<SeriesRecording, Void, Void> {
public AsyncDeleteSeriesRecordingTask(Context context) {
@@ -198,9 +176,7 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
}
}
- /**
- * Returns all {@link SeriesRecording}s.
- */
+ /** Returns all {@link SeriesRecording}s. */
public abstract static class AsyncDvrQuerySeriesRecordingTask
extends AsyncDvrDbTask<Void, Void, List<SeriesRecording>> {
public AsyncDvrQuerySeriesRecordingTask(Context context) {
@@ -214,8 +190,8 @@ public abstract class AsyncDvrDbTask<Params, Progress, Result>
return null;
}
List<SeriesRecording> scheduledRecordings = new ArrayList<>();
- try (Cursor c = sDbHelper.query(SeriesRecordings.TABLE_NAME,
- SeriesRecording.PROJECTION)) {
+ try (Cursor c =
+ sDbHelper.query(SeriesRecordings.TABLE_NAME, SeriesRecording.PROJECTION)) {
while (c.moveToNext() && !isCancelled()) {
scheduledRecordings.add(SeriesRecording.fromCursor(c));
}
diff --git a/src/com/android/tv/dvr/provider/DvrContract.java b/src/com/android/tv/dvr/provider/DvrContract.java
index f0aca18e..a5f2e2cd 100644
--- a/src/com/android/tv/dvr/provider/DvrContract.java
+++ b/src/com/android/tv/dvr/provider/DvrContract.java
@@ -55,11 +55,57 @@ public final class DvrContract {
/** The recording marked as canceled. */
public static final String STATE_RECORDING_CANCELED = "STATE_RECORDING_CANCELED";
+ /** The recording failed reason for other reasons */
+ public static final String FAILED_REASON_OTHER = "FAILED_REASON_OTHER";
+
+ /** The recording failed because the program ended before recording started. */
+ public static final String FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED =
+ "FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED";
+
+ /** The recording failed because it was not finished successfully */
+ public static final String FAILED_REASON_NOT_FINISHED = "FAILED_REASON_NOT_FINISHED";
+
+ /** The recording failed because the channel ID was invalid */
+ public static final String FAILED_REASON_INVALID_CHANNEL = "FAILED_REASON_INVALID_CHANNEL";
+
+ /** The recording failed because the scheduler was stopped */
+ public static final String FAILED_REASON_SCHEDULER_STOPPED
+ = "FAILED_REASON_SCHEDULER_STOPPED";
+
+ /** The recording failed because some messages were not sent to the message queue */
+ public static final String FAILED_REASON_MESSAGE_NOT_SENT =
+ "FAILED_REASON_MESSAGE_NOT_SENT";
+
+ /**
+ * The recording failed because it was failed to establish a connection to the recording
+ * session for the corresponding TV input.
+ */
+ public static final String FAILED_REASON_CONNECTION_FAILED =
+ "FAILED_REASON_CONNECTION_FAILED";
+
+ /**
+ * The recording failed because a required recording resource was not able to be
+ * allocated.
+ */
+ public static final String FAILED_REASON_RESOURCE_BUSY = "FAILED_REASON_RESOURCE_BUSY";
+
+ /** The recording failed because the input was not available */
+ public static final String FAILED_REASON_INPUT_UNAVAILABLE =
+ "FAILED_REASON_INPUT_UNAVAILABLE";
+
+ /** The recording failed because the input doesn't support recording */
+ public static final String FAILED_REASON_INPUT_DVR_UNSUPPORTED =
+ "FAILED_REASON_INPUT_DVR_UNSUPPORTED";
+
+ /** The recording failed because the space was not sufficient */
+ public static final String FAILED_REASON_INSUFFICIENT_SPACE =
+ "FAILED_REASON_INSUFFICIENT_SPACE";
+
/**
* The priority of this recording.
*
- * <p> The lowest number is recorded first. If there is a tie in priority then the lower id
- * wins. Defaults to {@value Long#MAX_VALUE}
+ * <p>The lowest number is recorded first. If there is a tie in priority then the lower id
+ * wins. Defaults to {@value Long#MAX_VALUE}
*
* <p>Type: INTEGER (long)
*/
@@ -68,8 +114,8 @@ public final class DvrContract {
/**
* The type of this recording.
*
- * <p>This value should be one of the followings: {@link #TYPE_PROGRAM} and
- * {@link #TYPE_TIMED}.
+ * <p>This value should be one of the followings: {@link #TYPE_PROGRAM} and {@link
+ * #TYPE_TIMED}.
*
* <p>This is a required field.
*
@@ -184,9 +230,9 @@ public final class DvrContract {
* The state of this recording.
*
* <p>This value should be one of the followings: {@link #STATE_RECORDING_NOT_STARTED},
- * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED},
- * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and
- * {@link #STATE_RECORDING_DELETED}.
+ * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED}, {@link
+ * #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and {@link
+ * #STATE_RECORDING_DELETED}.
*
* <p>This is a required field.
*
@@ -195,13 +241,20 @@ public final class DvrContract {
public static final String COLUMN_STATE = "state";
/**
+ * The reason of failure of this recording if it's failed.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_FAILED_REASON = "failed_reason";
+
+ /**
* The ID of the parent series recording.
*
* <p>Type: INTEGER (long)
*/
public static final String COLUMN_SERIES_RECORDING_ID = "series_recording_id";
- private Schedules() { }
+ private Schedules() {}
}
/** Column definition for Recording table. */
@@ -210,8 +263,8 @@ public final class DvrContract {
public static final String TABLE_NAME = "series_recording";
/**
- * This value is used for {@link #COLUMN_START_FROM_SEASON} and
- * {@link #COLUMN_START_FROM_EPISODE} to mean record all seasons or episodes.
+ * This value is used for {@link #COLUMN_START_FROM_SEASON} and {@link
+ * #COLUMN_START_FROM_EPISODE} to mean record all seasons or episodes.
*/
public static final int THE_BEGINNING = -1;
@@ -227,21 +280,17 @@ public final class DvrContract {
*/
public static final String OPTION_CHANNEL_ALL = "OPTION_CHANNEL_ALL";
- /**
- * The state indicates that it is a normal one.
- */
+ /** The state indicates that it is a normal one. */
public static final String STATE_SERIES_NORMAL = "STATE_SERIES_NORMAL";
- /**
- * The state indicates that it is stopped.
- */
+ /** The state indicates that it is stopped. */
public static final String STATE_SERIES_STOPPED = "STATE_SERIES_STOPPED";
/**
* The priority of this recording.
*
- * <p> The lowest number is recorded first. If there is a tie in priority then the lower id
- * wins. Defaults to {@value Long#MAX_VALUE}
+ * <p>The lowest number is recorded first. If there is a tie in priority then the lower id
+ * wins. Defaults to {@value Long#MAX_VALUE}
*
* <p>Type: INTEGER (long)
*/
@@ -266,7 +315,7 @@ public final class DvrContract {
public static final String COLUMN_CHANNEL_ID = "channel_id";
/**
- * The ID of the associated series to record.
+ * The ID of the associated series to record.
*
* <p>The id is an opaque but stable string.
*
@@ -300,8 +349,8 @@ public final class DvrContract {
public static final String COLUMN_LONG_DESCRIPTION = "long_description";
/**
- * The number of the earliest season to record. The
- * value {@link #THE_BEGINNING} means record all seasons.
+ * The number of the earliest season to record. The value {@link #THE_BEGINNING} means
+ * record all seasons.
*
* <p>Default value is {@value #THE_BEGINNING} {@link #THE_BEGINNING}.
*
@@ -310,7 +359,7 @@ public final class DvrContract {
public static final String COLUMN_START_FROM_SEASON = "start_from_season";
/**
- * The number of the earliest episode to record in {@link #COLUMN_START_FROM_SEASON}. The
+ * The number of the earliest episode to record in {@link #COLUMN_START_FROM_SEASON}. The
* value {@link #THE_BEGINNING} means record all episodes.
*
* <p>Default value is {@value #THE_BEGINNING} {@link #THE_BEGINNING}.
@@ -322,8 +371,8 @@ public final class DvrContract {
/**
* The series recording option which indicates the channels to record.
*
- * <p>This value should be one of the followings: {@link #OPTION_CHANNEL_ONE} and
- * {@link #OPTION_CHANNEL_ALL}. The default value is OPTION_CHANNEL_ONE.
+ * <p>This value should be one of the followings: {@link #OPTION_CHANNEL_ONE} and {@link
+ * #OPTION_CHANNEL_ALL}. The default value is OPTION_CHANNEL_ONE.
*
* <p>Type: TEXT
*/
@@ -338,6 +387,7 @@ public final class DvrContract {
* to get the canonical genre strings from the text stored in the column.
*
* <p>Type: TEXT
+ *
* @see android.media.tv.TvContract.Programs.Genres
* @see android.media.tv.TvContract.Programs.Genres#encode
* @see android.media.tv.TvContract.Programs.Genres#decode
@@ -350,10 +400,9 @@ public final class DvrContract {
* <p>The data in the column must be a URL, or a URI in one of the following formats:
*
* <ul>
- * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
- * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
- * </li>
- * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+ * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})
* </ul>
*
* <p>Type: TEXT
@@ -366,10 +415,9 @@ public final class DvrContract {
* <p>The data in the column must be a URL, or a URI in one of the following formats:
*
* <ul>
- * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
- * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
- * </li>
- * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+ * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})
* </ul>
*
* <p>Type: TEXT
@@ -379,15 +427,15 @@ public final class DvrContract {
/**
* The state of whether the series recording be canceled or not.
*
- * <p>This value should be one of the followings: {@link #STATE_SERIES_NORMAL} and
- * {@link #STATE_SERIES_STOPPED}. The default value is STATE_SERIES_NORMAL.
+ * <p>This value should be one of the followings: {@link #STATE_SERIES_NORMAL} and {@link
+ * #STATE_SERIES_STOPPED}. The default value is STATE_SERIES_NORMAL.
*
* <p>Type: TEXT
*/
public static final String COLUMN_STATE = "state";
- private SeriesRecordings() { }
+ private SeriesRecordings() {}
}
- private DvrContract() { }
+ private DvrContract() {}
}
diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
index 8b9481a9..41e5a66a 100644
--- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
+++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
@@ -26,98 +26,145 @@ import android.database.sqlite.SQLiteStatement;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
-
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
-/**
- * A data class for one recorded contents.
- */
+/** A data class for one recorded contents. */
public class DvrDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "DvrDatabaseHelper";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
- private static final int DATABASE_VERSION = 17;
+ private static final int DATABASE_VERSION = 18;
private static final String DB_NAME = "dvr.db";
private static final String SQL_CREATE_SCHEDULES =
- "CREATE TABLE " + Schedules.TABLE_NAME + "("
- + Schedules._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + Schedules.COLUMN_PRIORITY + " INTEGER DEFAULT "
- + ScheduledRecording.DEFAULT_PRIORITY + ","
- + Schedules.COLUMN_TYPE + " TEXT NOT NULL,"
- + Schedules.COLUMN_INPUT_ID + " TEXT NOT NULL,"
- + Schedules.COLUMN_CHANNEL_ID + " INTEGER NOT NULL,"
- + Schedules.COLUMN_PROGRAM_ID + " INTEGER,"
- + Schedules.COLUMN_PROGRAM_TITLE + " TEXT,"
- + Schedules.COLUMN_START_TIME_UTC_MILLIS + " INTEGER NOT NULL,"
- + Schedules.COLUMN_END_TIME_UTC_MILLIS + " INTEGER NOT NULL,"
- + Schedules.COLUMN_SEASON_NUMBER + " TEXT,"
- + Schedules.COLUMN_EPISODE_NUMBER + " TEXT,"
- + Schedules.COLUMN_EPISODE_TITLE + " TEXT,"
- + Schedules.COLUMN_PROGRAM_DESCRIPTION + " TEXT,"
- + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION + " TEXT,"
- + Schedules.COLUMN_PROGRAM_POST_ART_URI + " TEXT,"
- + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI + " TEXT,"
- + Schedules.COLUMN_STATE + " TEXT NOT NULL,"
- + Schedules.COLUMN_SERIES_RECORDING_ID + " INTEGER,"
- + "FOREIGN KEY(" + Schedules.COLUMN_SERIES_RECORDING_ID + ") "
- + "REFERENCES " + SeriesRecordings.TABLE_NAME
- + "(" + SeriesRecordings._ID + ") "
+ "CREATE TABLE "
+ + Schedules.TABLE_NAME
+ + "("
+ + Schedules._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Schedules.COLUMN_PRIORITY
+ + " INTEGER DEFAULT "
+ + ScheduledRecording.DEFAULT_PRIORITY
+ + ","
+ + Schedules.COLUMN_TYPE
+ + " TEXT NOT NULL,"
+ + Schedules.COLUMN_INPUT_ID
+ + " TEXT NOT NULL,"
+ + Schedules.COLUMN_CHANNEL_ID
+ + " INTEGER NOT NULL,"
+ + Schedules.COLUMN_PROGRAM_ID
+ + " INTEGER,"
+ + Schedules.COLUMN_PROGRAM_TITLE
+ + " TEXT,"
+ + Schedules.COLUMN_START_TIME_UTC_MILLIS
+ + " INTEGER NOT NULL,"
+ + Schedules.COLUMN_END_TIME_UTC_MILLIS
+ + " INTEGER NOT NULL,"
+ + Schedules.COLUMN_SEASON_NUMBER
+ + " TEXT,"
+ + Schedules.COLUMN_EPISODE_NUMBER
+ + " TEXT,"
+ + Schedules.COLUMN_EPISODE_TITLE
+ + " TEXT,"
+ + Schedules.COLUMN_PROGRAM_DESCRIPTION
+ + " TEXT,"
+ + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION
+ + " TEXT,"
+ + Schedules.COLUMN_PROGRAM_POST_ART_URI
+ + " TEXT,"
+ + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI
+ + " TEXT,"
+ + Schedules.COLUMN_STATE
+ + " TEXT NOT NULL,"
+ + Schedules.COLUMN_SERIES_RECORDING_ID
+ + " INTEGER,"
+ + "FOREIGN KEY("
+ + Schedules.COLUMN_SERIES_RECORDING_ID
+ + ") "
+ + "REFERENCES "
+ + SeriesRecordings.TABLE_NAME
+ + "("
+ + SeriesRecordings._ID
+ + ") "
+ "ON UPDATE CASCADE ON DELETE SET NULL);";
private static final String SQL_DROP_SCHEDULES = "DROP TABLE IF EXISTS " + Schedules.TABLE_NAME;
private static final String SQL_CREATE_SERIES_RECORDINGS =
- "CREATE TABLE " + SeriesRecordings.TABLE_NAME + "("
- + SeriesRecordings._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SeriesRecordings.COLUMN_PRIORITY + " INTEGER DEFAULT "
- + SeriesRecording.DEFAULT_PRIORITY + ","
- + SeriesRecordings.COLUMN_TITLE + " TEXT NOT NULL,"
- + SeriesRecordings.COLUMN_SHORT_DESCRIPTION + " TEXT,"
- + SeriesRecordings.COLUMN_LONG_DESCRIPTION + " TEXT,"
- + SeriesRecordings.COLUMN_INPUT_ID + " TEXT NOT NULL,"
- + SeriesRecordings.COLUMN_CHANNEL_ID + " INTEGER NOT NULL,"
- + SeriesRecordings.COLUMN_SERIES_ID + " TEXT NOT NULL,"
- + SeriesRecordings.COLUMN_START_FROM_SEASON + " INTEGER DEFAULT "
- + SeriesRecordings.THE_BEGINNING + ","
- + SeriesRecordings.COLUMN_START_FROM_EPISODE + " INTEGER DEFAULT "
- + SeriesRecordings.THE_BEGINNING + ","
- + SeriesRecordings.COLUMN_CHANNEL_OPTION + " TEXT DEFAULT "
- + SeriesRecordings.OPTION_CHANNEL_ONE + ","
- + SeriesRecordings.COLUMN_CANONICAL_GENRE + " TEXT,"
- + SeriesRecordings.COLUMN_POSTER_URI + " TEXT,"
- + SeriesRecordings.COLUMN_PHOTO_URI + " TEXT,"
- + SeriesRecordings.COLUMN_STATE + " TEXT)";
+ "CREATE TABLE "
+ + SeriesRecordings.TABLE_NAME
+ + "("
+ + SeriesRecordings._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + SeriesRecordings.COLUMN_PRIORITY
+ + " INTEGER DEFAULT "
+ + SeriesRecording.DEFAULT_PRIORITY
+ + ","
+ + SeriesRecordings.COLUMN_TITLE
+ + " TEXT NOT NULL,"
+ + SeriesRecordings.COLUMN_SHORT_DESCRIPTION
+ + " TEXT,"
+ + SeriesRecordings.COLUMN_LONG_DESCRIPTION
+ + " TEXT,"
+ + SeriesRecordings.COLUMN_INPUT_ID
+ + " TEXT NOT NULL,"
+ + SeriesRecordings.COLUMN_CHANNEL_ID
+ + " INTEGER NOT NULL,"
+ + SeriesRecordings.COLUMN_SERIES_ID
+ + " TEXT NOT NULL,"
+ + SeriesRecordings.COLUMN_START_FROM_SEASON
+ + " INTEGER DEFAULT "
+ + SeriesRecordings.THE_BEGINNING
+ + ","
+ + SeriesRecordings.COLUMN_START_FROM_EPISODE
+ + " INTEGER DEFAULT "
+ + SeriesRecordings.THE_BEGINNING
+ + ","
+ + SeriesRecordings.COLUMN_CHANNEL_OPTION
+ + " TEXT DEFAULT "
+ + SeriesRecordings.OPTION_CHANNEL_ONE
+ + ","
+ + SeriesRecordings.COLUMN_CANONICAL_GENRE
+ + " TEXT,"
+ + SeriesRecordings.COLUMN_POSTER_URI
+ + " TEXT,"
+ + SeriesRecordings.COLUMN_PHOTO_URI
+ + " TEXT,"
+ + SeriesRecordings.COLUMN_STATE
+ + " TEXT)";
- private static final String SQL_DROP_SERIES_RECORDINGS = "DROP TABLE IF EXISTS " +
- SeriesRecordings.TABLE_NAME;
+ private static final String SQL_DROP_SERIES_RECORDINGS =
+ "DROP TABLE IF EXISTS " + SeriesRecordings.TABLE_NAME;
private static final int SQL_DATA_TYPE_LONG = 0;
private static final int SQL_DATA_TYPE_INT = 1;
private static final int SQL_DATA_TYPE_STRING = 2;
- private static final ColumnInfo[] COLUMNS_SCHEDULES = new ColumnInfo[] {
- new ColumnInfo(Schedules._ID, SQL_DATA_TYPE_LONG),
- new ColumnInfo(Schedules.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG),
- new ColumnInfo(Schedules.COLUMN_TYPE, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG),
- new ColumnInfo(Schedules.COLUMN_PROGRAM_ID, SQL_DATA_TYPE_LONG),
- new ColumnInfo(Schedules.COLUMN_PROGRAM_TITLE, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_START_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG),
- new ColumnInfo(Schedules.COLUMN_END_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG),
- new ColumnInfo(Schedules.COLUMN_SEASON_NUMBER, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_EPISODE_NUMBER, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_EPISODE_TITLE, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_PROGRAM_DESCRIPTION, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_PROGRAM_POST_ART_URI, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_STATE, SQL_DATA_TYPE_STRING),
- new ColumnInfo(Schedules.COLUMN_SERIES_RECORDING_ID, SQL_DATA_TYPE_LONG)};
+ private static final ColumnInfo[] COLUMNS_SCHEDULES =
+ new ColumnInfo[] {
+ new ColumnInfo(Schedules._ID, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(Schedules.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(Schedules.COLUMN_TYPE, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(Schedules.COLUMN_PROGRAM_ID, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(Schedules.COLUMN_PROGRAM_TITLE, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_START_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(Schedules.COLUMN_END_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(Schedules.COLUMN_SEASON_NUMBER, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_EPISODE_NUMBER, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_EPISODE_TITLE, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_PROGRAM_DESCRIPTION, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_PROGRAM_POST_ART_URI, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_STATE, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_FAILED_REASON, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(Schedules.COLUMN_SERIES_RECORDING_ID, SQL_DATA_TYPE_LONG)
+ };
private static final String SQL_INSERT_SCHEDULES =
buildInsertSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES);
@@ -125,22 +172,24 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
buildUpdateSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES);
private static final String SQL_DELETE_SCHEDULES = buildDeleteSql(Schedules.TABLE_NAME);
- private static final ColumnInfo[] COLUMNS_SERIES_RECORDINGS = new ColumnInfo[] {
- new ColumnInfo(SeriesRecordings._ID, SQL_DATA_TYPE_LONG),
- new ColumnInfo(SeriesRecordings.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG),
- new ColumnInfo(SeriesRecordings.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG),
- new ColumnInfo(SeriesRecordings.COLUMN_SERIES_ID, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_TITLE, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_SEASON, SQL_DATA_TYPE_INT),
- new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_EPISODE, SQL_DATA_TYPE_INT),
- new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_OPTION, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_CANONICAL_GENRE, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_POSTER_URI, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_PHOTO_URI, SQL_DATA_TYPE_STRING),
- new ColumnInfo(SeriesRecordings.COLUMN_STATE, SQL_DATA_TYPE_STRING)};
+ private static final ColumnInfo[] COLUMNS_SERIES_RECORDINGS =
+ new ColumnInfo[] {
+ new ColumnInfo(SeriesRecordings._ID, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(SeriesRecordings.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(SeriesRecordings.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG),
+ new ColumnInfo(SeriesRecordings.COLUMN_SERIES_ID, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_TITLE, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_SEASON, SQL_DATA_TYPE_INT),
+ new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_EPISODE, SQL_DATA_TYPE_INT),
+ new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_OPTION, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_CANONICAL_GENRE, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_POSTER_URI, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_PHOTO_URI, SQL_DATA_TYPE_STRING),
+ new ColumnInfo(SeriesRecordings.COLUMN_STATE, SQL_DATA_TYPE_STRING)
+ };
private static final String SQL_INSERT_SERIES_RECORDINGS =
buildInsertSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS);
@@ -186,6 +235,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
private static String buildDeleteSql(String tableName) {
return "DELETE FROM " + tableName + " WHERE " + BaseColumns._ID + "=?";
}
+
public DvrDatabaseHelper(Context context) {
super(context.getApplicationContext(), DB_NAME, null, DATABASE_VERSION);
}
@@ -205,16 +255,20 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SCHEDULES);
- db.execSQL(SQL_DROP_SCHEDULES);
- if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SERIES_RECORDINGS);
- db.execSQL(SQL_DROP_SERIES_RECORDINGS);
- onCreate(db);
+ if (oldVersion < 17) {
+ if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SCHEDULES);
+ db.execSQL(SQL_DROP_SCHEDULES);
+ if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SERIES_RECORDINGS);
+ db.execSQL(SQL_DROP_SERIES_RECORDINGS);
+ onCreate(db);
+ }
+ if (oldVersion < 18) {
+ db.execSQL("ALTER TABLE " + Schedules.TABLE_NAME + " ADD COLUMN "
+ + Schedules.COLUMN_FAILED_REASON + " TEXT DEFAULT null;");
+ }
}
- /**
- * Handles the query request and returns a {@link Cursor}.
- */
+ /** Handles the query request and returns a {@link Cursor}. */
public Cursor query(String tableName, String[] projections) {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
@@ -222,9 +276,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
return builder.query(db, projections, null, null, null, null, null);
}
- /**
- * Inserts schedules.
- */
+ /** Inserts schedules. */
public void insertSchedules(ScheduledRecording... scheduledRecordings) {
SQLiteDatabase db = getWritableDatabase();
SQLiteStatement statement = db.compileStatement(SQL_INSERT_SCHEDULES);
@@ -242,9 +294,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Update schedules.
- */
+ /** Update schedules. */
public void updateSchedules(ScheduledRecording... scheduledRecordings) {
SQLiteDatabase db = getWritableDatabase();
SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SCHEDULES);
@@ -263,9 +313,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Delete schedules.
- */
+ /** Delete schedules. */
public void deleteSchedules(ScheduledRecording... scheduledRecordings) {
SQLiteDatabase db = getWritableDatabase();
SQLiteStatement statement = db.compileStatement(SQL_DELETE_SCHEDULES);
@@ -282,9 +330,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Inserts series recordings.
- */
+ /** Inserts series recordings. */
public void insertSeriesRecordings(SeriesRecording... seriesRecordings) {
SQLiteDatabase db = getWritableDatabase();
SQLiteStatement statement = db.compileStatement(SQL_INSERT_SERIES_RECORDINGS);
@@ -302,9 +348,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Update series recordings.
- */
+ /** Update series recordings. */
public void updateSeriesRecordings(SeriesRecording... seriesRecordings) {
SQLiteDatabase db = getWritableDatabase();
SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SERIES_RECORDINGS);
@@ -323,9 +367,7 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Delete series recordings.
- */
+ /** Delete series recordings. */
public void deleteSeriesRecordings(SeriesRecording... seriesRecordings) {
SQLiteDatabase db = getWritableDatabase();
SQLiteStatement statement = db.compileStatement(SQL_DELETE_SERIES_RECORDINGS);
@@ -342,8 +384,8 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
}
}
- private void bindColumns(SQLiteStatement statement, ColumnInfo[] columns,
- ContentValues values) {
+ private void bindColumns(
+ SQLiteStatement statement, ColumnInfo[] columns, ContentValues values) {
for (int i = 0; i < columns.length; ++i) {
ColumnInfo columnInfo = columns[i];
Object value = values.get(columnInfo.name);
@@ -362,14 +404,15 @@ public class DvrDatabaseHelper extends SQLiteOpenHelper {
statement.bindLong(i + 1, (Integer) value);
}
break;
- case SQL_DATA_TYPE_STRING: {
- if (TextUtils.isEmpty((String) value)) {
- statement.bindNull(i + 1);
- } else {
- statement.bindString(i + 1, (String) value);
+ case SQL_DATA_TYPE_STRING:
+ {
+ if (TextUtils.isEmpty((String) value)) {
+ statement.bindNull(i + 1);
+ } else {
+ statement.bindString(i + 1, (String) value);
+ }
+ break;
}
- break;
- }
}
}
}
diff --git a/src/com/android/tv/dvr/provider/DvrDbSync.java b/src/com/android/tv/dvr/provider/DvrDbSync.java
index ff391959..42bc8bcc 100644
--- a/src/com/android/tv/dvr/provider/DvrDbSync.java
+++ b/src/com/android/tv/dvr/provider/DvrDbSync.java
@@ -29,8 +29,7 @@ import android.os.Looper;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
@@ -41,7 +40,6 @@ import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask;
import com.android.tv.util.TvUriMatcher;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -50,6 +48,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* A class to synchronizes DVR DB with TvProvider.
@@ -70,28 +69,31 @@ public class DvrDbSync {
private final DvrManager mDvrManager;
private final DvrDataManagerImpl mDataManager;
private final ChannelDataManager mChannelDataManager;
+ private final Executor mDbExecutor;
private final Queue<Long> mProgramIdQueue = new LinkedList<>();
private QueryProgramTask mQueryProgramTask;
private final SeriesRecordingScheduler mSeriesRecordingScheduler;
- private final ContentObserver mContentObserver = new ContentObserver(new Handler(
- Looper.getMainLooper())) {
- @SuppressLint("SwitchIntDef")
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- switch (TvUriMatcher.match(uri)) {
- case TvUriMatcher.MATCH_PROGRAM:
- if (DEBUG) Log.d(TAG, "onProgramsUpdated");
- onProgramsUpdated();
- break;
- case TvUriMatcher.MATCH_PROGRAM_ID:
- if (DEBUG) {
- Log.d(TAG, "onProgramUpdated: programId=" + ContentUris.parseId(uri));
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @SuppressLint("SwitchIntDef")
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ switch (TvUriMatcher.match(uri)) {
+ case TvUriMatcher.MATCH_PROGRAM:
+ if (DEBUG) Log.d(TAG, "onProgramsUpdated");
+ onProgramsUpdated();
+ break;
+ case TvUriMatcher.MATCH_PROGRAM_ID:
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onProgramUpdated: programId=" + ContentUris.parseId(uri));
+ }
+ onProgramUpdated(ContentUris.parseId(uri));
+ break;
}
- onProgramUpdated(ContentUris.parseId(uri));
- break;
- }
- }
- };
+ }
+ };
private final ChannelDataManager.Listener mChannelDataManagerListener =
new ChannelDataManager.Listener() {
@@ -106,70 +108,76 @@ public class DvrDbSync {
}
@Override
- public void onChannelBrowsableChanged() { }
+ public void onChannelBrowsableChanged() {}
};
- private final ScheduledRecordingListener mScheduleListener = new ScheduledRecordingListener() {
- @Override
- public void onScheduledRecordingAdded(ScheduledRecording... schedules) {
- for (ScheduledRecording schedule : schedules) {
- addProgramIdToCheckIfNeeded(schedule);
- }
- startNextUpdateIfNeeded();
- }
+ private final ScheduledRecordingListener mScheduleListener =
+ new ScheduledRecordingListener() {
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ addProgramIdToCheckIfNeeded(schedule);
+ }
+ startNextUpdateIfNeeded();
+ }
- @Override
- public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
- for (ScheduledRecording schedule : schedules) {
- mProgramIdQueue.remove(schedule.getProgramId());
- }
- }
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ mProgramIdQueue.remove(schedule.getProgramId());
+ }
+ }
- @Override
- public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) {
- for (ScheduledRecording schedule : schedules) {
- mProgramIdQueue.remove(schedule.getProgramId());
- addProgramIdToCheckIfNeeded(schedule);
- }
- startNextUpdateIfNeeded();
- }
- };
+ @Override
+ public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ mProgramIdQueue.remove(schedule.getProgramId());
+ addProgramIdToCheckIfNeeded(schedule);
+ }
+ startNextUpdateIfNeeded();
+ }
+ };
public DvrDbSync(Context context, DvrDataManagerImpl dataManager) {
- this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager(),
- TvApplication.getSingletons(context).getDvrManager(),
- SeriesRecordingScheduler.getInstance(context));
+ this(
+ context,
+ dataManager,
+ TvSingletons.getSingletons(context).getChannelDataManager(),
+ TvSingletons.getSingletons(context).getDvrManager(),
+ SeriesRecordingScheduler.getInstance(context),
+ TvSingletons.getSingletons(context).getDbExecutor());
}
@VisibleForTesting
- DvrDbSync(Context context, DvrDataManagerImpl dataManager,
- ChannelDataManager channelDataManager, DvrManager dvrManager,
- SeriesRecordingScheduler seriesRecordingScheduler) {
+ DvrDbSync(
+ Context context,
+ DvrDataManagerImpl dataManager,
+ ChannelDataManager channelDataManager,
+ DvrManager dvrManager,
+ SeriesRecordingScheduler seriesRecordingScheduler,
+ Executor dbExecutor) {
mContext = context;
mDvrManager = dvrManager;
mDataManager = dataManager;
mChannelDataManager = channelDataManager;
mSeriesRecordingScheduler = seriesRecordingScheduler;
+ mDbExecutor = dbExecutor;
}
- /**
- * Starts the DB sync.
- */
+ /** Starts the DB sync. */
public void start() {
if (!mChannelDataManager.isDbLoadFinished()) {
mChannelDataManager.addListener(mChannelDataManagerListener);
return;
}
- mContext.getContentResolver().registerContentObserver(Programs.CONTENT_URI, true,
- mContentObserver);
+ mContext.getContentResolver()
+ .registerContentObserver(Programs.CONTENT_URI, true, mContentObserver);
mDataManager.addScheduledRecordingListener(mScheduleListener);
onChannelsUpdated();
onProgramsUpdated();
}
- /**
- * Stops the DB sync.
- */
+ /** Stops the DB sync. */
public void stop() {
mProgramIdQueue.clear();
if (mQueryProgramTask != null) {
@@ -185,14 +193,15 @@ public class DvrDbSync {
for (SeriesRecording r : mDataManager.getSeriesRecordings()) {
if (r.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE
&& !mChannelDataManager.doesChannelExistInDb(r.getChannelId())) {
- seriesRecordingsToUpdate.add(SeriesRecording.buildFrom(r)
- .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL)
- .setState(SeriesRecording.STATE_SERIES_STOPPED).build());
+ seriesRecordingsToUpdate.add(
+ SeriesRecording.buildFrom(r)
+ .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL)
+ .setState(SeriesRecording.STATE_SERIES_STOPPED)
+ .build());
}
}
if (!seriesRecordingsToUpdate.isEmpty()) {
- mDataManager.updateSeriesRecording(
- SeriesRecording.toArray(seriesRecordingsToUpdate));
+ mDataManager.updateSeriesRecording(SeriesRecording.toArray(seriesRecordingsToUpdate));
}
List<ScheduledRecording> schedulesToRemove = new ArrayList<>();
for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) {
@@ -202,8 +211,7 @@ public class DvrDbSync {
}
}
if (!schedulesToRemove.isEmpty()) {
- mDataManager.removeScheduledRecording(
- ScheduledRecording.toArray(schedulesToRemove));
+ mDataManager.removeScheduledRecording(ScheduledRecording.toArray(schedulesToRemove));
}
}
@@ -227,7 +235,7 @@ public class DvrDbSync {
if (programId != ScheduledRecording.ID_NOT_SET
&& !mProgramIdQueue.contains(programId)
&& (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
if (DEBUG) Log.d(TAG, "Program ID enqueued: " + programId);
mProgramIdQueue.offer(programId);
// There are schedules to be updated. Pause the SeriesRecordingScheduler until all the
@@ -258,7 +266,7 @@ public class DvrDbSync {
ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(programId);
if (schedule != null
&& (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
if (program == null) {
mDataManager.removeScheduledRecording(schedule);
if (schedule.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) {
@@ -270,15 +278,16 @@ public class DvrDbSync {
}
} else {
long currentTimeMs = System.currentTimeMillis();
- ScheduledRecording.Builder builder = ScheduledRecording.buildFrom(schedule)
- .setEndTimeMs(program.getEndTimeUtcMillis())
- .setSeasonNumber(program.getSeasonNumber())
- .setEpisodeNumber(program.getEpisodeNumber())
- .setEpisodeTitle(program.getEpisodeTitle())
- .setProgramDescription(program.getDescription())
- .setProgramLongDescription(program.getLongDescription())
- .setProgramPosterArtUri(program.getPosterArtUri())
- .setProgramThumbnailUri(program.getThumbnailUri());
+ ScheduledRecording.Builder builder =
+ ScheduledRecording.buildFrom(schedule)
+ .setEndTimeMs(program.getEndTimeUtcMillis())
+ .setSeasonNumber(program.getSeasonNumber())
+ .setEpisodeNumber(program.getEpisodeNumber())
+ .setEpisodeTitle(program.getEpisodeTitle())
+ .setProgramDescription(program.getDescription())
+ .setProgramLongDescription(program.getLongDescription())
+ .setProgramPosterArtUri(program.getPosterArtUri())
+ .setProgramThumbnailUri(program.getThumbnailUri());
boolean needUpdate = false;
// Check the series recording.
SeriesRecording seriesRecordingForOldSchedule =
@@ -289,9 +298,11 @@ public class DvrDbSync {
mDataManager.getSeriesRecording(program.getSeriesId());
if (seriesRecording == null) {
// The new program is episodic while the previous one isn't.
- SeriesRecording newSeriesRecording = mDvrManager.addSeriesRecording(
- program, Collections.singletonList(program),
- SeriesRecording.STATE_SERIES_STOPPED);
+ SeriesRecording newSeriesRecording =
+ mDvrManager.addSeriesRecording(
+ program,
+ Collections.singletonList(program),
+ SeriesRecording.STATE_SERIES_STOPPED);
builder.setSeriesRecordingId(newSeriesRecording.getId());
needUpdate = true;
} else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) {
@@ -302,10 +313,10 @@ public class DvrDbSync {
if (seriesRecordingForOldSchedule != null) {
seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule);
}
- } else if (!Objects.equals(schedule.getSeasonNumber(),
- program.getSeasonNumber())
- || !Objects.equals(schedule.getEpisodeNumber(),
- program.getEpisodeNumber())) {
+ } else if (!Objects.equals(
+ schedule.getSeasonNumber(), program.getSeasonNumber())
+ || !Objects.equals(
+ schedule.getEpisodeNumber(), program.getEpisodeNumber())) {
// The episode number has been changed.
if (seriesRecordingForOldSchedule != null) {
seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule);
@@ -318,23 +329,24 @@ public class DvrDbSync {
// Change start time only when the recording is not started yet.
boolean needToChangeStartTime =
schedule.getState() != ScheduledRecording.STATE_RECORDING_IN_PROGRESS
- && program.getStartTimeUtcMillis() != schedule.getStartTimeMs();
+ && program.getStartTimeUtcMillis() != schedule.getStartTimeMs();
if (needToChangeStartTime) {
builder.setStartTimeMs(program.getStartTimeUtcMillis());
needUpdate = true;
}
- if (needUpdate || schedule.getEndTimeMs() != program.getEndTimeUtcMillis()
+ if (needUpdate
+ || schedule.getEndTimeMs() != program.getEndTimeUtcMillis()
|| !Objects.equals(schedule.getSeasonNumber(), program.getSeasonNumber())
|| !Objects.equals(schedule.getEpisodeNumber(), program.getEpisodeNumber())
|| !Objects.equals(schedule.getEpisodeTitle(), program.getEpisodeTitle())
- || !Objects.equals(schedule.getProgramDescription(),
- program.getDescription())
- || !Objects.equals(schedule.getProgramLongDescription(),
- program.getLongDescription())
- || !Objects.equals(schedule.getProgramPosterArtUri(),
- program.getPosterArtUri())
- || !Objects.equals(schedule.getProgramThumbnailUri(),
- program.getThumbnailUri())) {
+ || !Objects.equals(
+ schedule.getProgramDescription(), program.getDescription())
+ || !Objects.equals(
+ schedule.getProgramLongDescription(), program.getLongDescription())
+ || !Objects.equals(
+ schedule.getProgramPosterArtUri(), program.getPosterArtUri())
+ || !Objects.equals(
+ schedule.getProgramThumbnailUri(), program.getThumbnailUri())) {
mDataManager.updateScheduledRecording(builder.build());
}
if (!seriesRecordingsToUpdate.isEmpty()) {
@@ -349,7 +361,7 @@ public class DvrDbSync {
private final long mProgramId;
QueryProgramTask(long programId) {
- super(mContext.getContentResolver(), programId);
+ super(mDbExecutor, mContext.getContentResolver(), programId);
mProgramId = programId;
}
diff --git a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
index ba0aca51..b7d9f3b3 100644
--- a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
+++ b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
@@ -25,18 +25,16 @@ import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.data.SeasonEpisodeNumber;
import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask;
import com.android.tv.util.AsyncDbTask.CursorFilter;
-import com.android.tv.util.PermissionUtils;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -44,11 +42,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-/**
- * A wrapper of AsyncProgramQueryTask to load the episodic programs for the series recordings.
- */
+/** A wrapper of AsyncProgramQueryTask to load the episodic programs for the series recordings. */
@TargetApi(Build.VERSION_CODES.N)
-abstract public class EpisodicProgramLoadTask {
+public abstract class EpisodicProgramLoadTask {
private static final String TAG = "EpisodicProgramLoadTask";
private static final int PROGRAM_ID_INDEX = Program.getColumnIndex(Programs._ID);
@@ -61,11 +57,15 @@ abstract public class EpisodicProgramLoadTask {
private static final String PARAM_END_TIME = "end_time";
private static final String PROGRAM_PREDICATE =
- Programs.COLUMN_START_TIME_UTC_MILLIS + ">? AND "
- + Programs.COLUMN_RECORDING_PROHIBITED + "=0";
+ Programs.COLUMN_START_TIME_UTC_MILLIS
+ + ">? AND "
+ + Programs.COLUMN_RECORDING_PROHIBITED
+ + "=0";
private static final String PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM =
- Programs.COLUMN_END_TIME_UTC_MILLIS + ">? AND "
- + Programs.COLUMN_RECORDING_PROHIBITED + "=0";
+ Programs.COLUMN_END_TIME_UTC_MILLIS
+ + ">? AND "
+ + Programs.COLUMN_RECORDING_PROHIBITED
+ + "=0";
private static final String CHANNEL_ID_PREDICATE = Programs.COLUMN_CHANNEL_ID + "=?";
private static final String PROGRAM_TITLE_PREDICATE = Programs.COLUMN_TITLE + "=?";
@@ -80,10 +80,7 @@ abstract public class EpisodicProgramLoadTask {
private final ArrayList<SeriesRecording> mSeriesRecordings = new ArrayList<>();
private AsyncProgramQueryTask mProgramQueryTask;
- /**
- *
- * Constructor used to load programs for one series recording with the given channel option.
- */
+ /** Constructor used to load programs for one series recording with the given channel option. */
public EpisodicProgramLoadTask(Context context, SeriesRecording seriesRecording) {
this(context, Collections.singletonList(seriesRecording));
}
@@ -94,64 +91,56 @@ abstract public class EpisodicProgramLoadTask {
*/
public EpisodicProgramLoadTask(Context context, Collection<SeriesRecording> seriesRecordings) {
mContext = context.getApplicationContext();
- mDataManager = TvApplication.getSingletons(context).getDvrDataManager();
+ mDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
mSeriesRecordings.addAll(seriesRecordings);
}
- /**
- * Returns the series recordings.
- */
+ /** Returns the series recordings. */
public List<SeriesRecording> getSeriesRecordings() {
return mSeriesRecordings;
}
- /**
- * Returns the program query task. It is {@code null} until it is executed.
- */
+ /** Returns the program query task. It is {@code null} until it is executed. */
@Nullable
public AsyncProgramQueryTask getTask() {
return mProgramQueryTask;
}
- /**
- * Enables loading current programs. The default value is {@code false}.
- */
+ /** Enables loading current programs. The default value is {@code false}. */
public EpisodicProgramLoadTask setLoadCurrentProgram(boolean loadCurrentProgram) {
- SoftPreconditions.checkState(mProgramQueryTask == null, TAG,
- "Can't change setting after execution.");
+ SoftPreconditions.checkState(
+ mProgramQueryTask == null, TAG, "Can't change setting after execution.");
mLoadCurrentProgram = loadCurrentProgram;
return this;
}
- /**
- * Enables already schedules episodes. The default value is {@code false}.
- */
+ /** Enables already schedules episodes. The default value is {@code false}. */
public EpisodicProgramLoadTask setLoadScheduledEpisode(boolean loadScheduledEpisode) {
- SoftPreconditions.checkState(mProgramQueryTask == null, TAG,
- "Can't change setting after execution.");
+ SoftPreconditions.checkState(
+ mProgramQueryTask == null, TAG, "Can't change setting after execution.");
mLoadScheduledEpisode = loadScheduledEpisode;
return this;
}
/**
- * Enables loading disallowed programs whose schedules were removed manually by the user.
- * The default value is {@code false}.
+ * Enables loading disallowed programs whose schedules were removed manually by the user. The
+ * default value is {@code false}.
*/
public EpisodicProgramLoadTask setLoadDisallowedProgram(boolean loadDisallowedProgram) {
- SoftPreconditions.checkState(mProgramQueryTask == null, TAG,
- "Can't change setting after execution.");
+ SoftPreconditions.checkState(
+ mProgramQueryTask == null, TAG, "Can't change setting after execution.");
mLoadDisallowedProgram = loadDisallowedProgram;
return this;
}
/**
- * Gives the option whether to ignore the channel option when matching programs.
- * If {@code ignoreChannelOption} is {@code true}, the program will be matched with
- * {@link SeriesRecording#OPTION_CHANNEL_ALL} option.
+ * Gives the option whether to ignore the channel option when matching programs. If {@code
+ * ignoreChannelOption} is {@code true}, the program will be matched with {@link
+ * SeriesRecording#OPTION_CHANNEL_ALL} option.
*/
public EpisodicProgramLoadTask setIgnoreChannelOption(boolean ignoreChannelOption) {
- SoftPreconditions.checkState(mProgramQueryTask == null, TAG,
- "Can't change setting after execution.");
+ SoftPreconditions.checkState(
+ mProgramQueryTask == null, TAG, "Can't change setting after execution.");
mIgnoreChannelOption = ignoreChannelOption;
return this;
}
@@ -162,12 +151,15 @@ abstract public class EpisodicProgramLoadTask {
* @see com.android.tv.util.AsyncDbTask#executeOnDbThread
*/
public void execute() {
- if (SoftPreconditions.checkState(mProgramQueryTask == null, TAG,
+ if (SoftPreconditions.checkState(
+ mProgramQueryTask == null,
+ TAG,
"Can't execute task: the task is already running.")) {
- mQueryAllChannels = mSeriesRecordings.size() > 1
- || mSeriesRecordings.get(0).getChannelOption()
- == SeriesRecording.OPTION_CHANNEL_ALL
- || mIgnoreChannelOption;
+ mQueryAllChannels =
+ mSeriesRecordings.size() > 1
+ || mSeriesRecordings.get(0).getChannelOption()
+ == SeriesRecording.OPTION_CHANNEL_ALL
+ || mIgnoreChannelOption;
mProgramQueryTask = createTask();
mProgramQueryTask.executeOnDbThread();
}
@@ -184,22 +176,22 @@ abstract public class EpisodicProgramLoadTask {
}
}
- /**
- * Runs on the UI thread after the program loading finishes successfully.
- */
- protected void onPostExecute(List<Program> programs) {
- }
+ /** Runs on the UI thread after the program loading finishes successfully. */
+ protected void onPostExecute(List<Program> programs) {}
- /**
- * Runs on the UI thread after the program loading was canceled.
- */
- protected void onCancelled(List<Program> programs) {
- }
+ /** Runs on the UI thread after the program loading was canceled. */
+ protected void onCancelled(List<Program> programs) {}
private AsyncProgramQueryTask createTask() {
SqlParams sqlParams = createSqlParams();
- return new AsyncProgramQueryTask(mContext.getContentResolver(), sqlParams.uri,
- sqlParams.selection, sqlParams.selectionArgs, null, sqlParams.filter) {
+ return new AsyncProgramQueryTask(
+ TvSingletons.getSingletons(mContext).getDbExecutor(),
+ mContext.getContentResolver(),
+ sqlParams.uri,
+ sqlParams.selection,
+ sqlParams.selectionArgs,
+ null,
+ sqlParams.filter) {
@Override
protected void onPostExecute(List<Program> programs) {
EpisodicProgramLoadTask.this.onPostExecute(programs);
@@ -217,8 +209,11 @@ abstract public class EpisodicProgramLoadTask {
if (PermissionUtils.hasAccessAllEpg(mContext)) {
sqlParams.uri = Programs.CONTENT_URI;
// Base
- StringBuilder selection = new StringBuilder(mLoadCurrentProgram
- ? PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM : PROGRAM_PREDICATE);
+ StringBuilder selection =
+ new StringBuilder(
+ mLoadCurrentProgram
+ ? PROGRAM_PREDICATE_WITH_CURRENT_PROGRAM
+ : PROGRAM_PREDICATE);
List<String> args = new ArrayList<>();
args.add(Long.toString(System.currentTimeMillis()));
// Channel option
@@ -237,15 +232,21 @@ abstract public class EpisodicProgramLoadTask {
} else {
// The query includes the current program. Will be filtered if needed.
if (mQueryAllChannels) {
- sqlParams.uri = Programs.CONTENT_URI.buildUpon()
- .appendQueryParameter(PARAM_START_TIME,
- String.valueOf(System.currentTimeMillis()))
- .appendQueryParameter(PARAM_END_TIME, String.valueOf(Long.MAX_VALUE))
- .build();
+ sqlParams.uri =
+ Programs.CONTENT_URI
+ .buildUpon()
+ .appendQueryParameter(
+ PARAM_START_TIME,
+ String.valueOf(System.currentTimeMillis()))
+ .appendQueryParameter(
+ PARAM_END_TIME, String.valueOf(Long.MAX_VALUE))
+ .build();
} else {
- sqlParams.uri = TvContract.buildProgramsUriForChannel(
- mSeriesRecordings.get(0).getChannelId(),
- System.currentTimeMillis(), Long.MAX_VALUE);
+ sqlParams.uri =
+ TvContract.buildProgramsUriForChannel(
+ mSeriesRecordings.get(0).getChannelId(),
+ System.currentTimeMillis(),
+ Long.MAX_VALUE);
}
sqlParams.selection = null;
sqlParams.selectionArgs = null;
@@ -292,16 +293,19 @@ abstract public class EpisodicProgramLoadTask {
for (SeriesRecording seriesRecording : mSeriesRecordings) {
boolean programMatches;
if (mIgnoreChannelOption) {
- programMatches = seriesRecording.matchProgram(program,
- SeriesRecording.OPTION_CHANNEL_ALL);
+ programMatches =
+ seriesRecording.matchProgram(
+ program, SeriesRecording.OPTION_CHANNEL_ALL);
} else {
programMatches = seriesRecording.matchProgram(program);
}
if (programMatches) {
return mLoadScheduledEpisode
- || !mSeasonEpisodeNumbers.contains(new SeasonEpisodeNumber(
- seriesRecording.getId(), program.getSeasonNumber(),
- program.getEpisodeNumber()));
+ || !mSeasonEpisodeNumbers.contains(
+ new SeasonEpisodeNumber(
+ seriesRecording.getId(),
+ program.getSeasonNumber(),
+ program.getEpisodeNumber()));
}
}
return false;
@@ -316,7 +320,8 @@ abstract public class EpisodicProgramLoadTask {
@Override
public boolean filter(Cursor c) {
return (mLoadCurrentProgram || c.getLong(START_TIME_INDEX) > System.currentTimeMillis())
- && c.getInt(RECORDING_PROHIBITED_INDEX) != 0 && super.filter(c);
+ && c.getInt(RECORDING_PROHIBITED_INDEX) != 0
+ && super.filter(c);
}
}
diff --git a/src/com/android/tv/dvr/recorder/ConflictChecker.java b/src/com/android/tv/dvr/recorder/ConflictChecker.java
index 8aa90116..bfd315e9 100644
--- a/src/com/android/tv/dvr/recorder/ConflictChecker.java
+++ b/src/com/android/tv/dvr/recorder/ConflictChecker.java
@@ -27,21 +27,19 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.ArraySet;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.OnTvViewChannelChangeListener;
import com.android.tv.MainActivity;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.WeakHandler;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
-
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -50,8 +48,9 @@ import java.util.concurrent.TimeUnit;
/**
* Checking the runtime conflict of DVR recording.
- * <p>
- * This class runs only while the {@link MainActivity} is resumed and holds the upcoming conflicts.
+ *
+ * <p>This class runs only while the {@link MainActivity} is resumed and holds the upcoming
+ * conflicts.
*/
@TargetApi(Build.VERSION_CODES.N)
@MainThread
@@ -87,24 +86,40 @@ public class ConflictChecker {
private final ScheduledRecordingListener mScheduledRecordingListener =
new ScheduledRecordingListener() {
- @Override
- public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
- if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + scheduledRecordings);
- mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
- }
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onScheduledRecordingAdded: "
+ + Arrays.toString(scheduledRecordings));
+ }
+ mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
+ }
- @Override
- public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
- if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + scheduledRecordings);
- mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
- }
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onScheduledRecordingRemoved: "
+ + Arrays.toString(scheduledRecordings));
+ }
+ mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
+ }
- @Override
- public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
- if (DEBUG) Log.d(TAG, "onScheduledRecordingStatusChanged: " + scheduledRecordings);
- mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
- }
- };
+ @Override
+ public void onScheduledRecordingStatusChanged(
+ ScheduledRecording... scheduledRecordings) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onScheduledRecordingStatusChanged: "
+ + Arrays.toString(scheduledRecordings));
+ }
+ mHandler.sendEmptyMessage(MSG_CHECK_CONFLICT);
+ }
+ };
private final OnTvViewChannelChangeListener mOnTvViewChannelChangeListener =
new OnTvViewChannelChangeListener() {
@@ -118,15 +133,13 @@ public class ConflictChecker {
public ConflictChecker(MainActivity mainActivity) {
mMainActivity = mainActivity;
- ApplicationSingletons appSingletons = TvApplication.getSingletons(mainActivity);
- mChannelDataManager = appSingletons.getChannelDataManager();
- mScheduleManager = appSingletons.getDvrScheduleManager();
- mSessionManager = appSingletons.getInputSessionManager();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(mainActivity);
+ mChannelDataManager = tvSingletons.getChannelDataManager();
+ mScheduleManager = tvSingletons.getDvrScheduleManager();
+ mSessionManager = tvSingletons.getInputSessionManager();
}
- /**
- * Starts checking the conflict.
- */
+ /** Starts checking the conflict. */
public void start() {
if (mStarted) {
return;
@@ -137,9 +150,7 @@ public class ConflictChecker {
mSessionManager.addOnTvViewChannelChangeListener(mOnTvViewChannelChangeListener);
}
- /**
- * Stops checking the conflict.
- */
+ /** Stops checking the conflict. */
public void stop() {
if (!mStarted) {
return;
@@ -150,23 +161,17 @@ public class ConflictChecker {
mHandler.removeCallbacksAndMessages(null);
}
- /**
- * Returns the upcoming conflicts.
- */
+ /** Returns the upcoming conflicts. */
public List<ScheduledRecording> getUpcomingConflicts() {
return new ArrayList<>(mUpcomingConflicts);
}
- /**
- * Adds a {@link OnUpcomingConflictChangeListener}.
- */
+ /** Adds a {@link OnUpcomingConflictChangeListener}. */
public void addOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) {
mOnUpcomingConflictChangeListeners.add(listener);
}
- /**
- * Removes the {@link OnUpcomingConflictChangeListener}.
- */
+ /** Removes the {@link OnUpcomingConflictChangeListener}. */
public void removeOnUpcomingConflictChangeListener(OnUpcomingConflictChangeListener listener) {
mOnUpcomingConflictChangeListeners.remove(listener);
}
@@ -177,9 +182,7 @@ public class ConflictChecker {
}
}
- /**
- * Remembers the user's decision to record while watching the channel.
- */
+ /** Remembers the user's decision to record while watching the channel. */
public void setCheckedConflictsForChannel(long mChannelId, List<ScheduledRecording> conflicts) {
mCheckedConflictsMap.put(mChannelId, new ArrayList<>(conflicts));
}
@@ -190,8 +193,7 @@ public class ConflictChecker {
if (DEBUG) Log.d(TAG, "Handling MSG_CHECK_CONFLICT");
mHandler.removeMessages(MSG_CHECK_CONFLICT);
mUpcomingConflicts.clear();
- if (!mScheduleManager.isInitialized()
- || !mChannelDataManager.isDbLoadFinished()) {
+ if (!mScheduleManager.isInitialized() || !mChannelDataManager.isDbLoadFinished()) {
mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, CHECK_RETRY_PERIOD_MS);
notifyUpcomingConflictChanged();
return;
@@ -209,8 +211,8 @@ public class ConflictChecker {
long channelId = ContentUris.parseId(channelUri);
Channel channel = mChannelDataManager.getChannel(channelId);
// The conflicts caused by watching the channel.
- List<ScheduledRecording> conflicts = mScheduleManager
- .getConflictingSchedulesForWatching(channel.getId());
+ List<ScheduledRecording> conflicts =
+ mScheduleManager.getConflictingSchedulesForWatching(channel.getId());
long earliestToCheck = Long.MAX_VALUE;
long currentTimeMs = System.currentTimeMillis();
for (ScheduledRecording schedule : conflicts) {
@@ -239,18 +241,15 @@ public class ConflictChecker {
}
}
if (earliestToCheck != Long.MAX_VALUE) {
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT,
- earliestToCheck - currentTimeMs);
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_CONFLICT, earliestToCheck - currentTimeMs);
}
if (DEBUG) Log.d(TAG, "upcoming conflicts: " + mUpcomingConflicts);
notifyUpcomingConflictChanged();
if (!mUpcomingConflicts.isEmpty()
&& !DvrUiHelper.isChannelWatchConflictDialogShown(mMainActivity)) {
// Don't show the conflict dialog if the user already knows.
- List<ScheduledRecording> checkedConflicts = mCheckedConflictsMap.get(
- channel.getId());
- if (checkedConflicts == null
- || !checkedConflicts.containsAll(mUpcomingConflicts)) {
+ List<ScheduledRecording> checkedConflicts = mCheckedConflictsMap.get(channel.getId());
+ if (checkedConflicts == null || !checkedConflicts.containsAll(mUpcomingConflicts)) {
DvrUiHelper.showChannelWatchConflictDialog(mMainActivity, channel);
}
}
@@ -271,9 +270,7 @@ public class ConflictChecker {
}
}
- /**
- * A listener for the change of upcoming conflicts.
- */
+ /** A listener for the change of upcoming conflicts. */
public interface OnUpcomingConflictChangeListener {
void onUpcomingConflictChange();
}
diff --git a/src/com/android/tv/dvr/recorder/DvrRecordingService.java b/src/com/android/tv/dvr/recorder/DvrRecordingService.java
index 5d324ca5..9fdbf062 100644
--- a/src/com/android/tv/dvr/recorder/DvrRecordingService.java
+++ b/src/com/android/tv/dvr/recorder/DvrRecordingService.java
@@ -29,21 +29,20 @@ import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.OnRecordingSessionChangeListener;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.util.Clock;
import com.android.tv.dvr.WritableDvrDataManager;
-import com.android.tv.util.Clock;
import com.android.tv.util.RecurringRunner;
/**
- * DVR recording service. This service should be a foreground service and send a notification
- * to users to do long-running recording task.
+ * DVR recording service. This service should be a foreground service and send a notification to
+ * users to do long-running recording task.
*
* <p>This service is waken up when there's a scheduled recording coming soon and at boot completed
* since schedules have to be loaded from databases in order to set new recording alarms, which
@@ -67,9 +66,9 @@ public class DvrRecordingService extends Service {
/**
* Starts the service in foreground.
*
- * @param startForRecording {@code true} if there are upcoming recordings in
- * {@link RecordingScheduler#SOON_DURATION_IN_MS} and the service is
- * started in foreground for those recordings.
+ * @param startForRecording {@code true} if there are upcoming recordings in {@link
+ * RecordingScheduler#SOON_DURATION_IN_MS} and the service is started in foreground for
+ * those recordings.
*/
@MainThread
static void startForegroundService(Context context, boolean startForRecording) {
@@ -99,7 +98,8 @@ public class DvrRecordingService extends Service {
@VisibleForTesting boolean mIsRecording;
private boolean mForeground;
- @VisibleForTesting final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener =
+ @VisibleForTesting
+ final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener =
new OnRecordingSessionChangeListener() {
@Override
public void onRecordingSessionChange(final boolean create, final int count) {
@@ -114,18 +114,22 @@ public class DvrRecordingService extends Service {
@Override
public void onCreate() {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate();
SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG);
sInstance = this;
- ApplicationSingletons singletons = TvApplication.getSingletons(this);
+ TvSingletons singletons = TvSingletons.getSingletons(this);
WritableDvrDataManager dataManager =
(WritableDvrDataManager) singletons.getDvrDataManager();
mSessionManager = singletons.getInputSessionManager();
mSessionManager.addOnRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
- mReaperRunner = new RecurringRunner(this, java.util.concurrent.TimeUnit.DAYS.toMillis(1),
- new ScheduledProgramReaper(dataManager, Clock.SYSTEM), null);
+ mReaperRunner =
+ new RecurringRunner(
+ this,
+ java.util.concurrent.TimeUnit.DAYS.toMillis(1),
+ new ScheduledProgramReaper(dataManager, Clock.SYSTEM),
+ null);
mReaperRunner.start();
mContentTitle = getString(R.string.dvr_notification_content_title);
mContentTextRecording = getString(R.string.dvr_notification_content_text_recording);
@@ -179,13 +183,16 @@ public class DvrRecordingService extends Service {
@VisibleForTesting
protected void startForegroundInternal(boolean hasUpcomingRecording) {
- // STOPSHIP: Replace the content title with real UX strings
- Notification.Builder builder = new Notification.Builder(this)
- .setContentTitle(mContentTitle)
- .setContentText(hasUpcomingRecording ? mContentTextRecording : mContentTextLoading)
- .setSmallIcon(R.drawable.ic_dvr);
- Notification notification = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
- builder.setChannelId(DVR_NOTIFICATION_CHANNEL_ID).build() : builder.build();
+ Notification.Builder builder =
+ new Notification.Builder(this)
+ .setContentTitle(mContentTitle)
+ .setContentText(
+ hasUpcomingRecording ? mContentTextRecording : mContentTextLoading)
+ .setSmallIcon(R.drawable.ic_dvr);
+ Notification notification =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ ? builder.setChannelId(DVR_NOTIFICATION_CHANNEL_ID).build()
+ : builder.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
}
@@ -196,10 +203,11 @@ public class DvrRecordingService extends Service {
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- // STOPSHIP: Replace the channel name with real UX strings
- mNotificationChannel = new NotificationChannel(DVR_NOTIFICATION_CHANNEL_ID,
- getString(R.string.dvr_notification_channel_name),
- NotificationManager.IMPORTANCE_LOW);
+ mNotificationChannel =
+ new NotificationChannel(
+ DVR_NOTIFICATION_CHANNEL_ID,
+ getString(R.string.dvr_notification_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
.createNotificationChannel(mNotificationChannel);
}
diff --git a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java
index f1c0020b..bb5ea99d 100644
--- a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java
+++ b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java
@@ -21,18 +21,16 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.RequiresApi;
+import com.android.tv.Starter;
+import com.android.tv.TvSingletons;
-import com.android.tv.TvApplication;
-
-/**
- * Signals the DVR to start recording shows <i>soon</i>.
- */
+/** Signals the DVR to start recording shows <i>soon</i>. */
@RequiresApi(Build.VERSION_CODES.N)
public class DvrStartRecordingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- TvApplication.setCurrentRunningProcess(context, true);
- RecordingScheduler scheduler = TvApplication.getSingletons(context).getRecordingScheduler();
+ Starter.start(context);
+ RecordingScheduler scheduler = TvSingletons.getSingletons(context).getRecordingScheduler();
if (scheduler != null) {
scheduler.updateAndStartServiceIfNeeded();
}
diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java
index fee4568e..1021b2bc 100644
--- a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java
+++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java
@@ -25,17 +25,15 @@ import android.support.annotation.VisibleForTesting;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
-
import com.android.tv.InputSessionManager;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.Clock;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.util.Clock;
import com.android.tv.util.CompositeComparator;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -43,9 +41,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
-/**
- * The scheduler for a TV input.
- */
+/** The scheduler for a TV input. */
public class InputTaskScheduler {
private static final String TAG = "InputTaskScheduler";
private static final boolean DEBUG = false;
@@ -66,9 +62,7 @@ public class InputTaskScheduler {
RecordingTask.END_TIME_COMPARATOR,
RecordingTask.ID_COMPARATOR);
- /**
- * Returns the comparator which the schedules are sorted with when executed.
- */
+ /** Returns the comparator which the schedules are sorted with when executed. */
public static Comparator<ScheduledRecording> getRecordingOrderComparator() {
return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR;
}
@@ -81,8 +75,8 @@ public class InputTaskScheduler {
private final long mId;
private final RecordingTask mTask;
- HandlerWrapper(Looper looper, ScheduledRecording scheduledRecording,
- RecordingTask recordingTask) {
+ HandlerWrapper(
+ Looper looper, ScheduledRecording scheduledRecording, RecordingTask recordingTask) {
super(looper, recordingTask);
mId = scheduledRecording.getId();
mTask = recordingTask;
@@ -94,7 +88,7 @@ public class InputTaskScheduler {
// The RecordingTask gets a chance first.
// It must return false to pass this message to here.
if (msg.what == MESSAGE_REMOVE) {
- if (DEBUG) Log.d(TAG, "done " + mId);
+ if (DEBUG) Log.d(TAG, "done " + mId);
mPendingRecordings.remove(mId);
}
removeCallbacksAndMessages(null);
@@ -120,17 +114,37 @@ public class InputTaskScheduler {
private final Object mInputLock = new Object();
private final RecordingTaskFactory mRecordingTaskFactory;
- public InputTaskScheduler(Context context, TvInputInfo input, Looper looper,
- ChannelDataManager channelDataManager, DvrManager dvrManager,
- DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock) {
- this(context, input, looper, channelDataManager, dvrManager, dataManager, sessionManager,
- clock, null);
+ public InputTaskScheduler(
+ Context context,
+ TvInputInfo input,
+ Looper looper,
+ ChannelDataManager channelDataManager,
+ DvrManager dvrManager,
+ DvrDataManager dataManager,
+ InputSessionManager sessionManager,
+ Clock clock) {
+ this(
+ context,
+ input,
+ looper,
+ channelDataManager,
+ dvrManager,
+ dataManager,
+ sessionManager,
+ clock,
+ null);
}
@VisibleForTesting
- InputTaskScheduler(Context context, TvInputInfo input, Looper looper,
- ChannelDataManager channelDataManager, DvrManager dvrManager,
- DvrDataManager dataManager, InputSessionManager sessionManager, Clock clock,
+ InputTaskScheduler(
+ Context context,
+ TvInputInfo input,
+ Looper looper,
+ ChannelDataManager channelDataManager,
+ DvrManager dvrManager,
+ DvrDataManager dataManager,
+ InputSessionManager sessionManager,
+ Clock clock,
RecordingTaskFactory recordingTaskFactory) {
if (DEBUG) Log.d(TAG, "Creating scheduler for " + input);
mContext = context;
@@ -142,22 +156,32 @@ public class InputTaskScheduler {
mSessionManager = sessionManager;
mClock = clock;
mMainThreadHandler = new Handler(Looper.getMainLooper());
- mRecordingTaskFactory = recordingTaskFactory != null ? recordingTaskFactory
- : new RecordingTaskFactory() {
- @Override
- public RecordingTask createRecordingTask(ScheduledRecording schedule, Channel channel,
- DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, Clock clock) {
- return new RecordingTask(mContext, schedule, channel, mDvrManager, mSessionManager,
- mDataManager, mClock);
- }
- };
+ mRecordingTaskFactory =
+ recordingTaskFactory != null
+ ? recordingTaskFactory
+ : new RecordingTaskFactory() {
+ @Override
+ public RecordingTask createRecordingTask(
+ ScheduledRecording schedule,
+ Channel channel,
+ DvrManager dvrManager,
+ InputSessionManager sessionManager,
+ WritableDvrDataManager dataManager,
+ Clock clock) {
+ return new RecordingTask(
+ mContext,
+ schedule,
+ channel,
+ mDvrManager,
+ mSessionManager,
+ mDataManager,
+ mClock);
+ }
+ };
mHandler = new WorkerThreadHandler(looper);
}
- /**
- * Adds a {@link ScheduledRecording}.
- */
+ /** Adds a {@link ScheduledRecording}. */
public void addSchedule(ScheduledRecording schedule) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_SCHEDULED_RECORDING, schedule));
}
@@ -173,9 +197,7 @@ public class InputTaskScheduler {
mHandler.sendEmptyMessage(MSG_BUILD_SCHEDULE);
}
- /**
- * Removes the {@link ScheduledRecording}.
- */
+ /** Removes the {@link ScheduledRecording}. */
public void removeSchedule(ScheduledRecording schedule) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_SCHEDULED_RECORDING, schedule));
}
@@ -194,9 +216,7 @@ public class InputTaskScheduler {
}
}
- /**
- * Updates the {@link ScheduledRecording}.
- */
+ /** Updates the {@link ScheduledRecording}. */
public void updateSchedule(ScheduledRecording schedule) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SCHEDULED_RECORDING, schedule));
}
@@ -224,18 +244,14 @@ public class InputTaskScheduler {
}
}
- /**
- * Updates the TV input.
- */
+ /** Updates the TV input. */
public void updateTvInputInfo(TvInputInfo input) {
synchronized (mInputLock) {
mInput = input;
}
}
- /**
- * Stops the input task scheduler.
- */
+ /** Stops the input task scheduler. */
public void stop() {
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessage(MSG_STOP_SCHEDULE);
@@ -262,7 +278,9 @@ public class InputTaskScheduler {
ScheduledRecording schedule = iter.next();
if (schedule.getEndTimeMs() - currentTimeMs
<= MIN_REMAIN_DURATION_PERCENT * schedule.getDuration()) {
- fail(schedule);
+ Log.e(TAG, "Error! Program ended before recording started:" + schedule);
+ fail(schedule,
+ ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED);
iter.remove();
}
}
@@ -274,7 +292,8 @@ public class InputTaskScheduler {
for (ScheduledRecording schedule : mWaitingSchedules.values()) {
if (schedule.getState() != ScheduledRecording.STATE_RECORDING_CANCELED
&& schedule.getStartTimeMs() - RecordingTask.RECORDING_EARLY_START_OFFSET_MS
- <= currentTimeMs && schedule.getEndTimeMs() > currentTimeMs) {
+ <= currentTimeMs
+ && schedule.getEndTimeMs() > currentTimeMs) {
schedulesToStart.add(schedule);
}
}
@@ -321,10 +340,12 @@ public class InputTaskScheduler {
earliest = schedule.getEndTimeMs();
}
} else {
- if (earliest > schedule.getStartTimeMs()
- - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) {
- earliest = schedule.getStartTimeMs()
- - RecordingTask.RECORDING_EARLY_START_OFFSET_MS;
+ if (earliest
+ > schedule.getStartTimeMs()
+ - RecordingTask.RECORDING_EARLY_START_OFFSET_MS) {
+ earliest =
+ schedule.getStartTimeMs()
+ - RecordingTask.RECORDING_EARLY_START_OFFSET_MS;
}
}
}
@@ -333,8 +354,9 @@ public class InputTaskScheduler {
private RecordingTask createRecordingTask(ScheduledRecording schedule) {
Channel channel = mChannelDataManager.getChannel(schedule.getChannelId());
- RecordingTask recordingTask = mRecordingTaskFactory.createRecordingTask(schedule, channel,
- mDvrManager, mSessionManager, mDataManager, mClock);
+ RecordingTask recordingTask =
+ mRecordingTaskFactory.createRecordingTask(
+ schedule, channel, mDvrManager, mSessionManager, mDataManager, mClock);
HandlerWrapper handlerWrapper = new HandlerWrapper(mLooper, schedule, recordingTask);
mPendingRecordings.put(schedule.getId(), handlerWrapper);
return recordingTask;
@@ -369,21 +391,24 @@ public class InputTaskScheduler {
return candidate;
}
- private void fail(ScheduledRecording schedule) {
+ private void fail(ScheduledRecording schedule, int reason) {
// It's called when the scheduling has been failed without creating RecordingTask.
- runOnMainHandler(new Runnable() {
- @Override
- public void run() {
- ScheduledRecording scheduleInManager =
- mDataManager.getScheduledRecording(schedule.getId());
- if (scheduleInManager != null) {
- // The schedule should be updated based on the object from DataManager in case
- // when it has been updated.
- mDataManager.changeState(scheduleInManager,
- ScheduledRecording.STATE_RECORDING_FAILED);
- }
- }
- });
+ runOnMainHandler(
+ new Runnable() {
+ @Override
+ public void run() {
+ ScheduledRecording scheduleInManager =
+ mDataManager.getScheduledRecording(schedule.getId());
+ if (scheduleInManager != null) {
+ // The schedule should be updated based on the object from DataManager
+ // in case when it has been updated.
+ mDataManager.changeState(
+ scheduleInManager,
+ ScheduledRecording.STATE_RECORDING_FAILED,
+ reason);
+ }
+ }
+ });
}
private void runOnMainHandler(Runnable runnable) {
@@ -396,9 +421,13 @@ public class InputTaskScheduler {
@VisibleForTesting
interface RecordingTaskFactory {
- RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, Channel channel,
- DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, Clock clock);
+ RecordingTask createRecordingTask(
+ ScheduledRecording scheduledRecording,
+ Channel channel,
+ DvrManager dvrManager,
+ InputSessionManager sessionManager,
+ WritableDvrDataManager dataManager,
+ Clock clock);
}
private class WorkerThreadHandler extends Handler {
@@ -417,6 +446,7 @@ public class InputTaskScheduler {
break;
case MSG_UPDATE_SCHEDULED_RECORDING:
handleUpdateSchedule((ScheduledRecording) msg.obj);
+ break;
case MSG_BUILD_SCHEDULE:
handleBuildSchedule();
break;
diff --git a/src/com/android/tv/dvr/recorder/RecordingScheduler.java b/src/com/android/tv/dvr/recorder/RecordingScheduler.java
index cbaf46b5..f309537d 100644
--- a/src/com/android/tv/dvr/recorder/RecordingScheduler.java
+++ b/src/com/android/tv/dvr/recorder/RecordingScheduler.java
@@ -31,11 +31,10 @@ import android.support.annotation.VisibleForTesting;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.InputSessionManager;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.util.Clock;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.Listener;
import com.android.tv.dvr.DvrDataManager;
@@ -44,22 +43,21 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.util.Clock;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
- * The core class to manage DVR schedule and run recording task.
- **
- * <p> This class is responsible for:
+ * The core class to manage DVR schedule and run recording task. *
+ *
+ * <p>This class is responsible for:
+ *
* <ul>
- * <li>Sending record commands to TV inputs</li>
- * <li>Resolving conflicting schedules, handling overlapping recording time durations, etc.</li>
+ * <li>Sending record commands to TV inputs
+ * <li>Resolving conflicting schedules, handling overlapping recording time durations, etc.
* </ul>
*
* <p>This should be a singleton associated with application's main process.
@@ -71,8 +69,8 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
private static final boolean DEBUG = false;
private static final String HANDLER_THREAD_NAME = "RecordingScheduler";
- private final static long SOON_DURATION_IN_MS = TimeUnit.MINUTES.toMillis(1);
- @VisibleForTesting final static long MS_TO_WAKE_BEFORE_START = TimeUnit.SECONDS.toMillis(30);
+ private static final long SOON_DURATION_IN_MS = TimeUnit.MINUTES.toMillis(1);
+ @VisibleForTesting static final long MS_TO_WAKE_BEFORE_START = TimeUnit.SECONDS.toMillis(30);
private final Looper mLooper;
private final InputSessionManager mSessionManager;
@@ -98,21 +96,22 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
}
};
- private Listener mChannelDataLoadListener = new Listener() {
- @Override
- public void onLoadFinished() {
- mChannelDataManager.removeListener(this);
- if (isDbLoaded()) {
- updateInternal();
- }
- }
+ private Listener mChannelDataLoadListener =
+ new Listener() {
+ @Override
+ public void onLoadFinished() {
+ mChannelDataManager.removeListener(this);
+ if (isDbLoaded()) {
+ updateInternal();
+ }
+ }
- @Override
- public void onChannelListUpdated() { }
+ @Override
+ public void onChannelListUpdated() {}
- @Override
- public void onChannelBrowsableChanged() { }
- };
+ @Override
+ public void onChannelBrowsableChanged() {}
+ };
/**
* Creates a scheduler to schedule alarms for scheduled recordings and create recording tasks.
@@ -120,21 +119,32 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
*/
public static RecordingScheduler createScheduler(Context context) {
SoftPreconditions.checkState(
- TvApplication.getSingletons(context).getRecordingScheduler() == null);
+ TvSingletons.getSingletons(context).getRecordingScheduler() == null);
HandlerThread handlerThread = new HandlerThread(HANDLER_THREAD_NAME);
handlerThread.start();
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
- return new RecordingScheduler(handlerThread.getLooper(),
- singletons.getDvrManager(), singletons.getInputSessionManager(),
+ TvSingletons singletons = TvSingletons.getSingletons(context);
+ return new RecordingScheduler(
+ handlerThread.getLooper(),
+ singletons.getDvrManager(),
+ singletons.getInputSessionManager(),
(WritableDvrDataManager) singletons.getDvrDataManager(),
- singletons.getChannelDataManager(), singletons.getTvInputManagerHelper(), context,
- Clock.SYSTEM, (AlarmManager) context.getSystemService(Context.ALARM_SERVICE));
+ singletons.getChannelDataManager(),
+ singletons.getTvInputManagerHelper(),
+ context,
+ Clock.SYSTEM,
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE));
}
@VisibleForTesting
- RecordingScheduler(Looper looper, DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, ChannelDataManager channelDataManager,
- TvInputManagerHelper inputManager, Context context, Clock clock,
+ RecordingScheduler(
+ Looper looper,
+ DvrManager dvrManager,
+ InputSessionManager sessionManager,
+ WritableDvrDataManager dataManager,
+ ChannelDataManager channelDataManager,
+ TvInputManagerHelper inputManager,
+ Context context,
+ Clock clock,
AlarmManager alarmManager) {
mLooper = looper;
mDvrManager = dvrManager;
@@ -159,9 +169,7 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
}
}
- /**
- * Start recording that will happen soon, and set the next alarm time.
- */
+ /** Start recording that will happen soon, and set the next alarm time. */
public void updateAndStartServiceIfNeeded() {
if (DEBUG) Log.d(TAG, "update and start service if needed");
if (isDbLoaded()) {
@@ -185,8 +193,10 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
}
private boolean updatePendingRecordings() {
- List<ScheduledRecording> scheduledRecordings = mDataManager
- .getScheduledRecordings(new Range<>(mLastStartTimePendingMs,
+ List<ScheduledRecording> scheduledRecordings =
+ mDataManager.getScheduledRecordings(
+ new Range<>(
+ mLastStartTimePendingMs,
mClock.currentTimeMillis() + SOON_DURATION_IN_MS),
ScheduledRecording.STATE_RECORDING_NOT_STARTED);
for (ScheduledRecording r : scheduledRecordings) {
@@ -198,7 +208,8 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
// recording service being wrongly pushed back to background in updateInternal().
return scheduledRecordings.size() > 0
|| (mLastStartTimePendingMs > mClock.currentTimeMillis()
- && mLastStartTimePendingMs < mClock.currentTimeMillis() + SOON_DURATION_IN_MS);
+ && mLastStartTimePendingMs
+ < mClock.currentTimeMillis() + SOON_DURATION_IN_MS);
}
private boolean isDbLoaded() {
@@ -269,18 +280,32 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, schedule.getInputId());
if (input == null) {
Log.e(TAG, "Can't find input for " + schedule);
- mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED);
+ mDataManager.changeState(
+ schedule,
+ ScheduledRecording.STATE_RECORDING_FAILED,
+ ScheduledRecording.FAILED_REASON_INPUT_UNAVAILABLE);
return;
}
if (!input.canRecord() || input.getTunerCount() <= 0) {
Log.e(TAG, "TV input doesn't support recording: " + input);
- mDataManager.changeState(schedule, ScheduledRecording.STATE_RECORDING_FAILED);
+ mDataManager.changeState(
+ schedule,
+ ScheduledRecording.STATE_RECORDING_FAILED,
+ ScheduledRecording.FAILED_REASON_INPUT_DVR_UNSUPPORTED);
return;
}
InputTaskScheduler inputTaskScheduler = mInputSchedulerMap.get(input.getId());
if (inputTaskScheduler == null) {
- inputTaskScheduler = new InputTaskScheduler(mContext, input, mLooper,
- mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mClock);
+ inputTaskScheduler =
+ new InputTaskScheduler(
+ mContext,
+ input,
+ mLooper,
+ mChannelDataManager,
+ mDvrManager,
+ mDataManager,
+ mSessionManager,
+ mClock);
mInputSchedulerMap.put(input.getId(), inputTaskScheduler);
}
inputTaskScheduler.addSchedule(schedule);
@@ -290,8 +315,9 @@ public class RecordingScheduler extends TvInputCallback implements ScheduledReco
}
private void updateNextAlarm() {
- long nextStartTime = mDataManager.getNextScheduledStartTimeAfter(
- Math.max(mLastStartTimePendingMs, mClock.currentTimeMillis()));
+ long nextStartTime =
+ mDataManager.getNextScheduledStartTimeAfter(
+ Math.max(mLastStartTimePendingMs, mClock.currentTimeMillis()));
if (nextStartTime != DvrDataManager.NEXT_START_TIME_NOT_FOUND) {
long wakeAt = nextStartTime - MS_TO_WAKE_BEFORE_START;
if (DEBUG) Log.d(TAG, "Set alarm to record at " + wakeAt);
diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java
index 14888056..07a29e51 100644
--- a/src/com/android/tv/dvr/recorder/RecordingTask.java
+++ b/src/com/android/tv/dvr/recorder/RecordingTask.java
@@ -26,24 +26,24 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.widget.Toast;
-
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.RecordingSession;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.Clock;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.recorder.InputTaskScheduler.HandlerWrapper;
-import com.android.tv.util.Clock;
import com.android.tv.util.Utils;
-
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
@@ -55,58 +55,45 @@ import java.util.concurrent.TimeUnit;
*/
@WorkerThread
@TargetApi(Build.VERSION_CODES.N)
-public class RecordingTask extends RecordingCallback implements Handler.Callback,
- DvrManager.Listener {
+public class RecordingTask extends RecordingCallback
+ implements Handler.Callback, DvrManager.Listener {
private static final String TAG = "RecordingTask";
private static final boolean DEBUG = false;
- /**
- * Compares the end time in ascending order.
- */
- public static final Comparator<RecordingTask> END_TIME_COMPARATOR
- = new Comparator<RecordingTask>() {
- @Override
- public int compare(RecordingTask lhs, RecordingTask rhs) {
- return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs());
- }
- };
-
- /**
- * Compares ID in ascending order.
- */
- public static final Comparator<RecordingTask> ID_COMPARATOR
- = new Comparator<RecordingTask>() {
- @Override
- public int compare(RecordingTask lhs, RecordingTask rhs) {
- return Long.compare(lhs.getScheduleId(), rhs.getScheduleId());
- }
- };
-
- /**
- * Compares the priority in ascending order.
- */
- public static final Comparator<RecordingTask> PRIORITY_COMPARATOR
- = new Comparator<RecordingTask>() {
- @Override
- public int compare(RecordingTask lhs, RecordingTask rhs) {
- return Long.compare(lhs.getPriority(), rhs.getPriority());
- }
- };
+ /** Compares the end time in ascending order. */
+ public static final Comparator<RecordingTask> END_TIME_COMPARATOR =
+ new Comparator<RecordingTask>() {
+ @Override
+ public int compare(RecordingTask lhs, RecordingTask rhs) {
+ return Long.compare(lhs.getEndTimeMs(), rhs.getEndTimeMs());
+ }
+ };
+
+ /** Compares ID in ascending order. */
+ public static final Comparator<RecordingTask> ID_COMPARATOR =
+ new Comparator<RecordingTask>() {
+ @Override
+ public int compare(RecordingTask lhs, RecordingTask rhs) {
+ return Long.compare(lhs.getScheduleId(), rhs.getScheduleId());
+ }
+ };
+
+ /** Compares the priority in ascending order. */
+ public static final Comparator<RecordingTask> PRIORITY_COMPARATOR =
+ new Comparator<RecordingTask>() {
+ @Override
+ public int compare(RecordingTask lhs, RecordingTask rhs) {
+ return Long.compare(lhs.getPriority(), rhs.getPriority());
+ }
+ };
- @VisibleForTesting
- static final int MSG_INITIALIZE = 1;
- @VisibleForTesting
- static final int MSG_START_RECORDING = 2;
- @VisibleForTesting
- static final int MSG_STOP_RECORDING = 3;
- /**
- * Message to update schedule.
- */
+ @VisibleForTesting static final int MSG_INITIALIZE = 1;
+ @VisibleForTesting static final int MSG_START_RECORDING = 2;
+ @VisibleForTesting static final int MSG_STOP_RECORDING = 3;
+ /** Message to update schedule. */
public static final int MSG_UDPATE_SCHEDULE = 4;
- /**
- * The time when the start command will be sent before the recording starts.
- */
+ /** The time when the start command will be sent before the recording starts. */
public static final long RECORDING_EARLY_START_OFFSET_MS = TimeUnit.SECONDS.toMillis(3);
/**
* If the recording starts later than the scheduled start time or ends before the scheduled end
@@ -126,6 +113,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
ERROR,
RELEASED,
}
+
private final InputSessionManager mSessionManager;
private final DvrManager mDvrManager;
private final Context mContext;
@@ -142,9 +130,14 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
private Uri mRecordedProgramUri;
private boolean mCanceled;
- RecordingTask(Context context, ScheduledRecording scheduledRecording, Channel channel,
- DvrManager dvrManager, InputSessionManager sessionManager,
- WritableDvrDataManager dataManager, Clock clock) {
+ RecordingTask(
+ Context context,
+ ScheduledRecording scheduledRecording,
+ Channel channel,
+ DvrManager dvrManager,
+ InputSessionManager sessionManager,
+ WritableDvrDataManager dataManager,
+ Clock clock) {
mContext = context;
mScheduledRecording = scheduledRecording;
mChannel = channel;
@@ -163,8 +156,10 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
@Override
public boolean handleMessage(Message msg) {
if (DEBUG) Log.d(TAG, "handleMessage " + msg);
- SoftPreconditions.checkState(msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null,
- TAG, "Null handler trying to handle " + msg);
+ SoftPreconditions.checkState(
+ msg.what == HandlerWrapper.MESSAGE_REMOVE || mHandler != null,
+ TAG,
+ "Null handler trying to handle " + msg);
try {
switch (msg.what) {
case MSG_INITIALIZE:
@@ -185,7 +180,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
release();
return false;
default:
- SoftPreconditions.checkArgument(false, TAG, "unexpected message type " + msg);
+ SoftPreconditions.checkArgument(false, TAG, "unexpected message type %s", msg);
break;
}
return true;
@@ -200,7 +195,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
public void onDisconnected(String inputId) {
if (DEBUG) Log.d(TAG, "onDisconnected(" + inputId + ")");
if (mRecordingSession != null && mState != State.FINISHED) {
- failAndQuit();
+ failAndQuit(ScheduledRecording.FAILED_REASON_NOT_FINISHED);
}
}
@@ -208,7 +203,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
public void onConnectionFailed(String inputId) {
if (DEBUG) Log.d(TAG, "onConnectionFailed(" + inputId + ")");
if (mRecordingSession != null) {
- failAndQuit();
+ failAndQuit(ScheduledRecording.FAILED_REASON_CONNECTION_FAILED);
}
}
@@ -219,23 +214,27 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
return;
}
mState = State.CONNECTED;
- if (mHandler == null || !sendEmptyMessageAtAbsoluteTime(MSG_START_RECORDING,
- mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) {
- failAndQuit();
+ if (mHandler == null
+ || !sendEmptyMessageAtAbsoluteTime(
+ MSG_START_RECORDING,
+ mScheduledRecording.getStartTimeMs() - RECORDING_EARLY_START_OFFSET_MS)) {
+ failAndQuit(ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT);
}
}
@Override
public void onRecordingStopped(Uri recordedProgramUri) {
- if (DEBUG) Log.d(TAG, "onRecordingStopped");
+ Log.i(TAG, "Recording Stopped: " + mScheduledRecording);
+ Log.i(TAG, "Recording Stopped: stored as " + recordedProgramUri);
if (mRecordingSession == null) {
return;
}
mRecordedProgramUri = recordedProgramUri;
mState = State.FINISHED;
int state = ScheduledRecording.STATE_RECORDING_FINISHED;
- if (mStartedWithClipping || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS
- > mClock.currentTimeMillis()) {
+ if (mStartedWithClipping
+ || mScheduledRecording.getEndTimeMs() - CLIPPED_THRESHOLD_MS
+ > mClock.currentTimeMillis()) {
state = ScheduledRecording.STATE_RECORDING_CLIPPED;
}
updateRecordingState(state);
@@ -247,65 +246,89 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
@Override
public void onError(int reason) {
- if (DEBUG) Log.d(TAG, "onError reason " + reason);
+ Log.i(TAG, "Recording failed with code=" + reason + " for " + mScheduledRecording);
if (mRecordingSession == null) {
return;
}
+ int error;
switch (reason) {
case TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE:
- mMainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- if (TvApplication.getSingletons(mContext).getMainActivityWrapper()
- .isResumed()) {
- ScheduledRecording scheduledRecording = mDataManager
- .getScheduledRecording(mScheduledRecording.getId());
- if (scheduledRecording != null) {
- Toast.makeText(mContext.getApplicationContext(),
- mContext.getString(R.string
- .dvr_error_insufficient_space_description_one_recording,
- scheduledRecording.getProgramDisplayTitle(mContext)),
- Toast.LENGTH_LONG)
- .show();
+ Log.i(TAG, "Insufficient space to record " + mScheduledRecording);
+ mMainThreadHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (TvSingletons.getSingletons(mContext)
+ .getMainActivityWrapper()
+ .isResumed()) {
+ ScheduledRecording scheduledRecording =
+ mDataManager.getScheduledRecording(
+ mScheduledRecording.getId());
+ if (scheduledRecording != null) {
+ Toast.makeText(
+ mContext.getApplicationContext(),
+ mContext.getString(
+ R.string
+ .dvr_error_insufficient_space_description_one_recording,
+ scheduledRecording
+ .getProgramDisplayTitle(
+ mContext)),
+ Toast.LENGTH_LONG)
+ .show();
+ }
+ } else {
+ Utils.setRecordingFailedReason(
+ mContext.getApplicationContext(),
+ TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
+ Utils.addFailedScheduledRecordingInfo(
+ mContext.getApplicationContext(),
+ mScheduledRecording.getProgramDisplayTitle(mContext));
+ }
}
- } else {
- Utils.setRecordingFailedReason(mContext.getApplicationContext(),
- TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- Utils.addFailedScheduledRecordingInfo(mContext.getApplicationContext(),
- mScheduledRecording.getProgramDisplayTitle(mContext));
- }
- }
- });
- // Pass through
+ });
+ error = ScheduledRecording.FAILED_REASON_INSUFFICIENT_SPACE;
+ break;
+ case TvInputManager.RECORDING_ERROR_RESOURCE_BUSY:
+ error = ScheduledRecording.FAILED_REASON_RESOURCE_BUSY;
+ break;
default:
- failAndQuit();
+ error = ScheduledRecording.FAILED_REASON_OTHER;
break;
}
+ failAndQuit(error);
}
private void handleInit() {
if (DEBUG) Log.d(TAG, "handleInit " + mScheduledRecording);
if (mScheduledRecording.getEndTimeMs() < mClock.currentTimeMillis()) {
Log.w(TAG, "End time already past, not recording " + mScheduledRecording);
- failAndQuit();
+ failAndQuit(ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED);
return;
}
if (mChannel == null) {
Log.w(TAG, "Null channel for " + mScheduledRecording);
- failAndQuit();
+ failAndQuit(ScheduledRecording.FAILED_REASON_INVALID_CHANNEL);
return;
}
if (mChannel.getId() != mScheduledRecording.getChannelId()) {
- Log.w(TAG, "Channel" + mChannel + " does not match scheduled recording "
- + mScheduledRecording);
- failAndQuit();
+ Log.w(
+ TAG,
+ "Channel"
+ + mChannel
+ + " does not match scheduled recording "
+ + mScheduledRecording);
+ failAndQuit(ScheduledRecording.FAILED_REASON_INVALID_CHANNEL);
return;
}
String inputId = mChannel.getInputId();
- mRecordingSession = mSessionManager.createRecordingSession(inputId,
- "recordingTask-" + mScheduledRecording.getId(), this,
- mHandler, mScheduledRecording.getEndTimeMs());
+ mRecordingSession =
+ mSessionManager.createRecordingSession(
+ inputId,
+ "recordingTask-" + mScheduledRecording.getId(),
+ this,
+ mHandler,
+ mScheduledRecording.getEndTimeMs());
mState = State.SESSION_ACQUIRED;
mDvrManager.addListener(this, mHandler);
mRecordingSession.tune(inputId, mChannel.getUri());
@@ -313,8 +336,14 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
}
private void failAndQuit() {
+ failAndQuit(ScheduledRecording.FAILED_REASON_OTHER);
+ }
+
+ private void failAndQuit(Integer reason) {
if (DEBUG) Log.d(TAG, "failAndQuit");
- updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED);
+ updateRecordingState(
+ ScheduledRecording.STATE_RECORDING_FAILED,
+ reason);
mState = State.ERROR;
sendRemove();
}
@@ -322,16 +351,18 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
private void sendRemove() {
if (DEBUG) Log.d(TAG, "sendRemove");
if (mHandler != null) {
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(
- HandlerWrapper.MESSAGE_REMOVE));
+ mHandler.sendMessageAtFrontOfQueue(
+ mHandler.obtainMessage(HandlerWrapper.MESSAGE_REMOVE));
}
}
private void handleStartRecording() {
- if (DEBUG) Log.d(TAG, "handleStartRecording " + mScheduledRecording);
+ Log.i(TAG, "Start Recording: " + mScheduledRecording);
long programId = mScheduledRecording.getProgramId();
- mRecordingSession.startRecording(programId == ScheduledRecording.ID_NOT_SET ? null
- : TvContract.buildProgramUri(programId));
+ mRecordingSession.startRecording(
+ programId == ScheduledRecording.ID_NOT_SET
+ ? null
+ : TvContract.buildProgramUri(programId));
updateRecordingState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS);
// If it starts late, it's clipped.
if (mScheduledRecording.getStartTimeMs() + CLIPPED_THRESHOLD_MS
@@ -340,14 +371,14 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
}
mState = State.RECORDING_STARTED;
- if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING,
- mScheduledRecording.getEndTimeMs())) {
- failAndQuit();
+ if (!sendEmptyMessageAtAbsoluteTime(
+ MSG_STOP_RECORDING, mScheduledRecording.getEndTimeMs())) {
+ failAndQuit(ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT);
}
}
private void handleStopRecording() {
- if (DEBUG) Log.d(TAG, "handleStopRecording " + mScheduledRecording);
+ Log.i(TAG, "Stop Recording: " + mScheduledRecording);
mRecordingSession.stopRecording();
mState = State.RECORDING_STOP_REQUESTED;
}
@@ -362,7 +393,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
if (mState == State.RECORDING_STARTED) {
mHandler.removeMessages(MSG_STOP_RECORDING);
if (!sendEmptyMessageAtAbsoluteTime(MSG_STOP_RECORDING, schedule.getEndTimeMs())) {
- failAndQuit();
+ failAndQuit(ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT);
}
}
}
@@ -377,23 +408,17 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
return mScheduledRecording.getId();
}
- /**
- * Returns the priority.
- */
+ /** Returns the priority. */
public long getPriority() {
return mScheduledRecording.getPriority();
}
- /**
- * Returns the start time of the recording.
- */
+ /** Returns the start time of the recording. */
public long getStartTimeMs() {
return mScheduledRecording.getStartTimeMs();
}
- /**
- * Returns the end time of the recording.
- */
+ /** Returns the end time of the recording. */
public long getEndTimeMs() {
return mScheduledRecording.getEndTimeMs();
}
@@ -410,33 +435,53 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
long now = mClock.currentTimeMillis();
long delay = Math.max(0L, when - now);
if (DEBUG) {
- Log.d(TAG, "Sending message " + what + " with a delay of " + delay / 1000
- + " seconds to arrive at " + Utils.toIsoDateTimeString(when));
+ Log.d(
+ TAG,
+ "Sending message "
+ + what
+ + " with a delay of "
+ + delay / 1000
+ + " seconds to arrive at "
+ + CommonUtils.toIsoDateTimeString(when));
}
return mHandler.sendEmptyMessageDelayed(what, delay);
}
private void updateRecordingState(@ScheduledRecording.RecordingState int state) {
- if (DEBUG) Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state);
- mScheduledRecording = ScheduledRecording.buildFrom(mScheduledRecording).setState(state)
- .build();
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- ScheduledRecording schedule = mDataManager.getScheduledRecording(
- mScheduledRecording.getId());
- if (schedule == null) {
- // Schedule has been deleted. Delete the recorded program.
- removeRecordedProgram();
- } else {
- // Update the state based on the object in DataManager in case when it has been
- // updated. mScheduledRecording will be updated from
- // onScheduledRecordingStateChanged.
- mDataManager.updateScheduledRecording(ScheduledRecording.buildFrom(schedule)
- .setState(state).build());
- }
- }
- });
+ updateRecordingState(state, null);
+ }
+ private void updateRecordingState(
+ @ScheduledRecording.RecordingState int state, @Nullable Integer reason) {
+ if (DEBUG) {
+ Log.d(TAG, "Updating the state of " + mScheduledRecording + " to " + state);
+ }
+ mScheduledRecording =
+ ScheduledRecording.buildFrom(mScheduledRecording).setState(state).build();
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ ScheduledRecording schedule =
+ mDataManager.getScheduledRecording(mScheduledRecording.getId());
+ if (schedule == null) {
+ // Schedule has been deleted. Delete the recorded program.
+ removeRecordedProgram();
+ } else {
+ // Update the state based on the object in DataManager in case when it
+ // has been updated. mScheduledRecording will be updated from
+ // onScheduledRecordingStateChanged.
+ ScheduledRecording.Builder builder =
+ ScheduledRecording
+ .buildFrom(schedule)
+ .setState(state);
+ if (state == ScheduledRecording.STATE_RECORDING_FAILED
+ && reason != null) {
+ builder.setFailedReason(reason);
+ }
+ mDataManager.updateScheduledRecording(builder.build());
+ }
+ }
+ });
}
@Override
@@ -447,16 +492,12 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
stop();
}
- /**
- * Starts the task.
- */
+ /** Starts the task. */
public void start() {
mHandler.sendEmptyMessage(MSG_INITIALIZE);
}
- /**
- * Stops the task.
- */
+ /** Stops the task. */
public void stop() {
if (DEBUG) Log.d(TAG, "stop");
switch (mState) {
@@ -480,9 +521,7 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
}
}
- /**
- * Cancels the task
- */
+ /** Cancels the task */
public void cancel() {
if (DEBUG) Log.d(TAG, "cancel");
mCanceled = true;
@@ -490,12 +529,12 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
removeRecordedProgram();
}
- /**
- * Clean up the task.
- */
+ /** Clean up the task. */
public void cleanUp() {
if (mState == State.RECORDING_STARTED || mState == State.RECORDING_STOP_REQUESTED) {
- updateRecordingState(ScheduledRecording.STATE_RECORDING_FAILED);
+ updateRecordingState(
+ ScheduledRecording.STATE_RECORDING_FAILED,
+ ScheduledRecording.FAILED_REASON_SCHEDULER_STOPPED);
}
release();
if (mHandler != null) {
@@ -509,14 +548,15 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
}
private void removeRecordedProgram() {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- if (mRecordedProgramUri != null) {
- mDvrManager.removeRecordedProgram(mRecordedProgramUri);
- }
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mRecordedProgramUri != null) {
+ mDvrManager.removeRecordedProgram(mRecordedProgramUri);
+ }
+ }
+ });
}
private void runOnMainThread(Runnable runnable) {
diff --git a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java
index d958c4a1..dd106e1c 100644
--- a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java
+++ b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java
@@ -17,24 +17,18 @@
package com.android.tv.dvr.recorder;
import android.support.annotation.MainThread;
-import android.support.annotation.VisibleForTesting;
-
+import com.android.tv.common.util.Clock;
import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
-import com.android.tv.util.Clock;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-/**
- * Deletes {@link ScheduledRecording} older than {@value @DAYS} days.
- */
-class ScheduledProgramReaper implements Runnable {
+/** Deletes {@link ScheduledRecording} older than {@value @DAYS} days. */
+public class ScheduledProgramReaper implements Runnable {
- @VisibleForTesting
- static final int DAYS = 2;
+ public static final int DAYS = 7;
private final WritableDvrDataManager mDvrDataManager;
private final Clock mClock;
@@ -54,7 +48,7 @@ class ScheduledProgramReaper implements Runnable {
// series recording.
if (r.getEndTimeMs() < cutoff
&& (r.getSeriesRecordingId() == SeriesRecording.ID_NOT_SET
- || r.getState() != ScheduledRecording.STATE_RECORDING_FINISHED)) {
+ || r.getState() != ScheduledRecording.STATE_RECORDING_FINISHED)) {
toRemove.add(r);
}
}
diff --git a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
index 15508c24..4f7a789b 100644
--- a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
+++ b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
@@ -27,27 +27,23 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
-import com.android.tv.common.CollectionUtils;
-import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.experiments.Experiments;
+import com.android.tv.common.util.CollectionUtils;
+import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.data.Program;
-import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.epg.EpgReader;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.WritableDvrDataManager;
-import com.android.tv.dvr.data.SeasonEpisodeNumber;
import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
import com.android.tv.dvr.data.SeriesInfo;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
-import com.android.tv.experiments.Experiments;
-
-import com.android.tv.util.LocationUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -60,12 +56,14 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import javax.inject.Provider;
/**
- * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for
- * the {@link com.android.tv.dvr.data.SeriesRecording}.
- * <p>
- * The current implementation assumes that the series recordings are scheduled only for one channel.
+ * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for the {@link
+ * com.android.tv.dvr.data.SeriesRecording}.
+ *
+ * <p>The current implementation assumes that the series recordings are scheduled only for one
+ * channel.
*/
@TargetApi(Build.VERSION_CODES.N)
public class SeriesRecordingScheduler {
@@ -78,9 +76,7 @@ public class SeriesRecordingScheduler {
@SuppressLint("StaticFieldLeak")
private static SeriesRecordingScheduler sInstance;
- /**
- * Creates and returns the {@link SeriesRecordingScheduler}.
- */
+ /** Creates and returns the {@link SeriesRecordingScheduler}. */
public static synchronized SeriesRecordingScheduler getInstance(Context context) {
if (sInstance == null) {
sInstance = new SeriesRecordingScheduler(context);
@@ -100,54 +96,59 @@ public class SeriesRecordingScheduler {
private boolean mPaused;
private final Set<Long> mPendingSeriesRecordings = new ArraySet<>();
- private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() {
- @Override
- public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
- for (SeriesRecording seriesRecording : seriesRecordings) {
- executeFetchSeriesInfoTask(seriesRecording);
- }
- }
-
- @Override
- public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
- // Cancel the update.
- for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator();
- iter.hasNext(); ) {
- SeriesRecordingUpdateTask task = iter.next();
- if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings,
- SeriesRecording.ID_COMPARATOR).isEmpty()) {
- task.cancel(true);
- iter.remove();
+ private final SeriesRecordingListener mSeriesRecordingListener =
+ new SeriesRecordingListener() {
+ @Override
+ public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording seriesRecording : seriesRecordings) {
+ executeFetchSeriesInfoTask(seriesRecording);
+ }
}
- }
- for (SeriesRecording seriesRecording : seriesRecordings) {
- FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(seriesRecording.getId());
- if (task != null) {
- task.cancel(true);
- mFetchSeriesInfoTasks.remove(seriesRecording.getId());
+
+ @Override
+ public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
+ // Cancel the update.
+ for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator();
+ iter.hasNext(); ) {
+ SeriesRecordingUpdateTask task = iter.next();
+ if (CollectionUtils.subtract(
+ task.getSeriesRecordings(),
+ seriesRecordings,
+ SeriesRecording.ID_COMPARATOR)
+ .isEmpty()) {
+ task.cancel(true);
+ iter.remove();
+ }
+ }
+ for (SeriesRecording seriesRecording : seriesRecordings) {
+ FetchSeriesInfoTask task =
+ mFetchSeriesInfoTasks.get(seriesRecording.getId());
+ if (task != null) {
+ task.cancel(true);
+ mFetchSeriesInfoTasks.remove(seriesRecording.getId());
+ }
+ }
}
- }
- }
- @Override
- public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
- List<SeriesRecording> stopped = new ArrayList<>();
- List<SeriesRecording> normal = new ArrayList<>();
- for (SeriesRecording r : seriesRecordings) {
- if (r.isStopped()) {
- stopped.add(r);
- } else {
- normal.add(r);
+ @Override
+ public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
+ List<SeriesRecording> stopped = new ArrayList<>();
+ List<SeriesRecording> normal = new ArrayList<>();
+ for (SeriesRecording r : seriesRecordings) {
+ if (r.isStopped()) {
+ stopped.add(r);
+ } else {
+ normal.add(r);
+ }
+ }
+ if (!stopped.isEmpty()) {
+ onSeriesRecordingRemoved(SeriesRecording.toArray(stopped));
+ }
+ if (!normal.isEmpty()) {
+ updateSchedules(normal);
+ }
}
- }
- if (!stopped.isEmpty()) {
- onSeriesRecordingRemoved(SeriesRecording.toArray(stopped));
- }
- if (!normal.isEmpty()) {
- updateSchedules(normal);
- }
- }
- };
+ };
private final ScheduledRecordingListener mScheduledRecordingListener =
new ScheduledRecordingListener() {
@@ -166,7 +167,8 @@ public class SeriesRecordingScheduler {
List<ScheduledRecording> schedulesForUpdate = new ArrayList<>();
for (ScheduledRecording r : schedules) {
if ((r.getState() == ScheduledRecording.STATE_RECORDING_FAILED
- || r.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED)
+ || r.getState()
+ == ScheduledRecording.STATE_RECORDING_CLIPPED)
&& r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET
&& !TextUtils.isEmpty(r.getSeasonNumber())
&& !TextUtils.isEmpty(r.getEpisodeNumber())) {
@@ -205,18 +207,17 @@ public class SeriesRecordingScheduler {
private SeriesRecordingScheduler(Context context) {
mContext = context.getApplicationContext();
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mDvrManager = appSingletons.getDvrManager();
- mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager();
- mSharedPreferences = context.getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE);
- mFetchedSeriesIds.addAll(mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS,
- Collections.emptySet()));
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mDvrManager = tvSingletons.getDvrManager();
+ mDataManager = (WritableDvrDataManager) tvSingletons.getDvrDataManager();
+ mSharedPreferences =
+ context.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE);
+ mFetchedSeriesIds.addAll(
+ mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS, Collections.emptySet()));
}
- /**
- * Starts the scheduler.
- */
+ /** Starts the scheduler. */
@MainThread
public void start() {
SoftPreconditions.checkState(mDataManager.isInitialized());
@@ -261,15 +262,16 @@ public class SeriesRecordingScheduler {
private void executeFetchSeriesInfoTask(SeriesRecording seriesRecording) {
if (Experiments.CLOUD_EPG.get()) {
- FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording);
+ FetchSeriesInfoTask task =
+ new FetchSeriesInfoTask(
+ seriesRecording,
+ TvSingletons.getSingletons(mContext).providesEpgReader());
task.execute();
mFetchSeriesInfoTasks.put(seriesRecording.getId(), task);
}
}
- /**
- * Pauses the updates of the series recordings.
- */
+ /** Pauses the updates of the series recordings. */
public void pauseUpdate() {
if (DEBUG) Log.d(TAG, "Schedule paused");
if (mPaused) {
@@ -287,9 +289,7 @@ public class SeriesRecordingScheduler {
}
}
- /**
- * Resumes the updates of the series recordings.
- */
+ /** Resumes the updates of the series recordings. */
public void resumeUpdate() {
if (DEBUG) Log.d(TAG, "Schedule resumed");
if (!mPaused) {
@@ -329,25 +329,28 @@ public class SeriesRecordingScheduler {
mPendingSeriesRecordings.add(r.getId());
}
if (DEBUG) {
- Log.d(TAG, "The scheduler has been paused. Adding to the pending list. size="
- + mPendingSeriesRecordings.size());
+ Log.d(
+ TAG,
+ "The scheduler has been paused. Adding to the pending list. size="
+ + mPendingSeriesRecordings.size());
}
return;
}
Set<SeriesRecording> previousSeriesRecordings = new HashSet<>();
for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator();
- iter.hasNext(); ) {
+ iter.hasNext(); ) {
SeriesRecordingUpdateTask task = iter.next();
- if (CollectionUtils.containsAny(task.getSeriesRecordings(), seriesRecordings,
- SeriesRecording.ID_COMPARATOR)) {
+ if (CollectionUtils.containsAny(
+ task.getSeriesRecordings(), seriesRecordings, SeriesRecording.ID_COMPARATOR)) {
// The task is affected by the seriesRecordings
task.cancel(true);
previousSeriesRecordings.addAll(task.getSeriesRecordings());
iter.remove();
}
}
- List<SeriesRecording> seriesRecordingsToUpdate = CollectionUtils.union(seriesRecordings,
- previousSeriesRecordings, SeriesRecording.ID_COMPARATOR);
+ List<SeriesRecording> seriesRecordingsToUpdate =
+ CollectionUtils.union(
+ seriesRecordings, previousSeriesRecordings, SeriesRecording.ID_COMPARATOR);
for (Iterator<SeriesRecording> iter = seriesRecordingsToUpdate.iterator();
iter.hasNext(); ) {
SeriesRecording seriesRecording = mDataManager.getSeriesRecording(iter.next().getId());
@@ -367,8 +370,8 @@ public class SeriesRecordingScheduler {
task.execute();
} else {
for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) {
- SeriesRecordingUpdateTask task = new SeriesRecordingUpdateTask(
- Collections.singletonList(seriesRecording));
+ SeriesRecordingUpdateTask task =
+ new SeriesRecordingUpdateTask(Collections.singletonList(seriesRecording));
mScheduleTasks.add(task);
if (DEBUG) Log.d(TAG, "Added schedule task: " + task);
task.execute();
@@ -389,8 +392,9 @@ public class SeriesRecordingScheduler {
* Pick one program per an episode.
*
* <p>Note that the programs which has been already scheduled have the highest priority, and all
- * of them are added even though they are the same episodes. That's because the schedules
- * should be added to the series recording.
+ * of them are added even though they are the same episodes. That's because the schedules should
+ * be added to the series recording.
+ *
* <p>If there are no existing schedules for an episode, one program which starts earlier is
* picked.
*/
@@ -399,11 +403,10 @@ public class SeriesRecordingScheduler {
return pickOneProgramPerEpisode(mDataManager, seriesRecordings, programs);
}
- /**
- * @see #pickOneProgramPerEpisode(List, List)
- */
+ /** @see #pickOneProgramPerEpisode(List, List) */
public static LongSparseArray<List<Program>> pickOneProgramPerEpisode(
- DvrDataManager dataManager, List<SeriesRecording> seriesRecordings,
+ DvrDataManager dataManager,
+ List<SeriesRecording> seriesRecordings,
List<Program> programs) {
// Initialize.
LongSparseArray<List<Program>> result = new LongSparseArray<>();
@@ -422,8 +425,11 @@ public class SeriesRecordingScheduler {
result.get(seriesRecordingId).add(program);
continue;
}
- SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber(seriesRecordingId,
- program.getSeasonNumber(), program.getEpisodeNumber());
+ SeasonEpisodeNumber seasonEpisodeNumber =
+ new SeasonEpisodeNumber(
+ seriesRecordingId,
+ program.getSeasonNumber(),
+ program.getEpisodeNumber());
List<Program> programsForEpisode = programsForEpisodeMap.get(seasonEpisodeNumber);
if (programsForEpisode == null) {
programsForEpisode = new ArrayList<>();
@@ -434,22 +440,24 @@ public class SeriesRecordingScheduler {
// Pick one program.
for (Entry<SeasonEpisodeNumber, List<Program>> entry : programsForEpisodeMap.entrySet()) {
List<Program> programsForEpisode = entry.getValue();
- Collections.sort(programsForEpisode, new Comparator<Program>() {
- @Override
- public int compare(Program lhs, Program rhs) {
- // Place the existing schedule first.
- boolean lhsScheduled = isProgramScheduled(dataManager, lhs);
- boolean rhsScheduled = isProgramScheduled(dataManager, rhs);
- if (lhsScheduled && !rhsScheduled) {
- return -1;
- }
- if (!lhsScheduled && rhsScheduled) {
- return 1;
- }
- // Sort by the start time in ascending order.
- return lhs.compareTo(rhs);
- }
- });
+ Collections.sort(
+ programsForEpisode,
+ new Comparator<Program>() {
+ @Override
+ public int compare(Program lhs, Program rhs) {
+ // Place the existing schedule first.
+ boolean lhsScheduled = isProgramScheduled(dataManager, lhs);
+ boolean rhsScheduled = isProgramScheduled(dataManager, rhs);
+ if (lhsScheduled && !rhsScheduled) {
+ return -1;
+ }
+ if (!lhsScheduled && rhsScheduled) {
+ return 1;
+ }
+ // Sort by the start time in ascending order.
+ return lhs.compareTo(rhs);
+ }
+ });
boolean added = false;
// Add all the scheduled programs
List<Program> programsForSeries = result.get(entry.getKey().seriesRecordingId);
@@ -469,8 +477,8 @@ public class SeriesRecordingScheduler {
private static boolean isProgramScheduled(DvrDataManager dataManager, Program program) {
ScheduledRecording schedule =
dataManager.getScheduledRecordingForProgramId(program.getId());
- return schedule != null && schedule.getState()
- == ScheduledRecording.STATE_RECORDING_NOT_STARTED;
+ return schedule != null
+ && schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED;
}
private void updateFetchedSeries() {
@@ -478,8 +486,8 @@ public class SeriesRecordingScheduler {
}
/**
- * This works only for the existing series recordings. Do not use this task for the
- * "adding series recording" UI.
+ * This works only for the existing series recordings. Do not use this task for the "adding
+ * series recording" UI.
*/
private class SeriesRecordingUpdateTask extends EpisodicProgramLoadTask {
SeriesRecordingUpdateTask(List<SeriesRecording> seriesRecordings) {
@@ -491,16 +499,17 @@ public class SeriesRecordingScheduler {
if (DEBUG) Log.d(TAG, "onPostExecute: updating schedules with programs:" + programs);
mScheduleTasks.remove(this);
if (programs == null) {
- Log.e(TAG, "Creating schedules for series recording failed: "
- + getSeriesRecordings());
+ Log.e(
+ TAG,
+ "Creating schedules for series recording failed: " + getSeriesRecordings());
return;
}
- LongSparseArray<List<Program>> seriesProgramMap = pickOneProgramPerEpisode(
- getSeriesRecordings(), programs);
+ LongSparseArray<List<Program>> seriesProgramMap =
+ pickOneProgramPerEpisode(getSeriesRecordings(), programs);
for (SeriesRecording seriesRecording : getSeriesRecordings()) {
// Check the series recording is still valid.
- SeriesRecording actualSeriesRecording = mDataManager.getSeriesRecording(
- seriesRecording.getId());
+ SeriesRecording actualSeriesRecording =
+ mDataManager.getSeriesRecording(seriesRecording.getId());
if (actualSeriesRecording == null || actualSeriesRecording.isStopped()) {
continue;
}
@@ -520,35 +529,39 @@ public class SeriesRecordingScheduler {
@Override
public String toString() {
return "SeriesRecordingUpdateTask:{"
- + "series_recordings=" + getSeriesRecordings()
+ + "series_recordings="
+ + getSeriesRecordings()
+ "}";
}
}
private class FetchSeriesInfoTask extends AsyncTask<Void, Void, SeriesInfo> {
- private SeriesRecording mSeriesRecording;
+ private final SeriesRecording mSeriesRecording;
+ private final Provider<EpgReader> mEpgReaderProvider;
- FetchSeriesInfoTask(SeriesRecording seriesRecording) {
+ FetchSeriesInfoTask(
+ SeriesRecording seriesRecording, Provider<EpgReader> epgReaderProvider) {
mSeriesRecording = seriesRecording;
+ mEpgReaderProvider = epgReaderProvider;
}
@Override
protected SeriesInfo doInBackground(Void... voids) {
- return EpgFetcher.createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext))
- .getSeriesInfo(mSeriesRecording.getSeriesId());
+ return mEpgReaderProvider.get().getSeriesInfo(mSeriesRecording.getSeriesId());
}
@Override
protected void onPostExecute(SeriesInfo seriesInfo) {
if (seriesInfo != null) {
- mDataManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording)
- .setTitle(seriesInfo.getTitle())
- .setDescription(seriesInfo.getDescription())
- .setLongDescription(seriesInfo.getLongDescription())
- .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds())
- .setPosterUri(seriesInfo.getPosterUri())
- .setPhotoUri(seriesInfo.getPhotoUri())
- .build());
+ mDataManager.updateSeriesRecording(
+ SeriesRecording.buildFrom(mSeriesRecording)
+ .setTitle(seriesInfo.getTitle())
+ .setDescription(seriesInfo.getDescription())
+ .setLongDescription(seriesInfo.getLongDescription())
+ .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds())
+ .setPosterUri(seriesInfo.getPosterUri())
+ .setPhotoUri(seriesInfo.getPhotoUri())
+ .build());
mFetchedSeriesIds.add(seriesInfo.getId());
updateFetchedSeries();
}
diff --git a/src/com/android/tv/dvr/ui/BigArguments.java b/src/com/android/tv/dvr/ui/BigArguments.java
index ec3b5065..0d6ff8b1 100644
--- a/src/com/android/tv/dvr/ui/BigArguments.java
+++ b/src/com/android/tv/dvr/ui/BigArguments.java
@@ -17,37 +17,27 @@
package com.android.tv.dvr.ui;
import android.support.annotation.NonNull;
-
import com.android.tv.common.SoftPreconditions;
-
import java.util.HashMap;
import java.util.Map;
-/**
- * Stores the object to pass through activities/fragments.
- */
+/** Stores the object to pass through activities/fragments. */
public class BigArguments {
- private final static String TAG = "BigArguments";
+ private static final String TAG = "BigArguments";
private static Map<String, Object> sBigArgumentMap = new HashMap<>();
- /**
- * Sets the argument.
- */
+ /** Sets the argument. */
public static void setArgument(String name, @NonNull Object value) {
SoftPreconditions.checkState(value != null, TAG, "Set argument, but value is null");
sBigArgumentMap.put(name, value);
}
- /**
- * Returns the argument which is associated to the name.
- */
+ /** Returns the argument which is associated to the name. */
public static Object getArgument(String name) {
return sBigArgumentMap.get(name);
}
- /**
- * Resets the arguments.
- */
+ /** Resets the arguments. */
public static void reset() {
sBigArgumentMap.clear();
}
diff --git a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
index cddece73..32679421 100644
--- a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
+++ b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
@@ -26,16 +26,14 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
-
import com.android.tv.R;
-
import java.util.Map;
/**
- * TODO: Remove this class once b/32405620 is fixed.
- * This class is for the workaround of b/32405620 and only for the shared element transition between
- * {@link com.android.tv.dvr.ui.browse.RecordingCardView} and
- * {@link com.android.tv.dvr.ui.browse.DvrDetailsActivity}.
+ * TODO: Remove this class once b/32405620 is fixed. This class is for the workaround of b/32405620
+ * and only for the shared element transition between {@link
+ * com.android.tv.dvr.ui.browse.RecordingCardView} and {@link
+ * com.android.tv.dvr.ui.browse.DvrDetailsActivity}.
*/
public class ChangeImageTransformWithScaledParent extends ChangeImageTransform {
private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
@@ -60,7 +58,8 @@ public class ChangeImageTransformWithScaledParent extends ChangeImageTransform {
View view = transitionValues.view;
Map<String, Object> values = transitionValues.values;
Matrix matrix = (Matrix) values.get(PROPNAME_MATRIX);
- if (matrix != null && view.getId() == R.id.details_overview_image
+ if (matrix != null
+ && view.getId() == R.id.details_overview_image
&& view instanceof ImageView) {
ImageView imageView = (ImageView) view;
if (imageView.getScaleType() == ScaleType.CENTER_INSIDE
@@ -68,10 +67,13 @@ public class ChangeImageTransformWithScaledParent extends ChangeImageTransform {
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
if (bitmap.getWidth() < imageView.getWidth()
&& bitmap.getHeight() < imageView.getHeight()) {
- float scale = imageView.getContext().getResources().getFraction(
- R.fraction.lb_focus_zoom_factor_medium, 1, 1);
- matrix.postScale(scale, scale, imageView.getWidth() / 2,
- imageView.getHeight() / 2);
+ float scale =
+ imageView
+ .getContext()
+ .getResources()
+ .getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1);
+ matrix.postScale(
+ scale, scale, imageView.getWidth() / 2, imageView.getHeight() / 2);
}
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
index 62327870..fce94230 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
@@ -24,13 +24,11 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.RecordedProgram;
-
import java.util.List;
/**
@@ -51,13 +49,19 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment {
public void onAttach(Context context) {
super.onAttach(context);
mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
- DvrManager dvrManager = TvApplication.getSingletons(context).getDvrManager();
- mDuplicate = dvrManager.getRecordedProgram(mProgram.getTitle(),
- mProgram.getSeasonNumber(), mProgram.getEpisodeNumber());
+ DvrManager dvrManager = TvSingletons.getSingletons(context).getDvrManager();
+ mDuplicate =
+ dvrManager.getRecordedProgram(
+ mProgram.getTitle(),
+ mProgram.getSeasonNumber(),
+ mProgram.getEpisodeNumber());
if (mDuplicate == null) {
dvrManager.addSchedule(mProgram);
- DvrUiHelper.showAddScheduleToast(context, mProgram.getTitle(),
- mProgram.getStartTimeUtcMillis(), mProgram.getEndTimeUtcMillis());
+ DvrUiHelper.showAddScheduleToast(
+ context,
+ mProgram.getTitle(),
+ mProgram.getStartTimeUtcMillis(),
+ mProgram.getEndTimeUtcMillis());
dismissDialog();
}
}
@@ -74,18 +78,21 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment {
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
Context context = getContext();
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_RECORD_ANYWAY)
- .title(R.string.dvr_action_record_anyway)
- .build());
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_WATCH)
- .title(R.string.dvr_action_watch_now)
- .build());
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_CANCEL)
- .title(R.string.dvr_action_record_cancel)
- .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_RECORD_ANYWAY)
+ .title(R.string.dvr_action_record_anyway)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_WATCH)
+ .title(R.string.dvr_action_watch_now)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_CANCEL)
+ .title(R.string.dvr_action_record_cancel)
+ .build());
}
@Override
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
index 6da75e55..456ad830 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
@@ -25,13 +25,11 @@ import android.support.annotation.NonNull;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.text.format.DateUtils;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
-
import java.util.List;
/**
@@ -52,13 +50,19 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment {
public void onAttach(Context context) {
super.onAttach(context);
mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
- DvrManager dvrManager = TvApplication.getSingletons(context).getDvrManager();
- mDuplicate = dvrManager.getScheduledRecording(mProgram.getTitle(),
- mProgram.getSeasonNumber(), mProgram.getEpisodeNumber());
+ DvrManager dvrManager = TvSingletons.getSingletons(context).getDvrManager();
+ mDuplicate =
+ dvrManager.getScheduledRecording(
+ mProgram.getTitle(),
+ mProgram.getSeasonNumber(),
+ mProgram.getEpisodeNumber());
if (mDuplicate == null) {
dvrManager.addSchedule(mProgram);
- DvrUiHelper.showAddScheduleToast(context, mProgram.getTitle(),
- mProgram.getStartTimeUtcMillis(), mProgram.getEndTimeUtcMillis());
+ DvrUiHelper.showAddScheduleToast(
+ context,
+ mProgram.getTitle(),
+ mProgram.getStartTimeUtcMillis(),
+ mProgram.getEndTimeUtcMillis());
dismissDialog();
}
}
@@ -67,9 +71,13 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment {
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = getString(R.string.dvr_already_scheduled_dialog_title);
- String description = getString(R.string.dvr_already_scheduled_dialog_description,
- DateUtils.formatDateTime(getContext(), mDuplicate.getStartTimeMs(),
- DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE));
+ String description =
+ getString(
+ R.string.dvr_already_scheduled_dialog_description,
+ DateUtils.formatDateTime(
+ getContext(),
+ mDuplicate.getStartTimeMs(),
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE));
Drawable image = getResources().getDrawable(R.drawable.ic_warning_white_96dp, null);
return new Guidance(title, description, null, image);
}
@@ -77,18 +85,21 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment {
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
Context context = getContext();
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_RECORD_ANYWAY)
- .title(R.string.dvr_action_record_anyway)
- .build());
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_RECORD_INSTEAD)
- .title(R.string.dvr_action_record_instead)
- .build());
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_CANCEL)
- .title(R.string.dvr_action_record_cancel)
- .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_RECORD_ANYWAY)
+ .title(R.string.dvr_action_record_anyway)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_RECORD_INSTEAD)
+ .title(R.string.dvr_action_record_instead)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_CANCEL)
+ .title(R.string.dvr_action_record_cancel)
+ .build());
}
@Override
diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
index 36659412..6be35cb2 100644
--- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
@@ -21,15 +21,13 @@ import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -43,8 +41,10 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen
Bundle args = getArguments();
if (args != null) {
long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
- mChannel = TvApplication.getSingletons(getContext()).getChannelDataManager()
- .getChannel(channelId);
+ mChannel =
+ TvSingletons.getSingletons(getContext())
+ .getChannelDataManager()
+ .getChannel(channelId);
}
SoftPreconditions.checkArgument(mChannel != null);
super.onCreate(savedInstanceState);
@@ -66,32 +66,36 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen
mDurations.add(TimeUnit.HOURS.toMillis(1));
mDurations.add(TimeUnit.HOURS.toMillis(3));
- actions.add(new GuidedAction.Builder(getContext())
- .id(++actionId)
- .title(R.string.recording_start_dialog_10_min_duration)
- .build());
- actions.add(new GuidedAction.Builder(getContext())
- .id(++actionId)
- .title(R.string.recording_start_dialog_30_min_duration)
- .build());
- actions.add(new GuidedAction.Builder(getContext())
- .id(++actionId)
- .title(R.string.recording_start_dialog_1_hour_duration)
- .build());
- actions.add(new GuidedAction.Builder(getContext())
- .id(++actionId)
- .title(R.string.recording_start_dialog_3_hours_duration)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(++actionId)
+ .title(R.string.recording_start_dialog_10_min_duration)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(++actionId)
+ .title(R.string.recording_start_dialog_30_min_duration)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(++actionId)
+ .title(R.string.recording_start_dialog_1_hour_duration)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(++actionId)
+ .title(R.string.recording_start_dialog_3_hours_duration)
+ .build());
}
@Override
public void onTrackedGuidedActionClicked(GuidedAction action) {
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
long duration = mDurations.get((int) action.getId());
long startTimeMs = System.currentTimeMillis();
long endTimeMs = System.currentTimeMillis() + duration;
- List<ScheduledRecording> conflicts = dvrManager.getConflictingSchedules(
- mChannel.getId(), startTimeMs, endTimeMs);
+ List<ScheduledRecording> conflicts =
+ dvrManager.getConflictingSchedules(mChannel.getId(), startTimeMs, endTimeMs);
dvrManager.addSchedule(mChannel, startTimeMs, endTimeMs);
if (conflicts.isEmpty()) {
dismissDialog();
@@ -102,8 +106,7 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen
args.putLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS, startTimeMs);
args.putLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS, endTimeMs);
fragment.setArguments(args);
- GuidedStepFragment.add(getFragmentManager(), fragment,
- R.id.halfsized_dialog_host);
+ GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host);
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
index 6f362e68..65759555 100644
--- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
@@ -27,18 +27,16 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.recorder.ConflictChecker;
import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener;
-import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -72,15 +70,16 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
@Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- actions.add(new GuidedAction.Builder(getContext())
- .clickAction(GuidedAction.ACTION_ID_OK)
- .build());
- actions.add(new GuidedAction.Builder(getContext())
- .id(ACTION_VIEW_SCHEDULES)
- .title(R.string.dvr_action_view_schedules)
- .build());
+ public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .clickAction(GuidedAction.ACTION_ID_OK)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(ACTION_VIEW_SCHEDULES)
+ .title(R.string.dvr_action_view_schedules)
+ .build());
}
@Override
@@ -114,33 +113,45 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
switch (titles.size()) {
case 0:
- Log.i(TAG, "Conflict has been resolved by any reason. Maybe input might have"
- + " been deleted.");
+ Log.i(
+ TAG,
+ "Conflict has been resolved by any reason. Maybe input might have"
+ + " been deleted.");
return null;
case 1:
- return getResources().getString(
- R.string.dvr_program_conflict_dialog_description_1, titles.get(0));
+ return getResources()
+ .getString(
+ R.string.dvr_program_conflict_dialog_description_1, titles.get(0));
case 2:
- return getResources().getString(
- R.string.dvr_program_conflict_dialog_description_2, titles.get(0),
- titles.get(1));
+ return getResources()
+ .getString(
+ R.string.dvr_program_conflict_dialog_description_2,
+ titles.get(0),
+ titles.get(1));
case 3:
- return getResources().getString(
- R.string.dvr_program_conflict_dialog_description_3, titles.get(0),
- titles.get(1));
+ return getResources()
+ .getString(
+ R.string.dvr_program_conflict_dialog_description_3,
+ titles.get(0),
+ titles.get(1));
default:
- return getResources().getQuantityString(
- R.plurals.dvr_program_conflict_dialog_description_many,
- titles.size() - LISTED_PROGRAM_COUNT, titles.get(0), titles.get(1),
- titles.size() - LISTED_PROGRAM_COUNT);
+ return getResources()
+ .getQuantityString(
+ R.plurals.dvr_program_conflict_dialog_description_many,
+ titles.size() - LISTED_PROGRAM_COUNT,
+ titles.get(0),
+ titles.get(1),
+ titles.size() - LISTED_PROGRAM_COUNT);
}
}
@Nullable
private String getScheduleTitle(ScheduledRecording schedule) {
if (schedule.getType() == ScheduledRecording.TYPE_TIMED) {
- Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager()
- .getChannel(schedule.getChannelId());
+ Channel channel =
+ TvSingletons.getSingletons(getContext())
+ .getChannelDataManager()
+ .getChannel(schedule.getChannelId());
if (channel != null) {
return channel.getDisplayName();
} else {
@@ -151,14 +162,13 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
}
- /**
- * A fragment to show the program conflict.
- */
+ /** A fragment to show the program conflict. */
public static class DvrProgramConflictFragment extends DvrConflictFragment {
private Program mProgram;
+
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
@@ -168,8 +178,10 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
SoftPreconditions.checkNotNull(input);
List<ScheduledRecording> conflicts = null;
if (input != null) {
- conflicts = TvApplication.getSingletons(getContext()).getDvrManager()
- .getConflictingSchedules(mProgram);
+ conflicts =
+ TvSingletons.getSingletons(getContext())
+ .getDvrManager()
+ .getConflictingSchedules(mProgram);
}
if (conflicts == null) {
conflicts = Collections.emptyList();
@@ -185,8 +197,10 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = getResources().getString(R.string.dvr_program_conflict_dialog_title);
- String descriptionPrefix = getString(
- R.string.dvr_program_conflict_dialog_description_prefix, mProgram.getTitle());
+ String descriptionPrefix =
+ getString(
+ R.string.dvr_program_conflict_dialog_description_prefix,
+ mProgram.getTitle());
String description = getConflictDescription();
if (description == null) {
dismissDialog();
@@ -201,21 +215,21 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
}
- /**
- * A fragment to show the channel recording conflict.
- */
+ /** A fragment to show the channel recording conflict. */
public static class DvrChannelRecordConflictFragment extends DvrConflictFragment {
private Channel mChannel;
private long mStartTimeMs;
private long mEndTimeMs;
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
- mChannel = TvApplication.getSingletons(getContext()).getChannelDataManager()
- .getChannel(channelId);
+ mChannel =
+ TvSingletons.getSingletons(getContext())
+ .getChannelDataManager()
+ .getChannel(channelId);
SoftPreconditions.checkArgument(mChannel != null);
TvInputInfo input = Utils.getTvInputInfoForChannelId(getContext(), mChannel.getId());
SoftPreconditions.checkNotNull(input);
@@ -223,8 +237,11 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
if (input != null) {
mStartTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS);
mEndTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS);
- conflicts = TvApplication.getSingletons(getContext()).getDvrManager()
- .getConflictingSchedules(mChannel.getId(), mStartTimeMs, mEndTimeMs);
+ conflicts =
+ TvSingletons.getSingletons(getContext())
+ .getDvrManager()
+ .getConflictingSchedules(
+ mChannel.getId(), mStartTimeMs, mEndTimeMs);
}
if (conflicts == null) {
conflicts = Collections.emptyList();
@@ -240,9 +257,10 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = getResources().getString(R.string.dvr_channel_conflict_dialog_title);
- String descriptionPrefix = getString(
- R.string.dvr_channel_conflict_dialog_description_prefix,
- mChannel.getDisplayName());
+ String descriptionPrefix =
+ getString(
+ R.string.dvr_channel_conflict_dialog_description_prefix,
+ mChannel.getDisplayName());
String description = getConflictDescription();
if (description == null) {
dismissDialog();
@@ -259,16 +277,16 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
/**
* A fragment to show the channel watching conflict.
- * <p>
- * This fragment is automatically closed when there are no upcoming conflicts.
+ *
+ * <p>This fragment is automatically closed when there are no upcoming conflicts.
*/
public static class DvrChannelWatchConflictFragment extends DvrConflictFragment
implements OnUpcomingConflictChangeListener {
private long mChannelId;
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
mChannelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
@@ -298,24 +316,27 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
@NonNull
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(
- R.string.dvr_epg_channel_watch_conflict_dialog_title);
- String description = getResources().getString(
- R.string.dvr_epg_channel_watch_conflict_dialog_description);
+ String title =
+ getResources().getString(R.string.dvr_epg_channel_watch_conflict_dialog_title);
+ String description =
+ getResources()
+ .getString(R.string.dvr_epg_channel_watch_conflict_dialog_description);
return new Guidance(title, description, null, null);
}
@Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- actions.add(new GuidedAction.Builder(getContext())
- .id(ACTION_DELETE_CONFLICT)
- .title(R.string.dvr_action_delete_schedule)
- .build());
- actions.add(new GuidedAction.Builder(getContext())
- .id(ACTION_CANCEL)
- .title(R.string.dvr_action_record_program)
- .build());
+ public void onCreateActions(
+ @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(ACTION_DELETE_CONFLICT)
+ .title(R.string.dvr_action_delete_schedule)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getContext())
+ .id(ACTION_CANCEL)
+ .title(R.string.dvr_action_record_program)
+ .build());
}
@Override
diff --git a/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java b/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java
new file mode 100644
index 00000000..677a6cbb
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import com.android.tv.TvSingletons;
+import com.android.tv.data.Program;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.util.Utils;
+import java.util.List;
+
+/**
+ * A fragment which shows the formation of a program.
+ */
+public class DvrFutureProgramInfoFragment extends DvrGuidedStepFragment {
+ private static final long ACTION_ID_VIEW_SCHEDULE = 1;
+ private ScheduledRecording mScheduledRecording;
+ private Program mProgram;
+
+ @Override
+ public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+ long startTime = mProgram.getStartTimeUtcMillis();
+ // TODO(b/71717923): use R.string when the strings are finalized
+ StringBuilder description = new StringBuilder()
+ .append("This program will start at ")
+ .append(Utils.getDurationString(getContext(), startTime, startTime, false));
+ if (mScheduledRecording != null) {
+ description.append("\nThis program has been scheduled for recording.");
+ }
+ return new GuidanceStylist.Guidance(
+ mProgram.getTitle(), description.toString(), null, null);
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ Activity activity = getActivity();
+ mProgram = getArguments().getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
+ mScheduledRecording =
+ TvSingletons.getSingletons(getContext())
+ .getDvrDataManager()
+ .getScheduledRecordingForProgramId(mProgram.getId());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(GuidedAction.ACTION_ID_OK)
+ .title(android.R.string.ok)
+ .build());
+ if (mScheduledRecording != null) {
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_ID_VIEW_SCHEDULE)
+ .title("View schedules")
+ .build());
+ }
+
+ }
+
+ @Override
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
+ if (action.getId() == ACTION_ID_VIEW_SCHEDULE) {
+ DvrUiHelper.startSchedulesActivity(getContext(), mScheduledRecording);
+ return;
+ }
+ dismissDialog();
+ }
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrFutureProgramInfoFragment";
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java b/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java
index 6b0c22ff..611962d0 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedActionsStylist.java
@@ -24,12 +24,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
-
import com.android.tv.R;
-/**
- * Stylist class used for DVR settings {@link GuidedStepFragment}.
- */
+/** Stylist class used for DVR settings {@link GuidedStepFragment}. */
public class DvrGuidedActionsStylist extends GuidedActionsStylist {
private static boolean sInitialized;
private static float sWidthWeight;
@@ -68,11 +65,13 @@ public class DvrGuidedActionsStylist extends GuidedActionsStylist {
return;
}
sInitialized = true;
- sItemHeight = context.getResources().getDimensionPixelSize(
- R.dimen.dvr_settings_one_line_action_container_height);
+ sItemHeight =
+ context.getResources()
+ .getDimensionPixelSize(
+ R.dimen.dvr_settings_one_line_action_container_height);
TypedValue outValue = new TypedValue();
- context.getResources().getValue(R.dimen.dvr_settings_button_actions_list_width_weight,
- outValue, true);
+ context.getResources()
+ .getValue(R.dimen.dvr_settings_button_actions_list_width_weight, outValue, true);
sWidthWeight = outValue.getFloat();
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
index ab852e10..a900cc70 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
@@ -26,31 +26,23 @@ import android.support.v17.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrStorageStatusManager;
-
import java.util.List;
public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment {
- /**
- * Action ID for "recording/scheduling the program anyway".
- */
+ /** Action ID for "recording/scheduling the program anyway". */
public static final int ACTION_RECORD_ANYWAY = 1;
- /**
- * Action ID for "deleting existed recordings".
- */
+ /** Action ID for "deleting existed recordings". */
public static final int ACTION_DELETE_RECORDINGS = 2;
- /**
- * Action ID for "cancelling current recording request".
- */
+ /** Action ID for "cancelling current recording request". */
public static final int ACTION_CANCEL_RECORDING = 3;
+
public static final String UNKNOWN_DVR_ACTION = "Unknown DVR Action";
private DvrManager mDvrManager;
@@ -63,13 +55,13 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ TvSingletons singletons = TvSingletons.getSingletons(context);
mDvrManager = singletons.getDvrManager();
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
VerticalGridView actionsList = getGuidedActionsStylist().getActionsGridView();
actionsList.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
@@ -122,32 +114,37 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment {
}
/**
- * The inner guided step fragment for
- * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
+ * The inner guided step fragment for {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
* .DvrNoFreeSpaceErrorDialogFragment}.
*/
public static class DvrNoFreeSpaceErrorFragment extends DvrGuidedStepFragment {
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
- return new GuidanceStylist.Guidance(getString(R.string.dvr_error_no_free_space_title),
- getString(R.string.dvr_error_no_free_space_description), null, null);
+ return new GuidanceStylist.Guidance(
+ getString(R.string.dvr_error_no_free_space_title),
+ getString(R.string.dvr_error_no_free_space_description),
+ null,
+ null);
}
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_RECORD_ANYWAY)
- .title(R.string.dvr_action_record_anyway)
- .build());
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_DELETE_RECORDINGS)
- .title(R.string.dvr_action_delete_recordings)
- .build());
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_CANCEL_RECORDING)
- .title(R.string.dvr_action_record_cancel)
- .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_RECORD_ANYWAY)
+ .title(R.string.dvr_action_record_anyway)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_DELETE_RECORDINGS)
+ .title(R.string.dvr_action_delete_recordings)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_CANCEL_RECORDING)
+ .title(R.string.dvr_action_record_cancel)
+ .build());
}
@Override
@@ -157,29 +154,32 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment {
}
/**
- * The inner guided step fragment for
- * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
+ * The inner guided step fragment for {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
* .DvrSmallSizedStorageErrorDialogFragment}.
*/
public static class DvrSmallSizedStorageErrorFragment extends DvrGuidedStepFragment {
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(
- R.string.dvr_error_small_sized_storage_title);
- String description = getResources().getString(
- R.string.dvr_error_small_sized_storage_description,
- DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024
- / 1024 / 1024);
+ String title = getResources().getString(R.string.dvr_error_small_sized_storage_title);
+ String description =
+ getResources()
+ .getString(
+ R.string.dvr_error_small_sized_storage_description,
+ RecordingStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES
+ / 1024
+ / 1024
+ / 1024);
return new GuidanceStylist.Guidance(title, description, null, null);
}
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(GuidedAction.ACTION_ID_OK)
- .title(android.R.string.ok)
- .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(GuidedAction.ACTION_ID_OK)
+ .title(android.R.string.ok)
+ .build());
}
@Override
@@ -192,4 +192,4 @@ public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment {
return "DvrSmallSizedStorageErrorFragment";
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
index f8ef3850..4a713703 100644
--- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
@@ -20,47 +20,26 @@ import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelWatchConflictFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.guide.ProgramGuide;
-import java.util.List;
-
public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
- /**
- * Key for input ID.
- * Type: String.
- */
+ /** Key for input ID. Type: String. */
public static final String KEY_INPUT_ID = "DvrHalfSizedDialogFragment.input_id";
- /**
- * Key for the program.
- * Type: {@link com.android.tv.data.Program}.
- */
+ /** Key for the program. Type: {@link com.android.tv.data.Program}. */
public static final String KEY_PROGRAM = "DvrHalfSizedDialogFragment.program";
- /**
- * Key for the channel ID.
- * Type: long.
- */
+ /** Key for the channel ID. Type: long. */
public static final String KEY_CHANNEL_ID = "DvrHalfSizedDialogFragment.channel_id";
- /**
- * Key for the recording start time in millisecond.
- * Type: long.
- */
+ /** Key for the recording start time in millisecond. Type: long. */
public static final String KEY_START_TIME_MS = "DvrHalfSizedDialogFragment.start_time_ms";
- /**
- * Key for the recording end time in millisecond.
- * Type: long.
- */
+ /** Key for the recording end time in millisecond. Type: long. */
public static final String KEY_END_TIME_MS = "DvrHalfSizedDialogFragment.end_time_ms";
@Override
@@ -93,14 +72,14 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
private DvrGuidedStepFragment mFragment;
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
mFragment = onCreateGuidedStepFragment();
mFragment.setArguments(getArguments());
mFragment.setOnActionClickListener(getOnActionClickListener());
- GuidedStepFragment.add(getChildFragmentManager(),
- mFragment, R.id.halfsized_dialog_host);
+ GuidedStepFragment.add(
+ getChildFragmentManager(), mFragment, R.id.halfsized_dialog_host);
return view;
}
@@ -158,19 +137,15 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
}
/** A dialog fragment for {@link DvrMissingStorageErrorFragment}. */
- public static class DvrMissingStorageErrorDialogFragment
- extends DvrGuidedStepDialogFragment {
+ public static class DvrMissingStorageErrorDialogFragment extends DvrGuidedStepDialogFragment {
@Override
protected DvrGuidedStepFragment onCreateGuidedStepFragment() {
return new DvrMissingStorageErrorFragment();
}
}
- /**
- * A dialog fragment to show error message when there is no enough free space to record.
- */
- public static class DvrNoFreeSpaceErrorDialogFragment
- extends DvrGuidedStepDialogFragment {
+ /** A dialog fragment to show error message when there is no enough free space to record. */
+ public static class DvrNoFreeSpaceErrorDialogFragment extends DvrGuidedStepDialogFragment {
@Override
protected DvrGuidedStepFragment onCreateGuidedStepFragment() {
return new DvrGuidedStepFragment.DvrNoFreeSpaceErrorFragment();
@@ -178,8 +153,7 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
}
/**
- * A dialog fragment to show error message when the current storage is too small to
- * support DVR
+ * A dialog fragment to show error message when the current storage is too small to support DVR
*/
public static class DvrSmallSizedStorageErrorDialogFragment
extends DvrGuidedStepDialogFragment {
@@ -212,4 +186,12 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
return new DvrAlreadyRecordedFragment();
}
}
-} \ No newline at end of file
+
+ /** A dialog fragment for {@link DvrFutureProgramInfoFragment}. */
+ public static class DvrFutureProgramInfoDialogFragment extends DvrGuidedStepDialogFragment {
+ @Override
+ protected DvrGuidedStepFragment onCreateGuidedStepFragment() {
+ return new DvrFutureProgramInfoFragment();
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
index 182416b6..6fba4d98 100644
--- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
@@ -22,19 +22,15 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
-
import java.util.ArrayList;
import java.util.List;
public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
- /**
- * Key for the failed scheduled recordings information.
- */
+ /** Key for the failed scheduled recordings information. */
public static final String FAILED_SCHEDULED_RECORDING_INFOS =
"failed_scheduled_recording_infos";
@@ -54,7 +50,8 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
}
SoftPreconditions.checkState(
mFailedScheduledRecordingInfos != null && !mFailedScheduledRecordingInfos.isEmpty(),
- TAG, "failed scheduled recording is null");
+ TAG,
+ "failed scheduled recording is null");
}
@Override
@@ -63,28 +60,39 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
String description;
int failedScheduledRecordingSize = mFailedScheduledRecordingInfos.size();
if (failedScheduledRecordingSize == 1) {
- title = getString(
- R.string.dvr_error_insufficient_space_title_one_recording,
- mFailedScheduledRecordingInfos.get(0));
- description = getString(
- R.string.dvr_error_insufficient_space_description_one_recording,
- mFailedScheduledRecordingInfos.get(0));
+ title =
+ getString(
+ R.string.dvr_error_insufficient_space_title_one_recording,
+ mFailedScheduledRecordingInfos.get(0));
+ description =
+ getString(
+ R.string.dvr_error_insufficient_space_description_one_recording,
+ mFailedScheduledRecordingInfos.get(0));
} else if (failedScheduledRecordingSize == 2) {
- title = getString(
- R.string.dvr_error_insufficient_space_title_two_recordings,
- mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1));
- description = getString(
- R.string.dvr_error_insufficient_space_description_two_recordings,
- mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1));
+ title =
+ getString(
+ R.string.dvr_error_insufficient_space_title_two_recordings,
+ mFailedScheduledRecordingInfos.get(0),
+ mFailedScheduledRecordingInfos.get(1));
+ description =
+ getString(
+ R.string.dvr_error_insufficient_space_description_two_recordings,
+ mFailedScheduledRecordingInfos.get(0),
+ mFailedScheduledRecordingInfos.get(1));
} else {
- title = getString(
- R.string.dvr_error_insufficient_space_title_three_or_more_recordings,
- mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1),
- mFailedScheduledRecordingInfos.get(2));
- description = getString(
- R.string.dvr_error_insufficient_space_description_three_or_more_recordings,
- mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1),
- mFailedScheduledRecordingInfos.get(2));
+ title =
+ getString(
+ R.string.dvr_error_insufficient_space_title_three_or_more_recordings,
+ mFailedScheduledRecordingInfos.get(0),
+ mFailedScheduledRecordingInfos.get(1),
+ mFailedScheduledRecordingInfos.get(2));
+ description =
+ getString(
+ R.string
+ .dvr_error_insufficient_space_description_three_or_more_recordings,
+ mFailedScheduledRecordingInfos.get(0),
+ mFailedScheduledRecordingInfos.get(1),
+ mFailedScheduledRecordingInfos.get(2));
}
return new Guidance(title, description, null, null);
}
@@ -92,15 +100,18 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .clickAction(GuidedAction.ACTION_ID_OK)
- .build());
- if (TvApplication.getSingletons(getContext()).getDvrManager().hasValidItems()) {
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_VIEW_RECENT_RECORDINGS)
- .title(getResources().getString(
- R.string.dvr_error_insufficient_space_action_view_recent_recordings))
- .build());
+ actions.add(
+ new GuidedAction.Builder(activity).clickAction(GuidedAction.ACTION_ID_OK).build());
+ if (TvSingletons.getSingletons(getContext()).getDvrManager().hasValidItems()) {
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_VIEW_RECENT_RECORDINGS)
+ .title(
+ getResources()
+ .getString(
+ R.string
+ .dvr_error_insufficient_space_action_view_recent_recordings))
+ .build());
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
index e726995f..e5f40260 100644
--- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
@@ -24,10 +24,8 @@ import android.provider.Settings;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.util.Log;
-
import com.android.tv.R;
import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
-
import java.util.List;
public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
@@ -44,22 +42,24 @@ public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = getResources().getString(R.string.dvr_error_missing_storage_title);
- String description = getResources().getString(
- R.string.dvr_error_missing_storage_description);
+ String description =
+ getResources().getString(R.string.dvr_error_missing_storage_description);
return new Guidance(title, description, null, null);
}
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_OK)
- .title(android.R.string.ok)
- .build());
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_OPEN_STORAGE_SETTINGS)
- .title(getResources().getString(R.string.dvr_action_error_storage_settings))
- .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_OK)
+ .title(android.R.string.ok)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_OPEN_STORAGE_SETTINGS)
+ .title(getResources().getString(R.string.dvr_action_error_storage_settings))
+ .build());
}
@Override
diff --git a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
index e4cb7243..5bb97e90 100644
--- a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
@@ -16,9 +16,11 @@
package com.android.tv.dvr.ui;
+import android.annotation.TargetApi;
import android.app.FragmentManager;
import android.content.Context;
import android.graphics.Typeface;
+import android.os.Build;
import android.os.Bundle;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
@@ -26,23 +28,20 @@ import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.data.SeriesRecording;
-
import java.util.ArrayList;
import java.util.List;
/** Fragment for DVR series recording settings. */
+@TargetApi(Build.VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment {
- /**
- * Name of series recording id starting the fragment.
- * Type: Long
- */
+ /** Name of series recording id starting the fragment. Type: Long */
public static final String COME_FROM_SERIES_RECORDING_ID = "series_recording_id";
private static final int ONE_TIME_RECORDING_ID = 0;
@@ -61,14 +60,14 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment {
public void onAttach(Context context) {
super.onAttach(context);
mSeriesRecordings.clear();
- mSeriesRecordings.add(new SeriesRecording.Builder()
- .setTitle(getString(R.string.dvr_priority_action_one_time_recording))
- .setPriority(Long.MAX_VALUE)
- .setId(ONE_TIME_RECORDING_ID)
- .build());
- DvrDataManager dvrDataManager = TvApplication.getSingletons(context).getDvrDataManager();
- long comeFromSeriesRecordingId =
- getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1);
+ mSeriesRecordings.add(
+ new SeriesRecording.Builder()
+ .setTitle(getString(R.string.dvr_priority_action_one_time_recording))
+ .setPriority(Long.MAX_VALUE)
+ .setId(ONE_TIME_RECORDING_ID)
+ .build());
+ DvrDataManager dvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
+ long comeFromSeriesRecordingId = getArguments().getLong(COME_FROM_SERIES_RECORDING_ID, -1);
for (SeriesRecording series : dvrDataManager.getSeriesRecordings()) {
if (series.getState() == SeriesRecording.STATE_SERIES_NORMAL
|| series.getId() == comeFromSeriesRecordingId) {
@@ -86,52 +85,62 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment {
@Override
public void onResume() {
super.onResume();
- setSelectedActionPosition(mComeFromSeriesRecording == null ? 1
- : mSeriesRecordings.indexOf(mComeFromSeriesRecording));
+ setSelectedActionPosition(
+ mComeFromSeriesRecording == null
+ ? 1
+ : mSeriesRecordings.indexOf(mComeFromSeriesRecording));
}
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String breadcrumb = mComeFromSeriesRecording == null ? null
- : mComeFromSeriesRecording.getTitle();
- return new Guidance(getString(R.string.dvr_priority_title),
- getString(R.string.dvr_priority_description), breadcrumb, null);
+ String breadcrumb =
+ mComeFromSeriesRecording == null ? null : mComeFromSeriesRecording.getTitle();
+ return new Guidance(
+ getString(R.string.dvr_priority_title),
+ getString(R.string.dvr_priority_description),
+ breadcrumb,
+ null);
}
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
int position = 0;
for (SeriesRecording seriesRecording : mSeriesRecordings) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(position++)
- .title(seriesRecording.getTitle())
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(position++)
+ .title(seriesRecording.getTitle())
+ .build());
}
}
@Override
public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_SAVE)
- .title(getString(R.string.dvr_priority_button_action_save))
- .build());
- actions.add(new GuidedAction.Builder(getActivity())
- .clickAction(GuidedAction.ACTION_ID_CANCEL)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_SAVE)
+ .title(getString(R.string.dvr_priority_button_action_save))
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .clickAction(GuidedAction.ACTION_ID_CANCEL)
+ .build());
}
@Override
public void onTrackedGuidedActionClicked(GuidedAction action) {
long actionId = action.getId();
if (actionId == ACTION_ID_SAVE) {
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
int size = mSeriesRecordings.size();
for (int i = 1; i < size; ++i) {
long priority = DvrScheduleManager.suggestSeriesPriority(size - i);
SeriesRecording seriesRecording = mSeriesRecordings.get(i);
if (seriesRecording.getPriority() != priority) {
- dvrManager.updateSeriesRecording(SeriesRecording.buildFrom(seriesRecording)
- .setPriority(priority).build());
+ dvrManager.updateSeriesRecording(
+ SeriesRecording.buildFrom(seriesRecording)
+ .setPriority(priority)
+ .build());
}
}
FragmentManager fragmentManager = getFragmentManager();
@@ -222,8 +231,9 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment {
private void updateItem(View itemView, int position) {
GuidedAction action = getActions().get(position);
action.setTitle(mSeriesRecordings.get(position).getTitle());
- boolean selected = mSelectedRecording != null
- && mSeriesRecordings.indexOf(mSelectedRecording) == position;
+ boolean selected =
+ mSelectedRecording != null
+ && mSeriesRecordings.indexOf(mSelectedRecording) == position;
TextView titleView = (TextView) itemView.findViewById(R.id.guidedactions_item_title);
ImageView imageView = (ImageView) itemView.findViewById(R.id.guidedactions_item_tail_image);
if (position == 0) {
@@ -259,4 +269,4 @@ public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment {
titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
index 390e0928..5251e140 100644
--- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
@@ -26,9 +26,8 @@ import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.text.format.DateUtils;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
@@ -36,21 +35,17 @@ import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.util.Utils;
-
import java.util.Collections;
import java.util.List;
/**
* A fragment which asks the user the type of the recording.
- * <p>
- * The program should be episodic and the series recording should not had been created yet.
+ *
+ * <p>The program should be episodic and the series recording should not had been created yet.
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrScheduleFragment extends DvrGuidedStepFragment {
- /**
- * Key for the whether to add the current program to series.
- * Type: boolean
- */
+ /** Key for the whether to add the current program to series. Type: boolean */
public static final String KEY_ADD_CURRENT_PROGRAM_TO_SERIES = "add_current_program_to_series";
private static final String TAG = "DvrScheduleFragment";
@@ -68,13 +63,18 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
mAddCurrentProgramToSeries = args.getBoolean(KEY_ADD_CURRENT_PROGRAM_TO_SERIES, false);
}
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
- SoftPreconditions.checkArgument(mProgram != null && mProgram.isEpisodic(), TAG,
- "The program should be episodic: " + mProgram);
+ DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
+ SoftPreconditions.checkArgument(
+ mProgram != null && mProgram.isEpisodic(),
+ TAG,
+ "The program should be episodic: %s ",
+ mProgram);
SeriesRecording seriesRecording = dvrManager.getSeriesRecording(mProgram);
- SoftPreconditions.checkArgument(seriesRecording == null
- || seriesRecording.isStopped(), TAG,
- "The series recording should be stopped or null: " + seriesRecording);
+ SoftPreconditions.checkArgument(
+ seriesRecording == null || seriesRecording.isStopped(),
+ TAG,
+ "The series recording should be stopped or null: %s",
+ seriesRecording);
super.onCreate(savedInstanceState);
}
@@ -96,23 +96,33 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
Context context = getContext();
String description;
if (mProgram.getStartTimeUtcMillis() <= System.currentTimeMillis()) {
- description = getString(R.string.dvr_action_record_episode_from_now_description,
- DateUtils.formatDateTime(context, mProgram.getEndTimeUtcMillis(),
- DateUtils.FORMAT_SHOW_TIME));
+ description =
+ getString(
+ R.string.dvr_action_record_episode_from_now_description,
+ DateUtils.formatDateTime(
+ context,
+ mProgram.getEndTimeUtcMillis(),
+ DateUtils.FORMAT_SHOW_TIME));
} else {
- description = Utils.getDurationString(context, mProgram.getStartTimeUtcMillis(),
- mProgram.getEndTimeUtcMillis(), true);
+ description =
+ Utils.getDurationString(
+ context,
+ mProgram.getStartTimeUtcMillis(),
+ mProgram.getEndTimeUtcMillis(),
+ true);
}
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_RECORD_EPISODE)
- .title(R.string.dvr_action_record_episode)
- .description(description)
- .build());
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_RECORD_SERIES)
- .title(R.string.dvr_action_record_series)
- .description(mProgram.getTitle())
- .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_RECORD_EPISODE)
+ .title(R.string.dvr_action_record_episode)
+ .description(description)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_RECORD_SERIES)
+ .title(R.string.dvr_action_record_series)
+ .description(mProgram.getTitle())
+ .build());
}
@Override
@@ -121,34 +131,50 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
getDvrManager().addSchedule(mProgram);
List<ScheduledRecording> conflicts = getDvrManager().getConflictingSchedules(mProgram);
if (conflicts.isEmpty()) {
- DvrUiHelper.showAddScheduleToast(getContext(), mProgram.getTitle(),
- mProgram.getStartTimeUtcMillis(), mProgram.getEndTimeUtcMillis());
+ DvrUiHelper.showAddScheduleToast(
+ getContext(),
+ mProgram.getTitle(),
+ mProgram.getStartTimeUtcMillis(),
+ mProgram.getEndTimeUtcMillis());
dismissDialog();
} else {
GuidedStepFragment fragment = new DvrProgramConflictFragment();
Bundle args = new Bundle();
args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, mProgram);
fragment.setArguments(args);
- GuidedStepFragment.add(getFragmentManager(), fragment,
- R.id.halfsized_dialog_host);
+ GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host);
}
} else if (action.getId() == ACTION_RECORD_SERIES) {
- SeriesRecording seriesRecording = TvApplication.getSingletons(getContext())
- .getDvrDataManager().getSeriesRecording(mProgram.getSeriesId());
+ SeriesRecording seriesRecording =
+ TvSingletons.getSingletons(getContext())
+ .getDvrDataManager()
+ .getSeriesRecording(mProgram.getSeriesId());
if (seriesRecording == null) {
- seriesRecording = getDvrManager().addSeriesRecording(mProgram,
- Collections.emptyList(), SeriesRecording.STATE_SERIES_STOPPED);
+ seriesRecording =
+ getDvrManager()
+ .addSeriesRecording(
+ mProgram,
+ Collections.emptyList(),
+ SeriesRecording.STATE_SERIES_STOPPED);
} else {
// Reset priority to the highest.
- seriesRecording = SeriesRecording.buildFrom(seriesRecording)
- .setPriority(TvApplication.getSingletons(getContext())
- .getDvrScheduleManager().suggestNewSeriesPriority())
- .build();
+ seriesRecording =
+ SeriesRecording.buildFrom(seriesRecording)
+ .setPriority(
+ TvSingletons.getSingletons(getContext())
+ .getDvrScheduleManager()
+ .suggestNewSeriesPriority())
+ .build();
getDvrManager().updateSeriesRecording(seriesRecording);
}
- DvrUiHelper.startSeriesSettingsActivity(getContext(),
- seriesRecording.getId(), null, true, true, true,
+ DvrUiHelper.startSeriesSettingsActivity(
+ getContext(),
+ seriesRecording.getId(),
+ null,
+ true,
+ true,
+ true,
mAddCurrentProgramToSeries ? mProgram : null);
dismissDialog();
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
index 667af34a..a2ae1f97 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
@@ -19,22 +19,17 @@ package com.android.tv.dvr.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
-/**
- * Activity to show details view in DVR.
- */
+/** Activity to show details view in DVR. */
public class DvrSeriesDeletionActivity extends Activity {
- /**
- * Name of series id added to the Intent.
- */
+ /** Name of series id added to the Intent. */
public static final String SERIES_RECORDING_ID = "series_recording_id";
@Override
public void onCreate(Bundle savedInstanceState) {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dvr_series_settings);
// Check savedInstanceState to prevent that activity is being showed with animation.
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
index 8bf8560f..685f0a58 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
@@ -26,9 +26,8 @@ import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.text.TextUtils;
import android.view.ViewGroup.LayoutParams;
import android.widget.Toast;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -37,7 +36,6 @@ import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.ui.GuidedActionsStylistWithDivider;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -45,9 +43,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-/**
- * Fragment for DVR series recording settings.
- */
+/** Fragment for DVR series recording settings. */
public class DvrSeriesDeletionFragment extends GuidedStepFragment {
private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
@@ -68,18 +64,23 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mSeriesRecordingId = getArguments()
- .getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1);
+ mSeriesRecordingId =
+ getArguments().getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1);
SoftPreconditions.checkArgument(mSeriesRecordingId != -1);
- mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
mDvrWatchedPositionManager =
- TvApplication.getSingletons(context).getDvrWatchedPositionManager();
+ TvSingletons.getSingletons(context).getDvrWatchedPositionManager();
mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId);
- mOneLineActionHeight = getResources().getDimensionPixelSize(
- R.dimen.dvr_settings_one_line_action_container_height);
+ mOneLineActionHeight =
+ getResources()
+ .getDimensionPixelSize(
+ R.dimen.dvr_settings_one_line_action_container_height);
if (mRecordings.isEmpty()) {
- Toast.makeText(getActivity(), getString(R.string.dvr_series_deletion_no_recordings),
- Toast.LENGTH_LONG).show();
+ Toast.makeText(
+ getActivity(),
+ getString(R.string.dvr_series_deletion_no_recordings),
+ Toast.LENGTH_LONG)
+ .show();
finishGuidedStepFragments();
return;
}
@@ -93,28 +94,35 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment {
if (series != null) {
breadcrumb = series.getTitle();
}
- return new Guidance(getString(R.string.dvr_series_deletion_title),
- getString(R.string.dvr_series_deletion_description), breadcrumb, null);
+ return new Guidance(
+ getString(R.string.dvr_series_deletion_title),
+ getString(R.string.dvr_series_deletion_description),
+ breadcrumb,
+ null);
}
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_SELECT_WATCHED)
- .title(getString(R.string.dvr_series_select_watched))
- .build());
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_SELECT_ALL)
- .title(getString(R.string.dvr_series_select_all))
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_SELECT_WATCHED)
+ .title(getString(R.string.dvr_series_select_watched))
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_SELECT_ALL)
+ .title(getString(R.string.dvr_series_select_all))
+ .build());
actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext()));
for (RecordedProgram recording : mRecordings) {
long watchedPositionMs =
mDvrWatchedPositionManager.getWatchedPosition(recording.getId());
String title = recording.getEpisodeDisplayTitle(getContext());
if (TextUtils.isEmpty(title)) {
- title = TextUtils.isEmpty(recording.getTitle()) ?
- getString(R.string.channel_banner_no_title) : recording.getTitle();
+ title =
+ TextUtils.isEmpty(recording.getTitle())
+ ? getString(R.string.channel_banner_no_title)
+ : recording.getTitle();
}
String description;
if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
@@ -123,24 +131,27 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment {
} else {
description = getString(R.string.dvr_series_never_watched);
}
- actions.add(new GuidedAction.Builder(getActivity())
- .id(recording.getId())
- .title(title)
- .description(description)
- .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(recording.getId())
+ .title(title)
+ .description(description)
+ .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID)
+ .build());
}
}
@Override
public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_DELETE)
- .title(getString(R.string.dvr_detail_delete))
- .build());
- actions.add(new GuidedAction.Builder(getActivity())
- .clickAction(GuidedAction.ACTION_ID_CANCEL)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_DELETE)
+ .title(getString(R.string.dvr_detail_delete))
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .clickAction(GuidedAction.ACTION_ID_CANCEL)
+ .build());
}
@Override
@@ -155,12 +166,19 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment {
}
}
if (!idsToDelete.isEmpty()) {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
+ DvrManager dvrManager = TvSingletons.getSingletons(getActivity()).getDvrManager();
dvrManager.removeRecordedPrograms(idsToDelete);
}
- Toast.makeText(getContext(), getResources().getQuantityString(
- R.plurals.dvr_msg_episodes_deleted, idsToDelete.size(), idsToDelete.size(),
- mRecordings.size()), Toast.LENGTH_LONG).show();
+ Toast.makeText(
+ getContext(),
+ getResources()
+ .getQuantityString(
+ R.plurals.dvr_msg_episodes_deleted,
+ idsToDelete.size(),
+ idsToDelete.size(),
+ mRecordings.size()),
+ Toast.LENGTH_LONG)
+ .show();
finishGuidedStepFragments();
} else if (actionId == GuidedAction.ACTION_ID_CANCEL) {
finishGuidedStepFragments();
@@ -218,13 +236,17 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment {
private String getWatchedString(long watchedPositionMs, long durationMs) {
if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) {
- return getResources().getString(R.string.dvr_series_watched_info_minutes,
- Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)),
- Utils.getRoundOffMinsFromMs(durationMs));
+ return getResources()
+ .getString(
+ R.string.dvr_series_watched_info_minutes,
+ Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)),
+ Utils.getRoundOffMinsFromMs(durationMs));
} else {
- return getResources().getString(R.string.dvr_series_watched_info_seconds,
- Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)),
- TimeUnit.MILLISECONDS.toSeconds(durationMs));
+ return getResources()
+ .getString(
+ R.string.dvr_series_watched_info_seconds,
+ Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)),
+ TimeUnit.MILLISECONDS.toSeconds(durationMs));
}
}
@@ -246,8 +268,10 @@ public class DvrSeriesDeletionFragment extends GuidedStepFragment {
}
private void updateSelectAllState(GuidedAction selectAll, boolean select) {
- selectAll.setTitle(select ? getString(R.string.dvr_series_deselect_all)
- : getString(R.string.dvr_series_select_all));
+ selectAll.setTitle(
+ select
+ ? getString(R.string.dvr_series_deselect_all)
+ : getString(R.string.dvr_series_select_all));
notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL));
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java
index 1a0d13d3..9acb5b5e 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledDialogActivity.java
@@ -19,18 +19,13 @@ package com.android.tv.dvr.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
-
import com.android.tv.R;
public class DvrSeriesScheduledDialogActivity extends Activity {
- /**
- * Name of series recording id added to the Intent.
- */
+ /** Name of series recording id added to the Intent. */
public static final String SERIES_RECORDING_ID = "series_recording_id";
- /**
- * Name of flag to check if the dialog should show view schedule option.
- */
+ /** Name of flag to check if the dialog should show view schedule option. */
public static final String SHOW_VIEW_SCHEDULE_OPTION = "show_view_schedule_option";
@Override
@@ -41,8 +36,8 @@ public class DvrSeriesScheduledDialogActivity extends Activity {
DvrSeriesScheduledFragment dvrSeriesScheduledFragment =
new DvrSeriesScheduledFragment();
dvrSeriesScheduledFragment.setArguments(getIntent().getExtras());
- GuidedStepFragment.addAsRoot(this, dvrSeriesScheduledFragment,
- R.id.halfsized_dialog_host);
+ GuidedStepFragment.addAsRoot(
+ this, dvrSeriesScheduledFragment, R.id.halfsized_dialog_host);
}
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
index 2c4bb3ea..edb62c96 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
@@ -22,28 +22,26 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidedAction;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
-
import java.util.List;
public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
/**
- * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}.
- * Type: List<{@link Program}>
+ * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}. Type:
+ * List<{@link Program}>
*/
public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs";
- private final static long SERIES_RECORDING_ID_NOT_SET = -1;
+ private static final long SERIES_RECORDING_ID_NOT_SET = -1;
- private final static int ACTION_VIEW_SCHEDULES = 1;
+ private static final int ACTION_VIEW_SCHEDULES = 1;
private SeriesRecording mSeriesRecording;
private boolean mShowViewScheduleOption;
@@ -57,26 +55,35 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- long seriesRecordingId = getArguments().getLong(
- DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, SERIES_RECORDING_ID_NOT_SET);
+ long seriesRecordingId =
+ getArguments()
+ .getLong(
+ DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID,
+ SERIES_RECORDING_ID_NOT_SET);
if (seriesRecordingId == SERIES_RECORDING_ID_NOT_SET) {
getActivity().finish();
return;
}
- mShowViewScheduleOption = getArguments().getBoolean(
- DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION);
- mSeriesRecording = TvApplication.getSingletons(context).getDvrDataManager()
- .getSeriesRecording(seriesRecordingId);
+ mShowViewScheduleOption =
+ getArguments()
+ .getBoolean(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION);
+ mSeriesRecording =
+ TvSingletons.getSingletons(context)
+ .getDvrDataManager()
+ .getSeriesRecording(seriesRecordingId);
if (mSeriesRecording == null) {
getActivity().finish();
return;
}
mPrograms = (List<Program>) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS);
BigArguments.reset();
- mSchedulesAddedCount = TvApplication.getSingletons(getContext()).getDvrManager()
- .getAvailableScheduledRecording(mSeriesRecording.getId()).size();
+ mSchedulesAddedCount =
+ TvSingletons.getSingletons(getContext())
+ .getDvrManager()
+ .getAvailableScheduledRecording(mSeriesRecording.getId())
+ .size();
DvrScheduleManager dvrScheduleManager =
- TvApplication.getSingletons(context).getDvrScheduleManager();
+ TvSingletons.getSingletons(context).getDvrScheduleManager();
List<ScheduledRecording> conflictingRecordings =
dvrScheduleManager.getConflictingSchedules(mSeriesRecording);
mHasConflict = !conflictingRecordings.isEmpty();
@@ -87,7 +94,7 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
++mOutThisSeriesConflictCount;
}
}
- }
+ }
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
@@ -104,14 +111,14 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Context context = getContext();
- actions.add(new GuidedAction.Builder(context)
- .clickAction(GuidedAction.ACTION_ID_OK)
- .build());
+ actions.add(
+ new GuidedAction.Builder(context).clickAction(GuidedAction.ACTION_ID_OK).build());
if (mShowViewScheduleOption) {
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_VIEW_SCHEDULES)
- .title(R.string.dvr_action_view_schedules)
- .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_VIEW_SCHEDULES)
+ .title(R.string.dvr_action_view_schedules)
+ .build());
}
}
@@ -119,13 +126,15 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_VIEW_SCHEDULES) {
Intent intent = new Intent(getActivity(), DvrSchedulesActivity.class);
- intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity
- .TYPE_SERIES_SCHEDULE);
- intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
+ intent.putExtra(
+ DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
+ DvrSchedulesActivity.TYPE_SERIES_SCHEDULE);
+ intent.putExtra(
+ DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
mSeriesRecording);
BigArguments.reset();
- BigArguments.setArgument(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms);
+ BigArguments.setArgument(
+ DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms);
startActivity(intent);
}
getActivity().finish();
@@ -148,33 +157,47 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
private String getDescription() {
if (!mHasConflict) {
- return getResources().getQuantityString(
- R.plurals.dvr_series_scheduled_no_conflict, mSchedulesAddedCount,
- mSchedulesAddedCount, mSeriesRecording.getTitle());
+ return getResources()
+ .getQuantityString(
+ R.plurals.dvr_series_scheduled_no_conflict,
+ mSchedulesAddedCount,
+ mSchedulesAddedCount,
+ mSeriesRecording.getTitle());
} else {
// mInThisSeriesConflictCount equals 0 and mOutThisSeriesConflictCount equals 0 means
// mHasConflict is false. So we don't need to check that case.
if (mInThisSeriesConflictCount != 0 && mOutThisSeriesConflictCount != 0) {
- return getResources().getQuantityString(
- R.plurals.dvr_series_scheduled_this_and_other_series_conflict,
- mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
- mInThisSeriesConflictCount + mOutThisSeriesConflictCount);
+ return getResources()
+ .getQuantityString(
+ R.plurals.dvr_series_scheduled_this_and_other_series_conflict,
+ mSchedulesAddedCount,
+ mSchedulesAddedCount,
+ mSeriesRecording.getTitle(),
+ mInThisSeriesConflictCount + mOutThisSeriesConflictCount);
} else if (mInThisSeriesConflictCount != 0) {
- return getResources().getQuantityString(
- R.plurals.dvr_series_recording_scheduled_only_this_series_conflict,
- mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
- mInThisSeriesConflictCount);
+ return getResources()
+ .getQuantityString(
+ R.plurals.dvr_series_recording_scheduled_only_this_series_conflict,
+ mSchedulesAddedCount,
+ mSchedulesAddedCount,
+ mSeriesRecording.getTitle(),
+ mInThisSeriesConflictCount);
} else {
if (mOutThisSeriesConflictCount == 1) {
- return getResources().getQuantityString(
- R.plurals.dvr_series_scheduled_only_other_series_one_conflict,
- mSchedulesAddedCount, mSchedulesAddedCount,
- mSeriesRecording.getTitle());
+ return getResources()
+ .getQuantityString(
+ R.plurals.dvr_series_scheduled_only_other_series_one_conflict,
+ mSchedulesAddedCount,
+ mSchedulesAddedCount,
+ mSeriesRecording.getTitle());
} else {
- return getResources().getQuantityString(
- R.plurals.dvr_series_scheduled_only_other_series_many_conflicts,
- mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
- mOutThisSeriesConflictCount);
+ return getResources()
+ .getQuantityString(
+ R.plurals.dvr_series_scheduled_only_other_series_many_conflicts,
+ mSchedulesAddedCount,
+ mSchedulesAddedCount,
+ mSeriesRecording.getTitle(),
+ mOutThisSeriesConflictCount);
}
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
index 6dd20b3a..1a51cf46 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
@@ -20,34 +20,27 @@ import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
import com.android.tv.common.SoftPreconditions;
-/**
- * Activity to show details view in DVR.
- */
+/** Activity to show details view in DVR. */
public class DvrSeriesSettingsActivity extends Activity {
- /**
- * Name of series id added to the Intent.
- * Type: Long
- */
+ /** Name of series id added to the Intent. Type: Long */
public static final String SERIES_RECORDING_ID = "series_recording_id";
/**
* Name of the boolean flag to decide if the series recording with empty schedule and recording
- * will be removed.
- * Type: boolean
+ * will be removed. Type: boolean
*/
public static final String REMOVE_EMPTY_SERIES_RECORDING = "remove_empty_series_recording";
/**
- * Name of the boolean flag to decide if the setting fragment should be translucent.
- * Type: boolean
+ * Name of the boolean flag to decide if the setting fragment should be translucent. Type:
+ * boolean
*/
public static final String IS_WINDOW_TRANSLUCENT = "windows_translucent";
/**
- * Name of the program list. The list contains the programs which belong to the series.
- * Type: List<{@link com.android.tv.data.Program}>
+ * Name of the program list. The list contains the programs which belong to the series. Type:
+ * List<{@link com.android.tv.data.Program}>
*/
public static final String PROGRAM_LIST = "program_list";
@@ -67,7 +60,7 @@ public class DvrSeriesSettingsActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dvr_series_settings);
long seriesRecordingId = getIntent().getLongExtra(SERIES_RECORDING_ID, -1);
@@ -83,8 +76,9 @@ public class DvrSeriesSettingsActivity extends Activity {
@Override
public void onAttachedToWindow() {
if (!getIntent().getExtras().getBoolean(IS_WINDOW_TRANSLUCENT, true)) {
- getWindow().setBackgroundDrawable(
- new ColorDrawable(getColor(R.color.common_tv_background)));
+ getWindow()
+ .setBackgroundDrawable(
+ new ColorDrawable(getColor(R.color.common_tv_background)));
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
index f28382da..eadb3b9e 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
@@ -16,20 +16,22 @@
package com.android.tv.dvr.ui;
+import android.annotation.TargetApi;
import android.app.FragmentManager;
import android.content.Context;
+import android.os.Build;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.util.LongSparseArray;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
+import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelImpl;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
@@ -37,20 +39,18 @@ import com.android.tv.dvr.data.SeasonEpisodeNumber;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.data.SeriesRecording.ChannelOption;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-/**
- * Fragment for DVR series recording settings.
- */
+/** Fragment for DVR series recording settings. */
+@TargetApi(Build.VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
public class DvrSeriesSettingsFragment extends GuidedStepFragment
implements DvrDataManager.SeriesRecordingListener {
private static final String TAG = "SeriesSettingsFragment";
- private static final boolean DEBUG = false;
private static final long ACTION_ID_PRIORITY = 10;
private static final long ACTION_ID_CHANNEL = 11;
@@ -85,19 +85,20 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
public void onAttach(Context context) {
super.onAttach(context);
mBackStackCount = getFragmentManager().getBackStackEntryCount();
- mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
mSeriesRecordingId = getArguments().getLong(DvrSeriesSettingsActivity.SERIES_RECORDING_ID);
mSeriesRecording = mDvrDataManager.getSeriesRecording(mSeriesRecordingId);
if (mSeriesRecording == null) {
getActivity().finish();
return;
}
- mShowViewScheduleOptionInDialog = getArguments().getBoolean(
- DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG);
+ mShowViewScheduleOptionInDialog =
+ getArguments()
+ .getBoolean(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG);
mCurrentProgram = getArguments().getParcelable(DvrSeriesSettingsActivity.CURRENT_PROGRAM);
mDvrDataManager.addSeriesRecordingListener(this);
- mPrograms = (List<Program>) BigArguments.getArgument(
- DvrSeriesSettingsActivity.PROGRAM_LIST);
+ mPrograms =
+ (List<Program>) BigArguments.getArgument(DvrSeriesSettingsActivity.PROGRAM_LIST);
BigArguments.reset();
if (mPrograms == null) {
getActivity().finish();
@@ -105,7 +106,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
}
Set<Long> channelIds = new HashSet<>();
ChannelDataManager channelDataManager =
- TvApplication.getSingletons(context).getChannelDataManager();
+ TvSingletons.getSingletons(context).getChannelDataManager();
for (Program program : mPrograms) {
long channelId = program.getChannelId();
if (channelIds.add(channelId)) {
@@ -126,7 +127,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL;
}
}
- mChannels.sort(Channel.CHANNEL_NUMBER_COMPARATOR);
+ mChannels.sort(ChannelImpl.CHANNEL_NUMBER_COMPARATOR);
mFragmentTitle = getString(R.string.dvr_series_settings_title);
mProrityActionTitle = getString(R.string.dvr_series_settings_priority);
mProrityActionHighestText = getString(R.string.dvr_series_settings_priority_highest);
@@ -150,8 +151,9 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
@Override
public void onDestroy() {
- if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments()
- .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) {
+ if (getFragmentManager().getBackStackEntryCount() == mBackStackCount
+ && getArguments()
+ .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) {
mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId);
}
super.onDestroy();
@@ -166,29 +168,33 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
@Override
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
- mPriorityGuidedAction = new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_PRIORITY)
- .title(mProrityActionTitle)
- .build();
+ mPriorityGuidedAction =
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_PRIORITY)
+ .title(mProrityActionTitle)
+ .build();
actions.add(mPriorityGuidedAction);
- mChannelsGuidedAction = new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_CHANNEL)
- .title(mChannelsActionTitle)
- .subActions(buildChannelSubAction())
- .build();
+ mChannelsGuidedAction =
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_CHANNEL)
+ .title(mChannelsActionTitle)
+ .subActions(buildChannelSubAction())
+ .build();
actions.add(mChannelsGuidedAction);
updateChannelsGuidedAction(false);
}
@Override
public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
- actions.add(new GuidedAction.Builder(getActivity())
- .clickAction(GuidedAction.ACTION_ID_OK)
- .build());
- actions.add(new GuidedAction.Builder(getActivity())
- .clickAction(GuidedAction.ACTION_ID_CANCEL)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .clickAction(GuidedAction.ACTION_ID_OK)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .clickAction(GuidedAction.ACTION_ID_CANCEL)
+ .build());
}
@Override
@@ -199,16 +205,18 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
|| mSeriesRecording.isStopped()
|| (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE
&& mSeriesRecording.getChannelId() != mSelectedChannelId)) {
- SeriesRecording.Builder builder = SeriesRecording.buildFrom(mSeriesRecording)
- .setChannelOption(mChannelOption)
- .setState(SeriesRecording.STATE_SERIES_NORMAL);
+ SeriesRecording.Builder builder =
+ SeriesRecording.buildFrom(mSeriesRecording)
+ .setChannelOption(mChannelOption)
+ .setState(SeriesRecording.STATE_SERIES_NORMAL);
if (mSelectedChannelId != Channel.INVALID_ID) {
builder.setChannelId(mSelectedChannelId);
}
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
- dvrManager.updateSeriesRecording(builder.build());
- if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL
- || mSelectedChannelId == mCurrentProgram.getChannelId())) {
+ DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
+ dvrManager.updateSeriesRecording(builder.build());
+ if (mCurrentProgram != null
+ && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL
+ || mSelectedChannelId == mCurrentProgram.getChannelId())) {
dvrManager.addSchedule(mCurrentProgram);
}
updateSchedulesToSeries();
@@ -222,7 +230,8 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
FragmentManager fragmentManager = getFragmentManager();
DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment();
Bundle args = new Bundle();
- args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
+ args.putLong(
+ DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
mSeriesRecording.getId());
fragment.setArguments(args);
GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame);
@@ -254,9 +263,9 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
private void updateChannelsGuidedAction(boolean notifyActionChanged) {
if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) {
mChannelsGuidedAction.setDescription(mChannelsActionAllText);
- } else if (mId2Channel.get(mSelectedChannelId) != null){
- mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId)
- .getDisplayText());
+ } else if (mId2Channel.get(mSelectedChannelId) != null) {
+ mChannelsGuidedAction.setDescription(
+ mId2Channel.get(mSelectedChannelId).getDisplayText());
}
if (notifyActionChanged) {
notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL));
@@ -282,8 +291,8 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
} else if (priorityOrder >= totalSeriesCount - 1) {
mPriorityGuidedAction.setDescription(mProrityActionLowestText);
} else {
- mPriorityGuidedAction.setDescription(getString(
- R.string.dvr_series_settings_priority_rank, priorityOrder + 1));
+ mPriorityGuidedAction.setDescription(
+ getString(R.string.dvr_series_settings_priority_rank, priorityOrder + 1));
}
notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
}
@@ -294,54 +303,66 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
for (ScheduledRecording r : mDvrDataManager.getScheduledRecordings(mSeriesRecordingId)) {
if (r.getState() != ScheduledRecording.STATE_RECORDING_FAILED
&& r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) {
- scheduledEpisodes.add(new SeasonEpisodeNumber(
- r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber()));
+ scheduledEpisodes.add(
+ new SeasonEpisodeNumber(
+ r.getSeriesRecordingId(),
+ r.getSeasonNumber(),
+ r.getEpisodeNumber()));
}
}
for (Program program : mPrograms) {
// Removes current programs and scheduled episodes out, matches the channel option.
if (program.getStartTimeUtcMillis() >= System.currentTimeMillis()
&& mSeriesRecording.matchProgram(program)
- && !scheduledEpisodes.contains(new SeasonEpisodeNumber(
- mSeriesRecordingId, program.getSeasonNumber(), program.getEpisodeNumber()))) {
+ && !scheduledEpisodes.contains(
+ new SeasonEpisodeNumber(
+ mSeriesRecordingId,
+ program.getSeasonNumber(),
+ program.getEpisodeNumber()))) {
recordingCandidates.add(program);
}
}
if (recordingCandidates.isEmpty()) {
return;
}
- List<Program> programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode(
- mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates)
- .get(mSeriesRecordingId);
+ List<Program> programsToSchedule =
+ SeriesRecordingScheduler.pickOneProgramPerEpisode(
+ mDvrDataManager,
+ Collections.singletonList(mSeriesRecording),
+ recordingCandidates)
+ .get(mSeriesRecordingId);
if (!programsToSchedule.isEmpty()) {
- TvApplication.getSingletons(getContext()).getDvrManager()
+ TvSingletons.getSingletons(getContext())
+ .getDvrManager()
.addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule);
}
}
private List<GuidedAction> buildChannelSubAction() {
List<GuidedAction> channelSubActions = new ArrayList<>();
- channelSubActions.add(new GuidedAction.Builder(getActivity())
- .id(SUB_ACTION_ID_CHANNEL_ALL)
- .title(mChannelsActionAllText)
- .build());
+ channelSubActions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(SUB_ACTION_ID_CHANNEL_ALL)
+ .title(mChannelsActionAllText)
+ .build());
for (Channel channel : mChannels) {
- channelSubActions.add(new GuidedAction.Builder(getActivity())
- .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId())
- .title(channel.getDisplayText())
- .build());
+ channelSubActions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId())
+ .title(channel.getDisplayText())
+ .build());
}
return channelSubActions;
}
private void showConfirmDialog() {
- DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording,
- mShowViewScheduleOptionInDialog, mPrograms);
+ DvrUiHelper.startSeriesScheduledDialogActivity(
+ getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog, mPrograms);
finishGuidedStepFragments();
}
@Override
- public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { }
+ public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {}
@Override
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
@@ -363,4 +384,4 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
index baa45793..e93387ab 100644
--- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
@@ -25,45 +25,36 @@ import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.data.ScheduledRecording;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* A fragment which asks the user to make a recording schedule for the program.
- * <p>
- * If the program belongs to a series and the series recording is not created yet, we will show the
- * option to record all the episodes of the series.
+ *
+ * <p>If the program belongs to a series and the series recording is not created yet, we will show
+ * the option to record all the episodes of the series.
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
- /**
- * The action ID for the stop action.
- */
+ /** The action ID for the stop action. */
public static final int ACTION_STOP = 1;
- /**
- * Key for the program.
- * Type: {@link com.android.tv.data.Program}.
- */
+ /** Key for the program. Type: {@link com.android.tv.data.Program}. */
public static final String KEY_REASON = "DvrStopRecordingFragment.type";
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_USER_STOP, REASON_ON_CONFLICT})
public @interface ReasonType {}
- /**
- * The dialog is shown because users want to stop some currently recording program.
- */
+ /** The dialog is shown because users want to stop some currently recording program. */
public static final int REASON_USER_STOP = 1;
/**
- * The dialog is shown because users want to record some program that is conflict to the
- * current recording program.
+ * The dialog is shown because users want to record some program that is conflict to the current
+ * recording program.
*/
public static final int REASON_ON_CONFLICT = 2;
@@ -74,7 +65,7 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
private final ScheduledRecordingListener mScheduledRecordingListener =
new ScheduledRecordingListener() {
@Override
- public void onScheduledRecordingAdded(ScheduledRecording... schedules) { }
+ public void onScheduledRecordingAdded(ScheduledRecording... schedules) {}
@Override
public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
@@ -91,7 +82,7 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
for (ScheduledRecording schedule : schedules) {
if (schedule.getId() == mSchedule.getId()
&& schedule.getState()
- != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
+ != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
dismissDialog();
return;
}
@@ -109,7 +100,7 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
dismissDialog();
return;
}
- mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
mDvrDataManager.addScheduledRecordingListener(mScheduledRecordingListener);
mStopReason = args.getInt(KEY_REASON);
}
@@ -128,8 +119,10 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
String title = getString(R.string.dvr_stop_recording_dialog_title);
String description;
if (mStopReason == REASON_ON_CONFLICT) {
- description = getString(R.string.dvr_stop_recording_dialog_description_on_conflict,
- mSchedule.getProgramDisplayTitle(getContext()));
+ description =
+ getString(
+ R.string.dvr_stop_recording_dialog_description_on_conflict,
+ mSchedule.getProgramDisplayTitle(getContext()));
} else {
description = getString(R.string.dvr_stop_recording_dialog_description);
}
@@ -140,13 +133,15 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
Context context = getContext();
- actions.add(new GuidedAction.Builder(context)
- .id(ACTION_STOP)
- .title(R.string.dvr_action_stop)
- .build());
- actions.add(new GuidedAction.Builder(context)
- .clickAction(GuidedAction.ACTION_ID_CANCEL)
- .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .id(ACTION_STOP)
+ .title(R.string.dvr_action_stop)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(context)
+ .clickAction(GuidedAction.ACTION_ID_CANCEL)
+ .build());
}
@Override
@@ -163,4 +158,4 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
return super.getTrackerLabelForGuidedAction(action);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java
index 5b880bd6..15abf902 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingDialogFragment.java
@@ -22,18 +22,15 @@ import android.support.v17.leanback.app.GuidedStepFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
-/**
- * A dialog fragment which contains {@link DvrStopSeriesRecordingFragment}.
- */
+/** A dialog fragment which contains {@link DvrStopSeriesRecordingFragment}. */
public class DvrStopSeriesRecordingDialogFragment extends DialogFragment {
public static final String DIALOG_TAG = "dialog_tag";
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.halfsized_dialog, container, false);
GuidedStepFragment fragment = new DvrStopSeriesRecordingFragment();
fragment.setArguments(getArguments());
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
index 7b56cfc1..99211fdb 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
@@ -25,25 +25,18 @@ import android.support.v17.leanback.widget.GuidedAction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
-
import java.util.ArrayList;
import java.util.List;
-/**
- * A fragment which asks the user to stop series recording.
- */
+/** A fragment which asks the user to stop series recording. */
public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment {
- /**
- * Key for the series recording to be stopped.
- */
+ /** Key for the series recording to be stopped. */
public static final String KEY_SERIES_RECORDING = "key_series_recoridng";
private static final int ACTION_STOP_SERIES_RECORDING = 1;
@@ -51,7 +44,8 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment {
private SeriesRecording mSeriesRecording;
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mSeriesRecording = getArguments().getParcelable(KEY_SERIES_RECORDING);
return super.onCreateView(inflater, container, savedInstanceState);
}
@@ -68,19 +62,21 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment {
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_STOP_SERIES_RECORDING)
- .title(R.string.dvr_series_schedules_stop_dialog_action_stop)
- .build());
- actions.add(new GuidedAction.Builder(activity)
- .clickAction(GuidedAction.ACTION_ID_CANCEL)
- .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .id(ACTION_STOP_SERIES_RECORDING)
+ .title(R.string.dvr_series_schedules_stop_dialog_action_stop)
+ .build());
+ actions.add(
+ new GuidedAction.Builder(activity)
+ .clickAction(GuidedAction.ACTION_ID_CANCEL)
+ .build());
}
@Override
public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_STOP_SERIES_RECORDING) {
- ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
DvrManager dvrManager = singletons.getDvrManager();
DvrDataManager dataManager = singletons.getDvrDataManager();
List<ScheduledRecording> toDelete = new ArrayList<>();
@@ -96,8 +92,10 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment {
if (!toDelete.isEmpty()) {
dvrManager.forceRemoveScheduledRecording(ScheduledRecording.toArray(toDelete));
}
- dvrManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording)
- .setState(SeriesRecording.STATE_SERIES_STOPPED).build());
+ dvrManager.updateSeriesRecording(
+ SeriesRecording.buildFrom(mSeriesRecording)
+ .setState(SeriesRecording.STATE_SERIES_STOPPED)
+ .build());
}
dismissDialog();
}
diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java
index 302fd6cd..16afbdef 100644
--- a/src/com/android/tv/dvr/ui/DvrUiHelper.java
+++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java
@@ -37,17 +37,18 @@ import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.widget.ImageView;
import android.widget.Toast;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.BaseProgram;
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
@@ -56,6 +57,7 @@ import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialog
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrFutureProgramInfoDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment;
@@ -65,21 +67,19 @@ import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErro
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
+import com.android.tv.dvr.ui.list.DvrHistoryActivity;
import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
-/**
- * A helper class for DVR UI.
- */
+/** A helper class for DVR UI. */
@MainThread
@TargetApi(Build.VERSION_CODES.N)
public class DvrUiHelper {
@@ -91,45 +91,43 @@ public class DvrUiHelper {
* Checks if the storage status is good for recording and shows error messages if needed.
*
* @param recordingRequestRunnable if the storage status is OK to record or users choose to
- * perform the operation anyway, this Runnable will run.
+ * perform the operation anyway, this Runnable will run.
*/
- public static void checkStorageStatusAndShowErrorMessage(Activity activity, String inputId,
- Runnable recordingRequestRunnable) {
- if (Utils.isBundledInput(inputId)) {
- switch (TvApplication.getSingletons(activity).getDvrStorageStatusManager()
+ public static void checkStorageStatusAndShowErrorMessage(
+ Activity activity, String inputId, Runnable recordingRequestRunnable) {
+ if (CommonUtils.isBundledInput(inputId)) {
+ switch (TvSingletons.getSingletons(activity)
+ .getRecordingStorageStatusManager()
.getDvrStorageStatus()) {
- case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL:
+ case RecordingStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL:
showDvrSmallSizedStorageErrorDialog(activity);
return;
- case DvrStorageStatusManager.STORAGE_STATUS_MISSING:
+ case RecordingStorageStatusManager.STORAGE_STATUS_MISSING:
showDvrMissingStorageErrorDialog(activity);
return;
- case DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT:
+ case RecordingStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT:
showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable);
return;
+ default: // fall out
}
}
recordingRequestRunnable.run();
}
- /**
- * Shows the schedule dialog.
- */
- public static void showScheduleDialog(Activity activity, Program program,
- boolean addCurrentProgramToSeries) {
+ /** Shows the schedule dialog. */
+ public static void showScheduleDialog(
+ Activity activity, Program program, boolean addCurrentProgramToSeries) {
if (SoftPreconditions.checkNotNull(program) == null) {
return;
}
Bundle args = new Bundle();
args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
- args.putBoolean(DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES,
- addCurrentProgramToSeries);
+ args.putBoolean(
+ DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES, addCurrentProgramToSeries);
showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true);
}
- /**
- * Shows the recording duration options dialog.
- */
+ /** Shows the recording duration options dialog. */
public static void showChannelRecordDurationOptions(Activity activity, Channel channel) {
if (SoftPreconditions.checkNotNull(channel) == null) {
return;
@@ -139,9 +137,7 @@ public class DvrUiHelper {
showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args);
}
- /**
- * Shows the dialog which says that the new schedule conflicts with others.
- */
+ /** Shows the dialog which says that the new schedule conflicts with others. */
public static void showScheduleConflictDialog(Activity activity, Program program) {
if (program == null) {
return;
@@ -151,9 +147,7 @@ public class DvrUiHelper {
showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true);
}
- /**
- * Shows the conflict dialog for the channel watching.
- */
+ /** Shows the conflict dialog for the channel watching. */
public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) {
if (channel == null) {
return;
@@ -163,63 +157,60 @@ public class DvrUiHelper {
showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args);
}
- /**
- * Shows DVR insufficient space error dialog.
- */
- public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity,
- Set<String> failedScheduledRecordingInfoSet) {
+ /** Shows DVR insufficient space error dialog. */
+ public static void showDvrInsufficientSpaceErrorDialog(
+ MainActivity activity, Set<String> failedScheduledRecordingInfoSet) {
Bundle args = new Bundle();
ArrayList<String> failedScheduledRecordingInfoArray =
new ArrayList<>(failedScheduledRecordingInfoSet);
- args.putStringArrayList(DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS,
+ args.putStringArrayList(
+ DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS,
failedScheduledRecordingInfoArray);
showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args);
- Utils.clearRecordingFailedReason(activity,
- TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
+ Utils.clearRecordingFailedReason(
+ activity, TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
Utils.clearFailedScheduledRecordingInfoSet(activity);
}
/**
* Shows DVR no free space error dialog.
*
- * @param recordingRequestRunnable the recording request to be executed when users choose
- * {@link DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}.
+ * @param recordingRequestRunnable the recording request to be executed when users choose {@link
+ * DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}.
*/
- public static void showDvrNoFreeSpaceErrorDialog(Activity activity,
- Runnable recordingRequestRunnable) {
+ public static void showDvrNoFreeSpaceErrorDialog(
+ Activity activity, Runnable recordingRequestRunnable) {
DvrHalfSizedDialogFragment fragment = new DvrNoFreeSpaceErrorDialogFragment();
- fragment.setOnActionClickListener(new HalfSizedDialogFragment.OnActionClickListener() {
- @Override
- public void onActionClick(long actionId) {
- if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) {
- recordingRequestRunnable.run();
- } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) {
- Intent intent = new Intent(activity, DvrBrowseActivity.class);
- activity.startActivity(intent);
- }
- }
- });
+ fragment.setOnActionClickListener(
+ new HalfSizedDialogFragment.OnActionClickListener() {
+ @Override
+ public void onActionClick(long actionId) {
+ if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) {
+ recordingRequestRunnable.run();
+ } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) {
+ Intent intent = new Intent(activity, DvrBrowseActivity.class);
+ activity.startActivity(intent);
+ }
+ }
+ });
showDialogFragment(activity, fragment, null);
}
- /**
- * Shows DVR missing storage error dialog.
- */
+ /** Shows DVR missing storage error dialog. */
private static void showDvrMissingStorageErrorDialog(Activity activity) {
showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null);
}
- /**
- * Shows DVR small sized storage error dialog.
- */
+ /** Shows DVR small sized storage error dialog. */
public static void showDvrSmallSizedStorageErrorDialog(Activity activity) {
showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null);
}
- /**
- * Shows stop recording dialog.
- */
- public static void showStopRecordingDialog(Activity activity, long channelId, int reason,
+ /** Shows stop recording dialog. */
+ public static void showStopRecordingDialog(
+ Activity activity,
+ long channelId,
+ int reason,
HalfSizedDialogFragment.OnActionClickListener listener) {
Bundle args = new Bundle();
args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId);
@@ -229,9 +220,7 @@ public class DvrUiHelper {
showDialogFragment(activity, fragment, args);
}
- /**
- * Shows "already scheduled" dialog.
- */
+ /** Shows "already scheduled" dialog. */
public static void showAlreadyScheduleDialog(Activity activity, Program program) {
if (program == null) {
return;
@@ -241,9 +230,7 @@ public class DvrUiHelper {
showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true);
}
- /**
- * Shows "already recorded" dialog.
- */
+ /** Shows "already recorded" dialog. */
public static void showAlreadyRecordedDialog(Activity activity, Program program) {
if (program == null) {
return;
@@ -253,37 +240,49 @@ public class DvrUiHelper {
showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true);
}
+ /** Shows program information dialog. */
+ public static void showProgramInfoDialog(Activity activity, Program program) {
+ if (program == null || !BuildConfig.ENG) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ showDialogFragment(activity, new DvrFutureProgramInfoDialogFragment(), args, false, true);
+ }
+
/**
* Handle the request of recording a current program. It will handle creating schedules and
* shows the proper dialog and toast message respectively for timed-recording and program
* recording cases.
*
- * @param addProgramToSeries denotes whether the program to be recorded should be added into
- * the series recording when users choose to record the entire series.
+ * @param addProgramToSeries denotes whether the program to be recorded should be added into the
+ * series recording when users choose to record the entire series.
*/
- public static void requestRecordingCurrentProgram(Activity activity,
- Channel channel, Program program, boolean addProgramToSeries) {
+ public static void requestRecordingCurrentProgram(
+ Activity activity, Channel channel, Program program, boolean addProgramToSeries) {
if (program == null) {
DvrUiHelper.showChannelRecordDurationOptions(activity, channel);
} else if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
- String msg = activity.getString(R.string.dvr_msg_current_program_scheduled,
- program.getTitle(), Utils.toTimeString(program.getEndTimeUtcMillis(), false));
+ String msg =
+ activity.getString(
+ R.string.dvr_msg_current_program_scheduled,
+ program.getTitle(),
+ Utils.toTimeString(program.getEndTimeUtcMillis(), false));
Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show();
}
}
/**
- * Handle the request of recording a future program. It will handle creating schedules and
- * shows the proper toast message.
+ * Handle the request of recording a future program. It will handle creating schedules and shows
+ * the proper toast message.
*
- * @param addProgramToSeries denotes whether the program to be recorded should be added into
- * the series recording when users choose to record the entire series.
+ * @param addProgramToSeries denotes whether the program to be recorded should be added into the
+ * series recording when users choose to record the entire series.
*/
- public static void requestRecordingFutureProgram(Activity activity,
- Program program, boolean addProgramToSeries) {
+ public static void requestRecordingFutureProgram(
+ Activity activity, Program program, boolean addProgramToSeries) {
if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
- String msg = activity.getString(
- R.string.dvr_msg_program_scheduled, program.getTitle());
+ String msg = activity.getString(R.string.dvr_msg_program_scheduled, program.getTitle());
ToastUtils.show(activity, msg, Toast.LENGTH_SHORT);
}
}
@@ -292,12 +291,12 @@ public class DvrUiHelper {
* Handles the action to create the new schedule. It returns {@code true} if the schedule is
* added and there's no additional UI, otherwise {@code false}.
*/
- private static boolean handleCreateSchedule(Activity activity, Program program,
- boolean addProgramToSeries) {
+ private static boolean handleCreateSchedule(
+ Activity activity, Program program, boolean addProgramToSeries) {
if (program == null) {
return false;
}
- DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager();
+ DvrManager dvrManager = TvSingletons.getSingletons(activity).getDvrManager();
if (!program.isEpisodic()) {
// One time recording.
dvrManager.addSchedule(program);
@@ -307,18 +306,24 @@ public class DvrUiHelper {
}
} else {
// Show recorded program rather than the schedule.
- RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(),
- program.getSeasonNumber(), program.getEpisodeNumber());
+ RecordedProgram recordedProgram =
+ dvrManager.getRecordedProgram(
+ program.getTitle(),
+ program.getSeasonNumber(),
+ program.getEpisodeNumber());
if (recordedProgram != null) {
DvrUiHelper.showAlreadyRecordedDialog(activity, program);
return false;
}
- ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(),
- program.getSeasonNumber(), program.getEpisodeNumber());
+ ScheduledRecording duplicate =
+ dvrManager.getScheduledRecording(
+ program.getTitle(),
+ program.getSeasonNumber(),
+ program.getEpisodeNumber());
if (duplicate != null
&& (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || duplicate.getState()
- == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ || duplicate.getState()
+ == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
DvrUiHelper.showAlreadyScheduleDialog(activity, program);
return false;
}
@@ -334,39 +339,44 @@ public class DvrUiHelper {
return true;
}
- private static void showDialogFragment(Activity activity,
- DvrHalfSizedDialogFragment dialogFragment, Bundle args) {
+ private static void showDialogFragment(
+ Activity activity, DvrHalfSizedDialogFragment dialogFragment, Bundle args) {
showDialogFragment(activity, dialogFragment, args, false, false);
}
- private static void showDialogFragment(Activity activity,
- DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory,
+ private static void showDialogFragment(
+ Activity activity,
+ DvrHalfSizedDialogFragment dialogFragment,
+ Bundle args,
+ boolean keepSidePanelHistory,
boolean keepProgramGuide) {
dialogFragment.setArguments(args);
if (activity instanceof MainActivity) {
- ((MainActivity) activity).getOverlayManager()
- .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment,
- keepSidePanelHistory, keepProgramGuide);
+ ((MainActivity) activity)
+ .getOverlayManager()
+ .showDialogFragment(
+ DvrHalfSizedDialogFragment.DIALOG_TAG,
+ dialogFragment,
+ keepSidePanelHistory,
+ keepProgramGuide);
} else {
- dialogFragment.show(activity.getFragmentManager(),
- DvrHalfSizedDialogFragment.DIALOG_TAG);
+ dialogFragment.show(
+ activity.getFragmentManager(), DvrHalfSizedDialogFragment.DIALOG_TAG);
}
}
- /**
- * Checks whether channel watch conflict dialog is open or not.
- */
+ /** Checks whether channel watch conflict dialog is open or not. */
public static boolean isChannelWatchConflictDialogShown(MainActivity activity) {
- return activity.getOverlayManager().getCurrentDialog() instanceof
- DvrChannelWatchConflictDialogFragment;
+ return activity.getOverlayManager().getCurrentDialog()
+ instanceof DvrChannelWatchConflictDialogFragment;
}
- private static ScheduledRecording getEarliestScheduledRecording(List<ScheduledRecording>
- recordings) {
+ private static ScheduledRecording getEarliestScheduledRecording(
+ List<ScheduledRecording> recordings) {
ScheduledRecording earlistScheduledRecording = null;
if (!recordings.isEmpty()) {
- Collections.sort(recordings,
- ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
+ Collections.sort(
+ recordings, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
earlistScheduledRecording = recordings.get(0);
}
return earlistScheduledRecording;
@@ -378,10 +388,10 @@ public class DvrUiHelper {
* @param programId the ID of the recorded program going to be played.
* @param seekTimeMs the seek position to initial playback.
* @param pinChecked {@code true} if the pin code for parental controls has already been
- * verified, otherwise {@code false}.
+ * verified, otherwise {@code false}.
*/
- public static void startPlaybackActivity(Context context, long programId,
- long seekTimeMs, boolean pinChecked) {
+ public static void startPlaybackActivity(
+ Context context, long programId, long seekTimeMs, boolean pinChecked) {
Intent intent = new Intent(context, DvrPlaybackActivity.class);
intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
@@ -391,51 +401,52 @@ public class DvrUiHelper {
context.startActivity(intent);
}
- /**
- * Shows the schedules activity to resolve the tune conflict.
- */
+ /** Shows the schedules activity to resolve the tune conflict. */
public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) {
if (channel == null) {
return;
}
- List<ScheduledRecording> conflicts = TvApplication.getSingletons(context).getDvrManager()
- .getConflictingSchedulesForTune(channel.getId());
+ List<ScheduledRecording> conflicts =
+ TvSingletons.getSingletons(context)
+ .getDvrManager()
+ .getConflictingSchedulesForTune(channel.getId());
startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
}
- /**
- * Shows the schedules activity to resolve the one time recording conflict.
- */
- public static void startSchedulesActivityForOneTimeRecordingConflict(Context context,
- List<ScheduledRecording> conflicts) {
+ /** Shows the schedules activity to resolve the one time recording conflict. */
+ public static void startSchedulesActivityForOneTimeRecordingConflict(
+ Context context, List<ScheduledRecording> conflicts) {
startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
}
- /**
- * Shows the schedules activity with full schedule.
- */
- public static void startSchedulesActivity(Context context, ScheduledRecording
- focusedScheduledRecording) {
+ /** Shows the schedules activity with full schedule. */
+ public static void startDvrHistoryActivity(Context context) {
+ Intent intent = new Intent(context, DvrHistoryActivity.class);
+ context.startActivity(intent);
+ }
+
+ /** Shows the schedules activity with full schedule. */
+ public static void startSchedulesActivity(
+ Context context, ScheduledRecording focusedScheduledRecording) {
Intent intent = new Intent(context, DvrSchedulesActivity.class);
- intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
- DvrSchedulesActivity.TYPE_FULL_SCHEDULE);
+ intent.putExtra(
+ DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_FULL_SCHEDULE);
if (focusedScheduledRecording != null) {
- intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING,
+ intent.putExtra(
+ DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING,
focusedScheduledRecording);
}
context.startActivity(intent);
}
- /**
- * Shows the schedules activity for series recording.
- */
- public static void startSchedulesActivityForSeries(Context context,
- SeriesRecording seriesRecording) {
+ /** Shows the schedules activity for series recording. */
+ public static void startSchedulesActivityForSeries(
+ Context context, SeriesRecording seriesRecording) {
Intent intent = new Intent(context, DvrSchedulesActivity.class);
- intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
- DvrSchedulesActivity.TYPE_SERIES_SCHEDULE);
- intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
- seriesRecording);
+ intent.putExtra(
+ DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity.TYPE_SERIES_SCHEDULE);
+ intent.putExtra(
+ DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING, seriesRecording);
context.startActivity(intent);
}
@@ -444,93 +455,125 @@ public class DvrUiHelper {
*
* @param programs list of programs which belong to the series.
*/
- public static void startSeriesSettingsActivity(Context context, long seriesRecordingId,
- @Nullable List<Program> programs, boolean removeEmptySeriesSchedule,
- boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog,
+ public static void startSeriesSettingsActivity(
+ Context context,
+ long seriesRecordingId,
+ @Nullable List<Program> programs,
+ boolean removeEmptySeriesSchedule,
+ boolean isWindowTranslucent,
+ boolean showViewScheduleOptionInDialog,
Program currentProgram) {
- SeriesRecording series = TvApplication.getSingletons(context).getDvrDataManager()
- .getSeriesRecording(seriesRecordingId);
+ SeriesRecording series =
+ TvSingletons.getSingletons(context)
+ .getDvrDataManager()
+ .getSeriesRecording(seriesRecordingId);
if (series == null) {
return;
}
if (programs != null) {
- startSeriesSettingsActivityInternal(context, seriesRecordingId, programs,
- removeEmptySeriesSchedule, isWindowTranslucent,
- showViewScheduleOptionInDialog, currentProgram);
+ startSeriesSettingsActivityInternal(
+ context,
+ seriesRecordingId,
+ programs,
+ removeEmptySeriesSchedule,
+ isWindowTranslucent,
+ showViewScheduleOptionInDialog,
+ currentProgram);
} else {
EpisodicProgramLoadTask episodicProgramLoadTask =
new EpisodicProgramLoadTask(context, series) {
- @Override
- protected void onPostExecute(List<Program> loadedPrograms) {
- sProgressDialog.dismiss();
- sProgressDialog = null;
- startSeriesSettingsActivityInternal(context, seriesRecordingId,
- loadedPrograms == null ? Collections.EMPTY_LIST : loadedPrograms,
- removeEmptySeriesSchedule, isWindowTranslucent,
- showViewScheduleOptionInDialog, currentProgram);
- }
- }.setLoadCurrentProgram(true)
- .setLoadDisallowedProgram(true)
- .setLoadScheduledEpisode(true)
- .setIgnoreChannelOption(true);
- sProgressDialog = ProgressDialog.show(context, null, context.getString(
- R.string.dvr_series_progress_message_reading_programs), true, true,
- new DialogInterface.OnCancelListener() {
@Override
- public void onCancel(DialogInterface dialogInterface) {
- episodicProgramLoadTask.cancel(true);
+ protected void onPostExecute(List<Program> loadedPrograms) {
+ sProgressDialog.dismiss();
sProgressDialog = null;
+ startSeriesSettingsActivityInternal(
+ context,
+ seriesRecordingId,
+ loadedPrograms == null
+ ? Collections.EMPTY_LIST
+ : loadedPrograms,
+ removeEmptySeriesSchedule,
+ isWindowTranslucent,
+ showViewScheduleOptionInDialog,
+ currentProgram);
}
- });
+ }.setLoadCurrentProgram(true)
+ .setLoadDisallowedProgram(true)
+ .setLoadScheduledEpisode(true)
+ .setIgnoreChannelOption(true);
+ sProgressDialog =
+ ProgressDialog.show(
+ context,
+ null,
+ context.getString(
+ R.string.dvr_series_progress_message_reading_programs),
+ true,
+ true,
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ episodicProgramLoadTask.cancel(true);
+ sProgressDialog = null;
+ }
+ });
episodicProgramLoadTask.execute();
}
}
- private static void startSeriesSettingsActivityInternal(Context context, long seriesRecordingId,
- @NonNull List<Program> programs, boolean removeEmptySeriesSchedule,
- boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog,
+ private static void startSeriesSettingsActivityInternal(
+ Context context,
+ long seriesRecordingId,
+ @NonNull List<Program> programs,
+ boolean removeEmptySeriesSchedule,
+ boolean isWindowTranslucent,
+ boolean showViewScheduleOptionInDialog,
Program currentProgram) {
- SoftPreconditions.checkState(programs != null,
- TAG, "Start series settings activity but programs is null");
+ SoftPreconditions.checkState(
+ programs != null, TAG, "Start series settings activity but programs is null");
Intent intent = new Intent(context, DvrSeriesSettingsActivity.class);
intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId);
BigArguments.reset();
BigArguments.setArgument(DvrSeriesSettingsActivity.PROGRAM_LIST, programs);
- intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING,
- removeEmptySeriesSchedule);
+ intent.putExtra(
+ DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING, removeEmptySeriesSchedule);
intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent);
- intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
+ intent.putExtra(
+ DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
showViewScheduleOptionInDialog);
intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram);
context.startActivity(intent);
}
- /**
- * Shows "series recording scheduled" dialog activity.
- */
- public static void StartSeriesScheduledDialogActivity(Context context,
- SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog,
+ /** Shows "series recording scheduled" dialog activity. */
+ public static void startSeriesScheduledDialogActivity(
+ Context context,
+ SeriesRecording seriesRecording,
+ boolean showViewScheduleOptionInDialog,
List<Program> programs) {
if (seriesRecording == null) {
return;
}
Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class);
- intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID,
- seriesRecording.getId());
- intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION,
+ intent.putExtra(
+ DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID, seriesRecording.getId());
+ intent.putExtra(
+ DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION,
showViewScheduleOptionInDialog);
BigArguments.reset();
- BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS,
- programs);
+ BigArguments.setArgument(
+ DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS, programs);
context.startActivity(intent);
}
/**
- * Shows the details activity for the DVR items. The type of DVR items may be
- * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}.
+ * Shows the details activity for the DVR items. The type of DVR items may be {@link
+ * ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}.
*/
- public static void startDetailsActivity(Activity activity, Object dvrItem,
- @Nullable ImageView imageView, boolean hideViewSchedule) {
+ public static void startDetailsActivity(
+ Activity activity,
+ Object dvrItem,
+ @Nullable ImageView imageView,
+ boolean hideViewSchedule) {
if (dvrItem == null) {
return;
}
@@ -544,6 +587,17 @@ public class DvrUiHelper {
viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW;
} else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW;
+ } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED
+ && schedule.getRecordedProgramId() != null) {
+ recordingId = schedule.getRecordedProgramId();
+ viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW;
+ } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
+ viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW;
+ hideViewSchedule = true;
+ // TODO(b/72638385): pass detailed error message
+ intent.putExtra(
+ DvrDetailsActivity.EXTRA_FAILED_MESSAGE,
+ activity.getString(R.string.dvr_recording_failed));
} else {
return;
}
@@ -561,89 +615,108 @@ public class DvrUiHelper {
intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule);
Bundle bundle = null;
if (imageView != null) {
- bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView,
- DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+ bundle =
+ ActivityOptionsCompat.makeSceneTransitionAnimation(
+ activity, imageView, DvrDetailsActivity.SHARED_ELEMENT_NAME)
+ .toBundle();
}
activity.startActivity(intent, bundle);
}
- /**
- * Shows the cancel all dialog for series schedules list.
- */
- public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity,
- SeriesRecording seriesRecording) {
+ /** Shows the cancel all dialog for series schedules list. */
+ public static void showCancelAllSeriesRecordingDialog(
+ DvrSchedulesActivity activity, SeriesRecording seriesRecording) {
DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment =
new DvrStopSeriesRecordingDialogFragment();
Bundle arguments = new Bundle();
- arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING,
- seriesRecording);
+ arguments.putParcelable(
+ DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING, seriesRecording);
dvrStopSeriesRecordingDialogFragment.setArguments(arguments);
- dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(),
- DvrStopSeriesRecordingDialogFragment.DIALOG_TAG);
+ dvrStopSeriesRecordingDialogFragment.show(
+ activity.getFragmentManager(), DvrStopSeriesRecordingDialogFragment.DIALOG_TAG);
}
- /**
- * Shows the series deletion activity.
- */
+ /** Shows the series deletion activity. */
public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) {
Intent intent = new Intent(context, DvrSeriesDeletionActivity.class);
intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId);
context.startActivity(intent);
}
- public static void showAddScheduleToast(Context context,
- String title, long startTimeMs, long endTimeMs) {
- String msg = (startTimeMs > System.currentTimeMillis()) ?
- context.getString(R.string.dvr_msg_program_scheduled, title)
- : context.getString(R.string.dvr_msg_current_program_scheduled, title,
- Utils.toTimeString(endTimeMs, false));
+ public static void showAddScheduleToast(
+ Context context, String title, long startTimeMs, long endTimeMs) {
+ String msg =
+ (startTimeMs > System.currentTimeMillis())
+ ? context.getString(R.string.dvr_msg_program_scheduled, title)
+ : context.getString(
+ R.string.dvr_msg_current_program_scheduled,
+ title,
+ Utils.toTimeString(endTimeMs, false));
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
- /**
- * Returns the styled schedule's title with its season and episode number.
- */
- public static CharSequence getStyledTitleWithEpisodeNumber(Context context,
- ScheduledRecording schedule, int episodeNumberStyleResId) {
- return getStyledTitleWithEpisodeNumber(context, schedule.getProgramTitle(),
- schedule.getSeasonNumber(), schedule.getEpisodeNumber(), episodeNumberStyleResId);
+ /** Returns the styled schedule's title with its season and episode number. */
+ public static CharSequence getStyledTitleWithEpisodeNumber(
+ Context context, ScheduledRecording schedule, int episodeNumberStyleResId) {
+ return getStyledTitleWithEpisodeNumber(
+ context,
+ schedule.getProgramTitle(),
+ schedule.getSeasonNumber(),
+ schedule.getEpisodeNumber(),
+ episodeNumberStyleResId);
}
- /**
- * Returns the styled program's title with its season and episode number.
- */
- public static CharSequence getStyledTitleWithEpisodeNumber(Context context,
- BaseProgram program, int episodeNumberStyleResId) {
- return getStyledTitleWithEpisodeNumber(context, program.getTitle(),
- program.getSeasonNumber(), program.getEpisodeNumber(), episodeNumberStyleResId);
+ /** Returns the styled program's title with its season and episode number. */
+ public static CharSequence getStyledTitleWithEpisodeNumber(
+ Context context, BaseProgram program, int episodeNumberStyleResId) {
+ return getStyledTitleWithEpisodeNumber(
+ context,
+ program.getTitle(),
+ program.getSeasonNumber(),
+ program.getEpisodeNumber(),
+ episodeNumberStyleResId);
}
@NonNull
- public static CharSequence getStyledTitleWithEpisodeNumber(Context context, String title,
- String seasonNumber, String episodeNumber, int episodeNumberStyleResId) {
+ public static CharSequence getStyledTitleWithEpisodeNumber(
+ Context context,
+ String title,
+ String seasonNumber,
+ String episodeNumber,
+ int episodeNumberStyleResId) {
if (TextUtils.isEmpty(title)) {
return "";
}
SpannableStringBuilder builder;
if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) {
- builder = TextUtils.isEmpty(episodeNumber) ? new SpannableStringBuilder(title) :
- new SpannableStringBuilder(Html.fromHtml(
- context.getString(R.string.program_title_with_episode_number_no_season,
- title, episodeNumber)));
+ builder =
+ TextUtils.isEmpty(episodeNumber)
+ ? new SpannableStringBuilder(title)
+ : new SpannableStringBuilder(Html.fromHtml(context.getString(
+ R.string.program_title_with_episode_number_no_season,
+ title,
+ episodeNumber)));
} else {
- builder = new SpannableStringBuilder(Html.fromHtml(
- context.getString(R.string.program_title_with_episode_number,
- title, seasonNumber, episodeNumber)));
+ builder =
+ new SpannableStringBuilder(
+ Html.fromHtml(
+ context.getString(
+ R.string.program_title_with_episode_number,
+ title,
+ seasonNumber,
+ episodeNumber)));
}
Object[] spans = builder.getSpans(0, builder.length(), Object.class);
if (spans.length > 0) {
if (episodeNumberStyleResId != 0) {
- builder.setSpan(new TextAppearanceSpan(context, episodeNumberStyleResId),
- builder.getSpanStart(spans[0]), builder.getSpanEnd(spans[0]),
+ builder.setSpan(
+ new TextAppearanceSpan(context, episodeNumberStyleResId),
+ builder.getSpanStart(spans[0]),
+ builder.getSpanEnd(spans[0]),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
builder.removeSpan(spans[0]);
}
return new SpannableString(builder);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/FadeBackground.java b/src/com/android/tv/dvr/ui/FadeBackground.java
index 4f06ebcf..373daaaf 100644
--- a/src/com/android/tv/dvr/ui/FadeBackground.java
+++ b/src/com/android/tv/dvr/ui/FadeBackground.java
@@ -28,12 +28,9 @@ import android.transition.TransitionValues;
import android.transition.Visibility;
import android.util.AttributeSet;
import android.view.ViewGroup;
-
import com.android.tv.R;
-/**
- * This transition fades in/out of the background of the view by changing the background color.
- */
+/** This transition fades in/out of the background of the view by changing the background color. */
public class FadeBackground extends Transition {
private final int mMode;
@@ -45,22 +42,22 @@ public class FadeBackground extends Transition {
}
@Override
- public void captureStartValues(TransitionValues transitionValues) { }
+ public void captureStartValues(TransitionValues transitionValues) {}
@Override
- public void captureEndValues(TransitionValues transitionValues) { }
+ public void captureEndValues(TransitionValues transitionValues) {}
@Override
- public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
- TransitionValues endValues) {
+ public Animator createAnimator(
+ ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
Drawable background = endValues.view.getBackground();
if (background instanceof ColorDrawable) {
int color = ((ColorDrawable) background).getColor();
- int transparentColor = Color.argb(0, Color.red(color), Color.green(color),
- Color.blue(color));
+ int transparentColor =
+ Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));
return mMode == Visibility.MODE_OUT
? ObjectAnimator.ofArgb(background, "color", transparentColor)
: ObjectAnimator.ofArgb(background, "color", transparentColor, color);
diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
index 8c0af9ed..1eb8080a 100644
--- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
+++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
@@ -19,9 +19,7 @@ package com.android.tv.dvr.ui;
import android.support.annotation.VisibleForTesting;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.PresenterSelector;
-
import com.android.tv.common.SoftPreconditions;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -45,8 +43,8 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
this(presenterSelector, comparator, Integer.MAX_VALUE);
}
- public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
- int maxItemCount) {
+ public SortedArrayAdapter(
+ PresenterSelector presenterSelector, Comparator<T> comparator, int maxItemCount) {
super(presenterSelector);
mComparator = comparator;
mMaxItemCount = maxItemCount;
@@ -88,9 +86,8 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* Adds an item in sorted order to the adapter.
*
* @param item The item to add in sorted order to the adapter.
- * @param insertToEnd If items are inserted in a more or less sorted fashion,
- * sets this parameter to {@code true} to search insertion position from
- * the end to save search time.
+ * @param insertToEnd If items are inserted in a more or less sorted fashion, sets this
+ * parameter to {@code true} to search insertion position from the end to save search time.
*/
public final void add(T item, boolean insertToEnd) {
long newItemId = getId(item);
@@ -127,9 +124,7 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
return removeWithId((T) item);
}
- /**
- * Removes an item which has the same ID as {@code item}.
- */
+ /** Removes an item which has the same ID as {@code item}. */
public boolean removeWithId(T item) {
int index = indexWithId(item);
return index >= 0 && index < size() && removeItems(index, 1) == 1;
@@ -166,6 +161,7 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
/**
* Changes an item in the list.
+ *
* @param item The item to change.
*/
public final void change(T item) {
@@ -181,9 +177,7 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
add(item);
}
- /**
- * Checks whether the item is in the list.
- */
+ /** Checks whether the item is in the list. */
public final boolean contains(T item) {
return indexWithId(item) != -1;
}
@@ -194,10 +188,10 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
}
/**
- * Returns the id of the the given {@code item}, which will be used in {@link #change} to
- * decide if the given item is already existed in the adapter.
+ * Returns the id of the the given {@code item}, which will be used in {@link #change} to decide
+ * if the given item is already existed in the adapter.
*
- * The id must be stable.
+ * <p>The id must be stable.
*/
protected abstract long getId(T item);
@@ -212,11 +206,9 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
return -1;
}
- /**
- * Finds the position that the given item should be inserted to keep the sorted order.
- */
+ /** Finds the position that the given item should be inserted to keep the sorted order. */
public int findInsertPosition(T item) {
- for (int i = size() - mExtraItemCount - 1; i >=0; i--) {
+ for (int i = size() - mExtraItemCount - 1; i >= 0; i--) {
T r = (T) get(i);
if (mComparator.compare(r, item) <= 0) {
return i + 1;
@@ -242,4 +234,4 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
}
return lb;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
index 5fe9c478..0172f76f 100644
--- a/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
@@ -19,8 +19,7 @@ package com.android.tv.dvr.ui;
import android.content.Context;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidedAction;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
/** A {@link GuidedStepFragment} with {@link Tracker} for analytics. */
@@ -30,7 +29,7 @@ public abstract class TrackedGuidedStepFragment extends GuidedStepFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mTracker = TvApplication.getSingletons(context).getAnalytics().getDefaultTracker();
+ mTracker = TvSingletons.getSingletons(context).getAnalytics().getDefaultTracker();
}
@Override
diff --git a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
index 38a78f5d..f3a6fea4 100644
--- a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
+++ b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
@@ -32,8 +32,8 @@ import android.widget.Button;
class ActionPresenterSelector extends PresenterSelector {
private final Presenter mOneLineActionPresenter = new OneLineActionPresenter();
private final Presenter mTwoLineActionPresenter = new TwoLineActionPresenter();
- private final Presenter[] mPresenters = new Presenter[] {
- mOneLineActionPresenter, mTwoLineActionPresenter};
+ private final Presenter[] mPresenters =
+ new Presenter[] {mOneLineActionPresenter, mTwoLineActionPresenter};
@Override
public Presenter getPresenter(Object item) {
@@ -65,8 +65,9 @@ class ActionPresenterSelector extends PresenterSelector {
class OneLineActionPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- View v = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.lb_action_1_line, parent, false);
+ View v =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.lb_action_1_line, parent, false);
return new ActionViewHolder(v, parent.getLayoutDirection());
}
@@ -87,8 +88,9 @@ class ActionPresenterSelector extends PresenterSelector {
class TwoLineActionPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- View v = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.lb_action_2_lines, parent, false);
+ View v =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.lb_action_2_lines, parent, false);
return new ActionViewHolder(v, parent.getLayoutDirection());
}
@@ -100,14 +102,20 @@ class ActionPresenterSelector extends PresenterSelector {
vh.mAction = action;
if (icon != null) {
- final int startPadding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
- final int endPadding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end);
+ final int startPadding =
+ vh.view
+ .getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
+ final int endPadding =
+ vh.view
+ .getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end);
vh.view.setPaddingRelative(startPadding, 0, endPadding, 0);
} else {
- final int padding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
+ final int padding =
+ vh.view
+ .getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
vh.view.setPaddingRelative(padding, 0, padding, 0);
}
vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
@@ -131,4 +139,4 @@ class ActionPresenterSelector extends PresenterSelector {
vh.mAction = null;
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
index bf18ddc0..7e7e1f75 100644
--- a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
@@ -21,9 +21,8 @@ import android.content.res.Resources;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
@@ -31,9 +30,7 @@ import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
import com.android.tv.dvr.ui.DvrUiHelper;
-/**
- * {@link RecordingDetailsFragment} for current recording in DVR.
- */
+/** {@link RecordingDetailsFragment} for current recording in DVR. */
public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
private static final int ACTION_STOP_RECORDING = 1;
@@ -41,7 +38,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
new DvrDataManager.ScheduledRecordingListener() {
@Override
- public void onScheduledRecordingAdded(ScheduledRecording... schedules) { }
+ public void onScheduledRecordingAdded(ScheduledRecording... schedules) {}
@Override
public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
@@ -58,7 +55,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
for (ScheduledRecording schedule : schedules) {
if (schedule.getId() == getRecording().getId()
&& schedule.getState()
- != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
+ != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
getActivity().finish();
return;
}
@@ -69,7 +66,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mDvrDataManger = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrDataManger = TvSingletons.getSingletons(context).getDvrDataManager();
mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener);
}
@@ -78,9 +75,13 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
SparseArrayObjectAdapter adapter =
new SparseArrayObjectAdapter(new ActionPresenterSelector());
Resources res = getResources();
- adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
- res.getString(R.string.dvr_detail_stop_recording), null,
- res.getDrawable(R.drawable.lb_ic_stop)));
+ adapter.set(
+ ACTION_STOP_RECORDING,
+ new Action(
+ ACTION_STOP_RECORDING,
+ res.getString(R.string.dvr_detail_stop_recording),
+ null,
+ res.getDrawable(R.drawable.lb_ic_stop)));
return adapter;
}
@@ -90,7 +91,8 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
@Override
public void onActionClicked(Action action) {
if (action.getId() == ACTION_STOP_RECORDING) {
- DvrUiHelper.showStopRecordingDialog(getActivity(),
+ DvrUiHelper.showStopRecordingDialog(
+ getActivity(),
getRecording().getChannelId(),
DvrStopRecordingFragment.REASON_USER_STOP,
new HalfSizedDialogFragment.OnActionClickListener() {
@@ -98,7 +100,7 @@ public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
public void onActionClick(long actionId) {
if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
DvrManager dvrManager =
- TvApplication.getSingletons(getContext())
+ TvSingletons.getSingletons(getContext())
.getDvrManager();
dvrManager.stopRecording(getRecording());
getActivity().finish();
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
index c1fa05d7..cba6293b 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
@@ -20,18 +20,15 @@ import android.content.Context;
import android.media.tv.TvContract;
import android.support.annotation.Nullable;
import android.text.TextUtils;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
+import com.android.tv.TvSingletons;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
-/**
- * A class for details content.
- */
+/** A class for details content. */
class DetailsContent {
/** Constant for invalid time. */
public static final long INVALID_TIME = -1;
@@ -44,8 +41,8 @@ class DetailsContent {
private String mBackgroundImageUri;
private boolean mUsingChannelLogo;
- static DetailsContent createFromRecordedProgram(Context context,
- RecordedProgram recordedProgram) {
+ static DetailsContent createFromRecordedProgram(
+ Context context, RecordedProgram recordedProgram) {
return new DetailsContent.Builder()
.setChannelId(recordedProgram.getChannelId())
.setProgramTitle(recordedProgram.getTitle())
@@ -53,32 +50,72 @@ class DetailsContent {
.setEpisodeNumber(recordedProgram.getEpisodeNumber())
.setStartTimeUtcMillis(recordedProgram.getStartTimeUtcMillis())
.setEndTimeUtcMillis(recordedProgram.getEndTimeUtcMillis())
- .setDescription(TextUtils.isEmpty(recordedProgram.getLongDescription())
- ? recordedProgram.getDescription() : recordedProgram.getLongDescription())
+ .setDescription(
+ TextUtils.isEmpty(recordedProgram.getLongDescription())
+ ? recordedProgram.getDescription()
+ : recordedProgram.getLongDescription())
.setPosterArtUri(recordedProgram.getPosterArtUri())
.setThumbnailUri(recordedProgram.getThumbnailUri())
.build(context);
}
- static DetailsContent createFromSeriesRecording(Context context,
- SeriesRecording seriesRecording) {
+ static DetailsContent createFromSeriesRecording(
+ Context context, SeriesRecording seriesRecording) {
return new DetailsContent.Builder()
.setChannelId(seriesRecording.getChannelId())
.setTitle(seriesRecording.getTitle())
- .setDescription(TextUtils.isEmpty(seriesRecording.getLongDescription())
- ? seriesRecording.getDescription() : seriesRecording.getLongDescription())
+ .setDescription(
+ TextUtils.isEmpty(seriesRecording.getLongDescription())
+ ? seriesRecording.getDescription()
+ : seriesRecording.getLongDescription())
.setPosterArtUri(seriesRecording.getPosterUri())
.setThumbnailUri(seriesRecording.getPhotoUri())
.build(context);
}
- static DetailsContent createFromScheduledRecording(Context context,
- ScheduledRecording scheduledRecording) {
- Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
- .getChannel(scheduledRecording.getChannelId());
- String description = !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) ?
- scheduledRecording.getProgramDescription()
- : scheduledRecording.getProgramLongDescription();
+ static DetailsContent createFromScheduledRecording(
+ Context context, ScheduledRecording scheduledRecording) {
+ Channel channel =
+ TvSingletons.getSingletons(context)
+ .getChannelDataManager()
+ .getChannel(scheduledRecording.getChannelId());
+ String description =
+ !TextUtils.isEmpty(scheduledRecording.getProgramDescription())
+ ? scheduledRecording.getProgramDescription()
+ : scheduledRecording.getProgramLongDescription();
+ if (TextUtils.isEmpty(description)) {
+ description = channel != null ? channel.getDescription() : null;
+ }
+ return new DetailsContent.Builder()
+ .setChannelId(scheduledRecording.getChannelId())
+ .setProgramTitle(scheduledRecording.getProgramTitle())
+ .setSeasonNumber(scheduledRecording.getSeasonNumber())
+ .setEpisodeNumber(scheduledRecording.getEpisodeNumber())
+ .setStartTimeUtcMillis(scheduledRecording.getStartTimeMs())
+ .setEndTimeUtcMillis(scheduledRecording.getEndTimeMs())
+ .setDescription(description)
+ .setPosterArtUri(scheduledRecording.getProgramPosterArtUri())
+ .setThumbnailUri(scheduledRecording.getProgramThumbnailUri())
+ .build(context);
+ }
+
+ static DetailsContent createFromFailedScheduledRecording(
+ Context context, ScheduledRecording scheduledRecording, String errMsg) {
+ Channel channel =
+ TvSingletons.getSingletons(context)
+ .getChannelDataManager()
+ .getChannel(scheduledRecording.getChannelId());
+ String description;
+ if (scheduledRecording.getState() == ScheduledRecording.STATE_RECORDING_FAILED
+ && errMsg != null) {
+ description = errMsg
+ + " (Error code: " + scheduledRecording.getFailedReason() + ")";
+ } else {
+ description =
+ !TextUtils.isEmpty(scheduledRecording.getProgramDescription())
+ ? scheduledRecording.getProgramDescription()
+ : scheduledRecording.getProgramLongDescription();
+ }
if (TextUtils.isEmpty(description)) {
description = channel != null ? channel.getDescription() : null;
}
@@ -95,60 +132,44 @@ class DetailsContent {
.build(context);
}
- private DetailsContent() { }
+ private DetailsContent() {}
- /**
- * Returns title.
- */
+ /** Returns title. */
public CharSequence getTitle() {
return mTitle;
}
- /**
- * Returns start time.
- */
+ /** Returns start time. */
public long getStartTimeUtcMillis() {
return mStartTimeUtcMillis;
}
- /**
- * Returns end time.
- */
+ /** Returns end time. */
public long getEndTimeUtcMillis() {
return mEndTimeUtcMillis;
}
- /**
- * Returns description.
- */
+ /** Returns description. */
public String getDescription() {
return mDescription;
}
- /**
- * Returns Logo image URI as a String.
- */
+ /** Returns Logo image URI as a String. */
public String getLogoImageUri() {
return mLogoImageUri;
}
- /**
- * Returns background image URI as a String.
- */
+ /** Returns background image URI as a String. */
public String getBackgroundImageUri() {
return mBackgroundImageUri;
}
- /**
- * Returns if image URIs are from its channels' logo.
- */
+ /** Returns if image URIs are from its channels' logo. */
public boolean isUsingChannelLogo() {
return mUsingChannelLogo;
}
- /**
- * Copies other details content.
- */
+ /** Copies other details content. */
public void copyFrom(DetailsContent other) {
if (this == other) {
return;
@@ -162,9 +183,7 @@ class DetailsContent {
mUsingChannelLogo = other.mUsingChannelLogo;
}
- /**
- * A class for building details content.
- */
+ /** A class for building details content. */
public static final class Builder {
private final DetailsContent mDetailsContent;
@@ -181,49 +200,37 @@ class DetailsContent {
mDetailsContent.mEndTimeUtcMillis = INVALID_TIME;
}
- /**
- * Sets title.
- */
+ /** Sets title. */
public Builder setTitle(CharSequence title) {
mDetailsContent.mTitle = title;
return this;
}
- /**
- * Sets start time.
- */
+ /** Sets start time. */
public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis;
return this;
}
- /**
- * Sets end time.
- */
+ /** Sets end time. */
public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis;
return this;
}
- /**
- * Sets description.
- */
+ /** Sets description. */
public Builder setDescription(String description) {
mDetailsContent.mDescription = description;
return this;
}
- /**
- * Sets logo image URI as a String.
- */
+ /** Sets logo image URI as a String. */
public Builder setLogoImageUri(String logoImageUri) {
mDetailsContent.mLogoImageUri = logoImageUri;
return this;
}
- /**
- * Sets background image URI as a String.
- */
+ /** Sets background image URI as a String. */
public Builder setBackgroundImageUri(String backgroundImageUri) {
mDetailsContent.mBackgroundImageUri = backgroundImageUri;
return this;
@@ -260,12 +267,18 @@ class DetailsContent {
}
private void createStyledTitle(Context context, Channel channel) {
- CharSequence title = DvrUiHelper.getStyledTitleWithEpisodeNumber(context,
- mProgramTitle, mSeasonNumber, mEpisodeNumber,
- R.style.text_appearance_card_view_episode_number);
+ CharSequence title =
+ DvrUiHelper.getStyledTitleWithEpisodeNumber(
+ context,
+ mProgramTitle,
+ mSeasonNumber,
+ mEpisodeNumber,
+ R.style.text_appearance_card_view_episode_number);
if (TextUtils.isEmpty(title)) {
- mDetailsContent.mTitle = channel != null ? channel.getDisplayName()
- : context.getResources().getString(R.string.no_program_information);
+ mDetailsContent.mTitle =
+ channel != null
+ ? channel.getDisplayName()
+ : context.getResources().getString(R.string.no_program_information);
} else {
mDetailsContent.mTitle = title;
}
@@ -288,20 +301,19 @@ class DetailsContent {
mDetailsContent.mBackgroundImageUri = mThumbnailUri;
}
if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) {
- String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId())
- .toString();
+ String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId()).toString();
mDetailsContent.mLogoImageUri = channelLogoUri;
mDetailsContent.mBackgroundImageUri = channelLogoUri;
mDetailsContent.mUsingChannelLogo = true;
}
}
- /**
- * Builds details content.
- */
+ /** Builds details content. */
public DetailsContent build(Context context) {
- Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
- .getChannel(mChannelId);
+ Channel channel =
+ TvSingletons.getSingletons(context)
+ .getChannelDataManager()
+ .getChannel(mChannelId);
if (mDetailsContent.mTitle == null) {
createStyledTitle(context, channel);
}
@@ -314,4 +326,4 @@ class DetailsContent {
return detailsContent;
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
index 09b57887..aec8c411 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
@@ -16,11 +16,11 @@
package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
@@ -33,24 +33,20 @@ import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityManager;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.ui.ViewUtils;
import com.android.tv.util.Utils;
/**
- * An {@link Presenter} for rendering a detailed description of an DVR item.
- * Typically this Presenter will be used in a
- * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter}.
- * Most codes of this class is originated from
- * {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
- * The latter class are re-used to provide a customized version of
- * {@link android.support.v17.leanback.widget.DetailsOverviewRow}.
+ * An {@link Presenter} for rendering a detailed description of an DVR item. Typically this
+ * Presenter will be used in a {@link
+ * android.support.v17.leanback.widget.DetailsOverviewRowPresenter}. Most codes of this class is
+ * originated from {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
+ * The latter class are re-used to provide a customized version of {@link
+ * android.support.v17.leanback.widget.DetailsOverviewRow}.
*/
class DetailsContentPresenter extends Presenter {
- /**
- * The ViewHolder for the {@link DetailsContentPresenter}.
- */
+ /** The ViewHolder for the {@link DetailsContentPresenter}. */
public static class ViewHolder extends Presenter.ViewHolder {
final TextView mTitle;
final TextView mSubtitle;
@@ -85,31 +81,40 @@ class DetailsContentPresenter extends Presenter {
return false;
}
final int bodyLines = mBody.getLineCount();
- int maxLines = mFullTextMode ? bodyLines :
- (mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines);
+ int maxLines =
+ mFullTextMode
+ ? bodyLines
+ : (mTitle.getLineCount() > 1
+ ? mBodyMinLines
+ : mBodyMaxLines);
if (bodyLines > maxLines) {
mReadMoreView.setVisibility(View.VISIBLE);
mDescriptionContainer.setFocusable(true);
mDescriptionContainer.setClickable(true);
- mDescriptionContainer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mFullTextMode = true;
- mReadMoreView.setVisibility(View.GONE);
- mDescriptionContainer.setFocusable((
- (AccessibilityManager) view.getContext()
- .getSystemService(
- Context.ACCESSIBILITY_SERVICE))
- .isEnabled());
- mDescriptionContainer.setClickable(false);
- mDescriptionContainer.setOnClickListener(null);
- int oldMaxLines = mBody.getMaxLines();
- mBody.setMaxLines(bodyLines);
- // Minus 1 from line difference to eliminate the space
- // originally occupied by "READ MORE"
- showFullText((bodyLines - oldMaxLines - 1) * mBodyLineSpacing);
- }
- });
+ mDescriptionContainer.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFullTextMode = true;
+ mReadMoreView.setVisibility(View.GONE);
+ mDescriptionContainer.setFocusable(
+ ((AccessibilityManager)
+ view.getContext()
+ .getSystemService(
+ Context
+ .ACCESSIBILITY_SERVICE))
+ .isEnabled());
+ mDescriptionContainer.setClickable(false);
+ mDescriptionContainer.setOnClickListener(null);
+ int oldMaxLines = mBody.getMaxLines();
+ mBody.setMaxLines(bodyLines);
+ // Minus 1 from line difference to eliminate the space
+ // originally occupied by "READ MORE"
+ showFullText(
+ (bodyLines - oldMaxLines - 1)
+ * mBodyLineSpacing);
+ }
+ });
}
if (mReadMoreView.getVisibility() == View.VISIBLE
&& mSubtitle.getVisibility() == View.VISIBLE) {
@@ -151,30 +156,42 @@ class DetailsContentPresenter extends Presenter {
// We have to explicitly set focusable to true here for accessibility, since we might
// set the view's focusable state when we need to show "READ MORE", which would remove
// the default focusable state for accessibility.
- mDescriptionContainer.setFocusable(((AccessibilityManager) view.getContext()
- .getSystemService(Context.ACCESSIBILITY_SERVICE)).isEnabled());
+ mDescriptionContainer.setFocusable(
+ ((AccessibilityManager)
+ view.getContext()
+ .getSystemService(Context.ACCESSIBILITY_SERVICE))
+ .isEnabled());
mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more);
FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
- final int titleAscent = view.getResources().getDimensionPixelSize(
- R.dimen.lb_details_description_title_baseline);
+ final int titleAscent =
+ view.getResources()
+ .getDimensionPixelSize(R.dimen.lb_details_description_title_baseline);
// Ascent is negative
mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
- mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize(
- R.dimen.lb_details_description_under_title_baseline_margin);
- mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize(
- R.dimen.dvr_details_description_under_subtitle_baseline_margin);
+ mUnderTitleBaselineMargin =
+ view.getResources()
+ .getDimensionPixelSize(
+ R.dimen.lb_details_description_under_title_baseline_margin);
+ mUnderSubtitleBaselineMargin =
+ view.getResources()
+ .getDimensionPixelSize(
+ R.dimen.dvr_details_description_under_subtitle_baseline_margin);
- mTitleLineSpacing = view.getResources().getDimensionPixelSize(
- R.dimen.lb_details_description_title_line_spacing);
- mBodyLineSpacing = view.getResources().getDimensionPixelSize(
- R.dimen.lb_details_description_body_line_spacing);
+ mTitleLineSpacing =
+ view.getResources()
+ .getDimensionPixelSize(
+ R.dimen.lb_details_description_title_line_spacing);
+ mBodyLineSpacing =
+ view.getResources()
+ .getDimensionPixelSize(
+ R.dimen.lb_details_description_body_line_spacing);
- mBodyMaxLines = view.getResources().getInteger(
- R.integer.lb_details_description_body_max_lines);
- mBodyMinLines = view.getResources().getInteger(
- R.integer.lb_details_description_body_min_lines);
+ mBodyMaxLines =
+ view.getResources().getInteger(R.integer.lb_details_description_body_max_lines);
+ mBodyMinLines =
+ view.getResources().getInteger(R.integer.lb_details_description_body_min_lines);
mTitleMaxLines = mTitle.getMaxLines();
mTitleFontMetricsInt = getFontMetricsInt(mTitle);
@@ -218,12 +235,14 @@ class DetailsContentPresenter extends Presenter {
private void showFullText(int heightDiff) {
final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame);
int nowHeight = ViewUtils.getLayoutHeight(detailsFrame);
- Animator expandAnimator = ViewUtils.createHeightAnimator(
- detailsFrame, nowHeight, nowHeight + heightDiff);
+ Animator expandAnimator =
+ ViewUtils.createHeightAnimator(detailsFrame, nowHeight, nowHeight + heightDiff);
expandAnimator.setDuration(mFullTextAnimationDuration);
- Animator shiftAnimator = ObjectAnimator.ofPropertyValuesHolder(detailsFrame,
- PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
- 0f, -(heightDiff / 2)));
+ Animator shiftAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(
+ detailsFrame,
+ PropertyValuesHolder.ofFloat(
+ View.TRANSLATION_Y, 0f, -(heightDiff / 2)));
shiftAnimator.setDuration(mFullTextAnimationDuration);
AnimatorSet fullTextAnimator = new AnimatorSet();
fullTextAnimator.playTogether(expandAnimator, shiftAnimator);
@@ -237,14 +256,17 @@ class DetailsContentPresenter extends Presenter {
public DetailsContentPresenter(Activity activity) {
super();
mActivity = activity;
- mFullTextAnimationDuration = mActivity.getResources()
- .getInteger(R.integer.dvr_details_full_text_animation_duration);
+ mFullTextAnimationDuration =
+ mActivity
+ .getResources()
+ .getInteger(R.integer.dvr_details_full_text_animation_duration);
}
@Override
public final ViewHolder onCreateViewHolder(ViewGroup parent) {
- View v = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.dvr_details_description, parent, false);
+ View v =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.dvr_details_description, parent, false);
return new ViewHolder(v);
}
@@ -263,8 +285,11 @@ class DetailsContentPresenter extends Presenter {
} else {
vh.mTitle.setText(detailsContent.getTitle());
vh.mTitle.setVisibility(View.VISIBLE);
- vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight()
- + vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier());
+ vh.mTitle.setLineSpacing(
+ vh.mTitleLineSpacing
+ - vh.mTitle.getLineHeight()
+ + vh.mTitle.getLineSpacingExtra(),
+ vh.mTitle.getLineSpacingMultiplier());
vh.mTitle.setMaxLines(vh.mTitleMaxLines);
}
setTopMargin(vh.mTitle, vh.mTitleMargin);
@@ -272,13 +297,19 @@ class DetailsContentPresenter extends Presenter {
boolean hasSubtitle = true;
if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME
&& detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) {
- vh.mSubtitle.setText(Utils.getDurationString(viewHolder.view.getContext(),
- detailsContent.getStartTimeUtcMillis(),
- detailsContent.getEndTimeUtcMillis(), false));
+ vh.mSubtitle.setText(
+ Utils.getDurationString(
+ viewHolder.view.getContext(),
+ detailsContent.getStartTimeUtcMillis(),
+ detailsContent.getEndTimeUtcMillis(),
+ false));
vh.mSubtitle.setVisibility(View.VISIBLE);
if (hasTitle) {
- setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin
- + vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent);
+ setTopMargin(
+ vh.mSubtitle,
+ vh.mUnderTitleBaselineMargin
+ + vh.mSubtitleFontMetricsInt.ascent
+ - vh.mTitleFontMetricsInt.descent);
} else {
setTopMargin(vh.mSubtitle, 0);
}
@@ -292,16 +323,23 @@ class DetailsContentPresenter extends Presenter {
} else {
vh.mBody.setText(detailsContent.getDescription());
vh.mBody.setVisibility(View.VISIBLE);
- vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight()
- + vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier());
+ vh.mBody.setLineSpacing(
+ vh.mBodyLineSpacing - vh.mBody.getLineHeight() + vh.mBody.getLineSpacingExtra(),
+ vh.mBody.getLineSpacingMultiplier());
if (hasSubtitle) {
- setTopMargin(vh.mDescriptionContainer, vh.mUnderSubtitleBaselineMargin
- + vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent
- - vh.mBody.getPaddingTop());
+ setTopMargin(
+ vh.mDescriptionContainer,
+ vh.mUnderSubtitleBaselineMargin
+ + vh.mBodyFontMetricsInt.ascent
+ - vh.mSubtitleFontMetricsInt.descent
+ - vh.mBody.getPaddingTop());
} else if (hasTitle) {
- setTopMargin(vh.mDescriptionContainer, vh.mUnderTitleBaselineMargin
- + vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent
- - vh.mBody.getPaddingTop());
+ setTopMargin(
+ vh.mDescriptionContainer,
+ vh.mUnderTitleBaselineMargin
+ + vh.mBodyFontMetricsInt.ascent
+ - vh.mTitleFontMetricsInt.descent
+ - vh.mBody.getPaddingTop());
} else {
setTopMargin(vh.mDescriptionContainer, 0);
}
@@ -309,11 +347,11 @@ class DetailsContentPresenter extends Presenter {
}
@Override
- public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { }
+ public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
private void setTopMargin(View view, int topMargin) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
lp.topMargin = topMargin;
view.setLayoutParams(lp);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
index 82fe9ce3..849360b8 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
@@ -23,9 +23,7 @@ import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
-/**
- * The Background Helper.
- */
+/** The Background Helper. */
class DetailsViewBackgroundHelper {
// Background delay serves to avoid kicking off expensive bitmap loading
// in case multiple backgrounds are set in quick succession.
@@ -59,11 +57,10 @@ class DetailsViewBackgroundHelper {
public DetailsViewBackgroundHelper(Activity activity) {
mBackgroundManager = BackgroundManager.getInstance(activity);
mBackgroundManager.attach(activity.getWindow());
+ mBackgroundManager.setAutoReleaseOnStop(false);
}
- /**
- * Sets the given image to background.
- */
+ /** Sets the given image to background. */
public void setBackground(Drawable background) {
if (mRunnable != null) {
mHandler.removeCallbacks(mRunnable);
@@ -72,18 +69,14 @@ class DetailsViewBackgroundHelper {
mHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS);
}
- /**
- * Sets the background color.
- */
+ /** Sets the background color. */
public void setBackgroundColor(int color) {
if (mBackgroundManager.isAttached()) {
mBackgroundManager.setColor(color);
}
}
- /**
- * Sets the background scrim.
- */
+ /** Sets the background scrim. */
public void setScrim(int color) {
if (mBackgroundManager.isAttached()) {
mBackgroundManager.setDimLayer(new ColorDrawable(color));
diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
index 07eec107..6cc1c7a1 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
@@ -20,19 +20,16 @@ import android.app.Activity;
import android.content.Intent;
import android.media.tv.TvInputManager;
import android.os.Bundle;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
-/**
- * {@link android.app.Activity} for DVR UI.
- */
+/** {@link android.app.Activity} for DVR UI. */
public class DvrBrowseActivity extends Activity {
private DvrBrowseFragment mFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.dvr_main);
mFragment = (DvrBrowseFragment) getFragmentManager().findFragmentById(R.id.dvr_frame);
@@ -49,4 +46,4 @@ public class DvrBrowseActivity extends Activity {
mFragment.showScheduledRow();
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
index cb3a5745..40b3a1f0 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
@@ -16,7 +16,9 @@
package com.android.tv.dvr.ui.browse;
+import android.annotation.TargetApi;
import android.content.Context;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BrowseFragment;
@@ -30,9 +32,9 @@ import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvFeatures;
+import com.android.tv.TvSingletons;
import com.android.tv.data.GenreItems;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
@@ -52,12 +54,15 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
-/**
- * {@link BrowseFragment} for DVR functions.
- */
-public class DvrBrowseFragment extends BrowseFragment implements
- RecordedProgramListener, ScheduledRecordingListener, SeriesRecordingListener,
- OnDvrScheduleLoadFinishedListener, OnRecordedProgramLoadFinishedListener {
+/** {@link BrowseFragment} for DVR functions. */
+@TargetApi(Build.VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
+public class DvrBrowseFragment extends BrowseFragment
+ implements RecordedProgramListener,
+ ScheduledRecordingListener,
+ SeriesRecordingListener,
+ OnDvrScheduleLoadFinishedListener,
+ OnRecordedProgramLoadFinishedListener {
private static final String TAG = "DvrBrowseFragment";
private static final boolean DEBUG = false;
@@ -67,7 +72,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
private boolean mShouldShowScheduleRow;
private boolean mEntranceTransitionEnded;
- private RecordedProgramAdapter mRecentAdapter;
+ private RecentRowAdapter mRecentAdapter;
private ScheduleAdapter mScheduleAdapter;
private SeriesAdapter mSeriesAdapter;
private RecordedProgramAdapter[] mGenreAdapters =
@@ -98,82 +103,143 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
};
- private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR = new Comparator<Object>() {
- @Override
- public int compare(Object lhs, Object rhs) {
- if (lhs instanceof SeriesRecording) {
- lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId());
- }
- if (rhs instanceof SeriesRecording) {
- rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId());
- }
- if (lhs instanceof RecordedProgram) {
- if (rhs instanceof RecordedProgram) {
- return RecordedProgram.START_TIME_THEN_ID_COMPARATOR.reversed()
- .compare((RecordedProgram) lhs, (RecordedProgram) rhs);
- } else {
- return -1;
+ private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR =
+ new Comparator<Object>() {
+ @Override
+ public int compare(Object lhs, Object rhs) {
+ if (lhs instanceof SeriesRecording) {
+ lhs = mSeriesId2LatestProgram.get(((SeriesRecording) lhs).getSeriesId());
+ }
+ if (rhs instanceof SeriesRecording) {
+ rhs = mSeriesId2LatestProgram.get(((SeriesRecording) rhs).getSeriesId());
+ }
+ if (lhs instanceof RecordedProgram) {
+ if (rhs instanceof RecordedProgram) {
+ return RecordedProgram.START_TIME_THEN_ID_COMPARATOR
+ .reversed()
+ .compare((RecordedProgram) lhs, (RecordedProgram) rhs);
+ } else {
+ return -1;
+ }
+ } else if (rhs instanceof RecordedProgram) {
+ return 1;
+ } else {
+ return 0;
+ }
}
- } else if (rhs instanceof RecordedProgram) {
- return 1;
- } else {
- return 0;
- }
- }
- };
+ };
- private static final Comparator<Object> SCHEDULE_COMPARATOR = new Comparator<Object>() {
- @Override
- public int compare(Object lhs, Object rhs) {
- if (lhs instanceof ScheduledRecording) {
- if (rhs instanceof ScheduledRecording) {
- return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
- .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs);
- } else {
- return -1;
+ private static final Comparator<Object> SCHEDULE_COMPARATOR =
+ new Comparator<Object>() {
+ @Override
+ public int compare(Object lhs, Object rhs) {
+ if (lhs instanceof ScheduledRecording) {
+ if (rhs instanceof ScheduledRecording) {
+ return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
+ .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs);
+ } else {
+ return -1;
+ }
+ } else if (rhs instanceof ScheduledRecording) {
+ return 1;
+ } else {
+ return 0;
+ }
}
- } else if (rhs instanceof ScheduledRecording) {
- return 1;
- } else {
- return 0;
- }
- }
- };
+ };
+
+ static final Comparator<Object> RECENT_ROW_COMPARATOR =
+ new Comparator<Object>() {
+ @Override
+ public int compare(Object lhs, Object rhs) {
+ if (lhs instanceof ScheduledRecording) {
+ if (rhs instanceof ScheduledRecording) {
+ return ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
+ .reversed()
+ .compare((ScheduledRecording) lhs, (ScheduledRecording) rhs);
+ } else if (rhs instanceof RecordedProgram) {
+ ScheduledRecording scheduled = (ScheduledRecording) lhs;
+ RecordedProgram recorded = (RecordedProgram) rhs;
+ int compare =
+ Long.compare(
+ recorded.getStartTimeUtcMillis(),
+ scheduled.getStartTimeMs());
+ // recorded program first when the start times are the same
+ return compare == 0 ? 1 : compare;
+ } else {
+ return -1;
+ }
+ } else if (lhs instanceof RecordedProgram) {
+ if (rhs instanceof RecordedProgram) {
+ return RecordedProgram.START_TIME_THEN_ID_COMPARATOR
+ .reversed()
+ .compare((RecordedProgram) lhs, (RecordedProgram) rhs);
+ } else if (rhs instanceof ScheduledRecording) {
+ RecordedProgram recorded = (RecordedProgram) lhs;
+ ScheduledRecording scheduled = (ScheduledRecording) rhs;
+ int compare =
+ Long.compare(
+ scheduled.getStartTimeMs(),
+ recorded.getStartTimeUtcMillis());
+ // recorded program first when the start times are the same
+ return compare == 0 ? -1 : compare;
+ } else {
+ return -1;
+ }
+ } else {
+ return !(rhs instanceof RecordedProgram)
+ && !(rhs instanceof ScheduledRecording)
+ ? 0 : 1;
+ }
+ }
+ };
private final DvrScheduleManager.OnConflictStateChangeListener mOnConflictStateChangeListener =
new DvrScheduleManager.OnConflictStateChangeListener() {
- @Override
- public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) {
- if (mScheduleAdapter != null) {
- for (ScheduledRecording schedule : schedules) {
- onScheduledRecordingConflictStatusChanged(schedule);
+ @Override
+ public void onConflictStateChange(
+ boolean conflict, ScheduledRecording... schedules) {
+ if (mScheduleAdapter != null) {
+ for (ScheduledRecording schedule : schedules) {
+ onScheduledRecordingConflictStatusChanged(schedule);
+ }
+ }
}
- }
- }
- };
+ };
- private final Runnable mUpdateRowsRunnable = new Runnable() {
- @Override
- public void run() {
- updateRows();
- }
- };
+ private final Runnable mUpdateRowsRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ updateRows();
+ }
+ };
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
Context context = getContext();
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ TvSingletons singletons = TvSingletons.getSingletons(context);
mDvrDataManager = singletons.getDvrDataManager();
mDvrScheudleManager = singletons.getDvrScheduleManager();
- mPresenterSelector = new ClassPresenterSelector()
- .addClassPresenter(ScheduledRecording.class,
- new ScheduledRecordingPresenter(context))
- .addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context))
- .addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context))
- .addClassPresenter(FullScheduleCardHolder.class,
- new FullSchedulesCardPresenter(context));
+ mPresenterSelector =
+ new ClassPresenterSelector()
+ .addClassPresenter(
+ ScheduledRecording.class, new ScheduledRecordingPresenter(context))
+ .addClassPresenter(
+ RecordedProgram.class, new RecordedProgramPresenter(context))
+ .addClassPresenter(
+ SeriesRecording.class, new SeriesRecordingPresenter(context))
+ .addClassPresenter(
+ FullScheduleCardHolder.class,
+ new FullSchedulesCardPresenter(context));
+
+ if (TvFeatures.DVR_FAILED_LIST.isEnabled(context)) {
+ mPresenterSelector.addClassPresenter(
+ DvrHistoryCardHolder.class,
+ new DvrHistoryCardPresenter(context));
+ }
mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context)));
mGenreLabels.add(getString(R.string.dvr_main_others));
prepareUiElements();
@@ -195,7 +261,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public void onDestroyView() {
- getView().getViewTreeObserver()
+ getView()
+ .getViewTreeObserver()
.removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
super.onDestroyView();
}
@@ -263,6 +330,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
for (ScheduledRecording scheduleRecording : scheduledRecordings) {
if (needToShowScheduledRecording(scheduleRecording)) {
mScheduleAdapter.add(scheduleRecording);
+ } else if (scheduleRecording.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
+ mRecentAdapter.add(scheduleRecording);
}
}
}
@@ -361,30 +430,44 @@ public class DvrBrowseFragment extends BrowseFragment implements
private boolean startBrowseIfDvrInitialized() {
if (mDvrDataManager.isInitialized()) {
// Setup rows
- mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
+ mRecentAdapter = new RecentRowAdapter(MAX_RECENT_ITEM_COUNT);
mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
mSeriesAdapter = new SeriesAdapter();
for (int i = 0; i < mGenreAdapters.length; i++) {
mGenreAdapters[i] = new RecordedProgramAdapter();
}
// Schedule Recordings.
- List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
+ // only get not started or in progress recordings
+ List<ScheduledRecording> schedules = mDvrDataManager.getAvailableScheduledRecordings();
onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
// Recorded Programs.
for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
handleRecordedProgramAdded(recordedProgram, false);
}
+ if (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext())) {
+ // only get failed recordings
+ for (ScheduledRecording scheduledRecording
+ : mDvrDataManager.getFailedScheduledRecordings()) {
+ onScheduledRecordingAdded(scheduledRecording);
+ }
+ mRecentAdapter.addExtraItem(DvrHistoryCardHolder.DVR_HISTORY_CARD_HOLDER);
+ }
// Series Recordings. Series recordings should be added after recorded programs, because
- // we build series recordings' latest program information while adding recorded programs.
+ // we build series recordings' latest program information while adding recorded
+ // programs.
List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
handleSeriesRecordingsAdded(recordings);
- mRecentRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_recent)), mRecentAdapter);
- mScheduledRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_scheduled)), mScheduleAdapter);
- mSeriesRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_series)), mSeriesAdapter);
+ mRecentRow =
+ new ListRow(
+ new HeaderItem(getString(R.string.dvr_main_recent)), mRecentAdapter);
+ mScheduledRow =
+ new ListRow(
+ new HeaderItem(getString(R.string.dvr_main_scheduled)),
+ mScheduleAdapter);
+ mSeriesRow =
+ new ListRow(
+ new HeaderItem(getString(R.string.dvr_main_series)), mSeriesAdapter);
mRowsAdapter.add(mScheduledRow);
updateRows();
// Initialize listeners
@@ -398,16 +481,18 @@ public class DvrBrowseFragment extends BrowseFragment implements
return false;
}
- private void handleRecordedProgramAdded(RecordedProgram recordedProgram,
- boolean updateSeriesRecording) {
+ private void handleRecordedProgramAdded(
+ RecordedProgram recordedProgram, boolean updateSeriesRecording) {
mRecentAdapter.add(recordedProgram);
String seriesId = recordedProgram.getSeriesId();
SeriesRecording seriesRecording = null;
if (seriesId != null) {
seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
- if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR
- .compare(latestProgram, recordedProgram) < 0) {
+ if (latestProgram == null
+ || RecordedProgram.START_TIME_THEN_ID_COMPARATOR.compare(
+ latestProgram, recordedProgram)
+ < 0) {
mSeriesId2LatestProgram.put(seriesId, recordedProgram);
if (updateSeriesRecording && seriesRecording != null) {
onSeriesRecordingChanged(seriesRecording);
@@ -415,8 +500,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
if (seriesRecording == null) {
- for (RecordedProgramAdapter adapter
- : getGenreAdapters(recordedProgram.getCanonicalGenres())) {
+ for (RecordedProgramAdapter adapter :
+ getGenreAdapters(recordedProgram.getCanonicalGenres())) {
adapter.add(recordedProgram);
}
}
@@ -436,8 +521,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
}
- for (RecordedProgramAdapter adapter
- : getGenreAdapters(recordedProgram.getCanonicalGenres())) {
+ for (RecordedProgramAdapter adapter :
+ getGenreAdapters(recordedProgram.getCanonicalGenres())) {
adapter.remove(recordedProgram);
}
}
@@ -449,8 +534,10 @@ public class DvrBrowseFragment extends BrowseFragment implements
if (seriesId != null) {
seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
RecordedProgram latestProgram = mSeriesId2LatestProgram.get(seriesId);
- if (latestProgram == null || RecordedProgram.START_TIME_THEN_ID_COMPARATOR
- .compare(latestProgram, recordedProgram) <= 0) {
+ if (latestProgram == null
+ || RecordedProgram.START_TIME_THEN_ID_COMPARATOR.compare(
+ latestProgram, recordedProgram)
+ <= 0) {
mSeriesId2LatestProgram.put(seriesId, recordedProgram);
if (seriesRecording != null) {
onSeriesRecordingChanged(seriesRecording);
@@ -463,8 +550,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
if (seriesRecording == null) {
- updateGenreAdapters(getGenreAdapters(
- recordedProgram.getCanonicalGenres()), recordedProgram);
+ updateGenreAdapters(
+ getGenreAdapters(recordedProgram.getCanonicalGenres()), recordedProgram);
} else {
updateGenreAdapters(new ArrayList<>(), recordedProgram);
}
@@ -474,8 +561,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
for (SeriesRecording seriesRecording : seriesRecordings) {
mSeriesAdapter.add(seriesRecording);
if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) {
- for (RecordedProgramAdapter adapter
- : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
+ for (RecordedProgramAdapter adapter :
+ getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
adapter.add(seriesRecording);
}
}
@@ -485,8 +572,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
private void handleSeriesRecordingsRemoved(List<SeriesRecording> seriesRecordings) {
for (SeriesRecording seriesRecording : seriesRecordings) {
mSeriesAdapter.remove(seriesRecording);
- for (RecordedProgramAdapter adapter
- : getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
+ for (RecordedProgramAdapter adapter :
+ getGenreAdapters(seriesRecording.getCanonicalGenreIds())) {
adapter.remove(seriesRecording);
}
}
@@ -496,8 +583,8 @@ public class DvrBrowseFragment extends BrowseFragment implements
for (SeriesRecording seriesRecording : seriesRecordings) {
mSeriesAdapter.change(seriesRecording);
if (mSeriesId2LatestProgram.get(seriesRecording.getSeriesId()) != null) {
- updateGenreAdapters(getGenreAdapters(
- seriesRecording.getCanonicalGenreIds()), seriesRecording);
+ updateGenreAdapters(
+ getGenreAdapters(seriesRecording.getCanonicalGenreIds()), seriesRecording);
} else {
// Remove series recording from all genre rows if it has no recorded program
updateGenreAdapters(new ArrayList<>(), seriesRecording);
@@ -512,7 +599,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
} else {
for (String genre : genres) {
int genreId = GenreItems.getId(genre);
- if(genreId >= mGenreAdapters.length) {
+ if (genreId >= mGenreAdapters.length) {
Log.d(TAG, "Wrong Genre ID: " + genreId);
} else {
result.add(mGenreAdapters[genreId]);
@@ -528,7 +615,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
result.add(mGenreAdapters[mGenreAdapters.length - 1]);
} else {
for (int genreId : genreIds) {
- if(genreId >= mGenreAdapters.length) {
+ if (genreId >= mGenreAdapters.length) {
Log.d(TAG, "Wrong Genre ID: " + genreId);
} else {
result.add(mGenreAdapters[genreId]);
@@ -554,8 +641,9 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
private void updateRows() {
- int visibleRowsCount = 1; // Schedule's Row will never be empty
- if (mRecentAdapter.isEmpty()) {
+ int visibleRowsCount = 1; // Schedule's Row will never be empty
+ int recentRowMinSize = TvFeatures.DVR_FAILED_LIST.isEnabled(getContext()) ? 1 : 0;
+ if (mRecentAdapter.size() <= recentRowMinSize) {
mRowsAdapter.remove(mRecentRow);
} else {
if (mRowsAdapter.indexOf(mRecentRow) < 0) {
@@ -597,8 +685,9 @@ public class DvrBrowseFragment extends BrowseFragment implements
RecordedProgram latestProgram = null;
for (RecordedProgram program :
mDvrDataManager.getRecordedPrograms(seriesRecording.getId())) {
- if (latestProgram == null || RecordedProgram
- .START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program) < 0) {
+ if (latestProgram == null
+ || RecordedProgram.START_TIME_THEN_ID_COMPARATOR.compare(latestProgram, program)
+ < 0) {
latestProgram = program;
}
}
@@ -622,17 +711,19 @@ public class DvrBrowseFragment extends BrowseFragment implements
private class SeriesAdapter extends SortedArrayAdapter<SeriesRecording> {
SeriesAdapter() {
- super(mPresenterSelector, new Comparator<SeriesRecording>() {
- @Override
- public int compare(SeriesRecording lhs, SeriesRecording rhs) {
- if (lhs.isStopped() && !rhs.isStopped()) {
- return 1;
- } else if (!lhs.isStopped() && rhs.isStopped()) {
- return -1;
- }
- return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs);
- }
- });
+ super(
+ mPresenterSelector,
+ new Comparator<SeriesRecording>() {
+ @Override
+ public int compare(SeriesRecording lhs, SeriesRecording rhs) {
+ if (lhs.isStopped() && !rhs.isStopped()) {
+ return 1;
+ } else if (!lhs.isStopped() && rhs.isStopped()) {
+ return -1;
+ }
+ return SeriesRecording.PRIORITY_COMPARATOR.compare(lhs, rhs);
+ }
+ });
}
@Override
@@ -662,4 +753,22 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
}
-} \ No newline at end of file
+
+ private class RecentRowAdapter extends SortedArrayAdapter<Object> {
+ RecentRowAdapter(int maxItemCount) {
+ super(mPresenterSelector, RECENT_ROW_COMPARATOR, maxItemCount);
+ }
+
+ @Override
+ public long getId(Object item) {
+ // We takes the inverse number for the ID of scheduled recordings to make the ID stable.
+ if (item instanceof ScheduledRecording) {
+ return -((ScheduledRecording) item).getId() - 1;
+ } else if (item instanceof RecordedProgram) {
+ return ((RecordedProgram) item).getId();
+ } else {
+ return -1;
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
index 35d21db8..0336b319 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
@@ -19,21 +19,16 @@ package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.os.Bundle;
import android.support.v17.leanback.app.DetailsFragment;
-
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
import android.view.View;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
import com.android.tv.dialog.PinDialogFragment;
-/**
- * Activity to show details view in DVR.
- */
+/** Activity to show details view in DVR. */
public class DvrDetailsActivity extends Activity implements PinDialogFragment.OnPinCheckedListener {
- /**
- * Name of record id added to the Intent.
- */
+ /** Name of record id added to the Intent. */
public static final String RECORDING_ID = "record_id";
/**
@@ -42,46 +37,38 @@ public class DvrDetailsActivity extends Activity implements PinDialogFragment.On
*/
public static final String HIDE_VIEW_SCHEDULE = "hide_view_schedule";
- /**
- * Name of details view's type added to the intent.
- */
+ /** Name of details view's type added to the intent. */
public static final String DETAILS_VIEW_TYPE = "details_view_type";
- /**
- * Name of shared element between activities.
- */
+ /** Name of shared element between activities. */
public static final String SHARED_ELEMENT_NAME = "shared_element";
- /**
- * CURRENT_RECORDING_VIEW refers to Current Recordings in DVR.
- */
+ /** Name of error message of a failed recording */
+ public static final String EXTRA_FAILED_MESSAGE = "failed_message";
+
+ /** CURRENT_RECORDING_VIEW refers to Current Recordings in DVR. */
public static final int CURRENT_RECORDING_VIEW = 1;
- /**
- * SCHEDULED_RECORDING_VIEW refers to Scheduled Recordings in DVR.
- */
+ /** SCHEDULED_RECORDING_VIEW refers to Scheduled Recordings in DVR. */
public static final int SCHEDULED_RECORDING_VIEW = 2;
- /**
- * RECORDED_PROGRAM_VIEW refers to Recorded programs in DVR.
- */
+ /** RECORDED_PROGRAM_VIEW refers to Recorded programs in DVR. */
public static final int RECORDED_PROGRAM_VIEW = 3;
- /**
- * SERIES_RECORDING_VIEW refers to series recording in DVR.
- */
+ /** SERIES_RECORDING_VIEW refers to series recording in DVR. */
public static final int SERIES_RECORDING_VIEW = 4;
private PinDialogFragment.OnPinCheckedListener mOnPinCheckedListener;
@Override
public void onCreate(Bundle savedInstanceState) {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dvr_details);
long recordId = getIntent().getLongExtra(RECORDING_ID, -1);
int detailsViewType = getIntent().getIntExtra(DETAILS_VIEW_TYPE, -1);
boolean hideViewSchedule = getIntent().getBooleanExtra(HIDE_VIEW_SCHEDULE, false);
+ String failedMsg = getIntent().getStringExtra(EXTRA_FAILED_MESSAGE);
if (recordId != -1 && detailsViewType != -1 && savedInstanceState == null) {
Bundle args = new Bundle();
args.putLong(RECORDING_ID, recordId);
@@ -90,6 +77,7 @@ public class DvrDetailsActivity extends Activity implements PinDialogFragment.On
detailsFragment = new CurrentRecordingDetailsFragment();
} else if (detailsViewType == SCHEDULED_RECORDING_VIEW) {
args.putBoolean(HIDE_VIEW_SCHEDULE, hideViewSchedule);
+ args.putString(EXTRA_FAILED_MESSAGE, failedMsg);
detailsFragment = new ScheduledRecordingDetailsFragment();
} else if (detailsViewType == RECORDED_PROGRAM_VIEW) {
detailsFragment = new RecordedProgramDetailsFragment();
@@ -97,8 +85,10 @@ public class DvrDetailsActivity extends Activity implements PinDialogFragment.On
detailsFragment = new SeriesRecordingDetailsFragment();
}
detailsFragment.setArguments(args);
- getFragmentManager().beginTransaction()
- .replace(R.id.dvr_details_view_frame, detailsFragment).commit();
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.dvr_details_view_frame, detailsFragment)
+ .commit();
}
// This is a workaround for the focus on O device
diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
index 19fb7117..8f4e4dab 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
@@ -36,21 +36,19 @@ import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.support.v17.leanback.widget.VerticalGridView;
import android.text.TextUtils;
import android.widget.Toast;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.parental.ParentalControlSettings;
-import com.android.tv.util.ImageLoader;
import com.android.tv.util.ToastUtils;
-import com.android.tv.util.Utils;
-
+import com.android.tv.util.images.ImageLoader;
import java.io.File;
abstract class DvrDetailsFragment extends DetailsFragment {
@@ -77,8 +75,8 @@ abstract class DvrDetailsFragment extends DetailsFragment {
public void onStart() {
super.onStart();
// TODO: remove the workaround of b/30401180.
- VerticalGridView container = (VerticalGridView) getActivity()
- .findViewById(R.id.container_list);
+ VerticalGridView container =
+ (VerticalGridView) getActivity().findViewById(R.id.container_list);
// Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout.
container.setItemAlignmentOffset(0);
container.setWindowAlignmentOffset(
@@ -86,27 +84,23 @@ abstract class DvrDetailsFragment extends DetailsFragment {
}
private void setupAdapter() {
- DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter(
- new DetailsContentPresenter(getActivity()));
- rowPresenter.setBackgroundColor(getResources().getColor(R.color.common_tv_background,
- null));
- rowPresenter.setSharedElementEnterTransition(getActivity(),
- DvrDetailsActivity.SHARED_ELEMENT_NAME);
+ DetailsOverviewRowPresenter rowPresenter =
+ new DetailsOverviewRowPresenter(new DetailsContentPresenter(getActivity()));
+ rowPresenter.setBackgroundColor(
+ getResources().getColor(R.color.common_tv_background, null));
+ rowPresenter.setSharedElementEnterTransition(
+ getActivity(), DvrDetailsActivity.SHARED_ELEMENT_NAME);
rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener());
mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter));
setAdapter(mRowsAdapter);
}
- /**
- * Returns details views' rows adapter.
- */
+ /** Returns details views' rows adapter. */
protected ArrayObjectAdapter getRowsAdapter() {
- return mRowsAdapter;
+ return mRowsAdapter;
}
- /**
- * Sets details overview.
- */
+ /** Sets details overview. */
protected void setDetailsOverviewRow(DetailsContent detailsContent) {
mDetailsOverview = new DetailsOverviewRow(detailsContent);
mDetailsOverview.setActionsAdapter(onCreateActionsAdapter());
@@ -114,9 +108,7 @@ abstract class DvrDetailsFragment extends DetailsFragment {
onLoadLogoAndBackgroundImages(detailsContent);
}
- /**
- * Creates and returns presenter selector will be used by rows adaptor.
- */
+ /** Creates and returns presenter selector will be used by rows adaptor. */
protected PresenterSelector onCreatePresenterSelector(
DetailsOverviewRowPresenter rowPresenter) {
ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
@@ -130,11 +122,9 @@ abstract class DvrDetailsFragment extends DetailsFragment {
* do anything after calling {@link #onCreate(Bundle)}. If there's something subclasses have to
* do after the super class did onCreate, it should override this method and put the codes here.
*/
- protected void onCreateInternal() { }
+ protected void onCreateInternal() {}
- /**
- * Updates actions of details overview.
- */
+ /** Updates actions of details overview. */
protected void updateActions() {
mDetailsOverview.setActionsAdapter(onCreateActionsAdapter());
}
@@ -142,14 +132,12 @@ abstract class DvrDetailsFragment extends DetailsFragment {
/**
* Loads recording details according to the arguments the fragment got.
*
- * @return false if cannot find valid recordings, else return true. If the return value
- * is false, the detail activity and fragment will be ended.
+ * @return false if cannot find valid recordings, else return true. If the return value is
+ * false, the detail activity and fragment will be ended.
*/
abstract boolean onLoadRecordingDetails(Bundle args);
- /**
- * Creates actions users can interact with and their adaptor for this fragment.
- */
+ /** Creates actions users can interact with and their adaptor for this fragment. */
abstract SparseArrayObjectAdapter onCreateActionsAdapter();
/**
@@ -158,66 +146,76 @@ abstract class DvrDetailsFragment extends DetailsFragment {
*/
abstract OnActionClickedListener onCreateOnActionClickedListener();
- /**
- * Loads logo and background images for detail fragments.
- */
+ /** Loads logo and background images for detail fragments. */
protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) {
Drawable logoDrawable = null;
Drawable backgroundDrawable = null;
if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) {
- logoDrawable = getContext().getResources()
- .getDrawable(R.drawable.dvr_default_poster, null);
+ logoDrawable =
+ getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null);
mDetailsOverview.setImageDrawable(logoDrawable);
}
if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) {
- backgroundDrawable = getContext().getResources()
- .getDrawable(R.drawable.dvr_default_poster, null);
+ backgroundDrawable =
+ getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null);
mBackgroundHelper.setBackground(backgroundDrawable);
}
if (logoDrawable != null && backgroundDrawable != null) {
return;
}
- if (logoDrawable == null && backgroundDrawable == null
- && detailsContent.getLogoImageUri().equals(
- detailsContent.getBackgroundImageUri())) {
- ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(),
- new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE,
- getContext()));
+ if (logoDrawable == null
+ && backgroundDrawable == null
+ && detailsContent
+ .getLogoImageUri()
+ .equals(detailsContent.getBackgroundImageUri())) {
+ ImageLoader.loadBitmap(
+ getContext(),
+ detailsContent.getLogoImageUri(),
+ new MyImageLoaderCallback(
+ this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, getContext()));
return;
}
if (logoDrawable == null) {
int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width);
- int imageHeight = getResources()
- .getDimensionPixelSize(R.dimen.dvr_details_poster_height);
- ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(),
- imageWidth, imageHeight,
+ int imageHeight =
+ getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_height);
+ ImageLoader.loadBitmap(
+ getContext(),
+ detailsContent.getLogoImageUri(),
+ imageWidth,
+ imageHeight,
new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext()));
}
if (backgroundDrawable == null) {
- ImageLoader.loadBitmap(getContext(), detailsContent.getBackgroundImageUri(),
+ ImageLoader.loadBitmap(
+ getContext(),
+ detailsContent.getBackgroundImageUri(),
new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext()));
}
}
protected void startPlayback(RecordedProgram recordedProgram, long seekTimeMs) {
- if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) &&
- !isDataUriAccessible(recordedProgram.getDataUri())) {
+ if (CommonUtils.isInBundledPackageSet(recordedProgram.getPackageName())
+ && !isDataUriAccessible(recordedProgram.getDataUri())) {
// Since cleaning RecordedProgram from forgotten storage will take some time,
// ignore playback until cleaning is finished.
- ToastUtils.show(getContext(),
+ ToastUtils.show(
+ getContext(),
getContext().getResources().getString(R.string.dvr_toast_recording_deleted),
Toast.LENGTH_SHORT);
return;
}
long programId = recordedProgram.getId();
- ParentalControlSettings parental = TvApplication.getSingletons(getActivity())
- .getTvInputManagerHelper().getParentalControlSettings();
+ ParentalControlSettings parental =
+ TvSingletons.getSingletons(getActivity())
+ .getTvInputManagerHelper()
+ .getParentalControlSettings();
if (!parental.isParentalControlsEnabled()) {
DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false);
return;
}
ChannelDataManager channelDataManager =
- TvApplication.getSingletons(getActivity()).getChannelDataManager();
+ TvSingletons.getSingletons(getActivity()).getChannelDataManager();
Channel channel = channelDataManager.getChannel(recordedProgram.getChannelId());
if (channel != null && channel.isLocked()) {
checkPinToPlay(recordedProgram, seekTimeMs);
@@ -249,36 +247,43 @@ abstract class DvrDetailsFragment extends DetailsFragment {
private void checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs) {
SoftPreconditions.checkState(getActivity() instanceof DvrDetailsActivity);
if (getActivity() instanceof DvrDetailsActivity) {
- ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(new OnPinCheckedListener() {
- @Override
- public void onPinChecked(boolean checked, int type, String rating) {
- ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(null);
- if (checked && type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
- DvrUiHelper.startPlaybackActivity(getContext(), recordedProgram.getId(),
- seekTimeMs, true);
- }
- }
- });
+ ((DvrDetailsActivity) getActivity())
+ .setOnPinCheckListener(
+ new OnPinCheckedListener() {
+ @Override
+ public void onPinChecked(boolean checked, int type, String rating) {
+ ((DvrDetailsActivity) getActivity())
+ .setOnPinCheckListener(null);
+ if (checked
+ && type
+ == PinDialogFragment
+ .PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
+ DvrUiHelper.startPlaybackActivity(
+ getContext(),
+ recordedProgram.getId(),
+ seekTimeMs,
+ true);
+ }
+ }
+ });
PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM)
.show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
}
}
- private static class MyImageLoaderCallback extends
- ImageLoader.ImageLoaderCallback<DvrDetailsFragment> {
+ private static class MyImageLoaderCallback
+ extends ImageLoader.ImageLoaderCallback<DvrDetailsFragment> {
private final Context mContext;
private final int mLoadType;
- public MyImageLoaderCallback(DvrDetailsFragment fragment,
- int loadType, Context context) {
+ public MyImageLoaderCallback(DvrDetailsFragment fragment, int loadType, Context context) {
super(fragment);
mLoadType = loadType;
mContext = context;
}
@Override
- public void onBitmapLoaded(DvrDetailsFragment fragment,
- @Nullable Bitmap bitmap) {
+ public void onBitmapLoaded(DvrDetailsFragment fragment, @Nullable Bitmap bitmap) {
Drawable drawable;
int loadType = mLoadType;
if (bitmap == null) {
diff --git a/src/com/android/tv/config/ConfigKeys.java b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardHolder.java
index 7df033d2..c6288ef0 100644
--- a/src/com/android/tv/config/ConfigKeys.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.tv.config;
-
-/**
- * Static list of config keys.
- */
-public final class ConfigKeys {
+package com.android.tv.dvr.ui.browse;
+/** Special object for schedule preview; */
+final class DvrHistoryCardHolder {
+ /** Full schedule card holder. */
+ static final DvrHistoryCardHolder DVR_HISTORY_CARD_HOLDER = new DvrHistoryCardHolder();
- private ConfigKeys() {
- }
+ private DvrHistoryCardHolder() {}
}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java
new file mode 100644
index 00000000..62c050c9
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui.browse;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import com.android.tv.R;
+import com.android.tv.dvr.ui.DvrUiHelper;
+
+/** Presents a DVR history card view in the {@link DvrBrowseFragment}. */
+class DvrHistoryCardPresenter extends DvrItemPresenter<Object> {
+ private final Drawable mIconDrawable;
+ private final String mCardTitleText;
+
+ DvrHistoryCardPresenter(Context context) {
+ super(context);
+ mIconDrawable = mContext.getDrawable(R.drawable.dvr_full_schedule);
+ mCardTitleText = mContext.getString(R.string.dvr_history_card_view_title);
+ }
+
+ @Override
+ public DvrItemViewHolder onCreateDvrItemViewHolder() {
+ return new DvrItemViewHolder(new RecordingCardView(mContext));
+ }
+
+ @Override
+ public void onBindDvrItemViewHolder(DvrItemViewHolder vh, Object o) {
+ final RecordingCardView cardView = (RecordingCardView) vh.view;
+
+ cardView.setTitle(mCardTitleText);
+ cardView.setImage(mIconDrawable);
+ }
+
+ @Override
+ public void onUnbindViewHolder(ViewHolder vh) {
+ ((RecordingCardView) vh.view).reset();
+ super.onUnbindViewHolder(vh);
+ }
+
+ @Override
+ protected View.OnClickListener onCreateOnClickListener() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ DvrUiHelper.startDvrHistoryActivity(mContext);
+ }
+ };
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
index df0e61c1..4298d86a 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
@@ -23,18 +23,15 @@ import android.support.v17.leanback.widget.Presenter;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.ui.DvrUiHelper;
-
import java.util.HashSet;
import java.util.Set;
/**
* An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in
- * {@link DvrBrowseFragment}. DVR items might include:
- * {@link com.android.tv.dvr.data.ScheduledRecording},
- * {@link com.android.tv.dvr.data.RecordedProgram}, and
+ * {@link DvrBrowseFragment}. DVR items might include: {@link
+ * com.android.tv.dvr.data.ScheduledRecording}, {@link com.android.tv.dvr.data.RecordedProgram}, and
* {@link com.android.tv.dvr.data.SeriesRecording}.
*/
public abstract class DvrItemPresenter<T> extends Presenter {
@@ -51,9 +48,9 @@ public abstract class DvrItemPresenter<T> extends Presenter {
return (RecordingCardView) view;
}
- protected void onBound(T item) { }
+ protected void onBound(T item) {}
- protected void onUnbound() { }
+ protected void onUnbound() {}
}
DvrItemPresenter(Context context) {
@@ -94,9 +91,7 @@ public abstract class DvrItemPresenter<T> extends Presenter {
viewHolder.view.setOnClickListener(null);
}
- /**
- * Unbinds all bound view holders.
- */
+ /** Unbinds all bound view holders. */
public void unbindAllViewHolders() {
// When browse fragments are destroyed, RecyclerView would not call presenters'
// onUnbindViewHolder(). We should handle it by ourselves to prevent resources leaks.
@@ -105,36 +100,28 @@ public abstract class DvrItemPresenter<T> extends Presenter {
}
}
- /**
- * This method will be called when a {@link DvrItemViewHolder} is needed to be created.
- */
- abstract protected DvrItemViewHolder onCreateDvrItemViewHolder();
+ /** This method will be called when a {@link DvrItemViewHolder} is needed to be created. */
+ protected abstract DvrItemViewHolder onCreateDvrItemViewHolder();
- /**
- * This method will be called when a {@link DvrItemViewHolder} is bound to a DVR item.
- */
- abstract protected void onBindDvrItemViewHolder(DvrItemViewHolder viewHolder, T item);
+ /** This method will be called when a {@link DvrItemViewHolder} is bound to a DVR item. */
+ protected abstract void onBindDvrItemViewHolder(DvrItemViewHolder viewHolder, T item);
- /**
- * Returns context.
- */
+ /** Returns context. */
protected Context getContext() {
return mContext;
}
- /**
- * Creates {@link OnClickListener} for DVR library's card views.
- */
+ /** Creates {@link OnClickListener} for DVR library's card views. */
protected OnClickListener onCreateOnClickListener() {
return new OnClickListener() {
@Override
public void onClick(View view) {
if (view instanceof RecordingCardView) {
RecordingCardView v = (RecordingCardView) view;
- DvrUiHelper.startDetailsActivity((Activity) v.getContext(),
- v.getTag(), v.getImageView(), false);
+ DvrUiHelper.startDetailsActivity(
+ (Activity) v.getContext(), v.getTag(), v.getImageView(), false);
}
}
};
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
index 37a72eaf..a2d1cb28 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
@@ -19,7 +19,6 @@ package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.view.ViewGroup;
-
import com.android.tv.R;
/** A list row presenter to display expand/fold card views list. */
diff --git a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
index 311137a9..6def818f 100644
--- a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
+++ b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
@@ -16,14 +16,10 @@
package com.android.tv.dvr.ui.browse;
-/**
- * Special object for schedule preview;
- */
+/** Special object for schedule preview; */
final class FullScheduleCardHolder {
- /**
- * Full schedule card holder.
- */
+ /** Full schedule card holder. */
static final FullScheduleCardHolder FULL_SCHEDULE_CARD_HOLDER = new FullScheduleCardHolder();
- private FullScheduleCardHolder() { }
+ private FullScheduleCardHolder() {}
}
diff --git a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
index 94c67eec..af0f24c0 100644
--- a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
@@ -19,20 +19,15 @@ package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
-import android.view.ViewGroup;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
-
import java.util.Collections;
import java.util.List;
-/**
- * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
- */
+/** Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. */
class FullSchedulesCardPresenter extends DvrItemPresenter<Object> {
private final Drawable mIconDrawable;
private final String mCardTitleText;
@@ -54,16 +49,26 @@ class FullSchedulesCardPresenter extends DvrItemPresenter<Object> {
cardView.setTitle(mCardTitleText);
cardView.setImage(mIconDrawable);
- List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(mContext)
- .getDvrDataManager().getAvailableScheduledRecordings();
+ List<ScheduledRecording> scheduledRecordings =
+ TvSingletons.getSingletons(mContext)
+ .getDvrDataManager()
+ .getAvailableScheduledRecordings();
int fullDays = 0;
if (!scheduledRecordings.isEmpty()) {
- fullDays = Utils.computeDateDifference(System.currentTimeMillis(),
- Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR)
- .getStartTimeMs()) + 1;
+ fullDays =
+ Utils.computeDateDifference(
+ System.currentTimeMillis(),
+ Collections.max(
+ scheduledRecordings,
+ ScheduledRecording.START_TIME_COMPARATOR)
+ .getStartTimeMs())
+ + 1;
}
- cardView.setContent(mContext.getResources().getQuantityString(
- R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null);
+ cardView.setContent(
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays),
+ null);
}
@Override
@@ -81,4 +86,4 @@ class FullSchedulesCardPresenter extends DvrItemPresenter<Object> {
}
};
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
index eb9cb26c..47b1a198 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
@@ -22,17 +22,14 @@ import android.os.Bundle;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.data.RecordedProgram;
-/**
- * {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR.
- */
+/** {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR. */
public class RecordedProgramDetailsFragment extends DvrDetailsFragment
implements DvrDataManager.RecordedProgramListener {
private static final int ACTION_RESUME_PLAYING = 1;
@@ -47,17 +44,17 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
@Override
public void onCreate(Bundle savedInstanceState) {
- mDvrDataManager = TvApplication.getSingletons(getContext()).getDvrDataManager();
+ mDvrDataManager = TvSingletons.getSingletons(getContext()).getDvrDataManager();
mDvrDataManager.addRecordedProgramListener(this);
super.onCreate(savedInstanceState);
}
@Override
public void onCreateInternal() {
- mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity())
- .getDvrWatchedPositionManager();
- setDetailsOverviewRow(DetailsContent
- .createFromRecordedProgram(getContext(), mRecordedProgram));
+ mDvrWatchedPositionManager =
+ TvSingletons.getSingletons(getActivity()).getDvrWatchedPositionManager();
+ setDetailsOverviewRow(
+ DetailsContent.createFromRecordedProgram(getContext(), mRecordedProgram));
}
@Override
@@ -95,20 +92,36 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
Resources res = getResources();
if (mDvrWatchedPositionManager.getWatchedStatus(mRecordedProgram)
== DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) {
- adapter.set(ACTION_RESUME_PLAYING, new Action(ACTION_RESUME_PLAYING,
- res.getString(R.string.dvr_detail_resume_play), null,
- res.getDrawable(R.drawable.lb_ic_play)));
- adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING,
- res.getString(R.string.dvr_detail_play_from_beginning), null,
- res.getDrawable(R.drawable.lb_ic_replay)));
+ adapter.set(
+ ACTION_RESUME_PLAYING,
+ new Action(
+ ACTION_RESUME_PLAYING,
+ res.getString(R.string.dvr_detail_resume_play),
+ null,
+ res.getDrawable(R.drawable.lb_ic_play)));
+ adapter.set(
+ ACTION_PLAY_FROM_BEGINNING,
+ new Action(
+ ACTION_PLAY_FROM_BEGINNING,
+ res.getString(R.string.dvr_detail_play_from_beginning),
+ null,
+ res.getDrawable(R.drawable.lb_ic_replay)));
} else {
- adapter.set(ACTION_PLAY_FROM_BEGINNING, new Action(ACTION_PLAY_FROM_BEGINNING,
- res.getString(R.string.dvr_detail_watch), null,
- res.getDrawable(R.drawable.lb_ic_play)));
+ adapter.set(
+ ACTION_PLAY_FROM_BEGINNING,
+ new Action(
+ ACTION_PLAY_FROM_BEGINNING,
+ res.getString(R.string.dvr_detail_watch),
+ null,
+ res.getDrawable(R.drawable.lb_ic_play)));
}
- adapter.set(ACTION_DELETE_RECORDING, new Action(ACTION_DELETE_RECORDING,
- res.getString(R.string.dvr_detail_delete), null,
- res.getDrawable(R.drawable.ic_delete_32dp)));
+ adapter.set(
+ ACTION_DELETE_RECORDING,
+ new Action(
+ ACTION_DELETE_RECORDING,
+ res.getString(R.string.dvr_detail_delete),
+ null,
+ res.getDrawable(R.drawable.ic_delete_32dp)));
return adapter;
}
@@ -120,11 +133,13 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
if (action.getId() == ACTION_PLAY_FROM_BEGINNING) {
startPlayback(mRecordedProgram, TvInputManager.TIME_SHIFT_INVALID_TIME);
} else if (action.getId() == ACTION_RESUME_PLAYING) {
- startPlayback(mRecordedProgram, mDvrWatchedPositionManager
- .getWatchedPosition(mRecordedProgram.getId()));
+ startPlayback(
+ mRecordedProgram,
+ mDvrWatchedPositionManager.getWatchedPosition(
+ mRecordedProgram.getId()));
} else if (action.getId() == ACTION_DELETE_RECORDING) {
- DvrManager dvrManager = TvApplication
- .getSingletons(getActivity()).getDvrManager();
+ DvrManager dvrManager =
+ TvSingletons.getSingletons(getActivity()).getDvrManager();
dvrManager.removeRecordedProgram(mRecordedProgram);
getActivity().finish();
}
@@ -133,10 +148,10 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
}
@Override
- public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { }
+ public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {}
@Override
- public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { }
+ public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {}
@Override
public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
index 5fe162b6..e2db3ac4 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
@@ -18,17 +18,14 @@ package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.media.tv.TvInputManager;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.util.Utils;
-/**
- * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}.
- */
+/** Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}. */
public class RecordedProgramPresenter extends DvrItemPresenter<RecordedProgram> {
private final DvrWatchedPositionManager mDvrWatchedPositionManager;
private String mTodayString;
@@ -53,10 +50,16 @@ public class RecordedProgramPresenter extends DvrItemPresenter<RecordedProgram>
}
private void setProgressBar(long watchedPositionMs) {
- ((RecordingCardView) view).setProgressBar(
- (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) ? null
- : Math.min(100, (int) (100.0f * watchedPositionMs
- / mProgram.getDurationMillis())));
+ ((RecordingCardView) view)
+ .setProgressBar(
+ (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME)
+ ? null
+ : Math.min(
+ 100,
+ (int)
+ (100.0f
+ * watchedPositionMs
+ / mProgram.getDurationMillis())));
}
@Override
@@ -86,15 +89,15 @@ public class RecordedProgramPresenter extends DvrItemPresenter<RecordedProgram>
}
}
- RecordedProgramPresenter(Context context, boolean showEpisodeTitle,
- boolean expandTitleWhenFocused) {
+ RecordedProgramPresenter(
+ Context context, boolean showEpisodeTitle, boolean expandTitleWhenFocused) {
super(context);
mTodayString = mContext.getString(R.string.dvr_date_today);
mYesterdayString = mContext.getString(R.string.dvr_date_yesterday);
mDvrWatchedPositionManager =
- TvApplication.getSingletons(mContext).getDvrWatchedPositionManager();
- mProgressBarColor = mContext.getResources()
- .getColor(R.color.play_controls_progress_bar_watched);
+ TvSingletons.getSingletons(mContext).getDvrWatchedPositionManager();
+ mProgressBarColor =
+ mContext.getResources().getColor(R.color.play_controls_progress_bar_watched);
mShowEpisodeTitle = showEpisodeTitle;
mExpandTitleWhenFocused = expandTitleWhenFocused;
}
@@ -114,29 +117,37 @@ public class RecordedProgramPresenter extends DvrItemPresenter<RecordedProgram>
final RecordedProgramViewHolder viewHolder = (RecordedProgramViewHolder) baseHolder;
final RecordingCardView cardView = viewHolder.getView();
DetailsContent details = DetailsContent.createFromRecordedProgram(mContext, program);
- cardView.setTitle(mShowEpisodeTitle ?
- program.getEpisodeDisplayTitle(mContext) : details.getTitle());
+ cardView.setTitle(
+ mShowEpisodeTitle ? program.getEpisodeDisplayTitle(mContext) : details.getTitle());
cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo());
cardView.setContent(generateMajorContent(program), generateMinorContent(program));
cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri());
}
private String generateMajorContent(RecordedProgram program) {
- int dateDifference = Utils.computeDateDifference(program.getStartTimeUtcMillis(),
- System.currentTimeMillis());
+ int dateDifference =
+ Utils.computeDateDifference(
+ program.getStartTimeUtcMillis(), System.currentTimeMillis());
if (dateDifference == 0) {
return mTodayString;
} else if (dateDifference == 1) {
return mYesterdayString;
} else {
- return Utils.getDurationString(mContext, program.getStartTimeUtcMillis(),
- program.getStartTimeUtcMillis(), false, true, false, 0);
+ return Utils.getDurationString(
+ mContext,
+ program.getStartTimeUtcMillis(),
+ program.getStartTimeUtcMillis(),
+ false,
+ true,
+ false,
+ 0);
}
}
private String generateMinorContent(RecordedProgram program) {
int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis()));
- return mContext.getResources().getQuantityString(
- R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
+ return mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
}
}
diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
index 767addc8..fe3c52d9 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
@@ -31,20 +31,19 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.ui.ViewUtils;
-import com.android.tv.util.ImageLoader;
+import com.android.tv.util.images.ImageLoader;
/**
- * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording}
- * or {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}.
+ * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} or
+ * {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}.
*/
public class RecordingCardView extends BaseCardView {
// This value should be the same with
// android.support.v17.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS
- private final static int ANIMATION_DURATION = 150;
+ private static final int ANIMATION_DURATION = 150;
private final ImageView mImageView;
private final int mImageWidth;
private final int mImageHeight;
@@ -70,16 +69,19 @@ public class RecordingCardView extends BaseCardView {
}
public RecordingCardView(Context context, boolean expandTitleWhenFocused) {
- this(context, context.getResources().getDimensionPixelSize(
- R.dimen.dvr_library_card_image_layout_width), context.getResources()
- .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height),
+ this(
+ context,
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_width),
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height),
expandTitleWhenFocused);
}
- public RecordingCardView(Context context, int imageWidth, int imageHeight,
- boolean expandTitleWhenFocused) {
+ public RecordingCardView(
+ Context context, int imageWidth, int imageHeight, boolean expandTitleWhenFocused) {
super(context);
- //TODO(dvr): move these to the layout XML.
+ // TODO(dvr): move these to the layout XML.
setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA);
setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS);
setFocusable(true);
@@ -99,21 +101,27 @@ public class RecordingCardView extends BaseCardView {
mTitleArea = (FrameLayout) findViewById(R.id.title_area);
mFoldedTitleView = (TextView) findViewById(R.id.title_one_line);
mExpandedTitleView = (TextView) findViewById(R.id.title_two_lines);
- mFoldedTitleHeight = getResources()
- .getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height);
- mExpandedTitleHeight = getResources()
- .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height);
+ mFoldedTitleHeight =
+ getResources().getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height);
+ mExpandedTitleHeight =
+ getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height);
mExpandTitleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f).setDuration(ANIMATION_DURATION);
- mExpandTitleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- float value = (Float) valueAnimator.getAnimatedValue();
- mExpandedTitleView.setAlpha(value);
- mFoldedTitleView.setAlpha(1.0f - value);
- ViewUtils.setLayoutHeight(mTitleArea, (int) (mFoldedTitleHeight
- + (mExpandedTitleHeight - mFoldedTitleHeight) * value));
- }
- });
+ mExpandTitleAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float value = (Float) valueAnimator.getAnimatedValue();
+ mExpandedTitleView.setAlpha(value);
+ mFoldedTitleView.setAlpha(1.0f - value);
+ ViewUtils.setLayoutHeight(
+ mTitleArea,
+ (int)
+ (mFoldedTitleHeight
+ + (mExpandedTitleHeight - mFoldedTitleHeight)
+ * value));
+ }
+ });
mExpandTitleWhenFocused = expandTitleWhenFocused;
}
@@ -124,8 +132,12 @@ public class RecordingCardView extends BaseCardView {
// loading and drawing background images during activity transitions.
if (gainFocus) {
if (!TextUtils.isEmpty(mDetailBackgroundImageUri)) {
- ImageLoader.loadBitmap(getContext(), mDetailBackgroundImageUri,
- Integer.MAX_VALUE, Integer.MAX_VALUE, null);
+ ImageLoader.loadBitmap(
+ getContext(),
+ mDetailBackgroundImageUri,
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE,
+ null);
}
}
if (mExpandTitleWhenFocused) {
@@ -186,9 +198,7 @@ public class RecordingCardView extends BaseCardView {
}
}
- /**
- * Sets progress bar. If progress is {@code null}, hides progress bar.
- */
+ /** Sets progress bar. If progress is {@code null}, hides progress bar. */
void setProgressBar(Integer progress) {
if (progress == null) {
mProgressBar.setVisibility(View.GONE);
@@ -198,16 +208,14 @@ public class RecordingCardView extends BaseCardView {
}
}
- /**
- * Sets the color of progress bar.
- */
+ /** Sets the color of progress bar. */
void setProgressBarColor(int color) {
mProgressBar.getProgressDrawable().setTint(color);
}
/**
* Sets the image URI of the poster should be shown on the card view.
-
+ *
* @param isChannelLogo {@code true} if the image is from channels' logo.
*/
void setImageUri(String uri, boolean isChannelLogo) {
@@ -220,14 +228,16 @@ public class RecordingCardView extends BaseCardView {
if (TextUtils.isEmpty(uri)) {
mImageView.setImageDrawable(mDefaultImage);
} else {
- ImageLoader.loadBitmap(getContext(), uri, mImageWidth, mImageHeight,
+ ImageLoader.loadBitmap(
+ getContext(),
+ uri,
+ mImageWidth,
+ mImageHeight,
new RecordingCardImageLoaderCallback(this, uri));
}
}
- /**
- * Sets the {@link Drawable} of the poster should be shown on the card view.
- */
+ /** Sets the {@link Drawable} of the poster should be shown on the card view. */
public void setImage(Drawable image) {
if (image != null) {
mImageView.setImageDrawable(image);
@@ -255,9 +265,7 @@ public class RecordingCardView extends BaseCardView {
mDetailBackgroundImageUri = uri;
}
- /**
- * Returns image view.
- */
+ /** Returns image view. */
public ImageView getImageView() {
return mImageView;
}
@@ -287,4 +295,4 @@ public class RecordingCardView extends BaseCardView {
setContent(null, null);
mImageView.setImageDrawable(mDefaultImage);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
index 56ec357f..aa2ccf75 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
@@ -18,34 +18,35 @@ package com.android.tv.dvr.ui.browse;
import android.os.Bundle;
import android.support.v17.leanback.app.DetailsFragment;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.data.ScheduledRecording;
-/**
- * {@link DetailsFragment} for recordings in DVR.
- */
+/** {@link DetailsFragment} for recordings in DVR. */
abstract class RecordingDetailsFragment extends DvrDetailsFragment {
private ScheduledRecording mRecording;
@Override
protected void onCreateInternal() {
- setDetailsOverviewRow(DetailsContent
- .createFromScheduledRecording(getContext(), mRecording));
+ setDetailsOverviewRow(
+ DetailsContent.createFromScheduledRecording(getContext(), mRecording));
}
@Override
protected boolean onLoadRecordingDetails(Bundle args) {
long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID);
- mRecording = TvApplication.getSingletons(getContext()).getDvrDataManager()
- .getScheduledRecording(scheduledRecordingId);
+ mRecording =
+ TvSingletons.getSingletons(getContext())
+ .getDvrDataManager()
+ .getScheduledRecording(scheduledRecordingId);
return mRecording != null;
}
- /**
- * Returns {@link ScheduledRecording} for the current fragment.
- */
+ protected ScheduledRecording getScheduledRecording() {
+ return mRecording;
+ }
+
+ /** Returns {@link ScheduledRecording} for the current fragment. */
public ScheduledRecording getRecording() {
return mRecording;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
index 958f8bf8..302b8318 100644
--- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
@@ -21,16 +21,12 @@ import android.os.Bundle;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.text.TextUtils;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.ui.DvrUiHelper;
-/**
- * {@link RecordingDetailsFragment} for scheduled recording in DVR.
- */
+/** {@link RecordingDetailsFragment} for scheduled recording in DVR. */
public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment {
private static final int ACTION_VIEW_SCHEDULE = 1;
private static final int ACTION_CANCEL = 2;
@@ -38,11 +34,14 @@ public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment
private DvrManager mDvrManager;
private Action mScheduleAction;
private boolean mHideViewSchedule;
+ private String mFailedMessage;
@Override
public void onCreate(Bundle savedInstance) {
- mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
- mHideViewSchedule = getArguments().getBoolean(DvrDetailsActivity.HIDE_VIEW_SCHEDULE);
+ Bundle args = getArguments();
+ mDvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
+ mHideViewSchedule = args.getBoolean(DvrDetailsActivity.HIDE_VIEW_SCHEDULE);
+ mFailedMessage = args.getString(DvrDetailsActivity.EXTRA_FAILED_MESSAGE);
super.onCreate(savedInstance);
}
@@ -55,19 +54,37 @@ public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment
}
@Override
+ protected void onCreateInternal() {
+ if (mFailedMessage == null) {
+ super.onCreateInternal();
+ return;
+ }
+ setDetailsOverviewRow(
+ DetailsContent.createFromFailedScheduledRecording(
+ getContext(), getScheduledRecording(), mFailedMessage));
+ }
+
+ @Override
protected SparseArrayObjectAdapter onCreateActionsAdapter() {
SparseArrayObjectAdapter adapter =
new SparseArrayObjectAdapter(new ActionPresenterSelector());
Resources res = getResources();
if (!mHideViewSchedule) {
- mScheduleAction = new Action(ACTION_VIEW_SCHEDULE,
- res.getString(R.string.dvr_detail_view_schedule), null,
- res.getDrawable(getScheduleIconId()));
+ mScheduleAction =
+ new Action(
+ ACTION_VIEW_SCHEDULE,
+ res.getString(R.string.dvr_detail_view_schedule),
+ null,
+ res.getDrawable(getScheduleIconId()));
adapter.set(ACTION_VIEW_SCHEDULE, mScheduleAction);
}
- adapter.set(ACTION_CANCEL, new Action(ACTION_CANCEL,
- res.getString(R.string.dvr_detail_cancel_recording), null,
- res.getDrawable(R.drawable.ic_dvr_cancel_32dp)));
+ adapter.set(
+ ACTION_CANCEL,
+ new Action(
+ ACTION_CANCEL,
+ res.getString(R.string.dvr_detail_cancel_recording),
+ null,
+ res.getDrawable(R.drawable.ic_dvr_cancel_32dp)));
return adapter;
}
diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
index 273d3d19..8e028689 100644
--- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
@@ -18,18 +18,14 @@ package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.os.Handler;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
-
import java.util.concurrent.TimeUnit;
-/**
- * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
- */
+/** Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}. */
class ScheduledRecordingPresenter extends DvrItemPresenter<ScheduledRecording> {
private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
@@ -39,13 +35,14 @@ class ScheduledRecordingPresenter extends DvrItemPresenter<ScheduledRecording> {
private final class ScheduledRecordingViewHolder extends DvrItemViewHolder {
private final Handler mHandler = new Handler();
private ScheduledRecording mScheduledRecording;
- private final Runnable mProgressBarUpdater = new Runnable() {
- @Override
- public void run() {
- updateProgressBar();
- mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS);
- }
- };
+ private final Runnable mProgressBarUpdater =
+ new Runnable() {
+ @Override
+ public void run() {
+ updateProgressBar();
+ mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS);
+ }
+ };
ScheduledRecordingViewHolder(RecordingCardView view, int progressBarColor) {
super(view);
@@ -73,9 +70,17 @@ class ScheduledRecordingPresenter extends DvrItemPresenter<ScheduledRecording> {
int recordingState = mScheduledRecording.getState();
RecordingCardView cardView = (RecordingCardView) view;
if (recordingState == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
- cardView.setProgressBar(Math.max(0, Math.min((int) (100 *
- (System.currentTimeMillis() - mScheduledRecording.getStartTimeMs())
- / mScheduledRecording.getDuration()), 100)));
+ cardView.setProgressBar(
+ Math.max(
+ 0,
+ Math.min(
+ (int)
+ (100
+ * (System.currentTimeMillis()
+ - mScheduledRecording
+ .getStartTimeMs())
+ / mScheduledRecording.getDuration()),
+ 100)));
} else if (recordingState == ScheduledRecording.STATE_RECORDING_FINISHED) {
cardView.setProgressBar(100);
} else {
@@ -95,9 +100,10 @@ class ScheduledRecordingPresenter extends DvrItemPresenter<ScheduledRecording> {
public ScheduledRecordingPresenter(Context context) {
super(context);
- mDvrManager = TvApplication.getSingletons(mContext).getDvrManager();
- mProgressBarColor = mContext.getResources()
- .getColor(R.color.play_controls_recording_icon_color_on_focus);
+ mDvrManager = TvSingletons.getSingletons(mContext).getDvrManager();
+ mProgressBarColor =
+ mContext.getResources()
+ .getColor(R.color.play_controls_recording_icon_color_on_focus);
}
@Override
@@ -106,33 +112,61 @@ class ScheduledRecordingPresenter extends DvrItemPresenter<ScheduledRecording> {
}
@Override
- public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder,
- ScheduledRecording recording) {
+ public void onBindDvrItemViewHolder(
+ DvrItemViewHolder baseHolder, ScheduledRecording recording) {
final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
final RecordingCardView cardView = viewHolder.getView();
DetailsContent details = DetailsContent.createFromScheduledRecording(mContext, recording);
cardView.setTitle(details.getTitle());
cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo());
- cardView.setAffiliatedIcon(mDvrManager.isConflicting(recording) ?
- R.drawable.ic_warning_white_32dp : 0);
+ if (mDvrManager.isConflicting(recording)) {
+ cardView.setAffiliatedIcon(R.drawable.ic_warning_white_32dp);
+ } else if (recording.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
+ cardView.setAffiliatedIcon(R.drawable.ic_error_white_48dp);
+ } else {
+ cardView.setAffiliatedIcon(0);
+ }
cardView.setContent(generateMajorContent(recording), null);
cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri());
}
private String generateMajorContent(ScheduledRecording recording) {
- int dateDifference = Utils.computeDateDifference(System.currentTimeMillis(),
- recording.getStartTimeMs());
+ if (recording.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
+ return mContext.getString(R.string.dvr_recording_failed);
+ }
+ int dateDifference =
+ Utils.computeDateDifference(System.currentTimeMillis(), recording.getStartTimeMs());
if (dateDifference <= 0) {
- return mContext.getString(R.string.dvr_date_today_time,
- Utils.getDurationString(mContext, recording.getStartTimeMs(),
- recording.getEndTimeMs(), false, false, true, 0));
+ return mContext.getString(
+ R.string.dvr_date_today_time,
+ Utils.getDurationString(
+ mContext,
+ recording.getStartTimeMs(),
+ recording.getEndTimeMs(),
+ false,
+ false,
+ true,
+ 0));
} else if (dateDifference == 1) {
- return mContext.getString(R.string.dvr_date_tomorrow_time,
- Utils.getDurationString(mContext, recording.getStartTimeMs(),
- recording.getEndTimeMs(), false, false, true, 0));
+ return mContext.getString(
+ R.string.dvr_date_tomorrow_time,
+ Utils.getDurationString(
+ mContext,
+ recording.getStartTimeMs(),
+ recording.getEndTimeMs(),
+ false,
+ false,
+ true,
+ 0));
} else {
- return Utils.getDurationString(mContext, recording.getStartTimeMs(),
- recording.getStartTimeMs(), false, true, false, 0);
+ return Utils.getDurationString(
+ mContext,
+ recording.getStartTimeMs(),
+ recording.getStartTimeMs(),
+ false,
+ true,
+ false,
+ 0);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
index c2aa8e98..2cd191a7 100644
--- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
@@ -32,9 +32,8 @@ import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.text.TextUtils;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.BaseProgram;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
@@ -42,16 +41,13 @@ import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.dvr.ui.SortedArrayAdapter;
-
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-/**
- * {@link DetailsFragment} for series recording in DVR.
- */
-public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implements
- DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener {
+/** {@link DetailsFragment} for series recording in DVR. */
+public class SeriesRecordingDetailsFragment extends DvrDetailsFragment
+ implements DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener {
private static final int ACTION_WATCH = 1;
private static final int ACTION_SERIES_SCHEDULES = 2;
private static final int ACTION_DELETE = 3;
@@ -77,7 +73,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
@Override
public void onCreate(Bundle savedInstanceState) {
- mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
+ mDvrDataManager = TvSingletons.getSingletons(getActivity()).getDvrDataManager();
mWatchLabel = getString(R.string.dvr_detail_watch);
mResumeLabel = getString(R.string.dvr_detail_series_resume);
mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null);
@@ -87,8 +83,8 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
@Override
protected void onCreateInternal() {
- mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity())
- .getDvrWatchedPositionManager();
+ mDvrWatchedPositionManager =
+ TvSingletons.getSingletons(getActivity()).getDvrWatchedPositionManager();
setDetailsOverviewRow(DetailsContent.createFromSeriesRecording(getContext(), mSeries));
setupRecordedProgramsRow();
mDvrDataManager.addSeriesRecordingListener(this);
@@ -119,27 +115,31 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mActionsAdapter.clear(ACTION_WATCH);
} else {
String episodeStatus;
- if(mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram)
+ if (mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram)
== DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) {
episodeStatus = mResumeLabel;
- mInitialPlaybackPositionMs = mDvrWatchedPositionManager
- .getWatchedPosition(mRecommendRecordedProgram.getId());
+ mInitialPlaybackPositionMs =
+ mDvrWatchedPositionManager.getWatchedPosition(
+ mRecommendRecordedProgram.getId());
} else {
episodeStatus = mWatchLabel;
mInitialPlaybackPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
}
- String episodeDisplayNumber = mRecommendRecordedProgram.getEpisodeDisplayNumber(
- getContext());
- mActionsAdapter.set(ACTION_WATCH, new Action(ACTION_WATCH,
- episodeStatus, episodeDisplayNumber, mWatchDrawable));
+ String episodeDisplayNumber =
+ mRecommendRecordedProgram.getEpisodeDisplayNumber(getContext());
+ mActionsAdapter.set(
+ ACTION_WATCH,
+ new Action(ACTION_WATCH, episodeStatus, episodeDisplayNumber, mWatchDrawable));
}
}
@Override
protected boolean onLoadRecordingDetails(Bundle args) {
long recordId = args.getLong(DvrDetailsActivity.RECORDING_ID);
- mSeries = TvApplication.getSingletons(getActivity()).getDvrDataManager()
- .getSeriesRecording(recordId);
+ mSeries =
+ TvSingletons.getSingletons(getActivity())
+ .getDvrDataManager()
+ .getSeriesRecording(recordId);
if (mSeries == null) {
return false;
}
@@ -162,12 +162,19 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mActionsAdapter = new SparseArrayObjectAdapter(new ActionPresenterSelector());
Resources res = getResources();
updateWatchAction();
- mActionsAdapter.set(ACTION_SERIES_SCHEDULES, new Action(ACTION_SERIES_SCHEDULES,
- getString(R.string.dvr_detail_view_schedule), null,
- res.getDrawable(R.drawable.ic_schedule_32dp, null)));
- mDeleteAction = new Action(ACTION_DELETE,
- getString(R.string.dvr_detail_series_delete), null,
- res.getDrawable(R.drawable.ic_delete_32dp, null));
+ mActionsAdapter.set(
+ ACTION_SERIES_SCHEDULES,
+ new Action(
+ ACTION_SERIES_SCHEDULES,
+ getString(R.string.dvr_detail_view_schedule),
+ null,
+ res.getDrawable(R.drawable.ic_schedule_32dp, null)));
+ mDeleteAction =
+ new Action(
+ ACTION_DELETE,
+ getString(R.string.dvr_detail_series_delete),
+ null,
+ res.getDrawable(R.drawable.ic_delete_32dp, null));
if (!mRecordedPrograms.isEmpty()) {
mActionsAdapter.set(ACTION_DELETE, mDeleteAction);
}
@@ -207,11 +214,9 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
};
}
- /**
- * The programs are sorted by season number and episode number.
- */
+ /** The programs are sorted by season number and episode number. */
private RecordedProgram getRecommendProgram(List<RecordedProgram> programs) {
- for (int i = programs.size() - 1 ; i >= 0 ; i--) {
+ for (int i = programs.size() - 1; i >= 0; i--) {
RecordedProgram program = programs.get(i);
int watchedStatus = mDvrWatchedPositionManager.getWatchedStatus(program);
if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_NEW) {
@@ -230,7 +235,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
}
@Override
- public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { }
+ public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {}
@Override
public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
@@ -308,8 +313,10 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
for (int i = rowsAdaptor.size() - 1; i >= 0; i--) {
Object row = rowsAdaptor.get(i);
if (row instanceof ListRow) {
- int compareResult = BaseProgram.numberCompare(seasonNumber,
- ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber);
+ int compareResult =
+ BaseProgram.numberCompare(
+ seasonNumber,
+ ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber);
if (compareResult == 0) {
return (ListRow) row;
} else if (compareResult < 0) {
@@ -321,18 +328,25 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
}
private ListRow createNewSeasonRow(String seasonNumber, int position) {
- String seasonTitle = seasonNumber.isEmpty() ? mSeries.getTitle()
- : getString(R.string.dvr_detail_series_season_title, seasonNumber);
+ String seasonTitle =
+ seasonNumber.isEmpty()
+ ? mSeries.getTitle()
+ : getString(R.string.dvr_detail_series_season_title, seasonNumber);
HeaderItem header = new HeaderItem(mSeasonRowCount++, seasonTitle);
ClassPresenterSelector selector = new ClassPresenterSelector();
selector.addClassPresenter(RecordedProgram.class, mRecordedProgramPresenter);
- ListRow row = new ListRow(header, new SeasonRowAdapter(selector,
- new Comparator<RecordedProgram>() {
- @Override
- public int compare(RecordedProgram lhs, RecordedProgram rhs) {
- return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs);
- }
- }, seasonNumber));
+ ListRow row =
+ new ListRow(
+ header,
+ new SeasonRowAdapter(
+ selector,
+ new Comparator<RecordedProgram>() {
+ @Override
+ public int compare(RecordedProgram lhs, RecordedProgram rhs) {
+ return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs);
+ }
+ },
+ seasonNumber));
getRowsAdapter().add(position, row);
return row;
}
@@ -340,7 +354,9 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
private class SeasonRowAdapter extends SortedArrayAdapter<RecordedProgram> {
private String mSeasonNumber;
- SeasonRowAdapter(PresenterSelector selector, Comparator<RecordedProgram> comparator,
+ SeasonRowAdapter(
+ PresenterSelector selector,
+ Comparator<RecordedProgram> comparator,
String seasonNumber) {
super(selector, comparator);
mSeasonNumber = seasonNumber;
@@ -351,4 +367,4 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
return program.getId();
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
index e508259d..14f9dceb 100644
--- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
@@ -19,10 +19,8 @@ package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.media.tv.TvInputManager;
import android.text.TextUtils;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
@@ -32,27 +30,29 @@ import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListen
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
-
import java.util.List;
-/**
- * Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}.
- */
+/** Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}. */
class SeriesRecordingPresenter extends DvrItemPresenter<SeriesRecording> {
private final DvrDataManager mDvrDataManager;
private final DvrManager mDvrManager;
private final DvrWatchedPositionManager mWatchedPositionManager;
- private final class SeriesRecordingViewHolder extends DvrItemViewHolder implements
- WatchedPositionChangedListener, ScheduledRecordingListener, RecordedProgramListener {
+ private final class SeriesRecordingViewHolder extends DvrItemViewHolder
+ implements WatchedPositionChangedListener,
+ ScheduledRecordingListener,
+ RecordedProgramListener {
private SeriesRecording mSeriesRecording;
private RecordingCardView mCardView;
private DvrDataManager mDvrDataManager;
private DvrManager mDvrManager;
private DvrWatchedPositionManager mWatchedPositionManager;
- SeriesRecordingViewHolder(RecordingCardView view, DvrDataManager dvrDataManager,
- DvrManager dvrManager, DvrWatchedPositionManager watchedPositionManager) {
+ SeriesRecordingViewHolder(
+ RecordingCardView view,
+ DvrDataManager dvrDataManager,
+ DvrManager dvrManager,
+ DvrWatchedPositionManager watchedPositionManager) {
super(view);
mCardView = view;
mDvrDataManager = dvrDataManager;
@@ -92,8 +92,8 @@ class SeriesRecordingPresenter extends DvrItemPresenter<SeriesRecording> {
public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
boolean needToUpdateCardView = false;
for (RecordedProgram recordedProgram : recordedPrograms) {
- if (TextUtils.equals(recordedProgram.getSeriesId(),
- mSeriesRecording.getSeriesId())) {
+ if (TextUtils.equals(
+ recordedProgram.getSeriesId(), mSeriesRecording.getSeriesId())) {
mDvrDataManager.removeScheduledRecordingListener(this);
mWatchedPositionManager.addListener(this, recordedProgram.getId());
needToUpdateCardView = true;
@@ -108,8 +108,8 @@ class SeriesRecordingPresenter extends DvrItemPresenter<SeriesRecording> {
public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
boolean needToUpdateCardView = false;
for (RecordedProgram recordedProgram : recordedPrograms) {
- if (TextUtils.equals(recordedProgram.getSeriesId(),
- mSeriesRecording.getSeriesId())) {
+ if (TextUtils.equals(
+ recordedProgram.getSeriesId(), mSeriesRecording.getSeriesId())) {
if (mWatchedPositionManager.getWatchedPosition(recordedProgram.getId())
== TvInputManager.TIME_SHIFT_INVALID_TIME) {
mWatchedPositionManager.removeListener(this, recordedProgram.getId());
@@ -177,14 +177,15 @@ class SeriesRecordingPresenter extends DvrItemPresenter<SeriesRecording> {
quantityStringID = R.plurals.dvr_count_new_recordings;
}
}
- mCardView.setContent(mCardView.getResources()
- .getQuantityString(quantityStringID, count, count), null);
+ mCardView.setContent(
+ mCardView.getResources().getQuantityString(quantityStringID, count, count),
+ null);
}
}
public SeriesRecordingPresenter(Context context) {
super(context);
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ TvSingletons singletons = TvSingletons.getSingletons(context);
mDvrDataManager = singletons.getDvrDataManager();
mDvrManager = singletons.getDvrManager();
mWatchedPositionManager = singletons.getDvrWatchedPositionManager();
@@ -192,8 +193,11 @@ class SeriesRecordingPresenter extends DvrItemPresenter<SeriesRecording> {
@Override
public DvrItemViewHolder onCreateDvrItemViewHolder() {
- return new SeriesRecordingViewHolder(new RecordingCardView(mContext), mDvrDataManager,
- mDvrManager, mWatchedPositionManager);
+ return new SeriesRecordingViewHolder(
+ new RecordingCardView(mContext),
+ mDvrDataManager,
+ mDvrManager,
+ mWatchedPositionManager);
}
@Override
diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
index b9407b15..77a63508 100644
--- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
@@ -1,18 +1,18 @@
/*
-* Copyright (C) 2016 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License
-*/
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
package com.android.tv.dvr.ui.list;
@@ -23,23 +23,17 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.data.ScheduledRecording;
-/**
- * A base fragment to show the list of schedule recordings.
- */
+/** A base fragment to show the list of schedule recordings. */
public abstract class BaseDvrSchedulesFragment extends DetailsFragment
implements DvrDataManager.ScheduledRecordingListener,
- DvrScheduleManager.OnConflictStateChangeListener {
- /**
- * The key for scheduled recording which has be selected in the list.
- */
+ DvrScheduleManager.OnConflictStateChangeListener {
+ /** The key for scheduled recording which has be selected in the list. */
public static final String SCHEDULES_KEY_SCHEDULED_RECORDING =
"schedules_key_scheduled_recording";
@@ -55,15 +49,15 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment
mRowsAdapter = onCreateRowsAdapter(presenterSelector);
setAdapter(mRowsAdapter);
mRowsAdapter.start();
- ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
singletons.getDvrDataManager().addScheduledRecordingListener(this);
singletons.getDvrScheduleManager().addOnConflictStateChangeListener(this);
mEmptyInfoScreenView = (TextView) getActivity().findViewById(R.id.empty_info_screen);
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
int firstItemPosition = getFirstItemPosition();
if (firstItemPosition != -1) {
@@ -72,16 +66,12 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment
return view;
}
- /**
- * Returns rows adapter.
- */
+ /** Returns rows adapter. */
protected ScheduleRowAdapter getRowsAdapter() {
return mRowsAdapter;
}
- /**
- * Shows the empty message.
- */
+ /** Shows the empty message. */
void showEmptyMessage(int messageId) {
mEmptyInfoScreenView.setText(messageId);
if (mEmptyInfoScreenView.getVisibility() != View.VISIBLE) {
@@ -89,9 +79,7 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment
}
}
- /**
- * Hides the empty message.
- */
+ /** Hides the empty message. */
void hideEmptyMessage() {
if (mEmptyInfoScreenView.getVisibility() == View.VISIBLE) {
mEmptyInfoScreenView.setVisibility(View.GONE);
@@ -99,39 +87,32 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment
}
@Override
- public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
- Bundle savedInstanceState) {
+ public View onInflateTitleView(
+ LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
// Workaround of b/31046014
return null;
}
@Override
public void onDestroy() {
- ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
singletons.getDvrScheduleManager().removeOnConflictStateChangeListener(this);
singletons.getDvrDataManager().removeScheduledRecordingListener(this);
mRowsAdapter.stop();
super.onDestroy();
}
- /**
- * Creates header row presenter.
- */
+ /** Creates header row presenter. */
public abstract SchedulesHeaderRowPresenter onCreateHeaderRowPresenter();
- /**
- * Creates rows presenter.
- */
+ /** Creates rows presenter. */
public abstract ScheduleRowPresenter onCreateRowPresenter();
- /**
- * Creates rows adapter.
- */
- public abstract ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelecor);
+ /** Creates rows adapter. */
+ public abstract ScheduleRowAdapter onCreateRowsAdapter(
+ ClassPresenterSelector presenterSelector);
- /**
- * Gets the first focus position in schedules list.
- */
+ /** Gets the first focus position in schedules list. */
protected int getFirstItemPosition() {
for (int i = 0; i < mRowsAdapter.size(); i++) {
if (mRowsAdapter.get(i) instanceof ScheduleRow) {
@@ -176,4 +157,4 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java b/src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java
new file mode 100644
index 00000000..623975e1
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.list;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.tv.R;
+import com.android.tv.Starter;
+
+/** Activity to show the recording history. */
+public class DvrHistoryActivity extends Activity {
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ Starter.start(this);
+ // Pass null to prevent automatically re-creating fragments
+ super.onCreate(null);
+ setContentView(R.layout.activity_dvr_history);
+ DvrHistoryFragment dvrHistoryFragment = new DvrHistoryFragment();
+ getFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, dvrHistoryFragment)
+ .commit();
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java b/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java
new file mode 100644
index 00000000..0ca05fac
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.list;
+
+import android.os.Bundle;
+import android.support.v17.leanback.app.DetailsFragment;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import com.android.tv.R;
+import com.android.tv.TvSingletons;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
+
+/** A fragment to show the DVR history. */
+public class DvrHistoryFragment extends DetailsFragment
+ implements DvrDataManager.ScheduledRecordingListener,
+ DvrDataManager.RecordedProgramListener {
+
+ private DvrHistoryRowAdapter mRowsAdapter;
+ private TextView mEmptyInfoScreenView;
+ private DvrDataManager mDvrDataManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
+ presenterSelector.addClassPresenter(
+ SchedulesHeaderRow.class, new DateHeaderRowPresenter(getContext()));
+ presenterSelector.addClassPresenter(
+ ScheduleRow.class, new ScheduleRowPresenter(getContext()));
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
+ mRowsAdapter = new DvrHistoryRowAdapter(
+ getContext(), presenterSelector, singletons.getClock());
+ setAdapter(mRowsAdapter);
+ mRowsAdapter.start();
+ mDvrDataManager = singletons.getDvrDataManager();
+ mDvrDataManager.addScheduledRecordingListener(this);
+ mDvrDataManager.addRecordedProgramListener(this);
+ mEmptyInfoScreenView = (TextView) getActivity().findViewById(R.id.empty_info_screen);
+ }
+
+ @Override
+ public void onDestroy() {
+ mDvrDataManager.removeScheduledRecordingListener(this);
+ mDvrDataManager.removeRecordedProgramListener(this);
+ super.onDestroy();
+ }
+
+ /** Shows the empty message. */
+ void showEmptyMessage() {
+ mEmptyInfoScreenView.setText(R.string.dvr_history_empty_state);
+ if (mEmptyInfoScreenView.getVisibility() != View.VISIBLE) {
+ mEmptyInfoScreenView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /** Hides the empty message. */
+ void hideEmptyMessage() {
+ if (mEmptyInfoScreenView.getVisibility() == View.VISIBLE) {
+ mEmptyInfoScreenView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public View onInflateTitleView(
+ LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+ // Workaround of b/31046014
+ return null;
+ }
+
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
+ if (mRowsAdapter != null) {
+ for (ScheduledRecording recording : scheduledRecordings) {
+ mRowsAdapter.onScheduledRecordingAdded(recording);
+ }
+ if (mRowsAdapter.size() > 0) {
+ hideEmptyMessage();
+ }
+ }
+ }
+
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
+ if (mRowsAdapter != null) {
+ for (ScheduledRecording recording : scheduledRecordings) {
+ mRowsAdapter.onScheduledRecordingRemoved(recording);
+ }
+ if (mRowsAdapter.size() == 0) {
+ showEmptyMessage();
+ }
+ }
+ }
+
+ @Override
+ public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
+ if (mRowsAdapter != null) {
+ for (ScheduledRecording recording : scheduledRecordings) {
+ mRowsAdapter.onScheduledRecordingUpdated(recording);
+ }
+ if (mRowsAdapter.size() == 0) {
+ showEmptyMessage();
+ } else {
+ hideEmptyMessage();
+ }
+ }
+ }
+
+ @Override
+ public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
+ if (mRowsAdapter != null) {
+ for (RecordedProgram p : recordedPrograms) {
+ mRowsAdapter.onScheduledRecordingAdded(p);
+ }
+ if (mRowsAdapter.size() > 0) {
+ hideEmptyMessage();
+ }
+ }
+
+ }
+
+ @Override
+ public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
+ if (mRowsAdapter != null) {
+ for (RecordedProgram program : recordedPrograms) {
+ mRowsAdapter.onScheduledRecordingUpdated(program);
+ }
+ if (mRowsAdapter.size() == 0) {
+ showEmptyMessage();
+ } else {
+ hideEmptyMessage();
+ }
+ }
+ }
+
+ @Override
+ public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
+ if (mRowsAdapter != null) {
+ for (RecordedProgram p : recordedPrograms) {
+ mRowsAdapter.onScheduledRecordingRemoved(p);
+ }
+ if (mRowsAdapter.size() == 0) {
+ showEmptyMessage();
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java
new file mode 100644
index 00000000..156d1a7e
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.list;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.text.format.DateUtils;
+import android.util.Log;
+import com.android.tv.R;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.util.Clock;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.recorder.ScheduledProgramReaper;
+import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
+import com.android.tv.util.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/** An adapter for DVR history. */
+@TargetApi(VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
+class DvrHistoryRowAdapter extends ArrayObjectAdapter {
+ private static final String TAG = "DvrHistoryRowAdapter";
+ private static final boolean DEBUG = false;
+
+ private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
+ private static final int MAX_HISTORY_DAYS = ScheduledProgramReaper.DAYS;
+
+ private final Context mContext;
+ private final Clock mClock;
+ private final DvrDataManager mDvrDataManager;
+ private final List<String> mTitles = new ArrayList<>();
+ private final Map<Long, ScheduledRecording> mRecordedProgramScheduleMap = new HashMap<>();
+
+ public DvrHistoryRowAdapter(
+ Context context, ClassPresenterSelector classPresenterSelector, Clock clock) {
+ super(classPresenterSelector);
+ mContext = context;
+ mClock = clock;
+ mDvrDataManager = TvSingletons.getSingletons(mContext).getDvrDataManager();
+ mTitles.add(mContext.getString(R.string.dvr_date_today));
+ mTitles.add(mContext.getString(R.string.dvr_date_yesterday));
+ }
+
+ /** Returns context. */
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /** Starts row adapter. */
+ public void start() {
+ clear();
+ List<ScheduledRecording> recordingList = mDvrDataManager.getFailedScheduledRecordings();
+ List<RecordedProgram> recordedProgramList = mDvrDataManager.getRecordedPrograms();
+
+ recordingList.addAll(
+ recordedProgramsToScheduledRecordings(recordedProgramList, MAX_HISTORY_DAYS));
+ recordingList
+ .sort(ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed());
+ long deadLine = Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis());
+ for (int i = 0; i < recordingList.size(); ) {
+ ArrayList<ScheduledRecording> section = new ArrayList<>();
+ while (i < recordingList.size() && recordingList.get(i).getStartTimeMs() >= deadLine) {
+ section.add(recordingList.get(i++));
+ }
+ if (!section.isEmpty()) {
+ SchedulesHeaderRow headerRow =
+ new DateHeaderRow(
+ calculateHeaderDate(deadLine),
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_section_subtitle,
+ section.size(),
+ section.size()),
+ section.size(),
+ deadLine);
+ add(headerRow);
+ for (ScheduledRecording recording : section) {
+ add(new ScheduleRow(recording, headerRow));
+ }
+ }
+ deadLine -= ONE_DAY_MS;
+ }
+ }
+
+ private String calculateHeaderDate(long timeMs) {
+ int titleIndex =
+ (int)
+ ((Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis()) - timeMs)
+ / ONE_DAY_MS);
+ String headerDate;
+ if (titleIndex < mTitles.size()) {
+ headerDate = mTitles.get(titleIndex);
+ } else {
+ headerDate =
+ DateUtils.formatDateTime(
+ getContext(),
+ timeMs,
+ DateUtils.FORMAT_SHOW_WEEKDAY
+ | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_ABBREV_MONTH);
+ }
+ return headerDate;
+ }
+
+ private List<ScheduledRecording> recordedProgramsToScheduledRecordings(
+ List<RecordedProgram> programs, int maxDays) {
+ List<ScheduledRecording> result = new ArrayList<>();
+ for (RecordedProgram recordedProgram : programs) {
+ ScheduledRecording scheduledRecording =
+ recordedProgramsToScheduledRecordings(recordedProgram, maxDays);
+ if (scheduledRecording != null) {
+ result.add(scheduledRecording);
+ }
+ }
+ return result;
+ }
+
+ @Nullable
+ private ScheduledRecording recordedProgramsToScheduledRecordings(
+ RecordedProgram program, int maxDays) {
+ long firstMillisecondToday = Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis());
+ if (maxDays
+ < Utils.computeDateDifference(
+ program.getStartTimeUtcMillis(),
+ firstMillisecondToday)) {
+ return null;
+ }
+ ScheduledRecording scheduledRecording = ScheduledRecording.builder(program).build();
+ mRecordedProgramScheduleMap.put(program.getId(), scheduledRecording);
+ return scheduledRecording;
+ }
+
+ public void onScheduledRecordingAdded(ScheduledRecording schedule) {
+ if (DEBUG) {
+ Log.d(TAG, "onScheduledRecordingAdded: " + schedule);
+ }
+ if (findRowByScheduledRecording(schedule) == null
+ && (schedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED
+ || schedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED
+ || schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED)) {
+ addScheduleRow(schedule);
+ }
+ }
+
+ public void onScheduledRecordingAdded(RecordedProgram program) {
+ if (DEBUG) {
+ Log.d(TAG, "onScheduledRecordingAdded: " + program);
+ }
+ if (mRecordedProgramScheduleMap.get(program.getId()) != null) {
+ return;
+ }
+ ScheduledRecording schedule =
+ recordedProgramsToScheduledRecordings(program, MAX_HISTORY_DAYS);
+ if (schedule == null) {
+ return;
+ }
+ addScheduleRow(schedule);
+ }
+
+ public void onScheduledRecordingRemoved(ScheduledRecording schedule) {
+ if (DEBUG) {
+ Log.d(TAG, "onScheduledRecordingRemoved: " + schedule);
+ }
+ ScheduleRow row = findRowByScheduledRecording(schedule);
+ if (row != null) {
+ removeScheduleRow(row);
+ notifyArrayItemRangeChanged(indexOf(row), 1);
+ }
+ }
+
+ public void onScheduledRecordingRemoved(RecordedProgram program) {
+ if (DEBUG) {
+ Log.d(TAG, "onScheduledRecordingRemoved: " + program);
+ }
+ ScheduledRecording scheduledRecording = mRecordedProgramScheduleMap.get(program.getId());
+ if (scheduledRecording != null) {
+ mRecordedProgramScheduleMap.remove(program.getId());
+ ScheduleRow row = findRowByRecordedProgram(program);
+ if (row != null) {
+ removeScheduleRow(row);
+ notifyArrayItemRangeChanged(indexOf(row), 1);
+ }
+ }
+ }
+
+ public void onScheduledRecordingUpdated(ScheduledRecording schedule) {
+ if (DEBUG) {
+ Log.d(TAG, "onScheduledRecordingUpdated: " + schedule);
+ }
+ ScheduleRow row = findRowByScheduledRecording(schedule);
+ if (row != null) {
+ row.setSchedule(schedule);
+ if (schedule.getState() != ScheduledRecording.STATE_RECORDING_FAILED) {
+ // Only handle failed schedules. Finished schedules are handled as recorded programs
+ removeScheduleRow(row);
+ }
+ notifyArrayItemRangeChanged(indexOf(row), 1);
+ }
+ }
+
+ public void onScheduledRecordingUpdated(RecordedProgram program) {
+ if (DEBUG) {
+ Log.d(TAG, "onScheduledRecordingUpdated: " + program);
+ }
+ ScheduleRow row = findRowByRecordedProgram(program);
+ if (row != null) {
+ removeScheduleRow(row);
+ notifyArrayItemRangeChanged(indexOf(row), 1);
+ ScheduledRecording schedule = mRecordedProgramScheduleMap.get(program.getId());
+ if (schedule != null) {
+ mRecordedProgramScheduleMap.remove(program.getId());
+ }
+ }
+ onScheduledRecordingAdded(program);
+ }
+
+ private void addScheduleRow(ScheduledRecording recording) {
+ // This method must not be called from inherited class.
+ SoftPreconditions.checkState(getClass().equals(DvrHistoryRowAdapter.class));
+ if (recording != null) {
+ int pre = -1;
+ int index = 0;
+ for (; index < size(); index++) {
+ if (get(index) instanceof ScheduleRow) {
+ ScheduleRow scheduleRow = (ScheduleRow) get(index);
+ if (ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed()
+ .compare(scheduleRow.getSchedule(), recording) > 0) {
+ break;
+ }
+ pre = index;
+ }
+ }
+ long deadLine = Utils.getFirstMillisecondOfDay(recording.getStartTimeMs());
+ if (pre >= 0 && getHeaderRow(pre).getDeadLineMs() == deadLine) {
+ SchedulesHeaderRow headerRow = ((ScheduleRow) get(pre)).getHeaderRow();
+ headerRow.setItemCount(headerRow.getItemCount() + 1);
+ ScheduleRow addedRow = new ScheduleRow(recording, headerRow);
+ add(++pre, addedRow);
+ updateHeaderDescription(headerRow);
+ } else if (index < size() && getHeaderRow(index).getDeadLineMs() == deadLine) {
+ SchedulesHeaderRow headerRow = ((ScheduleRow) get(index)).getHeaderRow();
+ headerRow.setItemCount(headerRow.getItemCount() + 1);
+ ScheduleRow addedRow = new ScheduleRow(recording, headerRow);
+ add(index, addedRow);
+ updateHeaderDescription(headerRow);
+ } else {
+ SchedulesHeaderRow headerRow =
+ new DateHeaderRow(
+ calculateHeaderDate(deadLine),
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_section_subtitle, 1, 1),
+ 1,
+ deadLine);
+ add(++pre, headerRow);
+ ScheduleRow addedRow = new ScheduleRow(recording, headerRow);
+ add(pre, addedRow);
+ }
+ }
+ }
+
+ private DateHeaderRow getHeaderRow(int index) {
+ return ((DateHeaderRow) ((ScheduleRow) get(index)).getHeaderRow());
+ }
+
+ /** Gets which {@link ScheduleRow} the {@link ScheduledRecording} belongs to. */
+ private ScheduleRow findRowByScheduledRecording(ScheduledRecording recording) {
+ if (recording == null) {
+ return null;
+ }
+ for (int i = 0; i < size(); i++) {
+ Object item = get(i);
+ if (item instanceof ScheduleRow && ((ScheduleRow) item).getSchedule() != null) {
+ if (((ScheduleRow) item).getSchedule().getId() == recording.getId()) {
+ return (ScheduleRow) item;
+ }
+ }
+ }
+ return null;
+ }
+
+ private ScheduleRow findRowByRecordedProgram(RecordedProgram program) {
+ if (program == null) {
+ return null;
+ }
+ for (int i = 0; i < size(); i++) {
+ Object item = get(i);
+ if (item instanceof ScheduleRow) {
+ ScheduleRow row = (ScheduleRow) item;
+ if (row.hasRecordedProgram()
+ && row.getSchedule().getRecordedProgramId() == program.getId()) {
+ return (ScheduleRow) item;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void removeScheduleRow(ScheduleRow scheduleRow) {
+ // This method must not be called from inherited class.
+ SoftPreconditions.checkState(getClass().equals(DvrHistoryRowAdapter.class));
+ if (scheduleRow != null) {
+ scheduleRow.setSchedule(null);
+ SchedulesHeaderRow headerRow = scheduleRow.getHeaderRow();
+ remove(scheduleRow);
+ // Changes the count information of header which the removed row belongs to.
+ if (headerRow != null) {
+ int currentCount = headerRow.getItemCount();
+ headerRow.setItemCount(--currentCount);
+ if (headerRow.getItemCount() == 0) {
+ remove(headerRow);
+ } else {
+ replace(indexOf(headerRow), headerRow);
+ updateHeaderDescription(headerRow);
+ }
+ }
+ }
+ }
+
+ private void updateHeaderDescription(SchedulesHeaderRow headerRow) {
+ headerRow.setDescription(
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_section_subtitle,
+ headerRow.getItemCount(),
+ headerRow.getItemCount()));
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
index a0410bb3..82b85630 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
@@ -20,23 +20,19 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.annotation.IntDef;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
import com.android.tv.data.Program;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.dvr.ui.BigArguments;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
-/**
- * Activity to show the list of recording schedules.
- */
+/** Activity to show the list of recording schedules. */
public class DvrSchedulesActivity extends Activity {
/**
* The key for the type of the schedules which will be listed in the list. The type of the value
@@ -47,9 +43,7 @@ public class DvrSchedulesActivity extends Activity {
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_FULL_SCHEDULE, TYPE_SERIES_SCHEDULE})
public @interface ScheduleListType {}
- /**
- * A type which means the activity will display the full scheduled recordings.
- */
+ /** A type which means the activity will display the full scheduled recordings. */
public static final int TYPE_FULL_SCHEDULE = 0;
/**
* A type which means the activity will display a scheduled recording list of a series
@@ -59,7 +53,7 @@ public class DvrSchedulesActivity extends Activity {
@Override
public void onCreate(final Bundle savedInstanceState) {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
// Pass null to prevent automatically re-creating fragments
super.onCreate(null);
setContentView(R.layout.activity_dvr_schedules);
@@ -67,20 +61,29 @@ public class DvrSchedulesActivity extends Activity {
if (scheduleType == TYPE_FULL_SCHEDULE) {
DvrSchedulesFragment schedulesFragment = new DvrSchedulesFragment();
schedulesFragment.setArguments(getIntent().getExtras());
- getFragmentManager().beginTransaction().add(
- R.id.fragment_container, schedulesFragment).commit();
+ getFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, schedulesFragment)
+ .commit();
} else if (scheduleType == TYPE_SERIES_SCHEDULE) {
- if (BigArguments.getArgument(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS) != null) {
+ if (BigArguments.getArgument(
+ DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS)
+ != null) {
// The programs will be passed to the DvrSeriesSchedulesFragment, so don't need
// to reset the BigArguments.
showDvrSeriesSchedulesFragment(getIntent().getExtras());
} else {
- final ProgressDialog dialog = ProgressDialog.show(this, null, getString(
- R.string.dvr_series_progress_message_reading_programs));
- SeriesRecording seriesRecording = getIntent().getExtras()
- .getParcelable(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_RECORDING);
+ final ProgressDialog dialog =
+ ProgressDialog.show(
+ this,
+ null,
+ getString(R.string.dvr_series_progress_message_reading_programs));
+ SeriesRecording seriesRecording =
+ getIntent()
+ .getExtras()
+ .getParcelable(
+ DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_RECORDING);
// To get programs faster, hold the update of the series schedules.
SeriesRecordingScheduler.getInstance(this).pauseUpdate();
new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) {
@@ -110,7 +113,9 @@ public class DvrSchedulesActivity extends Activity {
private void showDvrSeriesSchedulesFragment(Bundle args) {
DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
schedulesFragment.setArguments(args);
- getFragmentManager().beginTransaction().add(
- R.id.fragment_container, schedulesFragment).commit();
+ getFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, schedulesFragment)
+ .commit();
}
}
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java
index c906c62a..c86721e8 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFocusView.java
@@ -23,12 +23,9 @@ import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-
import com.android.tv.R;
-/**
- * A view used for focus in schedules list.
- */
+/** A view used for focus in schedules list. */
public class DvrSchedulesFocusView extends View {
private final Paint mPaint;
private final RectF mRoundRectF = new RectF();
@@ -76,13 +73,11 @@ public class DvrSchedulesFocusView extends View {
private int getRoundRectRadius() {
if (TextUtils.equals(mViewTag, mHeaderFocusViewTag)) {
- return getResources().getDimensionPixelSize(
- R.dimen.dvr_schedules_header_selector_radius);
+ return getResources()
+ .getDimensionPixelSize(R.dimen.dvr_schedules_header_selector_radius);
} else if (TextUtils.equals(mViewTag, mItemFocusViewTag)) {
return getResources().getDimensionPixelSize(R.dimen.dvr_schedules_selector_radius);
}
return 0;
}
}
-
-
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
index 3cbb500a..d97b61f4 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
@@ -18,14 +18,11 @@ package com.android.tv.dvr.ui.list;
import android.os.Bundle;
import android.support.v17.leanback.widget.ClassPresenterSelector;
-
import com.android.tv.R;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
-/**
- * A fragment to show the list of schedule recordings.
- */
+/** A fragment to show the list of schedule recordings. */
public class DvrSchedulesFragment extends BaseDvrSchedulesFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -46,8 +43,8 @@ public class DvrSchedulesFragment extends BaseDvrSchedulesFragment {
}
@Override
- public ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelecor) {
- return new ScheduleRowAdapter(getContext(), presenterSelecor);
+ public ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelector) {
+ return new ScheduleRowAdapter(getContext(), presenterSelector);
}
@Override
@@ -73,11 +70,11 @@ public class DvrSchedulesFragment extends BaseDvrSchedulesFragment {
if (args != null) {
recording = args.getParcelable(SCHEDULES_KEY_SCHEDULED_RECORDING);
}
- final int selectedPostion = getRowsAdapter().indexOf(
- getRowsAdapter().findRowByScheduledRecording(recording));
+ final int selectedPostion =
+ getRowsAdapter().indexOf(getRowsAdapter().findRowByScheduledRecording(recording));
if (selectedPostion != -1) {
return selectedPostion;
}
return super.getFirstItemPosition();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
index 57e7a88f..d376e358 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
@@ -1,18 +1,18 @@
/*
-* Copyright (C) 2016 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License
-*/
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
package com.android.tv.dvr.ui.list;
@@ -30,10 +30,8 @@ import android.transition.Fade;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
@@ -41,25 +39,21 @@ import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
import com.android.tv.dvr.ui.BigArguments;
-
import java.util.Collections;
import java.util.List;
-/**
- * A fragment to show the list of series schedule recordings.
- */
+/** A fragment to show the list of series schedule recordings. */
@TargetApi(Build.VERSION_CODES.N)
public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
/**
- * The key for series recording whose scheduled recording list will be displayed.
- * Type: {@link SeriesRecording}
+ * The key for series recording whose scheduled recording list will be displayed. Type: {@link
+ * SeriesRecording}
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_RECORDING =
"series_schedules_key_series_recording";
/**
- * The key for programs which belong to the series recording whose scheduled recording list
- * will be displayed.
- * Type: List<{@link Program}>
+ * The key for programs which belong to the series recording whose scheduled recording list will
+ * be displayed. Type: List<{@link Program}>
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS =
"series_schedules_key_series_programs";
@@ -73,7 +67,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
private final SeriesRecordingListener mSeriesRecordingListener =
new SeriesRecordingListener() {
@Override
- public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { }
+ public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {}
@Override
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
@@ -101,26 +95,28 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
};
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- executeProgramLoadingTask();
- }
- };
+ private final ContentObserver mContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ executeProgramLoadingTask();
+ }
+ };
- private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() { }
+ private final ChannelDataManager.Listener mChannelListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {}
- @Override
- public void onChannelListUpdated() {
- executeProgramLoadingTask();
- }
+ @Override
+ public void onChannelListUpdated() {
+ executeProgramLoadingTask();
+ }
- @Override
- public void onChannelBrowsableChanged() { }
- };
+ @Override
+ public void onChannelBrowsableChanged() {}
+ };
public DvrSeriesSchedulesFragment() {
setEnterTransition(new Fade(Fade.IN));
@@ -132,8 +128,8 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
Bundle args = getArguments();
if (args != null) {
mSeriesRecording = args.getParcelable(SERIES_SCHEDULES_KEY_SERIES_RECORDING);
- mPrograms = (List<Program>) BigArguments.getArgument(
- SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
+ mPrograms =
+ (List<Program>) BigArguments.getArgument(SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
BigArguments.reset();
}
if (args == null || mPrograms == null) {
@@ -144,18 +140,19 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
mChannelDataManager = singletons.getChannelDataManager();
mChannelDataManager.addListener(mChannelListener);
mDvrDataManager = singletons.getDvrDataManager();
mDvrDataManager.addSeriesRecordingListener(mSeriesRecordingListener);
- getContext().getContentResolver().registerContentObserver(Programs.CONTENT_URI, true,
- mContentObserver);
+ getContext()
+ .getContentResolver()
+ .registerContentObserver(Programs.CONTENT_URI, true, mContentObserver);
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
onProgramsUpdated();
return super.onCreateView(inflater, container, savedInstanceState);
}
@@ -218,14 +215,16 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
if (mProgramLoadTask != null) {
mProgramLoadTask.cancel(true);
}
- mProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
- @Override
- protected void onPostExecute(List<Program> programs) {
- mPrograms = programs == null ? Collections.EMPTY_LIST : programs;
- onProgramsUpdated();
- }
- };
- mProgramLoadTask.setLoadCurrentProgram(true)
+ mProgramLoadTask =
+ new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ mPrograms = programs == null ? Collections.EMPTY_LIST : programs;
+ onProgramsUpdated();
+ }
+ };
+ mProgramLoadTask
+ .setLoadCurrentProgram(true)
.setLoadDisallowedProgram(true)
.setLoadScheduledEpisode(true)
.setIgnoreChannelOption(true)
diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
index 2af832ec..d5808412 100644
--- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
+++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
@@ -17,29 +17,27 @@
package com.android.tv.dvr.ui.list;
import android.content.Context;
-
import com.android.tv.data.Program;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.ScheduledRecording.Builder;
import com.android.tv.dvr.ui.DvrUiHelper;
-/**
- * A class for the episodic program.
- */
+/** A class for the episodic program. */
class EpisodicProgramRow extends ScheduleRow {
private final String mInputId;
private final Program mProgram;
- public EpisodicProgramRow(String inputId, Program program, ScheduledRecording recording,
+ public EpisodicProgramRow(
+ String inputId,
+ Program program,
+ ScheduledRecording recording,
SchedulesHeaderRow headerRow) {
super(recording, headerRow);
mInputId = inputId;
mProgram = program;
}
- /**
- * Returns the program.
- */
+ /** Returns the program. */
public Program getProgram() {
return mProgram;
}
@@ -82,9 +80,6 @@ class EpisodicProgramRow extends ScheduleRow {
@Override
public String toString() {
- return super.toString()
- + "(inputId=" + mInputId
- + ",program=" + mProgram
- + ")";
+ return super.toString() + "(inputId=" + mInputId + ",program=" + mProgram + ")";
}
}
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
index 91ba393a..b739c18f 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
@@ -18,14 +18,11 @@ package com.android.tv.dvr.ui.list;
import android.content.Context;
import android.support.annotation.Nullable;
-
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
-/**
- * A class for schedule recording row.
- */
+/** A class for schedule recording row. */
class ScheduleRow {
private final SchedulesHeaderRow mHeaderRow;
@Nullable private ScheduledRecording mSchedule;
@@ -37,113 +34,89 @@ class ScheduleRow {
mHeaderRow = headerRow;
}
- /**
- * Gets which {@link SchedulesHeaderRow} this schedule row belongs to.
- */
+ /** Gets which {@link SchedulesHeaderRow} this schedule row belongs to. */
public SchedulesHeaderRow getHeaderRow() {
return mHeaderRow;
}
- /**
- * Returns the recording schedule.
- */
+ /** Returns the recording schedule. */
@Nullable
public ScheduledRecording getSchedule() {
return mSchedule;
}
- /**
- * Checks if the stop recording has been requested or not.
- */
+ /** Checks if the stop recording has been requested or not. */
public boolean isStopRecordingRequested() {
return mStopRecordingRequested;
}
- /**
- * Sets the flag of stop recording request.
- */
+ /** Sets the flag of stop recording request. */
public void setStopRecordingRequested(boolean stopRecordingRequested) {
SoftPreconditions.checkState(!mStartRecordingRequested);
mStopRecordingRequested = stopRecordingRequested;
}
- /**
- * Checks if the start recording has been requested or not.
- */
+ /** Checks if the start recording has been requested or not. */
public boolean isStartRecordingRequested() {
return mStartRecordingRequested;
}
- /**
- * Sets the flag of start recording request.
- */
+ /** Sets the flag of start recording request. */
public void setStartRecordingRequested(boolean startRecordingRequested) {
SoftPreconditions.checkState(!mStopRecordingRequested);
mStartRecordingRequested = startRecordingRequested;
}
- /**
- * Sets the recording schedule.
- */
+ /** Sets the recording schedule. */
public void setSchedule(@Nullable ScheduledRecording schedule) {
mSchedule = schedule;
}
- /**
- * Returns the channel ID.
- */
+ /** Returns the channel ID. */
public long getChannelId() {
return mSchedule != null ? mSchedule.getChannelId() : -1;
}
- /**
- * Returns the start time.
- */
+ /** Returns the start time. */
public long getStartTimeMs() {
return mSchedule != null ? mSchedule.getStartTimeMs() : -1;
}
- /**
- * Returns the end time.
- */
+ /** Returns the end time. */
public long getEndTimeMs() {
return mSchedule != null ? mSchedule.getEndTimeMs() : -1;
}
- /**
- * Returns the duration.
- */
+ /** Returns the duration. */
public final long getDuration() {
return getEndTimeMs() - getStartTimeMs();
}
- /**
- * Checks if the program is on air.
- */
+ /** Checks if the program is on air. */
public final boolean isOnAir() {
long currentTimeMs = System.currentTimeMillis();
return getStartTimeMs() <= currentTimeMs && getEndTimeMs() > currentTimeMs;
}
- /**
- * Checks if the schedule is not started.
- */
+ /** Checks if the schedule is not started. */
public final boolean isRecordingNotStarted() {
return mSchedule != null
&& mSchedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED;
}
- /**
- * Checks if the schedule is in progress.
- */
+ /** Checks if the schedule is in progress. */
public final boolean isRecordingInProgress() {
return mSchedule != null
&& mSchedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS;
}
- /**
- * Checks if the schedule has been canceled or not.
- */
+ /** Checks if the schedule is failed. */
+ public final boolean isRecordingFailed() {
+ return mSchedule != null
+ && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED;
+ }
+
+ /** Checks if the schedule has been canceled or not. */
public final boolean isScheduleCanceled() {
return mSchedule != null
&& mSchedule.getState() == ScheduledRecording.STATE_RECORDING_CANCELED;
@@ -152,28 +125,29 @@ class ScheduleRow {
public boolean isRecordingFinished() {
return mSchedule != null
&& (mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED
- || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED
- || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED);
+ || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED
+ || mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED);
}
- /**
- * Creates and returns the new schedule with the existing information.
- */
+ public boolean hasRecordedProgram() {
+ return mSchedule != null
+ && mSchedule.getRecordedProgramId() != null
+ && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED;
+ }
+
+ /** Creates and returns the new schedule with the existing information. */
public ScheduledRecording.Builder createNewScheduleBuilder() {
return mSchedule != null ? ScheduledRecording.buildFrom(mSchedule) : null;
}
- /**
- * Returns the program title with episode number.
- */
+ /** Returns the program title with episode number. */
public String getProgramTitleWithEpisodeNumber(Context context) {
- return mSchedule != null ? DvrUiHelper.getStyledTitleWithEpisodeNumber(context,
- mSchedule, 0).toString() : null;
+ return mSchedule != null
+ ? DvrUiHelper.getStyledTitleWithEpisodeNumber(context, mSchedule, 0).toString()
+ : null;
}
- /**
- * Returns the program title including the season/episode number.
- */
+ /** Returns the program title including the season/episode number. */
public String getEpisodeDisplayTitle(Context context) {
return mSchedule != null ? mSchedule.getEpisodeDisplayTitle(context) : null;
}
@@ -181,15 +155,16 @@ class ScheduleRow {
@Override
public String toString() {
return getClass().getSimpleName()
- + "(schedule=" + mSchedule
- + ",stopRecordingRequested=" + mStopRecordingRequested
- + ",startRecordingRequested=" + mStartRecordingRequested
+ + "(schedule="
+ + mSchedule
+ + ",stopRecordingRequested="
+ + mStopRecordingRequested
+ + ",startRecordingRequested="
+ + mStartRecordingRequested
+ ")";
}
- /**
- * Checks if the {@code schedule} is for the program or channel.
- */
+ /** Checks if the {@code schedule} is for the program or channel. */
public boolean matchSchedule(ScheduledRecording schedule) {
if (mSchedule == null) {
return false;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
index 97d60473..ef4a4337 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
@@ -16,7 +16,9 @@
package com.android.tv.dvr.ui.list;
+import android.annotation.TargetApi;
import android.content.Context;
+import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -27,11 +29,11 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -40,14 +42,14 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-/**
- * An adapter for {@link ScheduleRow}.
- */
+/** An adapter for {@link ScheduleRow}. */
+@TargetApi(VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
class ScheduleRowAdapter extends ArrayObjectAdapter {
private static final String TAG = "ScheduleRowAdapter";
private static final boolean DEBUG = false;
- private final static long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
+ private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
private static final int MSG_UPDATE_ROW = 1;
@@ -55,16 +57,17 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
private final List<String> mTitles = new ArrayList<>();
private final Set<ScheduleRow> mPendingUpdate = new ArraySet<>();
- private final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_UPDATE_ROW) {
- long currentTimeMs = System.currentTimeMillis();
- handleUpdateRow(currentTimeMs);
- sendNextUpdateMessage(currentTimeMs);
- }
- }
- };
+ private final Handler mHandler =
+ new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_UPDATE_ROW) {
+ long currentTimeMs = System.currentTimeMillis();
+ handleUpdateRow(currentTimeMs);
+ sendNextUpdateMessage(currentTimeMs);
+ }
+ }
+ };
public ScheduleRowAdapter(Context context, ClassPresenterSelector classPresenterSelector) {
super(classPresenterSelector);
@@ -73,37 +76,41 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
mTitles.add(mContext.getString(R.string.dvr_date_tomorrow));
}
- /**
- * Returns context.
- */
+ /** Returns context. */
protected Context getContext() {
return mContext;
}
- /**
- * Starts schedule row adapter.
- */
+ /** Starts schedule row adapter. */
public void start() {
clear();
- List<ScheduledRecording> recordingList = TvApplication.getSingletons(mContext)
- .getDvrDataManager().getNonStartedScheduledRecordings();
- recordingList.addAll(TvApplication.getSingletons(mContext).getDvrDataManager()
- .getStartedRecordings());
- Collections.sort(recordingList,
- ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
+ List<ScheduledRecording> recordingList =
+ TvSingletons.getSingletons(mContext)
+ .getDvrDataManager()
+ .getNonStartedScheduledRecordings();
+ recordingList.addAll(
+ TvSingletons.getSingletons(mContext).getDvrDataManager().getStartedRecordings());
+ Collections.sort(
+ recordingList, ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
long deadLine = Utils.getLastMillisecondOfDay(System.currentTimeMillis());
- for (int i = 0; i < recordingList.size();) {
+ for (int i = 0; i < recordingList.size(); ) {
ArrayList<ScheduledRecording> section = new ArrayList<>();
while (i < recordingList.size() && recordingList.get(i).getStartTimeMs() < deadLine) {
section.add(recordingList.get(i++));
}
if (!section.isEmpty()) {
- SchedulesHeaderRow headerRow = new DateHeaderRow(calculateHeaderDate(deadLine),
- mContext.getResources().getQuantityString(
- R.plurals.dvr_schedules_section_subtitle, section.size(), section.size()),
- section.size(), deadLine);
+ SchedulesHeaderRow headerRow =
+ new DateHeaderRow(
+ calculateHeaderDate(deadLine),
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_section_subtitle,
+ section.size(),
+ section.size()),
+ section.size(),
+ deadLine);
add(headerRow);
- for(ScheduledRecording recording : section){
+ for (ScheduledRecording recording : section) {
add(new ScheduleRow(recording, headerRow));
}
}
@@ -113,25 +120,29 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
private String calculateHeaderDate(long deadLine) {
- int titleIndex = (int) ((deadLine -
- Utils.getLastMillisecondOfDay(System.currentTimeMillis())) / ONE_DAY_MS);
+ int titleIndex =
+ (int)
+ ((deadLine - Utils.getLastMillisecondOfDay(System.currentTimeMillis()))
+ / ONE_DAY_MS);
String headerDate;
if (titleIndex < mTitles.size()) {
headerDate = mTitles.get(titleIndex);
} else {
- headerDate = DateUtils.formatDateTime(getContext(), deadLine,
- DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_ABBREV_MONTH);
+ headerDate =
+ DateUtils.formatDateTime(
+ getContext(),
+ deadLine,
+ DateUtils.FORMAT_SHOW_WEEKDAY
+ | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_ABBREV_MONTH);
}
return headerDate;
}
- /**
- * Stops schedules row adapter.
- */
+ /** Stops schedules row adapter. */
public void stop() {
mHandler.removeCallbacksAndMessages(null);
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ DvrManager dvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
for (int i = 0; i < size(); i++) {
if (get(i) instanceof ScheduleRow) {
ScheduleRow row = (ScheduleRow) get(i);
@@ -142,9 +153,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
}
- /**
- * Gets which {@link ScheduleRow} the {@link ScheduledRecording} belongs to.
- */
+ /** Gets which {@link ScheduleRow} the {@link ScheduledRecording} belongs to. */
public ScheduleRow findRowByScheduledRecording(ScheduledRecording recording) {
if (recording == null) {
return null;
@@ -167,7 +176,8 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
continue;
}
ScheduleRow row = (ScheduleRow) item;
- if (row.getSchedule() != null && row.isStartRecordingRequested()
+ if (row.getSchedule() != null
+ && row.isStartRecordingRequested()
&& row.matchSchedule(schedule)) {
return row;
}
@@ -185,7 +195,8 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
if (get(index) instanceof ScheduleRow) {
ScheduleRow scheduleRow = (ScheduleRow) get(index);
if (ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.compare(
- scheduleRow.getSchedule(), recording) > 0) {
+ scheduleRow.getSchedule(), recording)
+ > 0) {
break;
}
pre = index;
@@ -205,9 +216,14 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
add(index, addedRow);
updateHeaderDescription(headerRow);
} else {
- SchedulesHeaderRow headerRow = new DateHeaderRow(calculateHeaderDate(deadLine),
- mContext.getResources().getQuantityString(
- R.plurals.dvr_schedules_section_subtitle, 1, 1), 1, deadLine);
+ SchedulesHeaderRow headerRow =
+ new DateHeaderRow(
+ calculateHeaderDate(deadLine),
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_section_subtitle, 1, 1),
+ 1,
+ deadLine);
add(++pre, headerRow);
ScheduleRow addedRow = new ScheduleRow(recording, headerRow);
add(pre, addedRow);
@@ -241,14 +257,15 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
private void updateHeaderDescription(SchedulesHeaderRow headerRow) {
- headerRow.setDescription(mContext.getResources().getQuantityString(
- R.plurals.dvr_schedules_section_subtitle,
- headerRow.getItemCount(), headerRow.getItemCount()));
+ headerRow.setDescription(
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_section_subtitle,
+ headerRow.getItemCount(),
+ headerRow.getItemCount()));
}
- /**
- * Called when a schedule recording is added to dvr date manager.
- */
+ /** Called when a schedule recording is added to dvr date manager. */
public void onScheduledRecordingAdded(ScheduledRecording schedule) {
if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + schedule);
ScheduleRow row = findRowWithStartRequest(schedule);
@@ -263,9 +280,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
}
- /**
- * Called when a schedule recording is removed from dvr date manager.
- */
+ /** Called when a schedule recording is removed from dvr date manager. */
public void onScheduledRecordingRemoved(ScheduledRecording schedule) {
if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + schedule);
ScheduleRow row = findRowByScheduledRecording(schedule);
@@ -276,9 +291,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
}
- /**
- * Called when a schedule recording is updated in dvr date manager.
- */
+ /** Called when a schedule recording is updated in dvr date manager. */
public void onScheduledRecordingUpdated(ScheduledRecording schedule, boolean conflictChange) {
if (DEBUG) Log.d(TAG, "onScheduledRecordingUpdated: " + schedule);
ScheduleRow row = findRowByScheduledRecording(schedule);
@@ -329,9 +342,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
}
- /**
- * Checks if there is a row which requested start/stop recording.
- */
+ /** Checks if there is a row which requested start/stop recording. */
protected boolean isStartOrStopRequested() {
for (int i = 0; i < size(); i++) {
Object item = get(i);
@@ -345,16 +356,12 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
return false;
}
- /**
- * Delays update of the row.
- */
+ /** Delays update of the row. */
protected void addPendingUpdate(ScheduleRow row) {
mPendingUpdate.add(row);
}
- /**
- * Executes the pending updates.
- */
+ /** Executes the pending updates. */
protected void executePendingUpdate() {
for (ScheduleRow row : mPendingUpdate) {
int index = indexOf(row);
@@ -365,21 +372,17 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
mPendingUpdate.clear();
}
- /**
- * To check whether the recording should be kept or not.
- */
+ /** To check whether the recording should be kept or not. */
protected boolean willBeKept(ScheduledRecording schedule) {
// CANCELED state means that the schedule was removed temporarily, which should be shown
// in the list so that the user can reschedule it.
return schedule.getEndTimeMs() > System.currentTimeMillis()
&& (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS
- || schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || schedule.getState() == ScheduledRecording.STATE_RECORDING_CANCELED);
+ || schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
+ || schedule.getState() == ScheduledRecording.STATE_RECORDING_CANCELED);
}
- /**
- * Handle the message to update/remove rows.
- */
+ /** Handle the message to update/remove rows. */
protected void handleUpdateRow(long currentTimeMs) {
for (int i = 0; i < size(); i++) {
Object item = get(i);
@@ -392,9 +395,7 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
}
}
- /**
- * Returns the next update time. Return {@link Long#MAX_VALUE} if no timer is necessary.
- */
+ /** Returns the next update time. Return {@link Long#MAX_VALUE} if no timer is necessary. */
protected long getNextTimerMs(long currentTimeMs) {
long earliest = Long.MAX_VALUE;
for (int i = 0; i < size(); i++) {
@@ -411,15 +412,12 @@ class ScheduleRowAdapter extends ArrayObjectAdapter {
return earliest;
}
- /**
- * Send update message at the time returned by {@link #getNextTimerMs}.
- */
+ /** Send update message at the time returned by {@link #getNextTimerMs}. */
protected final void sendNextUpdateMessage(long currentTimeMs) {
mHandler.removeMessages(MSG_UPDATE_ROW);
long nextTime = getNextTimerMs(currentTimeMs);
if (nextTime != Long.MAX_VALUE) {
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_ROW,
- nextTime - System.currentTimeMillis());
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE_ROW, nextTime - System.currentTimeMillis());
}
}
}
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
index dc4e3c41..38d3d582 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
@@ -18,7 +18,6 @@ package com.android.tv.dvr.ui.list;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
@@ -37,11 +36,11 @@ import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvFeatures;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
@@ -50,21 +49,22 @@ import com.android.tv.dvr.ui.DvrStopRecordingFragment;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
-/**
- * A RowPresenter for {@link ScheduleRow}.
- */
+/** A RowPresenter for {@link ScheduleRow}. */
@TargetApi(Build.VERSION_CODES.N)
class ScheduleRowPresenter extends RowPresenter {
private static final String TAG = "ScheduleRowPresenter";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ACTION_START_RECORDING, ACTION_STOP_RECORDING, ACTION_CREATE_SCHEDULE,
- ACTION_REMOVE_SCHEDULE})
+ @IntDef({
+ ACTION_START_RECORDING,
+ ACTION_STOP_RECORDING,
+ ACTION_CREATE_SCHEDULE,
+ ACTION_REMOVE_SCHEDULE
+ })
public @interface ScheduleRowAction {}
/** An action to start recording. */
public static final int ACTION_START_RECORDING = 1;
@@ -85,9 +85,7 @@ class ScheduleRowPresenter extends RowPresenter {
private int mLastFocusedViewId;
- /**
- * A ViewHolder for {@link ScheduleRow}
- */
+ /** A ViewHolder for {@link ScheduleRow} */
public static class ScheduleRowViewHolder extends RowPresenter.ViewHolder {
private ScheduleRowPresenter mPresenter;
@ScheduleRowAction private int[] mActions;
@@ -118,29 +116,30 @@ class ScheduleRowPresenter extends RowPresenter {
new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean focused) {
- view.post(new Runnable() {
- @Override
- public void run() {
- if (view.isFocused()) {
- mPresenter.mLastFocusedViewId = view.getId();
- }
- updateSelector();
- }
- });
+ view.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (view.isFocused()) {
+ mPresenter.mLastFocusedViewId = view.getId();
+ }
+ updateSelector();
+ }
+ });
}
};
public ScheduleRowViewHolder(View view, ScheduleRowPresenter presenter) {
super(view);
mPresenter = presenter;
- mLtr = view.getContext().getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ mLtr =
+ view.getContext().getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
mInfoContainer = (LinearLayout) view.findViewById(R.id.info_container);
- mSecondActionContainer = (RelativeLayout) view.findViewById(
- R.id.action_second_container);
+ mSecondActionContainer =
+ (RelativeLayout) view.findViewById(R.id.action_second_container);
mSecondActionView = (ImageView) view.findViewById(R.id.action_second);
- mFirstActionContainer = (RelativeLayout) view.findViewById(
- R.id.action_first_container);
+ mFirstActionContainer = (RelativeLayout) view.findViewById(R.id.action_first_container);
mFirstActionView = (ImageView) view.findViewById(R.id.action_first);
mSelectorView = view.findViewById(R.id.selector);
mTimeView = (TextView) view.findViewById(R.id.time);
@@ -151,47 +150,48 @@ class ScheduleRowPresenter extends RowPresenter {
Resources res = view.getResources();
mSelectorTranslationDelta =
res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin)
- - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_focus_translation_delta);
- mSelectorWidthDelta = res.getDimensionPixelSize(
- R.dimen.dvr_schedules_item_focus_width_delta);
+ - res.getDimensionPixelSize(
+ R.dimen.dvr_schedules_item_focus_translation_delta);
+ mSelectorWidthDelta =
+ res.getDimensionPixelSize(R.dimen.dvr_schedules_item_focus_width_delta);
mRoundRectRadius = res.getDimensionPixelSize(R.dimen.dvr_schedules_selector_radius);
- int fullWidth = res.getDimensionPixelSize(
- R.dimen.dvr_schedules_item_width)
- - 2 * res.getDimensionPixelSize(R.dimen.dvr_schedules_layout_padding);
+ int fullWidth =
+ res.getDimensionPixelSize(R.dimen.dvr_schedules_item_width)
+ - 2 * res.getDimensionPixelSize(R.dimen.dvr_schedules_layout_padding);
mInfoContainerTargetWidthWithNoAction = fullWidth + 2 * mRoundRectRadius;
- mInfoContainerTargetWidthWithOneAction = fullWidth
- - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin)
- - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_delete_width)
- + mRoundRectRadius + mSelectorWidthDelta;
- mInfoContainerTargetWidthWithTwoAction = mInfoContainerTargetWidthWithOneAction
- - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin)
- - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_icon_size);
+ mInfoContainerTargetWidthWithOneAction =
+ fullWidth
+ - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin)
+ - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_delete_width)
+ + mRoundRectRadius
+ + mSelectorWidthDelta;
+ mInfoContainerTargetWidthWithTwoAction =
+ mInfoContainerTargetWidthWithOneAction
+ - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_section_margin)
+ - res.getDimensionPixelSize(R.dimen.dvr_schedules_item_icon_size);
mInfoContainer.setOnFocusChangeListener(mOnFocusChangeListener);
mFirstActionContainer.setOnFocusChangeListener(mOnFocusChangeListener);
mSecondActionContainer.setOnFocusChangeListener(mOnFocusChangeListener);
}
- /**
- * Returns time view.
- */
+ /** Returns time view. */
public TextView getTimeView() {
return mTimeView;
}
- /**
- * Returns title view.
- */
+ /** Returns title view. */
public TextView getProgramTitleView() {
return mProgramTitleView;
}
private void updateSelector() {
- int animationDuration = mSelectorView.getResources().getInteger(
- android.R.integer.config_shortAnimTime);
+ int animationDuration =
+ mSelectorView.getResources().getInteger(android.R.integer.config_shortAnimTime);
DecelerateInterpolator interpolator = new DecelerateInterpolator();
- if (mInfoContainer.isFocused() || mSecondActionContainer.isFocused()
+ if (mInfoContainer.isFocused()
+ || mSecondActionContainer.isFocused()
|| mFirstActionContainer.isFocused()) {
final ViewGroup.LayoutParams lp = mSelectorView.getLayoutParams();
final int targetWidth;
@@ -208,33 +208,50 @@ class ScheduleRowPresenter extends RowPresenter {
} else if (mSecondActionContainer.isFocused()) {
targetWidth = Math.max(mSecondActionContainer.getWidth(), 2 * mRoundRectRadius);
} else {
- targetWidth = mFirstActionContainer.getWidth() + mRoundRectRadius
- + mSelectorTranslationDelta;
+ targetWidth =
+ mFirstActionContainer.getWidth()
+ + mRoundRectRadius
+ + mSelectorTranslationDelta;
}
float targetTranslationX;
if (mInfoContainer.isFocused()) {
- targetTranslationX = mLtr ? mInfoContainer.getLeft() - mRoundRectRadius
- - mSelectorView.getLeft() :
- mInfoContainer.getRight() + mRoundRectRadius - mSelectorView.getRight();
+ targetTranslationX =
+ mLtr
+ ? mInfoContainer.getLeft()
+ - mRoundRectRadius
+ - mSelectorView.getLeft()
+ : mInfoContainer.getRight()
+ + mRoundRectRadius
+ - mSelectorView.getRight();
} else if (mSecondActionContainer.isFocused()) {
if (mSecondActionContainer.getWidth() > 2 * mRoundRectRadius) {
- targetTranslationX = mLtr ? mSecondActionContainer.getLeft() -
- mSelectorView.getLeft()
- : mSecondActionContainer.getRight() - mSelectorView.getRight();
+ targetTranslationX =
+ mLtr
+ ? mSecondActionContainer.getLeft() - mSelectorView.getLeft()
+ : mSecondActionContainer.getRight()
+ - mSelectorView.getRight();
} else {
- targetTranslationX = mLtr ? mSecondActionContainer.getLeft() -
- (mRoundRectRadius - mSecondActionContainer.getWidth() / 2) -
- mSelectorView.getLeft()
- : mSecondActionContainer.getRight() +
- (mRoundRectRadius - mSecondActionContainer.getWidth() / 2) -
- mSelectorView.getRight();
+ targetTranslationX =
+ mLtr
+ ? mSecondActionContainer.getLeft()
+ - (mRoundRectRadius
+ - mSecondActionContainer.getWidth() / 2)
+ - mSelectorView.getLeft()
+ : mSecondActionContainer.getRight()
+ + (mRoundRectRadius
+ - mSecondActionContainer.getWidth() / 2)
+ - mSelectorView.getRight();
}
} else {
- targetTranslationX = mLtr ? mFirstActionContainer.getLeft()
- - mSelectorTranslationDelta - mSelectorView.getLeft()
- : mFirstActionContainer.getRight() + mSelectorTranslationDelta
- - mSelectorView.getRight();
+ targetTranslationX =
+ mLtr
+ ? mFirstActionContainer.getLeft()
+ - mSelectorTranslationDelta
+ - mSelectorView.getLeft()
+ : mFirstActionContainer.getRight()
+ + mSelectorTranslationDelta
+ - mSelectorView.getRight();
}
if (mSelectorView.getAlpha() == 0) {
@@ -246,57 +263,72 @@ class ScheduleRowPresenter extends RowPresenter {
// animate the selector in and to the proper width and translation X.
final float deltaWidth = lp.width - targetWidth;
mSelectorView.animate().cancel();
- mSelectorView.animate().translationX(targetTranslationX).alpha(1f)
- .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- // Set width to the proper width for this animation step.
- lp.width = targetWidth + Math.round(
- deltaWidth * (1f - animation.getAnimatedFraction()));
- mSelectorView.requestLayout();
- }
- }).setDuration(animationDuration).setInterpolator(interpolator).start();
+ mSelectorView
+ .animate()
+ .translationX(targetTranslationX)
+ .alpha(1f)
+ .setUpdateListener(
+ animation -> {
+ // Set width to the proper width for this animation step.
+ float fraction = 1f - animation.getAnimatedFraction();
+ lp.width = targetWidth + Math.round(deltaWidth * fraction);
+ mSelectorView.requestLayout();
+ })
+ .setDuration(animationDuration)
+ .setInterpolator(interpolator)
+ .start();
if (mPendingAnimationRunnable != null) {
mPendingAnimationRunnable.run();
mPendingAnimationRunnable = null;
}
} else {
mSelectorView.animate().cancel();
- mSelectorView.animate().alpha(0f).setDuration(animationDuration)
- .setInterpolator(interpolator).setUpdateListener(null).start();
+ mSelectorView
+ .animate()
+ .alpha(0f)
+ .setDuration(animationDuration)
+ .setInterpolator(interpolator)
+ .setUpdateListener(null)
+ .start();
}
}
- /**
- * Grey out the information body.
- */
+ /** Grey out the information body. */
public void greyOutInfo() {
- mTimeView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info_grey, null));
- mProgramTitleView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info_grey, null));
- mInfoSeparatorView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info_grey, null));
- mChannelNameView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info_grey, null));
- mConflictInfoView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info_grey, null));
+ mTimeView.setTextColor(
+ mInfoContainer
+ .getResources()
+ .getColor(R.color.dvr_schedules_item_info_grey, null));
+ mProgramTitleView.setTextColor(
+ mInfoContainer
+ .getResources()
+ .getColor(R.color.dvr_schedules_item_info_grey, null));
+ mInfoSeparatorView.setTextColor(
+ mInfoContainer
+ .getResources()
+ .getColor(R.color.dvr_schedules_item_info_grey, null));
+ mChannelNameView.setTextColor(
+ mInfoContainer
+ .getResources()
+ .getColor(R.color.dvr_schedules_item_info_grey, null));
+ mConflictInfoView.setTextColor(
+ mInfoContainer
+ .getResources()
+ .getColor(R.color.dvr_schedules_item_info_grey, null));
}
- /**
- * Reverse grey out operation.
- */
+ /** Reverse grey out operation. */
public void whiteBackInfo() {
- mTimeView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info, null));
- mProgramTitleView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_main, null));
- mInfoSeparatorView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info, null));
- mChannelNameView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info, null));
- mConflictInfoView.setTextColor(mInfoContainer.getResources().getColor(R.color
- .dvr_schedules_item_info, null));
+ mTimeView.setTextColor(
+ mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null));
+ mProgramTitleView.setTextColor(
+ mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_main, null));
+ mInfoSeparatorView.setTextColor(
+ mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null));
+ mChannelNameView.setTextColor(
+ mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null));
+ mConflictInfoView.setTextColor(
+ mInfoContainer.getResources().getColor(R.color.dvr_schedules_item_info, null));
}
}
@@ -304,32 +336,29 @@ class ScheduleRowPresenter extends RowPresenter {
setHeaderPresenter(null);
setSelectEffectEnabled(false);
mContext = context;
- mDvrManager = TvApplication.getSingletons(context).getDvrManager();
- mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager();
- mTunerConflictWillNotBeRecordedInfo = mContext.getString(
- R.string.dvr_schedules_tuner_conflict_will_not_be_recorded_info);
- mTunerConflictWillBePartiallyRecordedInfo = mContext.getString(
- R.string.dvr_schedules_tuner_conflict_will_be_partially_recorded);
- mAnimationDuration = mContext.getResources().getInteger(
- android.R.integer.config_shortAnimTime);
+ mDvrManager = TvSingletons.getSingletons(context).getDvrManager();
+ mDvrScheduleManager = TvSingletons.getSingletons(context).getDvrScheduleManager();
+ mTunerConflictWillNotBeRecordedInfo =
+ mContext.getString(R.string.dvr_schedules_tuner_conflict_will_not_be_recorded_info);
+ mTunerConflictWillBePartiallyRecordedInfo =
+ mContext.getString(
+ R.string.dvr_schedules_tuner_conflict_will_be_partially_recorded);
+ mAnimationDuration =
+ mContext.getResources().getInteger(android.R.integer.config_shortAnimTime);
}
@Override
public ViewHolder createRowViewHolder(ViewGroup parent) {
- return onGetScheduleRowViewHolder(LayoutInflater.from(mContext)
- .inflate(R.layout.dvr_schedules_item, parent, false));
+ return onGetScheduleRowViewHolder(
+ LayoutInflater.from(mContext).inflate(R.layout.dvr_schedules_item, parent, false));
}
- /**
- * Returns context.
- */
+ /** Returns context. */
protected Context getContext() {
return mContext;
}
- /**
- * Returns DVR manager.
- */
+ /** Returns DVR manager. */
protected DvrManager getDvrManager() {
return mDvrManager;
}
@@ -341,54 +370,77 @@ class ScheduleRowPresenter extends RowPresenter {
ScheduleRow row = (ScheduleRow) item;
@ScheduleRowAction int[] actions = getAvailableActions(row);
viewHolder.mActions = actions;
- viewHolder.mInfoContainer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (isInfoClickable(row)) {
- onInfoClicked(row);
- }
- }
- });
+ viewHolder.mInfoContainer.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (isInfoClickable(row)) {
+ onInfoClicked(row);
+ }
+ }
+ });
- viewHolder.mFirstActionContainer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onActionClicked(actions[0], row);
- }
- });
+ viewHolder.mFirstActionContainer.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onActionClicked(actions[0], row);
+ }
+ });
- viewHolder.mSecondActionContainer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onActionClicked(actions[1], row);
- }
- });
+ viewHolder.mSecondActionContainer.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onActionClicked(actions[1], row);
+ }
+ });
viewHolder.mTimeView.setText(onGetRecordingTimeText(row));
String programInfoText = onGetProgramInfoText(row);
if (TextUtils.isEmpty(programInfoText)) {
int durationMins = Math.max(1, Utils.getRoundOffMinsFromMs(row.getDuration()));
- programInfoText = mContext.getResources().getQuantityString(
- R.plurals.dvr_schedules_recording_duration, durationMins, durationMins);
+ programInfoText =
+ mContext.getResources()
+ .getQuantityString(
+ R.plurals.dvr_schedules_recording_duration,
+ durationMins,
+ durationMins);
}
String channelName = getChannelNameText(row);
viewHolder.mProgramTitleView.setText(programInfoText);
- viewHolder.mInfoSeparatorView.setVisibility((!TextUtils.isEmpty(programInfoText)
- && !TextUtils.isEmpty(channelName)) ? View.VISIBLE : View.GONE);
+ viewHolder.mInfoSeparatorView.setVisibility(
+ (!TextUtils.isEmpty(programInfoText) && !TextUtils.isEmpty(channelName))
+ ? View.VISIBLE
+ : View.GONE);
viewHolder.mChannelNameView.setText(channelName);
if (actions != null) {
switch (actions.length) {
case 2:
viewHolder.mSecondActionView.setImageResource(getImageForAction(actions[1]));
- // pass through
+ // fall through
case 1:
viewHolder.mFirstActionView.setImageResource(getImageForAction(actions[0]));
break;
+ default: // fall out
}
}
- if (mDvrManager.isConflicting(row.getSchedule())) {
+ ScheduledRecording schedule = row.getSchedule();
+ if (mDvrManager.isConflicting(schedule)
+ || (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext())
+ && schedule != null
+ && schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED)) {
String conflictInfo;
- if (mDvrScheduleManager.isPartiallyConflicting(row.getSchedule())) {
+ if (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext())
+ && schedule != null
+ && schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
+ // TODO(b/72638385): show real error messages
+ // TODO(b/72638385): use a better name for ConflictInfoXXX
+ conflictInfo = "Failed";
+ if (schedule.getFailedReason() != null) {
+ conflictInfo += " (Error code: " + schedule.getFailedReason() + ")";
+ }
+ } else if (mDvrScheduleManager.isPartiallyConflicting(row.getSchedule())) {
conflictInfo = mTunerConflictWillBePartiallyRecordedInfo;
} else {
conflictInfo = mTunerConflictWillNotBeRecordedInfo;
@@ -422,51 +474,48 @@ class ScheduleRowPresenter extends RowPresenter {
}
}
- /**
- * Returns view holder for schedule row.
- */
+ /** Returns view holder for schedule row. */
protected ScheduleRowViewHolder onGetScheduleRowViewHolder(View view) {
return new ScheduleRowViewHolder(view, this);
}
- /**
- * Returns time text for time view from scheduled recording.
- */
+ /** Returns time text for time view from scheduled recording. */
protected String onGetRecordingTimeText(ScheduleRow row) {
- return Utils.getDurationString(mContext, row.getStartTimeMs(), row.getEndTimeMs(), true,
- false, true, 0);
+ return Utils.getDurationString(
+ mContext, row.getStartTimeMs(), row.getEndTimeMs(), true, false, true, 0);
}
- /**
- * Returns program info text for program title view.
- */
+ /** Returns program info text for program title view. */
protected String onGetProgramInfoText(ScheduleRow row) {
return row.getProgramTitleWithEpisodeNumber(mContext);
}
private String getChannelNameText(ScheduleRow row) {
- Channel channel = TvApplication.getSingletons(mContext).getChannelDataManager()
- .getChannel(row.getChannelId());
- return channel == null ? null :
- TextUtils.isEmpty(channel.getDisplayName()) ? channel.getDisplayNumber() :
- channel.getDisplayName().trim() + " " + channel.getDisplayNumber();
+ Channel channel =
+ TvSingletons.getSingletons(mContext)
+ .getChannelDataManager()
+ .getChannel(row.getChannelId());
+ return channel == null
+ ? null
+ : TextUtils.isEmpty(channel.getDisplayName())
+ ? channel.getDisplayNumber()
+ : channel.getDisplayName().trim() + " " + channel.getDisplayNumber();
}
- /**
- * Called when user click Info in {@link ScheduleRow}.
- */
+ /** Called when user click Info in {@link ScheduleRow}. */
protected void onInfoClicked(ScheduleRow row) {
DvrUiHelper.startDetailsActivity((Activity) mContext, row.getSchedule(), null, true);
}
private boolean isInfoClickable(ScheduleRow row) {
- return row.getSchedule() != null
- && (row.getSchedule().isNotStarted() || row.getSchedule().isInProgress());
+ ScheduledRecording schedule = row.getSchedule();
+ return schedule != null
+ && (schedule.isNotStarted()
+ || schedule.isInProgress()
+ || schedule.isFinished());
}
- /**
- * Called when the button in a row is clicked.
- */
+ /** Called when the button in a row is clicked. */
protected void onActionClicked(@ScheduleRowAction final int action, ScheduleRow row) {
switch (action) {
case ACTION_START_RECORDING:
@@ -481,12 +530,11 @@ class ScheduleRowPresenter extends RowPresenter {
case ACTION_REMOVE_SCHEDULE:
onRemoveSchedule(row);
break;
+ default: // fall out
}
}
- /**
- * Action handler for {@link #ACTION_START_RECORDING}.
- */
+ /** Action handler for {@link #ACTION_START_RECORDING}. */
protected void onStartRecording(ScheduleRow row) {
ScheduledRecording schedule = row.getSchedule();
if (schedule == null) {
@@ -495,12 +543,16 @@ class ScheduleRowPresenter extends RowPresenter {
}
// Checks if there are current recordings that will be stopped by schedule this program.
// If so, shows confirmation dialog to users.
- List<ScheduledRecording> conflictSchedules = mDvrScheduleManager.getConflictingSchedules(
- schedule.getChannelId(), System.currentTimeMillis(), schedule.getEndTimeMs());
+ List<ScheduledRecording> conflictSchedules =
+ mDvrScheduleManager.getConflictingSchedules(
+ schedule.getChannelId(),
+ System.currentTimeMillis(),
+ schedule.getEndTimeMs());
for (int i = conflictSchedules.size() - 1; i >= 0; i--) {
ScheduledRecording conflictSchedule = conflictSchedules.get(i);
if (conflictSchedule.isInProgress()) {
- DvrUiHelper.showStopRecordingDialog((Activity) mContext,
+ DvrUiHelper.showStopRecordingDialog(
+ (Activity) mContext,
conflictSchedule.getChannelId(),
DvrStopRecordingFragment.REASON_ON_CONFLICT,
new HalfSizedDialogFragment.OnActionClickListener() {
@@ -523,26 +575,27 @@ class ScheduleRowPresenter extends RowPresenter {
if (row.isRecordingNotStarted()) {
mDvrManager.setHighestPriority(row.getSchedule());
} else if (row.isRecordingFinished()) {
- mDvrManager.addSchedule(ScheduledRecording.buildFrom(row.getSchedule())
- .setId(ScheduledRecording.ID_NOT_SET)
- .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)
- .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule()))
- .build());
+ mDvrManager.addSchedule(
+ ScheduledRecording.buildFrom(row.getSchedule())
+ .setId(ScheduledRecording.ID_NOT_SET)
+ .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)
+ .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule()))
+ .build());
} else {
- SoftPreconditions.checkState(false, TAG, "Invalid row state to start recording: "
- + row);
+ SoftPreconditions.checkState(
+ false, TAG, "Invalid row state to start recording: " + row);
return;
}
- String msg = mContext.getString(R.string.dvr_msg_current_program_scheduled,
- row.getSchedule().getProgramTitle(),
- Utils.toTimeString(row.getEndTimeMs(), false));
+ String msg =
+ mContext.getString(
+ R.string.dvr_msg_current_program_scheduled,
+ row.getSchedule().getProgramTitle(),
+ Utils.toTimeString(row.getEndTimeMs(), false));
ToastUtils.show(mContext, msg, Toast.LENGTH_SHORT);
}
}
- /**
- * Action handler for {@link #ACTION_STOP_RECORDING}.
- */
+ /** Action handler for {@link #ACTION_STOP_RECORDING}. */
protected void onStopRecording(ScheduleRow row) {
if (row.getSchedule() == null) {
// This row has been deleted.
@@ -555,15 +608,15 @@ class ScheduleRowPresenter extends RowPresenter {
if (TextUtils.isEmpty(deletedInfo)) {
deletedInfo = getChannelNameText(row);
}
- ToastUtils.show(mContext, mContext.getResources()
- .getString(R.string.dvr_schedules_deletion_info, deletedInfo),
+ ToastUtils.show(
+ mContext,
+ mContext.getResources()
+ .getString(R.string.dvr_schedules_deletion_info, deletedInfo),
Toast.LENGTH_SHORT);
}
}
- /**
- * Action handler for {@link #ACTION_CREATE_SCHEDULE}.
- */
+ /** Action handler for {@link #ACTION_CREATE_SCHEDULE}. */
protected void onCreateSchedule(ScheduleRow row) {
if (row.getSchedule() == null) {
// This row has been deleted.
@@ -571,12 +624,15 @@ class ScheduleRowPresenter extends RowPresenter {
}
if (!row.isOnAir()) {
if (row.isScheduleCanceled()) {
- mDvrManager.updateScheduledRecording(ScheduledRecording.buildFrom(row.getSchedule())
- .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)
- .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule()))
- .build());
- String msg = mContext.getString(R.string.dvr_msg_program_scheduled,
- row.getSchedule().getProgramTitle());
+ mDvrManager.updateScheduledRecording(
+ ScheduledRecording.buildFrom(row.getSchedule())
+ .setState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)
+ .setPriority(mDvrManager.suggestHighestPriority(row.getSchedule()))
+ .build());
+ String msg =
+ mContext.getString(
+ R.string.dvr_msg_program_scheduled,
+ row.getSchedule().getProgramTitle());
ToastUtils.show(mContext, msg, Toast.LENGTH_SHORT);
} else if (mDvrManager.isConflicting(row.getSchedule())) {
mDvrManager.setHighestPriority(row.getSchedule());
@@ -584,9 +640,7 @@ class ScheduleRowPresenter extends RowPresenter {
}
}
- /**
- * Action handler for {@link #ACTION_REMOVE_SCHEDULE}.
- */
+ /** Action handler for {@link #ACTION_REMOVE_SCHEDULE}. */
protected void onRemoveSchedule(ScheduleRow row) {
if (row.getSchedule() == null) {
// This row has been deleted.
@@ -605,13 +659,19 @@ class ScheduleRowPresenter extends RowPresenter {
mDvrManager.removeScheduledRecording(row.getSchedule());
} else if (row.isRecordingNotStarted()) {
deletedInfo = getDeletedInfo(row);
- mDvrManager.updateScheduledRecording(ScheduledRecording.buildFrom(row.getSchedule())
- .setState(ScheduledRecording.STATE_RECORDING_CANCELED)
- .build());
+ mDvrManager.updateScheduledRecording(
+ ScheduledRecording.buildFrom(row.getSchedule())
+ .setState(ScheduledRecording.STATE_RECORDING_CANCELED)
+ .build());
+ } else if (row.isRecordingFailed()) {
+ deletedInfo = getDeletedInfo(row);
+ mDvrManager.removeScheduledRecording(row.getSchedule());
}
}
if (deletedInfo != null) {
- ToastUtils.show(mContext, mContext.getResources()
+ ToastUtils.show(
+ mContext,
+ mContext.getResources()
.getString(R.string.dvr_schedules_deletion_info, deletedInfo),
Toast.LENGTH_SHORT);
}
@@ -631,9 +691,7 @@ class ScheduleRowPresenter extends RowPresenter {
updateActionContainer(vh, selected);
}
- /**
- * Internal method for onRowViewSelected, can be customized by subclass.
- */
+ /** Internal method for onRowViewSelected, can be customized by subclass. */
private void updateActionContainer(ViewHolder vh, boolean selected) {
ScheduleRowViewHolder viewHolder = (ScheduleRowViewHolder) vh;
viewHolder.mSecondActionContainer.animate().setListener(null).cancel();
@@ -643,38 +701,43 @@ class ScheduleRowPresenter extends RowPresenter {
case 2:
prepareShowActionView(viewHolder.mSecondActionContainer);
prepareShowActionView(viewHolder.mFirstActionContainer);
- viewHolder.mPendingAnimationRunnable = new Runnable() {
- @Override
- public void run() {
- showActionView(viewHolder.mSecondActionContainer);
- showActionView(viewHolder.mFirstActionContainer);
- }
- };
+ viewHolder.mPendingAnimationRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ showActionView(viewHolder.mSecondActionContainer);
+ showActionView(viewHolder.mFirstActionContainer);
+ }
+ };
break;
case 1:
prepareShowActionView(viewHolder.mFirstActionContainer);
- viewHolder.mPendingAnimationRunnable = new Runnable() {
- @Override
- public void run() {
- hideActionView(viewHolder.mSecondActionContainer, View.GONE);
- showActionView(viewHolder.mFirstActionContainer);
- }
- };
+ viewHolder.mPendingAnimationRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ hideActionView(viewHolder.mSecondActionContainer, View.GONE);
+ showActionView(viewHolder.mFirstActionContainer);
+ }
+ };
if (mLastFocusedViewId == R.id.action_second_container) {
mLastFocusedViewId = R.id.info_container;
}
break;
case 0:
default:
- viewHolder.mPendingAnimationRunnable = new Runnable() {
- @Override
- public void run() {
- hideActionView(viewHolder.mSecondActionContainer, View.GONE);
- hideActionView(viewHolder.mFirstActionContainer, View.GONE);
- }
- };
+ viewHolder.mPendingAnimationRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ hideActionView(viewHolder.mSecondActionContainer, View.GONE);
+ hideActionView(viewHolder.mFirstActionContainer, View.GONE);
+ }
+ };
mLastFocusedViewId = R.id.info_container;
- SoftPreconditions.checkState(viewHolder.mInfoContainer.isFocusable(), TAG,
+ SoftPreconditions.checkState(
+ viewHolder.mInfoContainer.isFocusable(),
+ TAG,
"No focusable view in this row: " + viewHolder);
break;
}
@@ -685,7 +748,7 @@ class ScheduleRowPresenter extends RowPresenter {
// requestFocus() explicitly.
if (view.hasFocus()) {
viewHolder.mPendingAnimationRunnable.run();
- } else if (view.isFocusable()){
+ } else if (view.isFocusable()) {
view.requestFocus();
} else {
viewHolder.view.requestFocus();
@@ -705,17 +768,16 @@ class ScheduleRowPresenter extends RowPresenter {
view.setVisibility(View.VISIBLE);
}
- /**
- * Add animation when view is visible.
- */
+ /** Add animation when view is visible. */
private void showActionView(View view) {
- view.animate().alpha(1.0f).setInterpolator(new DecelerateInterpolator())
- .setDuration(mAnimationDuration).start();
+ view.animate()
+ .alpha(1.0f)
+ .setInterpolator(new DecelerateInterpolator())
+ .setDuration(mAnimationDuration)
+ .start();
}
- /**
- * Add animation when view change to invisible.
- */
+ /** Add animation when view change to invisible. */
private void hideActionView(View view, int visibility) {
if (view.getVisibility() != View.VISIBLE) {
if (view.getVisibility() != visibility) {
@@ -723,15 +785,19 @@ class ScheduleRowPresenter extends RowPresenter {
}
return;
}
- view.animate().alpha(0.0f).setInterpolator(new DecelerateInterpolator())
+ view.animate()
+ .alpha(0.0f)
+ .setInterpolator(new DecelerateInterpolator())
.setDuration(mAnimationDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(visibility);
- view.animate().setListener(null);
- }
- }).start();
+ .setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(visibility);
+ view.animate().setListener(null);
+ }
+ })
+ .start();
}
/**
@@ -742,8 +808,8 @@ class ScheduleRowPresenter extends RowPresenter {
protected int[] getAvailableActions(ScheduleRow row) {
if (row.getSchedule() != null) {
if (row.isRecordingInProgress()) {
- return new int[]{ACTION_STOP_RECORDING};
- } else if (row.isOnAir()) {
+ return new int[] {ACTION_STOP_RECORDING};
+ } else if (row.isOnAir() && !row.hasRecordedProgram()) {
if (row.isRecordingNotStarted()) {
if (canResolveConflict()) {
// The "START" action can change the conflict states.
@@ -754,8 +820,12 @@ class ScheduleRowPresenter extends RowPresenter {
} else if (row.isRecordingFinished()) {
return new int[] {ACTION_START_RECORDING};
} else {
- SoftPreconditions.checkState(false, TAG, "Invalid row state in checking the"
- + " available actions(on air): " + row);
+ SoftPreconditions.checkState(
+ false,
+ TAG,
+ "Invalid row state in checking the"
+ + " available actions(on air): "
+ + row);
}
} else {
if (row.isScheduleCanceled()) {
@@ -764,36 +834,39 @@ class ScheduleRowPresenter extends RowPresenter {
return new int[] {ACTION_REMOVE_SCHEDULE, ACTION_CREATE_SCHEDULE};
} else if (row.isRecordingNotStarted()) {
return new int[] {ACTION_REMOVE_SCHEDULE};
+ } else if (row.isRecordingFailed()) {
+ return new int[] {ACTION_REMOVE_SCHEDULE};
+ } else if (row.isRecordingFinished()) {
+ return new int[] {};
} else {
- SoftPreconditions.checkState(false, TAG, "Invalid row state in checking the"
- + " available actions(future schedule): " + row);
+ SoftPreconditions.checkState(
+ false,
+ TAG,
+ "Invalid row state in checking the"
+ + " available actions(future schedule): "
+ + row);
}
}
}
return null;
}
- /**
- * Check if the conflict can be resolved in this screen.
- */
+ /** Check if the conflict can be resolved in this screen. */
protected boolean canResolveConflict() {
return true;
}
- /**
- * Check if the schedule should be kept after removing it.
- */
+ /** Check if the schedule should be kept after removing it. */
protected boolean shouldKeepScheduleAfterRemoving() {
return false;
}
- /**
- * Checks if the row should be grayed out.
- */
+ /** Checks if the row should be grayed out. */
protected boolean shouldBeGrayedOut(ScheduleRow row) {
return row.getSchedule() == null
- || (row.isOnAir() && !row.isRecordingInProgress())
+ || (row.isOnAir() && !row.isRecordingInProgress() && !row.hasRecordedProgram())
|| mDvrManager.isConflicting(row.getSchedule())
- || row.isScheduleCanceled();
+ || row.isScheduleCanceled()
+ || row.isRecordingFailed();
}
}
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
index 715ecb8c..bbddc07f 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
@@ -18,12 +18,9 @@ package com.android.tv.dvr.ui.list;
import com.android.tv.data.Program;
import com.android.tv.dvr.data.SeriesRecording;
-
import java.util.List;
-/**
- * A base class for the rows for schedules' header.
- */
+/** A base class for the rows for schedules' header. */
abstract class SchedulesHeaderRow {
private String mTitle;
private String mDescription;
@@ -35,51 +32,37 @@ abstract class SchedulesHeaderRow {
mDescription = description;
}
- /**
- * Sets title.
- */
+ /** Sets title. */
public void setTitle(String title) {
mTitle = title;
}
- /**
- * Sets description.
- */
+ /** Sets description. */
public void setDescription(String description) {
mDescription = description;
}
- /**
- * Sets count of items.
- */
+ /** Sets count of items. */
public void setItemCount(int itemCount) {
mItemCount = itemCount;
}
- /**
- * Returns title.
- */
+ /** Returns title. */
public String getTitle() {
return mTitle;
}
- /**
- * Returns description.
- */
+ /** Returns description. */
public String getDescription() {
return mDescription;
}
- /**
- * Returns count of items.
- */
+ /** Returns count of items. */
public int getItemCount() {
return mItemCount;
}
- /**
- * The header row which represent the date.
- */
+ /** The header row which represent the date. */
public static class DateHeaderRow extends SchedulesHeaderRow {
private long mDeadLineMs;
@@ -88,47 +71,41 @@ abstract class SchedulesHeaderRow {
mDeadLineMs = deadLineMs;
}
- /**
- * Returns the latest time of the list which belongs to the header row.
- */
+ /** Returns the latest time of the list which belongs to the header row. */
public long getDeadLineMs() {
return mDeadLineMs;
}
}
- /**
- * The header row which represent the series recording.
- */
+ /** The header row which represent the series recording. */
public static class SeriesRecordingHeaderRow extends SchedulesHeaderRow {
private SeriesRecording mSeriesRecording;
private List<Program> mPrograms;
- public SeriesRecordingHeaderRow(String title, String description, int itemCount,
- SeriesRecording series, List<Program> programs) {
+ public SeriesRecordingHeaderRow(
+ String title,
+ String description,
+ int itemCount,
+ SeriesRecording series,
+ List<Program> programs) {
super(title, description, itemCount);
mSeriesRecording = series;
mPrograms = programs;
}
- /**
- * Returns the list of programs which belong to the series.
- */
+ /** Returns the list of programs which belong to the series. */
public List<Program> getPrograms() {
return mPrograms;
}
- /**
- * Returns the series recording, it is for series schedules list.
- */
+ /** Returns the series recording, it is for series schedules list. */
public SeriesRecording getSeriesRecording() {
return mSeriesRecording;
}
- /**
- * Sets the series recording.
- */
+ /** Sets the series recording. */
public void setSeriesRecording(SeriesRecording seriesRecording) {
mSeriesRecording = seriesRecording;
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
index fe2033ba..eb01aba2 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
@@ -27,16 +27,13 @@ import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
-/**
- * A base class for RowPresenter for {@link SchedulesHeaderRow}
- */
+/** A base class for RowPresenter for {@link SchedulesHeaderRow} */
abstract class SchedulesHeaderRowPresenter extends RowPresenter {
private Context mContext;
@@ -46,23 +43,20 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
mContext = context;
}
- /**
- * Returns the context.
- */
+ /** Returns the context. */
Context getContext() {
return mContext;
}
- /**
- * A ViewHolder for {@link SchedulesHeaderRow}.
- */
+ /** A ViewHolder for {@link SchedulesHeaderRow}. */
public static class SchedulesHeaderRowViewHolder extends RowPresenter.ViewHolder {
private TextView mTitle;
private TextView mDescription;
public SchedulesHeaderRowViewHolder(Context context, ViewGroup parent) {
- super(LayoutInflater.from(context).inflate(R.layout.dvr_schedules_header, parent,
- false));
+ super(
+ LayoutInflater.from(context)
+ .inflate(R.layout.dvr_schedules_header, parent, false));
mTitle = (TextView) view.findViewById(R.id.header_title);
mDescription = (TextView) view.findViewById(R.id.header_description);
}
@@ -77,9 +71,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
headerViewHolder.mDescription.setText(header.getDescription());
}
- /**
- * A presenter for {@link SchedulesHeaderRow.DateHeaderRow}.
- */
+ /** A presenter for {@link SchedulesHeaderRow.DateHeaderRow}. */
public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter {
public DateHeaderRowPresenter(Context context) {
super(context);
@@ -90,10 +82,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
return new DateHeaderRowViewHolder(getContext(), parent);
}
- /**
- * A ViewHolder for
- * {@link SchedulesHeaderRow.DateHeaderRow}.
- */
+ /** A ViewHolder for {@link SchedulesHeaderRow.DateHeaderRow}. */
public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
public DateHeaderRowViewHolder(Context context, ViewGroup parent) {
super(context, parent);
@@ -101,9 +90,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
}
- /**
- * A presenter for {@link SeriesRecordingHeaderRow}.
- */
+ /** A presenter for {@link SeriesRecordingHeaderRow}. */
public static class SeriesRecordingHeaderRowPresenter extends SchedulesHeaderRowPresenter {
private final boolean mLtr;
private final Drawable mSettingsDrawable;
@@ -116,8 +103,9 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
public SeriesRecordingHeaderRowPresenter(Context context) {
super(context);
- mLtr = context.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ mLtr =
+ context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
mSettingsDrawable = context.getDrawable(R.drawable.ic_settings);
mCancelDrawable = context.getDrawable(R.drawable.ic_dvr_cancel_large);
mResumeDrawable = context.getDrawable(R.drawable.ic_record_start);
@@ -134,8 +122,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
@Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder viewHolder, Object item) {
super.onBindRowViewHolder(viewHolder, item);
- SeriesHeaderRowViewHolder headerViewHolder =
- (SeriesHeaderRowViewHolder) viewHolder;
+ SeriesHeaderRowViewHolder headerViewHolder = (SeriesHeaderRowViewHolder) viewHolder;
SeriesRecordingHeaderRow header = (SeriesRecordingHeaderRow) item;
headerViewHolder.mSeriesSettingsButton.setVisibility(
header.getSeriesRecording().isStopped() ? View.INVISIBLE : View.VISIBLE);
@@ -148,46 +135,59 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
headerViewHolder.mToggleStartStopButton.setText(mCancelAllInfo);
setTextDrawable(headerViewHolder.mToggleStartStopButton, mCancelDrawable);
}
- headerViewHolder.mSeriesSettingsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(),
- header.getPrograms(), false, false, false, null);
- }
- });
- headerViewHolder.mToggleStartStopButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- if (header.getSeriesRecording().isStopped()) {
- // Reset priority to the highest.
- SeriesRecording seriesRecording = SeriesRecording
- .buildFrom(header.getSeriesRecording())
- .setPriority(TvApplication.getSingletons(getContext())
- .getDvrScheduleManager().suggestNewSeriesPriority())
- .build();
- TvApplication.getSingletons(getContext()).getDvrManager()
- .updateSeriesRecording(seriesRecording);
- DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(),
- header.getPrograms(), false, false, false, null);
- } else {
- DvrUiHelper.showCancelAllSeriesRecordingDialog(
- (DvrSchedulesActivity) view.getContext(),
- header.getSeriesRecording());
- }
- }
- });
+ headerViewHolder.mSeriesSettingsButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ DvrUiHelper.startSeriesSettingsActivity(
+ getContext(),
+ header.getSeriesRecording().getId(),
+ header.getPrograms(),
+ false,
+ false,
+ false,
+ null);
+ }
+ });
+ headerViewHolder.mToggleStartStopButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (header.getSeriesRecording().isStopped()) {
+ // Reset priority to the highest.
+ SeriesRecording seriesRecording =
+ SeriesRecording.buildFrom(header.getSeriesRecording())
+ .setPriority(
+ TvSingletons.getSingletons(getContext())
+ .getDvrScheduleManager()
+ .suggestNewSeriesPriority())
+ .build();
+ TvSingletons.getSingletons(getContext())
+ .getDvrManager()
+ .updateSeriesRecording(seriesRecording);
+ DvrUiHelper.startSeriesSettingsActivity(
+ getContext(),
+ header.getSeriesRecording().getId(),
+ header.getPrograms(),
+ false,
+ false,
+ false,
+ null);
+ } else {
+ DvrUiHelper.showCancelAllSeriesRecordingDialog(
+ (DvrSchedulesActivity) view.getContext(),
+ header.getSeriesRecording());
+ }
+ }
+ });
}
private void setTextDrawable(TextView textView, Drawable drawableStart) {
- textView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, null,
- null);
+ textView.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ drawableStart, null, null, null);
}
- /**
- * A ViewHolder for {@link SeriesRecordingHeaderRow}.
- */
+ /** A ViewHolder for {@link SeriesRecordingHeaderRow}. */
public static class SeriesHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
private final TextView mSeriesSettingsButton;
private final TextView mToggleStartStopButton;
@@ -196,33 +196,40 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
private final View mSelector;
private View mLastFocusedView;
+
public SeriesHeaderRowViewHolder(Context context, ViewGroup parent) {
super(context, parent);
- mLtr = context.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ mLtr =
+ context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
view.findViewById(R.id.button_container).setVisibility(View.VISIBLE);
mSeriesSettingsButton = (TextView) view.findViewById(R.id.series_settings);
mToggleStartStopButton =
(TextView) view.findViewById(R.id.series_toggle_start_stop);
mSelector = view.findViewById(R.id.selector);
- OnFocusChangeListener onFocusChangeListener = new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean focused) {
- view.post(new Runnable() {
+ OnFocusChangeListener onFocusChangeListener =
+ new View.OnFocusChangeListener() {
@Override
- public void run() {
- updateSelector(view);
+ public void onFocusChange(View view, boolean focused) {
+ view.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ updateSelector(view);
+ }
+ });
}
- });
- }
- };
+ };
mSeriesSettingsButton.setOnFocusChangeListener(onFocusChangeListener);
mToggleStartStopButton.setOnFocusChangeListener(onFocusChangeListener);
}
private void updateSelector(View focusedView) {
- int animationDuration = mSelector.getContext().getResources()
- .getInteger(android.R.integer.config_shortAnimTime);
+ int animationDuration =
+ mSelector
+ .getContext()
+ .getResources()
+ .getInteger(android.R.integer.config_shortAnimTime);
DecelerateInterpolator interpolator = new DecelerateInterpolator();
if (focusedView.hasFocus()) {
@@ -246,21 +253,38 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
// animate the selector in and to the proper width and translation X.
final float deltaWidth = lp.width - targetWidth;
mSelector.animate().cancel();
- mSelector.animate().translationX(targetTranslationX).alpha(1f)
- .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- // Set width to the proper width for this animation step.
- lp.width = targetWidth + Math.round(
- deltaWidth * (1f - animation.getAnimatedFraction()));
- mSelector.requestLayout();
- }
- }).setDuration(animationDuration).setInterpolator(interpolator).start();
+ mSelector
+ .animate()
+ .translationX(targetTranslationX)
+ .alpha(1f)
+ .setUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ // Set width to the proper width for this animation
+ // step.
+ lp.width =
+ targetWidth
+ + Math.round(
+ deltaWidth
+ * (1f
+ - animation
+ .getAnimatedFraction()));
+ mSelector.requestLayout();
+ }
+ })
+ .setDuration(animationDuration)
+ .setInterpolator(interpolator)
+ .start();
mLastFocusedView = focusedView;
} else if (mLastFocusedView == focusedView) {
mSelector.animate().setUpdateListener(null).cancel();
- mSelector.animate().alpha(0f).setDuration(animationDuration)
- .setInterpolator(interpolator).start();
+ mSelector
+ .animate()
+ .alpha(0f)
+ .setDuration(animationDuration)
+ .setInterpolator(interpolator)
+ .start();
mLastFocusedView = null;
}
}
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
index 6b6de8b8..9a9c94ea 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
@@ -1,18 +1,18 @@
/*
-* Copyright (C) 2016 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License
-*/
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
package com.android.tv.dvr.ui.list;
@@ -23,10 +23,8 @@ import android.os.Build;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.util.ArrayMap;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
@@ -35,16 +33,13 @@ import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-/**
- * An adapter for series schedule row.
- */
+/** An adapter for series schedule row. */
@TargetApi(Build.VERSION_CODES.N)
class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
private static final String TAG = "SeriesRowAdapter";
@@ -57,7 +52,9 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
private final Map<Long, Program> mPrograms = new ArrayMap<>();
private SeriesRecordingHeaderRow mHeaderRow;
- public SeriesScheduleRowAdapter(Context context, ClassPresenterSelector classPresenterSelector,
+ public SeriesScheduleRowAdapter(
+ Context context,
+ ClassPresenterSelector classPresenterSelector,
SeriesRecording seriesRecording) {
super(context, classPresenterSelector);
mSeriesRecording = seriesRecording;
@@ -67,7 +64,7 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
} else {
mInputId = null;
}
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ TvSingletons singletons = TvSingletons.getSingletons(context);
mDvrManager = singletons.getDvrManager();
mDataManager = singletons.getDvrDataManager();
setHasStableIds(true);
@@ -83,9 +80,7 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
super.stop();
}
- /**
- * Sets the programs to show.
- */
+ /** Sets the programs to show. */
public void setPrograms(List<Program> programs) {
if (programs == null) {
programs = Collections.emptyList();
@@ -95,8 +90,13 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
List<Program> sortedPrograms = new ArrayList<>(programs);
Collections.sort(sortedPrograms);
List<EpisodicProgramRow> rows = new ArrayList<>();
- mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(),
- null, sortedPrograms.size(), mSeriesRecording, programs);
+ mHeaderRow =
+ new SeriesRecordingHeaderRow(
+ mSeriesRecording.getTitle(),
+ null,
+ sortedPrograms.size(),
+ mSeriesRecording,
+ programs);
for (Program program : sortedPrograms) {
ScheduledRecording schedule =
mDataManager.getScheduledRecordingForProgramId(program.getId());
@@ -122,8 +122,14 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
++conflicts;
}
}
- return conflicts == 0 ? null : getContext().getResources().getQuantityString(
- R.plurals.dvr_series_schedules_header_description, conflicts, conflicts);
+ return conflicts == 0
+ ? null
+ : getContext()
+ .getResources()
+ .getQuantityString(
+ R.plurals.dvr_series_schedules_header_description,
+ conflicts,
+ conflicts);
}
@Override
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
index c8503e0d..263c579e 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
@@ -1,33 +1,30 @@
/*
-* Copyright (C) 2016 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License
-*/
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
package com.android.tv.dvr.ui.list;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
-/**
- * A RowPresenter for series schedule row.
- */
+/** A RowPresenter for series schedule row. */
class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
private static final String TAG = "SeriesRowPresenter";
@@ -35,16 +32,18 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
public SeriesScheduleRowPresenter(Context context) {
super(context);
- mLtr = context.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ mLtr =
+ context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
}
public static class SeriesScheduleRowViewHolder extends ScheduleRowViewHolder {
public SeriesScheduleRowViewHolder(View view, ScheduleRowPresenter presenter) {
super(view, presenter);
ViewGroup.LayoutParams lp = getTimeView().getLayoutParams();
- lp.width = view.getResources().getDimensionPixelSize(
- R.dimen.dvr_series_schedules_item_time_width);
+ lp.width =
+ view.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_series_schedules_item_time_width);
getTimeView().setLayoutParams(lp);
}
}
@@ -56,8 +55,8 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
@Override
protected String onGetRecordingTimeText(ScheduleRow row) {
- return Utils.getDurationString(getContext(), row.getStartTimeMs(), row.getEndTimeMs(),
- false, true, true, 0);
+ return Utils.getDurationString(
+ getContext(), row.getStartTimeMs(), row.getEndTimeMs(), false, true, true, 0);
}
@Override
@@ -71,11 +70,17 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
SeriesScheduleRowViewHolder viewHolder = (SeriesScheduleRowViewHolder) vh;
EpisodicProgramRow row = (EpisodicProgramRow) item;
if (getDvrManager().isConflicting(row.getSchedule())) {
- viewHolder.getProgramTitleView().setCompoundDrawablePadding(getContext()
- .getResources().getDimensionPixelOffset(
- R.dimen.dvr_schedules_warning_icon_padding));
- viewHolder.getProgramTitleView().setCompoundDrawablesRelativeWithIntrinsicBounds(
- R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
+ viewHolder
+ .getProgramTitleView()
+ .setCompoundDrawablePadding(
+ getContext()
+ .getResources()
+ .getDimensionPixelOffset(
+ R.dimen.dvr_schedules_warning_icon_padding));
+ viewHolder
+ .getProgramTitleView()
+ .setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
} else {
viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
@@ -88,16 +93,16 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
@Override
protected void onStartRecording(ScheduleRow row) {
- SoftPreconditions.checkState(row.getSchedule() == null, TAG,
- "Start request with the existing schedule: " + row);
+ SoftPreconditions.checkState(
+ row.getSchedule() == null, TAG, "Start request with the existing schedule: " + row);
row.setStartRecordingRequested(true);
getDvrManager().addScheduleWithHighestPriority(((EpisodicProgramRow) row).getProgram());
}
@Override
protected void onStopRecording(ScheduleRow row) {
- SoftPreconditions.checkState(row.getSchedule() != null, TAG,
- "Stop request with the null schedule: " + row);
+ SoftPreconditions.checkState(
+ row.getSchedule() != null, TAG, "Stop request with the null schedule: " + row);
row.setStopRecordingRequested(true);
getDvrManager().stopRecording(row.getSchedule());
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
index 6824cfe2..b8b19adc 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
@@ -23,16 +23,13 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.util.Utils;
-/**
- * Activity to play a {@link RecordedProgram}.
- */
+/** Activity to play a {@link RecordedProgram}. */
public class DvrPlaybackActivity extends Activity implements OnPinCheckedListener {
private static final String TAG = "DvrPlaybackActivity";
private static final boolean DEBUG = false;
@@ -42,13 +39,14 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene
@Override
public void onCreate(Bundle savedInstanceState) {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setIntent(createProgramIntent(getIntent()));
setContentView(R.layout.activity_dvr_playback);
- mOverlayFragment = (DvrPlaybackOverlayFragment) getFragmentManager()
- .findFragmentById(R.id.dvr_playback_controls_fragment);
+ mOverlayFragment =
+ (DvrPlaybackOverlayFragment)
+ getFragmentManager().findFragmentById(R.id.dvr_playback_controls_fragment);
}
@Override
@@ -68,7 +66,8 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
float density = getResources().getDisplayMetrics().density;
- mOverlayFragment.onWindowSizeChanged((int) (newConfig.screenWidthDp * density),
+ mOverlayFragment.onWindowSizeChanged(
+ (int) (newConfig.screenWidthDp * density),
(int) (newConfig.screenHeightDp * density));
}
@@ -91,4 +90,4 @@ public class DvrPlaybackActivity extends Activity implements OnPinCheckedListene
void setOnPinCheckListener(OnPinCheckedListener listener) {
mOnPinCheckedListener = listener;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
index 8ef0041d..a6352d95 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
@@ -17,14 +17,11 @@
package com.android.tv.dvr.ui.playback;
import android.content.Context;
-
import com.android.tv.R;
import com.android.tv.dvr.ui.browse.RecordedProgramPresenter;
import com.android.tv.dvr.ui.browse.RecordingCardView;
-/**
- * This class is used to generate Views and bind Objects for related recordings in DVR playback.
- */
+/** This class is used to generate Views and bind Objects for related recordings in DVR playback. */
class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
private final int mRelatedRecordingCardWidth;
private final int mRelatedRecordingCardHeight;
@@ -39,7 +36,12 @@ class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
@Override
public DvrItemViewHolder onCreateDvrItemViewHolder() {
- return new RecordedProgramViewHolder(new RecordingCardView(
- getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true), null);
+ return new RecordedProgramViewHolder(
+ new RecordingCardView(
+ getContext(),
+ mRelatedRecordingCardWidth,
+ mRelatedRecordingCardHeight,
+ true),
+ null);
}
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
index 1a6ae187..59c90d11 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
@@ -44,8 +44,8 @@ import com.android.tv.util.TimeShiftUtils;
import java.util.ArrayList;
/**
- * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and
- * send command to the media controller. It also helps to update playback states displayed in the
+ * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and send
+ * command to the media controller. It also helps to update playback states displayed in the
* fragment according to information the media session provides.
*/
class DvrPlaybackControlHelper extends PlaybackControlGlue {
@@ -74,8 +74,9 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
mMediaController = activity.getMediaController();
mMediaController.registerCallback(mMediaControllerCallback);
mTransportControls = mMediaController.getTransportControls();
- mExtraPaddingTopForNoDescription = activity.getResources()
- .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
+ mExtraPaddingTopForNoDescription =
+ activity.getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
mClosedCaptioningAction = new ClosedCaptioningAction(activity);
mMultiAudioAction = new MultiAudioAction(activity);
createControlsRowPresenter();
@@ -90,42 +91,47 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
private void createControlsRowPresenter() {
AbstractDetailsDescriptionPresenter detailsPresenter =
new AbstractDetailsDescriptionPresenter() {
- @Override
- protected void onBindDescription(
- AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, Object object) {
- PlaybackControlGlue glue = (PlaybackControlGlue) object;
- if (glue.hasValidMedia()) {
- viewHolder.getTitle().setText(glue.getMediaTitle());
- viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
- } else {
- viewHolder.getTitle().setText("");
- viewHolder.getSubtitle().setText("");
- }
- if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) {
- viewHolder.view.setPadding(viewHolder.view.getPaddingLeft(),
- mExtraPaddingTopForNoDescription,
- viewHolder.view.getPaddingRight(), viewHolder.view.getPaddingBottom());
- }
- }
- };
+ @Override
+ protected void onBindDescription(
+ AbstractDetailsDescriptionPresenter.ViewHolder viewHolder,
+ Object object) {
+ PlaybackControlGlue glue = (PlaybackControlGlue) object;
+ if (glue.hasValidMedia()) {
+ viewHolder.getTitle().setText(glue.getMediaTitle());
+ viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
+ } else {
+ viewHolder.getTitle().setText("");
+ viewHolder.getSubtitle().setText("");
+ }
+ if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) {
+ viewHolder.view.setPadding(
+ viewHolder.view.getPaddingLeft(),
+ mExtraPaddingTopForNoDescription,
+ viewHolder.view.getPaddingRight(),
+ viewHolder.view.getPaddingBottom());
+ }
+ }
+ };
PlaybackControlsRowPresenter presenter =
new PlaybackControlsRowPresenter(detailsPresenter) {
- @Override
- protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
- super.onBindRowViewHolder(vh, item);
- vh.setOnKeyListener(DvrPlaybackControlHelper.this);
- }
-
- @Override
- protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
- super.onUnbindRowViewHolder(vh);
- vh.setOnKeyListener(null);
- }
- };
- presenter.setProgressColor(getContext().getResources()
- .getColor(R.color.play_controls_progress_bar_watched));
- presenter.setBackgroundColor(getContext().getResources()
- .getColor(R.color.play_controls_body_background_enabled));
+ @Override
+ protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+ super.onBindRowViewHolder(vh, item);
+ vh.setOnKeyListener(DvrPlaybackControlHelper.this);
+ }
+
+ @Override
+ protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
+ super.onUnbindRowViewHolder(vh);
+ vh.setOnKeyListener(null);
+ }
+ };
+ presenter.setProgressColor(
+ getContext().getResources().getColor(R.color.play_controls_progress_bar_watched));
+ presenter.setBackgroundColor(
+ getContext()
+ .getResources()
+ .getColor(R.color.play_controls_body_background_enabled));
setControlsRowPresenter(presenter);
}
@@ -166,37 +172,40 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
return false;
}
int state = playbackState.getState();
- return state != PlaybackState.STATE_NONE && state != PlaybackState.STATE_CONNECTING
+ return state != PlaybackState.STATE_NONE
+ && state != PlaybackState.STATE_CONNECTING
&& state != PlaybackState.STATE_PAUSED;
}
- /**
- * Returns the ID of the media under playback.
- */
+ /** Returns the ID of the media under playback. */
public String getMediaId() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? null
+ return mediaMetadata == null
+ ? null
: mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
}
@Override
public CharSequence getMediaTitle() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? ""
+ return mediaMetadata == null
+ ? ""
: mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
}
@Override
public CharSequence getMediaSubtitle() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? ""
+ return mediaMetadata == null
+ ? ""
: mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE);
}
@Override
public int getMediaDuration() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? 0
+ return mediaMetadata == null
+ ? 0
: (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
}
@@ -225,19 +234,18 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
return (int) playbackState.getPosition();
}
- /**
- * Unregister media controller's callback.
- */
+ /** Unregister media controller's callback. */
void unregisterCallback() {
mMediaController.unregisterCallback(mMediaControllerCallback);
}
/**
* Update the secondary controls row.
- * @param hasClosedCaption {@code true} to show the closed caption selection button,
- * {@code false} to hide it.
- * @param hasMultiAudio {@code true} to show the audio track selection button,
- * {@code false} to hide it.
+ *
+ * @param hasClosedCaption {@code true} to show the closed caption selection button, {@code
+ * false} to hide it.
+ * @param hasMultiAudio {@code true} to show the audio track selection button, {@code false} to
+ * hide it.
*/
void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) {
if (hasClosedCaption) {
@@ -274,7 +282,7 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
mTransportControls.play();
} else if (speedId <= -PLAYBACK_SPEED_FAST_L0) {
mTransportControls.rewind();
- } else if (speedId >= PLAYBACK_SPEED_FAST_L0){
+ } else if (speedId >= PLAYBACK_SPEED_FAST_L0) {
mTransportControls.fastForward();
}
}
@@ -284,12 +292,10 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
mTransportControls.pause();
}
- /**
- * Notifies closed caption being enabled/disabled to update related UI.
- */
+ /** Notifies closed caption being enabled/disabled to update related UI. */
void onSubtitleTrackStateChanged(boolean enabled) {
- mClosedCaptioningAction.setIndex(enabled ?
- ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
+ mClosedCaptioningAction.setIndex(
+ enabled ? ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
}
private void onStateChanged(int state, long positionMs, int speedLevel) {
@@ -344,7 +350,9 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId);
DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment();
sideFragment.setArguments(args);
- mFragment.getFragmentManager().beginTransaction()
+ mFragment
+ .getFragmentManager()
+ .beginTransaction()
.hide(mFragment)
.replace(R.id.dvr_playback_side_fragment, sideFragment)
.addToBackStack(null)
@@ -367,7 +375,7 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
private static class MultiAudioAction extends MultiAction {
MultiAudioAction(Context context) {
super(AUDIO_ACTION_ID);
- setDrawables(new Drawable[]{context.getDrawable(R.drawable.ic_tvoption_multi_track)});
+ setDrawables(new Drawable[] {context.getDrawable(R.drawable.ic_tvoption_multi_track)});
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
index 843d2dbe..bef036eb 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
@@ -28,17 +28,15 @@ import android.media.tv.TvContract;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.text.TextUtils;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.data.RecordedProgram;
-import com.android.tv.util.ImageLoader;
import com.android.tv.util.TimeShiftUtils;
import com.android.tv.util.Utils;
+import com.android.tv.util.images.ImageLoader;
class DvrPlaybackMediaSessionHelper {
private static final String TAG = "DvrPlaybackMediaSessionHelper";
@@ -55,49 +53,52 @@ class DvrPlaybackMediaSessionHelper {
private final DvrWatchedPositionManager mDvrWatchedPositionManager;
private final ChannelDataManager mChannelDataManager;
- public DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag,
- DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment) {
+ public DvrPlaybackMediaSessionHelper(
+ Activity activity,
+ String mediaSessionTag,
+ DvrPlayer dvrPlayer,
+ DvrPlaybackOverlayFragment overlayFragment) {
mActivity = activity;
mDvrPlayer = dvrPlayer;
mDvrWatchedPositionManager =
- TvApplication.getSingletons(activity).getDvrWatchedPositionManager();
- mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager();
- mDvrPlayer.setCallback(new DvrPlayer.DvrPlayerCallback() {
- @Override
- public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {
- updateMediaSessionPlaybackState();
- }
+ TvSingletons.getSingletons(activity).getDvrWatchedPositionManager();
+ mChannelDataManager = TvSingletons.getSingletons(activity).getChannelDataManager();
+ mDvrPlayer.setCallback(
+ new DvrPlayer.DvrPlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {
+ updateMediaSessionPlaybackState();
+ }
- @Override
- public void onPlaybackPositionChanged(long positionMs) {
- updateMediaSessionPlaybackState();
- if (mDvrPlayer.isPlaybackPrepared()) {
- mDvrWatchedPositionManager
- .setWatchedPosition(mDvrPlayer.getProgram().getId(), positionMs);
- }
- }
+ @Override
+ public void onPlaybackPositionChanged(long positionMs) {
+ updateMediaSessionPlaybackState();
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrWatchedPositionManager.setWatchedPosition(
+ mDvrPlayer.getProgram().getId(), positionMs);
+ }
+ }
- @Override
- public void onPlaybackEnded() {
- // TODO: Deal with watched over recordings in DVR library
- RecordedProgram nextEpisode =
- overlayFragment.getNextEpisode(mDvrPlayer.getProgram());
- if (nextEpisode == null) {
- mDvrPlayer.reset();
- mActivity.finish();
- } else {
- Intent intent = new Intent(activity, DvrPlaybackActivity.class);
- intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId());
- mActivity.startActivity(intent);
- }
- }
- });
+ @Override
+ public void onPlaybackEnded() {
+ // TODO: Deal with watched over recordings in DVR library
+ RecordedProgram nextEpisode =
+ overlayFragment.getNextEpisode(mDvrPlayer.getProgram());
+ if (nextEpisode == null) {
+ mDvrPlayer.reset();
+ mActivity.finish();
+ } else {
+ Intent intent = new Intent(activity, DvrPlaybackActivity.class);
+ intent.putExtra(
+ Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId());
+ mActivity.startActivity(intent);
+ }
+ }
+ });
initializeMediaSession(mediaSessionTag);
}
- /**
- * Stops DVR player and release media session.
- */
+ /** Stops DVR player and release media session. */
public void release() {
if (mDvrPlayer != null) {
mDvrPlayer.reset();
@@ -108,13 +109,15 @@ class DvrPlaybackMediaSessionHelper {
}
}
- /**
- * Updates media session's playback state and speed.
- */
+ /** Updates media session's playback state and speed. */
public void updateMediaSessionPlaybackState() {
- mMediaSession.setPlaybackState(new PlaybackState.Builder()
- .setState(mDvrPlayer.getPlaybackState(), mDvrPlayer.getPlaybackPosition(),
- mSpeedLevel).build());
+ mMediaSession.setPlaybackState(
+ new PlaybackState.Builder()
+ .setState(
+ mDvrPlayer.getPlaybackState(),
+ mDvrPlayer.getPlaybackPosition(),
+ mSpeedLevel)
+ .build());
}
/**
@@ -132,42 +135,35 @@ class DvrPlaybackMediaSessionHelper {
}
}
- /**
- * Returns the recorded program now playing.
- */
+ /** Returns the recorded program now playing. */
public RecordedProgram getProgram() {
return mDvrPlayer.getProgram();
}
- /**
- * Checks if the recorded program is the same as now playing one.
- */
+ /** Checks if the recorded program is the same as now playing one. */
public boolean isCurrentProgram(RecordedProgram program) {
return program != null && program.equals(getProgram());
}
- /**
- * Returns playback state.
- */
+ /** Returns playback state. */
public int getPlaybackState() {
return mDvrPlayer.getPlaybackState();
}
- /**
- * Returns the underlying DVR player.
- */
+ /** Returns the underlying DVR player. */
public DvrPlayer getDvrPlayer() {
return mDvrPlayer;
}
private void initializeMediaSession(String mediaSessionTag) {
mMediaSession = new MediaSession(mActivity, mediaSessionTag);
- mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
- | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mNowPlayingCardWidth = mActivity.getResources()
- .getDimensionPixelSize(R.dimen.notif_card_img_max_width);
- mNowPlayingCardHeight = mActivity.getResources()
- .getDimensionPixelSize(R.dimen.notif_card_img_height);
+ mMediaSession.setFlags(
+ MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mNowPlayingCardWidth =
+ mActivity.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
+ mNowPlayingCardHeight =
+ mActivity.getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
mMediaSession.setCallback(new MediaSessionCallback());
mActivity.setMediaController(
new MediaController(mActivity, mMediaSession.getSessionToken()));
@@ -179,11 +175,17 @@ class DvrPlaybackMediaSessionHelper {
String cardTitleText = program.getTitle();
if (TextUtils.isEmpty(cardTitleText)) {
Channel channel = mChannelDataManager.getChannel(program.getChannelId());
- cardTitleText = (channel != null) ? channel.getDisplayName()
- : mActivity.getString(R.string.no_program_information);
+ cardTitleText =
+ (channel != null)
+ ? channel.getDisplayName()
+ : mActivity.getString(R.string.no_program_information);
}
- final MediaMetadata currentMetadata = updateMetadataTextInfo(program.getId(), cardTitleText,
- program.getDescription(), mProgramDurationMs);
+ final MediaMetadata currentMetadata =
+ updateMetadataTextInfo(
+ program.getId(),
+ cardTitleText,
+ program.getDescription(),
+ mProgramDurationMs);
String posterArtUri = program.getPosterArtUri();
if (posterArtUri == null) {
posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString();
@@ -192,12 +194,18 @@ class DvrPlaybackMediaSessionHelper {
mMediaSession.setActive(true);
}
- private void updatePosterArt(RecordedProgram program, MediaMetadata currentMetadata,
- @Nullable Bitmap posterArt, @Nullable String posterArtUri) {
+ private void updatePosterArt(
+ RecordedProgram program,
+ MediaMetadata currentMetadata,
+ @Nullable Bitmap posterArt,
+ @Nullable String posterArtUri) {
if (posterArt != null) {
updateMetadataImageInfo(program, currentMetadata, posterArt, 0);
} else if (posterArtUri != null) {
- ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth,
+ ImageLoader.loadBitmap(
+ mActivity,
+ posterArtUri,
+ mNowPlayingCardWidth,
mNowPlayingCardHeight,
new ProgramPosterArtCallback(mActivity, program, currentMetadata));
} else {
@@ -205,13 +213,12 @@ class DvrPlaybackMediaSessionHelper {
}
}
- private class ProgramPosterArtCallback extends
- ImageLoader.ImageLoaderCallback<Activity> {
+ private class ProgramPosterArtCallback extends ImageLoader.ImageLoaderCallback<Activity> {
private final RecordedProgram mRecordedProgram;
private final MediaMetadata mCurrentMetadata;
- public ProgramPosterArtCallback(Activity activity, RecordedProgram program,
- MediaMetadata metadata) {
+ public ProgramPosterArtCallback(
+ Activity activity, RecordedProgram program, MediaMetadata metadata) {
super(activity);
mRecordedProgram = program;
mCurrentMetadata = metadata;
@@ -225,8 +232,8 @@ class DvrPlaybackMediaSessionHelper {
}
}
- private MediaMetadata updateMetadataTextInfo(final long programId, final String title,
- final String subtitle, final long duration) {
+ private MediaMetadata updateMetadataTextInfo(
+ final long programId, final String title, final String subtitle, final long duration) {
MediaMetadata.Builder builder = new MediaMetadata.Builder();
builder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Long.toString(programId))
.putString(MediaMetadata.METADATA_KEY_TITLE, title)
@@ -239,8 +246,11 @@ class DvrPlaybackMediaSessionHelper {
return metadata;
}
- private void updateMetadataImageInfo(final RecordedProgram program,
- final MediaMetadata currentMetadata, final Bitmap posterArt, final int imageResId) {
+ private void updateMetadataImageInfo(
+ final RecordedProgram program,
+ final MediaMetadata currentMetadata,
+ final Bitmap posterArt,
+ final int imageResId) {
if (mMediaSession != null && (posterArt != null || imageResId != 0)) {
MediaMetadata.Builder builder = new MediaMetadata.Builder(currentMetadata);
if (posterArt != null) {
@@ -255,7 +265,8 @@ class DvrPlaybackMediaSessionHelper {
@Override
protected void onPostExecute(Bitmap programPosterArt) {
- if (mMediaSession != null && programPosterArt != null
+ if (mMediaSession != null
+ && programPosterArt != null
&& isCurrentProgram(program)) {
builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt);
mMediaSession.setMetadata(builder.build());
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
index 783ae682..d3374cfa 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
@@ -21,12 +21,12 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvTrackInfo;
-import android.os.Bundle;
import android.media.session.PlaybackState;
+import android.media.tv.TvContentRating;
import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
+import android.os.Bundle;
import android.support.v17.leanback.app.PlaybackFragment;
import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
@@ -37,14 +37,13 @@ import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
-import android.util.Log;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.BaseProgram;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dvr.DvrDataManager;
@@ -57,9 +56,8 @@ import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvTrackInfoUtils;
import com.android.tv.util.Utils;
-
-import java.util.List;
import java.util.ArrayList;
+import java.util.List;
public class DvrPlaybackOverlayFragment extends PlaybackFragment {
// TODO: Handles audio focus. Deals with block and ratings.
@@ -104,15 +102,25 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
- mVerticalPaddingBase = getActivity().getResources()
- .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base);
- mPaddingWithoutRelatedRow = getActivity().getResources()
- .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_related_row);
- mPaddingWithoutSecondaryRow = getActivity().getResources()
- .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_secondary_row);
- mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
- mContentRatingsManager = TvApplication.getSingletons(getContext())
- .getTvInputManagerHelper().getContentRatingsManager();
+ mVerticalPaddingBase =
+ getActivity()
+ .getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base);
+ mPaddingWithoutRelatedRow =
+ getActivity()
+ .getResources()
+ .getDimensionPixelOffset(
+ R.dimen.dvr_playback_overlay_padding_top_no_related_row);
+ mPaddingWithoutSecondaryRow =
+ getActivity()
+ .getResources()
+ .getDimensionPixelOffset(
+ R.dimen.dvr_playback_overlay_padding_top_no_secondary_row);
+ mDvrDataManager = TvSingletons.getSingletons(getActivity()).getDvrDataManager();
+ mContentRatingsManager =
+ TvSingletons.getSingletons(getContext())
+ .getTvInputManagerHelper()
+ .getContentRatingsManager();
if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
mDvrDataManager.addRecordedProgramLoadFinishedListener(
new DvrDataManager.OnRecordedProgramLoadFinishedListener() {
@@ -124,14 +132,14 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
preparePlayback(getActivity().getIntent());
}
}
- }
- );
+ });
} else if (!handleIntent(getActivity().getIntent(), true)) {
return;
}
Point size = new Point();
((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
- .getDisplay(Display.DEFAULT_DISPLAY).getSize(size);
+ .getDisplay(Display.DEFAULT_DISPLAY)
+ .getSize(size);
mWindowWidth = size.x;
mWindowHeight = size.y;
mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight;
@@ -152,19 +160,20 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view);
mBlockScreenView = getActivity().findViewById(R.id.block_screen);
mDvrPlayer = new DvrPlayer(mTvView);
- mMediaSessionHelper = new DvrPlaybackMediaSessionHelper(
- getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this);
+ mMediaSessionHelper =
+ new DvrPlaybackMediaSessionHelper(
+ getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this);
mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
mRelatedRecordingsRow = getRelatedRecordingsRow();
mDvrPlayer.setOnTracksAvailabilityChangedListener(
new DvrPlayer.OnTracksAvailabilityChangedListener() {
@Override
- public void onTracksAvailabilityChanged(boolean hasClosedCaption,
- boolean hasMultiAudio) {
+ public void onTracksAvailabilityChanged(
+ boolean hasClosedCaption, boolean hasMultiAudio) {
mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio);
if (hasClosedCaption) {
- mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE,
- mOnSubtitleTrackSelectedListener);
+ mDvrPlayer.setOnTrackSelectedListener(
+ TvTrackInfo.TYPE_SUBTITLE, mOnSubtitleTrackSelectedListener);
selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE);
} else {
mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null);
@@ -175,15 +184,18 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
updateVerticalPosition();
mPlaybackControlHelper.getHost().notifyPlaybackRowChanged();
}
- });
- mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() {
- @Override
- public void onAspectRatioChanged(float videoAspectRatio) {
- updateAspectRatio(videoAspectRatio);
- }
- });
- mPinChecked = getActivity().getIntent()
- .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
+ });
+ mDvrPlayer.setOnAspectRatioChangedListener(
+ new DvrPlayer.OnAspectRatioChangedListener() {
+ @Override
+ public void onAspectRatioChanged(float videoAspectRatio) {
+ updateAspectRatio(videoAspectRatio);
+ }
+ });
+ mPinChecked =
+ getActivity()
+ .getIntent()
+ .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
mDvrPlayer.setOnContentBlockedListener(
new DvrPlayer.OnContentBlockedListener() {
@Override
@@ -221,20 +233,25 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
PinDialogFragment.DIALOG_TAG);
}
});
- setOnItemViewClickedListener(new BaseOnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Object row) {
- if (itemViewHolder.view instanceof RecordingCardView) {
- setFadingEnabled(false);
- long programId = ((RecordedProgram) itemViewHolder.view.getTag()).getId();
- if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
- Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
- intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
- getContext().startActivity(intent);
- }
- }
- });
+ setOnItemViewClickedListener(
+ new BaseOnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(
+ Presenter.ViewHolder itemViewHolder,
+ Object item,
+ RowPresenter.ViewHolder rowViewHolder,
+ Object row) {
+ if (itemViewHolder.view instanceof RecordingCardView) {
+ setFadingEnabled(false);
+ long programId =
+ ((RecordedProgram) itemViewHolder.view.getTag()).getId();
+ if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
+ Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
+ getContext().startActivity(intent);
+ }
+ }
+ });
if (mProgram != null) {
setUpRows();
preparePlayback(getActivity().getIntent());
@@ -265,9 +282,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
super.onDestroy();
}
- /**
- * Passes the intent to the fragment.
- */
+ /** Passes the intent to the fragment. */
public void onNewIntent(Intent intent) {
if (mDvrDataManager.isRecordedProgramLoadFinished() && handleIntent(intent, false)) {
preparePlayback(intent);
@@ -275,8 +290,8 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
}
/**
- * Should be called when windows' size is changed in order to notify DVR player
- * to update it's view width/height and position.
+ * Should be called when windows' size is changed in order to notify DVR player to update it's
+ * view width/height and position.
*/
public void onWindowSizeChanged(final int windowWidth, final int windowHeight) {
mWindowWidth = windowWidth;
@@ -285,9 +300,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
updateAspectRatio(mAppliedAspectRatio);
}
- /**
- * Returns next recorded episode in the same series as now playing program.
- */
+ /** Returns next recorded episode in the same series as now playing program. */
public RecordedProgram getNextEpisode(RecordedProgram program) {
int position = mRelatedRecordingsRowAdapter.findInsertPosition(program);
if (position == mRelatedRecordingsRowAdapter.size()) {
@@ -299,9 +312,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
/**
* Returns the tracks of the give type of the current playback.
-
- * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
- * or {@link TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}.
+ *
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} or {@link
+ * TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}.
*/
public ArrayList<TvTrackInfo> getTracks(int trackType) {
if (trackType == TvTrackInfo.TYPE_AUDIO) {
@@ -312,18 +325,16 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
return null;
}
- /**
- * Returns the ID of the selected track of the given type.
- */
+ /** Returns the ID of the selected track of the given type. */
public String getSelectedTrackId(int trackType) {
return mDvrPlayer.getSelectedTrackId(trackType);
}
/**
* Returns the language setting of the given track type.
-
- * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
- * or {@link TvTrackInfo#TYPE_AUDIO}.
+ *
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} or {@link
+ * TvTrackInfo#TYPE_AUDIO}.
* @return {@code null} if no language has been set for the given track type.
*/
TvTrackInfo getTrackSetting(int trackType) {
@@ -332,10 +343,11 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
/**
* Selects the given audio or subtitle track for DVR playback.
- * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
- * or {@link TvTrackInfo#TYPE_AUDIO}.
+ *
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE} or {@link
+ * TvTrackInfo#TYPE_AUDIO}.
* @param selectedTrack {@code null} to disable the audio or subtitle track according to
- * trackType.
+ * trackType.
*/
void selectTrack(int trackType, TvTrackInfo selectedTrack) {
if (mDvrPlayer.isPlaybackPrepared()) {
@@ -346,8 +358,11 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
private boolean handleIntent(Intent intent, boolean finishActivity) {
mProgram = getProgramFromIntent(intent);
if (mProgram == null) {
- Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ getActivity(),
+ getString(R.string.dvr_program_not_found),
+ Toast.LENGTH_SHORT)
+ .show();
if (finishActivity) {
getActivity().finish();
}
@@ -359,12 +374,18 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
private void selectBestMatchedTrack(int trackType) {
TvTrackInfo selectedTrack = getTrackSetting(trackType);
if (selectedTrack != null) {
- TvTrackInfo bestMatchedTrack = TvTrackInfoUtils.getBestTrackInfo(getTracks(trackType),
- selectedTrack.getId(), selectedTrack.getLanguage(),
- trackType == TvTrackInfo.TYPE_AUDIO ? selectedTrack.getAudioChannelCount() : 0);
- if (bestMatchedTrack != null && (trackType == TvTrackInfo.TYPE_AUDIO || Utils
- .isEqualLanguage(bestMatchedTrack.getLanguage(),
- selectedTrack.getLanguage()))) {
+ TvTrackInfo bestMatchedTrack =
+ TvTrackInfoUtils.getBestTrackInfo(
+ getTracks(trackType),
+ selectedTrack.getId(),
+ selectedTrack.getLanguage(),
+ trackType == TvTrackInfo.TYPE_AUDIO
+ ? selectedTrack.getAudioChannelCount()
+ : 0);
+ if (bestMatchedTrack != null
+ && (trackType == TvTrackInfo.TYPE_AUDIO
+ || Utils.isEqualLanguage(
+ bestMatchedTrack.getLanguage(), selectedTrack.getLanguage()))) {
selectTrack(trackType, bestMatchedTrack);
return;
}
@@ -421,7 +442,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
}
if (mRelatedRecordingsRowAdapter.size() == 0) {
mRowsAdapter.remove(mRelatedRecordingsRow);
- } else if (wasEmpty){
+ } else if (wasEmpty) {
mRowsAdapter.add(mRelatedRecordingsRow);
}
updateVerticalPosition();
@@ -446,8 +467,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
private ListRow getRelatedRecordingsRow() {
mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
- HeaderItem header = new HeaderItem(0,
- getActivity().getString(R.string.dvr_playback_related_recordings));
+ HeaderItem header =
+ new HeaderItem(
+ 0, getActivity().getString(R.string.dvr_playback_related_recordings));
return new ListRow(header, mRelatedRecordingsRowAdapter);
}
@@ -457,8 +479,8 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
}
private long getSeekTimeFromIntent(Intent intent) {
- return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME,
- TvInputManager.TIME_SHIFT_INVALID_TIME);
+ return intent.getLongExtra(
+ Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, TvInputManager.TIME_SHIFT_INVALID_TIME);
}
private void updateVerticalPosition() {
@@ -491,4 +513,4 @@ public class DvrPlaybackOverlayFragment extends PlaybackFragment {
return item.getId();
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
index e49870f1..b4481df8 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
@@ -26,24 +26,16 @@ import android.transition.Transition;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
import com.android.tv.util.TvSettings;
-
import java.util.List;
import java.util.Locale;
-/**
- * Fragment for DVR playback closed-caption/multi-audio settings.
- */
+/** Fragment for DVR playback closed-caption/multi-audio settings. */
public class DvrPlaybackSideFragment extends GuidedStepFragment {
- /**
- * The tag for passing track infos to side fragments.
- */
+ /** The tag for passing track infos to side fragments. */
public static final String TRACK_INFOS = "dvr_key_track_infos";
- /**
- * The tag for passing selected track's ID to side fragments.
- */
+ /** The tag for passing selected track's ID to side fragments. */
public static final String SELECTED_TRACK_ID = "dvr_key_selected_track_id";
private static final int ACTION_ID_NO_SUBTITLE = -1;
@@ -60,39 +52,42 @@ public class DvrPlaybackSideFragment extends GuidedStepFragment {
mTrackInfos = getArguments().getParcelableArrayList(TRACK_INFOS);
mTrackType = mTrackInfos.get(0).getType();
mSelectedTrackId = getArguments().getString(SELECTED_TRACK_ID);
- mOverlayFragment = ((DvrPlaybackOverlayFragment) getFragmentManager()
- .findFragmentById(R.id.dvr_playback_controls_fragment));
+ mOverlayFragment =
+ ((DvrPlaybackOverlayFragment)
+ getFragmentManager().findFragmentById(R.id.dvr_playback_controls_fragment));
super.onCreate(savedInstanceState);
}
@Override
- public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateBackgroundView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View backgroundView = super.onCreateBackgroundView(inflater, container, savedInstanceState);
- backgroundView.setBackgroundColor(getResources()
- .getColor(R.color.lb_playback_controls_background_light));
+ backgroundView.setBackgroundColor(
+ getResources().getColor(R.color.lb_playback_controls_background_light));
return backgroundView;
}
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
if (mTrackType == TvTrackInfo.TYPE_SUBTITLE) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_ID_NO_SUBTITLE)
- .title(getString(R.string.closed_caption_option_item_off))
- .checkSetId(CHECK_SET_ID)
- .checked(mSelectedTrackId == null)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_NO_SUBTITLE)
+ .title(getString(R.string.closed_caption_option_item_off))
+ .checkSetId(CHECK_SET_ID)
+ .checked(mSelectedTrackId == null)
+ .build());
}
for (int i = 0; i < mTrackInfos.size(); i++) {
TvTrackInfo info = mTrackInfos.get(i);
boolean checked = TextUtils.equals(info.getId(), mSelectedTrackId);
- GuidedAction action = new GuidedAction.Builder(getActivity())
- .id(i)
- .title(getTrackLabel(info, i))
- .checkSetId(CHECK_SET_ID)
- .checked(checked)
- .build();
+ GuidedAction action =
+ new GuidedAction.Builder(getActivity())
+ .id(i)
+ .title(getTrackLabel(info, i))
+ .checkSetId(CHECK_SET_ID)
+ .checked(checked)
+ .build();
actions.add(action);
if (checked) {
mSelectedTrack = info;
@@ -136,8 +131,8 @@ public class DvrPlaybackSideFragment extends GuidedStepFragment {
if (track.getLanguage() != null) {
return new Locale(track.getLanguage()).getDisplayName();
}
- return track.getType() == TvTrackInfo.TYPE_SUBTITLE ?
- getString(R.string.closed_caption_unknown_language, trackIndex + 1)
+ return track.getType() == TvTrackInfo.TYPE_SUBTITLE
+ ? getString(R.string.closed_caption_unknown_language, trackIndex + 1)
: getString(R.string.multi_audio_unknown_language);
}
@@ -151,4 +146,4 @@ public class DvrPlaybackSideFragment extends GuidedStepFragment {
t.excludeTarget(R.id.guidedstep_background, true);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
index 7226c666..85bb31b2 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
@@ -24,9 +24,7 @@ import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.text.TextUtils;
import android.util.Log;
-
import com.android.tv.dvr.data.RecordedProgram;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -35,17 +33,13 @@ class DvrPlayer {
private static final String TAG = "DvrPlayer";
private static final boolean DEBUG = false;
- /**
- * The max rewinding speed supported by DVR player.
- */
+ /** The max rewinding speed supported by DVR player. */
public static final int MAX_REWIND_SPEED = 256;
- /**
- * The max fast-forwarding speed supported by DVR player.
- */
+ /** The max fast-forwarding speed supported by DVR player. */
public static final int MAX_FAST_FORWARD_SPEED = 256;
private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2);
- private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826
+ private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826
private RecordedProgram mProgram;
private long mInitialSeekPositionMs;
@@ -71,49 +65,39 @@ class DvrPlayer {
public static class DvrPlayerCallback {
/**
- * Called when the playback position is changed. The normal updating frequency is
- * around 1 sec., which is restricted to the implementation of
- * {@link android.media.tv.TvInputService}.
+ * Called when the playback position is changed. The normal updating frequency is around 1
+ * sec., which is restricted to the implementation of {@link
+ * android.media.tv.TvInputService}.
*/
- public void onPlaybackPositionChanged(long positionMs) { }
- /**
- * Called when the playback state or the playback speed is changed.
- */
- public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { }
- /**
- * Called when the playback toward the end.
- */
- public void onPlaybackEnded() { }
+ public void onPlaybackPositionChanged(long positionMs) {}
+ /** Called when the playback state or the playback speed is changed. */
+ public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {}
+ /** Called when the playback toward the end. */
+ public void onPlaybackEnded() {}
}
public interface OnAspectRatioChangedListener {
/**
* Called when the Video's aspect ratio is changed.
*
- * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios.
- * Listeners should handle it carefully.
+ * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios. Listeners
+ * should handle it carefully.
*/
void onAspectRatioChanged(float videoAspectRatio);
}
public interface OnContentBlockedListener {
- /**
- * Called when the Video's aspect ratio is changed.
- */
+ /** Called when the Video's aspect ratio is changed. */
void onContentBlocked(TvContentRating rating);
}
public interface OnTracksAvailabilityChangedListener {
- /**
- * Called when the Video's subtitle or audio tracks are changed.
- */
+ /** Called when the Video's subtitle or audio tracks are changed. */
void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio);
}
public interface OnTrackSelectedListener {
- /**
- * Called when certain subtitle or audio track is selected.
- */
+ /** Called when certain subtitle or audio track is selected. */
void onTrackSelected(String selectedTrackId);
}
@@ -143,9 +127,7 @@ class DvrPlayer {
mCallback.onPlaybackStateChanged(mPlaybackState, 1);
}
- /**
- * Resumes playback.
- */
+ /** Resumes playback. */
public void play() throws IllegalStateException {
if (DEBUG) Log.d(TAG, "play()");
if (!isPlaybackPrepared()) {
@@ -163,9 +145,7 @@ class DvrPlayer {
mCallback.onPlaybackStateChanged(mPlaybackState, 1);
}
- /**
- * Pauses playback.
- */
+ /** Pauses playback. */
public void pause() throws IllegalStateException {
if (DEBUG) Log.d(TAG, "pause()");
if (!isPlaybackPrepared()) {
@@ -187,8 +167,8 @@ class DvrPlayer {
}
/**
- * Fast-forwards playback with the given speed. If the given speed is larger than
- * {@value #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}.
+ * Fast-forwards playback with the given speed. If the given speed is larger than {@value
+ * #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}.
*/
public void fastForward(int speed) throws IllegalStateException {
if (DEBUG) Log.d(TAG, "fastForward()");
@@ -209,8 +189,8 @@ class DvrPlayer {
}
/**
- * Rewinds playback with the given speed. If the given speed is larger than
- * {@value #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}.
+ * Rewinds playback with the given speed. If the given speed is larger than {@value
+ * #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}.
*/
public void rewind(int speed) throws IllegalStateException {
if (DEBUG) Log.d(TAG, "rewind()");
@@ -230,9 +210,7 @@ class DvrPlayer {
mCallback.onPlaybackStateChanged(mPlaybackState, speed);
}
- /**
- * Seeks playback to the specified position.
- */
+ /** Seeks playback to the specified position. */
public void seekTo(long positionMs) throws IllegalStateException {
if (DEBUG) Log.d(TAG, "seekTo()");
if (!isPlaybackPrepared()) {
@@ -244,17 +222,15 @@ class DvrPlayer {
positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS);
if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs);
mTvView.timeShiftSeekTo(positionMs + mStartPositionMs);
- if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING ||
- mPlaybackState == PlaybackState.STATE_REWINDING) {
+ if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
+ || mPlaybackState == PlaybackState.STATE_REWINDING) {
mPlaybackState = PlaybackState.STATE_PLAYING;
mTvView.timeShiftResume();
mCallback.onPlaybackStateChanged(mPlaybackState, 1);
}
}
- /**
- * Resets playback.
- */
+ /** Resets playback. */
public void reset() {
if (DEBUG) Log.d(TAG, "reset()");
mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1);
@@ -269,9 +245,7 @@ class DvrPlayer {
mSelectedSubtitleTrackId = null;
}
- /**
- * Sets callbacks for playback.
- */
+ /** Sets callbacks for playback. */
public void setCallback(DvrPlayerCallback callback) {
if (callback != null) {
mCallback = callback;
@@ -280,23 +254,17 @@ class DvrPlayer {
}
}
- /**
- * Sets the listener to aspect ratio changing.
- */
+ /** Sets the listener to aspect ratio changing. */
public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) {
mOnAspectRatioChangedListener = listener;
}
- /**
- * Sets the listener to content blocking.
- */
+ /** Sets the listener to content blocking. */
public void setOnContentBlockedListener(OnContentBlockedListener listener) {
mOnContentBlockedListener = listener;
}
- /**
- * Sets the listener to tracks changing.
- */
+ /** Sets the listener to tracks changing. */
public void setOnTracksAvailabilityChangedListener(
OnTracksAvailabilityChangedListener listener) {
mOnTracksAvailabilityChangedListener = listener;
@@ -305,8 +273,8 @@ class DvrPlayer {
/**
* Sets the listener to tracks of the given type being selected.
*
- * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO}
- * or {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO} or {@link
+ * TvTrackInfo#TYPE_SUBTITLE}.
*/
public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) {
if (trackType == TvTrackInfo.TYPE_AUDIO) {
@@ -316,9 +284,7 @@ class DvrPlayer {
}
}
- /**
- * Gets the listener to tracks of the given type being selected.
- */
+ /** Gets the listener to tracks of the given type being selected. */
public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) {
if (trackType == TvTrackInfo.TYPE_AUDIO) {
return mOnAudioTrackSelectedListener;
@@ -328,9 +294,7 @@ class DvrPlayer {
return null;
}
- /**
- * Sets recorded programs for playback. If the player is playing another program, stops it.
- */
+ /** Sets recorded programs for playback. If the player is playing another program, stops it. */
public void setProgram(RecordedProgram program, long initialSeekPositionMs) {
if (mProgram != null && mProgram.equals(program)) {
return;
@@ -342,51 +306,37 @@ class DvrPlayer {
mProgram = program;
}
- /**
- * Returns the recorded program now playing.
- */
+ /** Returns the recorded program now playing. */
public RecordedProgram getProgram() {
return mProgram;
}
- /**
- * Returns the currrent playback posistion in msecs.
- */
+ /** Returns the currrent playback posistion in msecs. */
public long getPlaybackPosition() {
return mTimeShiftCurrentPositionMs;
}
- /**
- * Returns the playback speed currently used.
- */
+ /** Returns the playback speed currently used. */
public int getPlaybackSpeed() {
return (int) mPlaybackParams.getSpeed();
}
- /**
- * Returns the playback state defined in {@link android.media.session.PlaybackState}.
- */
+ /** Returns the playback state defined in {@link android.media.session.PlaybackState}. */
public int getPlaybackState() {
return mPlaybackState;
}
- /**
- * Returns the subtitle tracks of the current playback.
- */
+ /** Returns the subtitle tracks of the current playback. */
public ArrayList<TvTrackInfo> getSubtitleTracks() {
return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE));
}
- /**
- * Returns the audio tracks of the current playback.
- */
+ /** Returns the audio tracks of the current playback. */
public ArrayList<TvTrackInfo> getAudioTracks() {
return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_AUDIO));
}
- /**
- * Returns the ID of the selected track of the given type.
- */
+ /** Returns the ID of the selected track of the given type. */
public String getSelectedTrackId(int trackType) {
if (trackType == TvTrackInfo.TYPE_AUDIO) {
return mSelectedAudioTrackId;
@@ -396,9 +346,7 @@ class DvrPlayer {
return null;
}
- /**
- * Returns if playback of the recorded program is started.
- */
+ /** Returns if playback of the recorded program is started. */
public boolean isPlaybackPrepared() {
return mPlaybackState != PlaybackState.STATE_NONE
&& mPlaybackState != PlaybackState.STATE_CONNECTING;
@@ -449,125 +397,138 @@ class DvrPlayer {
}
private void setTvViewCallbacks() {
- mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() {
- @Override
- public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
- if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs);
- mStartPositionMs = timeMs;
- if (mTimeShiftPlayAvailable) {
- resumeToWatchedPositionIfNeeded();
- }
- }
+ mTvView.setTimeShiftPositionCallback(
+ new TvView.TimeShiftPositionCallback() {
+ @Override
+ public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
+ if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs);
+ mStartPositionMs = timeMs;
+ if (mTimeShiftPlayAvailable) {
+ resumeToWatchedPositionIfNeeded();
+ }
+ }
- @Override
- public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
- if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs);
- if (!mTimeShiftPlayAvailable) {
- // Workaround of b/31436263
- return;
- }
- // Workaround of b/32211561, TIF won't report start position when TIS report
- // its start position as 0. In that case, we have to do the prework of playback
- // on the first time we get current position, and the start position should be 0
- // at that time.
- if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) {
- mStartPositionMs = 0;
- resumeToWatchedPositionIfNeeded();
- }
- timeMs -= mStartPositionMs;
- if (mPlaybackState == PlaybackState.STATE_REWINDING
- && timeMs <= REWIND_POSITION_MARGIN_MS) {
- play();
- } else {
- mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0);
- mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs);
- if (timeMs >= mProgram.getDurationMillis()) {
- pause();
- mCallback.onPlaybackEnded();
+ @Override
+ public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
+ if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs);
+ if (!mTimeShiftPlayAvailable) {
+ // Workaround of b/31436263
+ return;
+ }
+ // Workaround of b/32211561, TIF won't report start position when TIS report
+ // its start position as 0. In that case, we have to do the prework of
+ // playback
+ // on the first time we get current position, and the start position should
+ // be 0
+ // at that time.
+ if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) {
+ mStartPositionMs = 0;
+ resumeToWatchedPositionIfNeeded();
+ }
+ timeMs -= mStartPositionMs;
+ if (mPlaybackState == PlaybackState.STATE_REWINDING
+ && timeMs <= REWIND_POSITION_MARGIN_MS) {
+ play();
+ } else {
+ mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0);
+ mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs);
+ if (timeMs >= mProgram.getDurationMillis()) {
+ pause();
+ mCallback.onPlaybackEnded();
+ }
+ }
}
- }
- }
- });
- mTvView.setCallback(new TvView.TvInputCallback() {
- @Override
- public void onTimeShiftStatusChanged(String inputId, int status) {
- if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status);
- if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
- && mPlaybackState == PlaybackState.STATE_CONNECTING) {
- mTimeShiftPlayAvailable = true;
- if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
- // onTimeShiftStatusChanged is sometimes called after
- // onTimeShiftStartPositionChanged is called. In this case,
- // resumeToWatchedPositionIfNeeded needs to be called here.
- resumeToWatchedPositionIfNeeded();
+ });
+ mTvView.setCallback(
+ new TvView.TvInputCallback() {
+ @Override
+ public void onTimeShiftStatusChanged(String inputId, int status) {
+ if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status);
+ if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
+ && mPlaybackState == PlaybackState.STATE_CONNECTING) {
+ mTimeShiftPlayAvailable = true;
+ if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
+ // onTimeShiftStatusChanged is sometimes called after
+ // onTimeShiftStartPositionChanged is called. In this case,
+ // resumeToWatchedPositionIfNeeded needs to be called here.
+ resumeToWatchedPositionIfNeeded();
+ }
+ }
}
- }
- }
-
- @Override
- public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
- boolean hasClosedCaption =
- !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty();
- boolean hasMultiAudio = mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1;
- if ((hasClosedCaption != mHasClosedCaption || hasMultiAudio != mHasMultiAudio)
- && mOnTracksAvailabilityChangedListener != null) {
- mOnTracksAvailabilityChangedListener
- .onTracksAvailabilityChanged(hasClosedCaption, hasMultiAudio);
- }
- mHasClosedCaption = hasClosedCaption;
- mHasMultiAudio = hasMultiAudio;
- }
- @Override
- public void onTrackSelected(String inputId, int type, String trackId) {
- if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) {
- setSelectedTrackId(type, trackId);
- OnTrackSelectedListener listener = getOnTrackSelectedListener(type);
- if (listener != null) {
- listener.onTrackSelected(trackId);
+ @Override
+ public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
+ boolean hasClosedCaption =
+ !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty();
+ boolean hasMultiAudio =
+ mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1;
+ if ((hasClosedCaption != mHasClosedCaption
+ || hasMultiAudio != mHasMultiAudio)
+ && mOnTracksAvailabilityChangedListener != null) {
+ mOnTracksAvailabilityChangedListener.onTracksAvailabilityChanged(
+ hasClosedCaption, hasMultiAudio);
+ }
+ mHasClosedCaption = hasClosedCaption;
+ mHasMultiAudio = hasMultiAudio;
}
- } else if (type == TvTrackInfo.TYPE_VIDEO && trackId != null
- && mOnAspectRatioChangedListener != null) {
- List<TvTrackInfo> trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
- if (trackInfos != null) {
- for (TvTrackInfo trackInfo : trackInfos) {
- if (trackInfo.getId().equals(trackId)) {
- float videoAspectRatio;
- int videoWidth = trackInfo.getVideoWidth();
- int videoHeight = trackInfo.getVideoHeight();
- if (videoWidth > 0 && videoHeight > 0) {
- videoAspectRatio = trackInfo.getVideoPixelAspectRatio()
- * trackInfo.getVideoWidth() / trackInfo.getVideoHeight();
- } else {
- // Aspect ratio is unknown. Pass the message to listeners.
- videoAspectRatio = 0;
- }
- if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
- if (mAspectRatio != videoAspectRatio || videoAspectRatio == 0) {
- mOnAspectRatioChangedListener
- .onAspectRatioChanged(videoAspectRatio);
- mAspectRatio = videoAspectRatio;
- return;
+
+ @Override
+ public void onTrackSelected(String inputId, int type, String trackId) {
+ if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) {
+ setSelectedTrackId(type, trackId);
+ OnTrackSelectedListener listener = getOnTrackSelectedListener(type);
+ if (listener != null) {
+ listener.onTrackSelected(trackId);
+ }
+ } else if (type == TvTrackInfo.TYPE_VIDEO
+ && trackId != null
+ && mOnAspectRatioChangedListener != null) {
+ List<TvTrackInfo> trackInfos =
+ mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
+ if (trackInfos != null) {
+ for (TvTrackInfo trackInfo : trackInfos) {
+ if (trackInfo.getId().equals(trackId)) {
+ float videoAspectRatio;
+ int videoWidth = trackInfo.getVideoWidth();
+ int videoHeight = trackInfo.getVideoHeight();
+ if (videoWidth > 0 && videoHeight > 0) {
+ videoAspectRatio =
+ trackInfo.getVideoPixelAspectRatio()
+ * trackInfo.getVideoWidth()
+ / trackInfo.getVideoHeight();
+ } else {
+ // Aspect ratio is unknown. Pass the message to
+ // listeners.
+ videoAspectRatio = 0;
+ }
+ if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
+ if (mAspectRatio != videoAspectRatio
+ || videoAspectRatio == 0) {
+ mOnAspectRatioChangedListener.onAspectRatioChanged(
+ videoAspectRatio);
+ mAspectRatio = videoAspectRatio;
+ return;
+ }
+ }
}
}
}
}
- }
- }
- @Override
- public void onContentBlocked(String inputId, TvContentRating rating) {
- if (mOnContentBlockedListener != null) {
- mOnContentBlockedListener.onContentBlocked(rating);
- }
- }
- });
+ @Override
+ public void onContentBlocked(String inputId, TvContentRating rating) {
+ if (mOnContentBlockedListener != null) {
+ mOnContentBlockedListener.onContentBlocked(rating);
+ }
+ }
+ });
}
private void resumeToWatchedPositionIfNeeded() {
if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
- mTvView.timeShiftSeekTo(getRealSeekPosition(mInitialSeekPositionMs,
- SEEK_POSITION_MARGIN_MS) + mStartPositionMs);
+ mTvView.timeShiftSeekTo(
+ getRealSeekPosition(mInitialSeekPositionMs, SEEK_POSITION_MARGIN_MS)
+ + mStartPositionMs);
mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
}
if (mPauseOnPrepared) {
@@ -580,4 +541,4 @@ class DvrPlayer {
}
mCallback.onPlaybackStateChanged(mPlaybackState, 1);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/experiments/ExperimentFlag.java b/src/com/android/tv/experiments/ExperimentFlag.java
deleted file mode 100644
index c0cbd643..00000000
--- a/src/com/android/tv/experiments/ExperimentFlag.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.experiments;
-
-import android.support.annotation.VisibleForTesting;
-
-/**
- * Experiments return values based on user, device and other criteria.
- */
-public final class ExperimentFlag<T> {
-
- private static boolean sAllowOverrides = false;
-
- @VisibleForTesting
- public static void initForTest() {
- sAllowOverrides = true;
- }
-
- /** Returns a boolean experiment */
- public static ExperimentFlag<Boolean> createFlag(
- boolean defaultValue) {
- return new ExperimentFlag<>(
- defaultValue);
- }
-
- private final T mDefaultValue;
-
- private T mOverrideValue = null;
- private boolean mOverridden = false;
-
- private ExperimentFlag(
- T defaultValue) {
- mDefaultValue = defaultValue;
- }
-
- /** Returns value for this experiment */
- public T get() {
- return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue;
- }
-
- @VisibleForTesting
- public void override(T t) {
- if (sAllowOverrides) {
- mOverridden = true;
- mOverrideValue = t;
- }
- }
-
- @VisibleForTesting
- public void resetOverride() {
- mOverridden = false;
- }
-
-
-
-}
diff --git a/src/com/android/tv/experiments/Experiments.java b/src/com/android/tv/experiments/Experiments.java
deleted file mode 100644
index 53cce979..00000000
--- a/src/com/android/tv/experiments/Experiments.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.experiments;
-
-import static com.android.tv.experiments.ExperimentFlag.createFlag;
-
-import com.android.tv.common.BuildConfig;
-
-/**
- * Set of experiments visible in AOSP.
- *
- * <p>This file is maintained by hand.
- */
-public final class Experiments {
- public static final ExperimentFlag<Boolean> CLOUD_EPG = createFlag(
- true);
-
- public static final ExperimentFlag<Boolean> ENABLE_UNRATED_CONTENT_SETTINGS =
- createFlag(
- false);
-
- /**
- * Allow developer features such as the dev menu and other aids.
- *
- * <p>These features are available to select users(aka fishfooders) on production builds.
- */
- public static final ExperimentFlag<Boolean> ENABLE_DEVELOPER_FEATURES = createFlag(
- BuildConfig.ENG);
-
- private Experiments() {}
-}
diff --git a/src/com/android/tv/guide/GenreListAdapter.java b/src/com/android/tv/guide/GenreListAdapter.java
index ce19eb2d..b4baf421 100644
--- a/src/com/android/tv/guide/GenreListAdapter.java
+++ b/src/com/android/tv/guide/GenreListAdapter.java
@@ -24,15 +24,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.data.GenreItems;
-
import java.util.List;
-/**
- * Adapts the genre items obtained from {@link GenreItems} to the program guide side panel.
- */
+/** Adapts the genre items obtained from {@link GenreItems} to the program guide side panel. */
class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHolder> {
private static final String TAG = "GenreListAdapter";
private static final boolean DEBUG = false;
@@ -45,13 +41,14 @@ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHol
GenreListAdapter(Context context, ProgramManager programManager, ProgramGuide guide) {
mContext = context;
mProgramManager = programManager;
- mProgramManager.addListener(new ProgramManager.ListenerAdapter() {
- @Override
- public void onGenresUpdated() {
- mGenreLabels = GenreItems.getLabels(mContext);
- notifyDataSetChanged();
- }
- });
+ mProgramManager.addListener(
+ new ProgramManager.ListenerAdapter() {
+ @Override
+ public void onGenresUpdated() {
+ mGenreLabels = GenreItems.getLabels(mContext);
+ notifyDataSetChanged();
+ }
+ });
mProgramGuide = guide;
}
@@ -80,23 +77,24 @@ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHol
@Override
public GenreRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
- itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View view) {
- // Animation is not meaningful now, skip it.
- view.getStateListAnimator().jumpToCurrentState();
- }
-
- @Override
- public void onViewDetachedFromWindow(View view) {
- // Do nothing
- }
- });
+ itemView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ // Animation is not meaningful now, skip it.
+ view.getStateListAnimator().jumpToCurrentState();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ // Do nothing
+ }
+ });
return new GenreRowHolder(itemView, mProgramGuide);
}
- static class GenreRowHolder extends RecyclerView.ViewHolder implements
- View.OnFocusChangeListener {
+ static class GenreRowHolder extends RecyclerView.ViewHolder
+ implements View.OnFocusChangeListener {
private final ProgramGuide mProgramGuide;
private int mGenreId;
@@ -119,8 +117,13 @@ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHol
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) {
if (DEBUG) {
- Log.d(TAG, "onFocusChanged " + ((TextView) view).getText()
- + "(" + mGenreId + ") hasFocus");
+ Log.d(
+ TAG,
+ "onFocusChanged "
+ + ((TextView) view).getText()
+ + "("
+ + mGenreId
+ + ") hasFocus");
}
mProgramGuide.requestGenreChange(mGenreId);
}
diff --git a/src/com/android/tv/guide/GuideUtils.java b/src/com/android/tv/guide/GuideUtils.java
index 403d00b5..51c14fd4 100644
--- a/src/com/android/tv/guide/GuideUtils.java
+++ b/src/com/android/tv/guide/GuideUtils.java
@@ -17,11 +17,9 @@
package com.android.tv.guide;
import android.graphics.Rect;
-import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
-
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@@ -30,8 +28,8 @@ class GuideUtils {
private static int sWidthPerHour = 0;
/**
- * Sets the width in pixels that corresponds to an hour in program guide.
- * Assume that this is called from main thread only, so, no synchronization.
+ * Sets the width in pixels that corresponds to an hour in program guide. Assume that this is
+ * called from main thread only, so, no synchronization.
*/
static void setWidthPerHour(int widthPerHour) {
sWidthPerHour = widthPerHour;
@@ -44,30 +42,29 @@ class GuideUtils {
return (int) (millis * sWidthPerHour / TimeUnit.HOURS.toMillis(1));
}
- /**
- * Gets the number of pixels in program guide table that corresponds to the given range.
- */
+ /** Gets the number of pixels in program guide table that corresponds to the given range. */
static int convertMillisToPixel(long startMillis, long endMillis) {
// Convert to pixels first to avoid accumulation of rounding errors.
return GuideUtils.convertMillisToPixel(endMillis)
- GuideUtils.convertMillisToPixel(startMillis);
}
- /**
- * Gets the time in millis that corresponds to the given pixels in the program guide.
- */
+ /** Gets the time in millis that corresponds to the given pixels in the program guide. */
static long convertPixelToMillis(int pixel) {
return pixel * TimeUnit.HOURS.toMillis(1) / sWidthPerHour;
}
/**
* Return the view should be focused in the given program row according to the focus range.
-
+ *
* @param keepCurrentProgramFocused If {@code true}, focuses on the current program if possible,
- * else falls back the general logic.
+ * else falls back the general logic.
*/
- static View findNextFocusedProgram(View programRow, int focusRangeLeft,
- int focusRangeRight, boolean keepCurrentProgramFocused) {
+ static View findNextFocusedProgram(
+ View programRow,
+ int focusRangeLeft,
+ int focusRangeRight,
+ boolean keepCurrentProgramFocused) {
ArrayList<View> focusables = new ArrayList<>();
findFocusables(programRow, focusables);
@@ -102,9 +99,10 @@ class GuideUtils {
maxFullyOverlappedWidth = width;
}
} else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) {
- int overlappedWidth = (focusRangeLeft <= focusableRect.left) ?
- focusRangeRight - focusableRect.left
- : focusableRect.right - focusRangeLeft;
+ int overlappedWidth =
+ (focusRangeLeft <= focusableRect.left)
+ ? focusRangeRight - focusableRect.left
+ : focusableRect.right - focusRangeLeft;
if (overlappedWidth > maxPartiallyOverlappedWidth) {
nextFocusIndex = i;
maxPartiallyOverlappedWidth = overlappedWidth;
@@ -118,16 +116,14 @@ class GuideUtils {
}
/**
- * Returns {@code true} if the program displayed in the give
- * {@link com.android.tv.guide.ProgramItemView} is a current program.
+ * Returns {@code true} if the program displayed in the give {@link
+ * com.android.tv.guide.ProgramItemView} is a current program.
*/
static boolean isCurrentProgram(ProgramItemView view) {
return view.getTableEntry().isCurrentProgram();
}
- /**
- * Returns {@code true} if the given view is a descendant of the give container.
- */
+ /** Returns {@code true} if the given view is a descendant of the give container. */
static boolean isDescendant(ViewGroup container, View view) {
if (view == null) {
return false;
@@ -152,5 +148,5 @@ class GuideUtils {
}
}
- private GuideUtils() { }
+ private GuideUtils() {}
}
diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java
index 58436425..caafb045 100644
--- a/src/com/android/tv/guide/ProgramGrid.java
+++ b/src/com/android/tv/guide/ProgramGrid.java
@@ -25,15 +25,11 @@ import android.util.Log;
import android.util.Range;
import android.view.View;
import android.view.ViewTreeObserver;
-
import com.android.tv.R;
import com.android.tv.ui.OnRepeatedKeyInterceptListener;
-
import java.util.concurrent.TimeUnit;
-/**
- * A {@link VerticalGridView} for the program table view.
- */
+/** A {@link VerticalGridView} for the program table view. */
public class ProgramGrid extends VerticalGridView {
private static final String TAG = "ProgramGrid";
@@ -84,7 +80,7 @@ public class ProgramGrid extends VerticalGridView {
private final int mRowHeight;
private final int mDetailHeight;
- private final int mSelectionRow; // Row that is focused
+ private final int mSelectionRow; // Row that is focused
private View mLastFocusedView;
private final Rect mTempRect = new Rect();
@@ -97,8 +93,8 @@ public class ProgramGrid extends VerticalGridView {
interface ChildFocusListener {
/**
- * Is called before focus is moved. Only children to {@code ProgramGrid} will be passed.
- * See {@code ProgramGrid#setChildFocusListener(ChildFocusListener)}.
+ * Is called before focus is moved. Only children to {@code ProgramGrid} will be passed. See
+ * {@code ProgramGrid#setChildFocusListener(ChildFocusListener)}.
*/
void onRequestChildFocus(View oldFocus, View newFocus);
}
@@ -207,16 +203,13 @@ public class ProgramGrid extends VerticalGridView {
}
/**
- * Initializes ProgramGrid. It should be called before the view is actually attached to
- * Window.
+ * Initializes ProgramGrid. It should be called before the view is actually attached to Window.
*/
void initialize(ProgramManager programManager) {
mProgramManager = programManager;
}
- /**
- * Registers a listener focus events occurring on children to the {@code ProgramGrid}.
- */
+ /** Registers a listener focus events occurring on children to the {@code ProgramGrid}. */
void setChildFocusListener(ChildFocusListener childFocusListener) {
mChildFocusListener = childFocusListener;
}
@@ -226,8 +219,8 @@ public class ProgramGrid extends VerticalGridView {
}
/**
- * Resets focus states. If the logic to keep the last focus needs to be cleared, it should
- * be called.
+ * Resets focus states. If the logic to keep the last focus needs to be cleared, it should be
+ * called.
*/
void resetFocusState() {
mLastFocusedView = null;
@@ -255,8 +248,8 @@ public class ProgramGrid extends VerticalGridView {
Log.w(TAG, "No child view has focus");
return null;
}
- int nextChildIndex = direction == View.FOCUS_UP ? focusedChildIndex - 1
- : focusedChildIndex + 1;
+ int nextChildIndex =
+ direction == View.FOCUS_UP ? focusedChildIndex - 1 : focusedChildIndex + 1;
if (nextChildIndex < 0 || nextChildIndex >= getChildCount()) {
// Wraparound if reached head or end
if (getSelectedPosition() == 0) {
@@ -268,8 +261,12 @@ public class ProgramGrid extends VerticalGridView {
}
return focused;
}
- View nextFocusedProgram = GuideUtils.findNextFocusedProgram(getChildAt(nextChildIndex),
- mFocusRangeLeft, mFocusRangeRight, mKeepCurrentProgramFocused);
+ View nextFocusedProgram =
+ GuideUtils.findNextFocusedProgram(
+ getChildAt(nextChildIndex),
+ mFocusRangeLeft,
+ mFocusRangeRight,
+ mKeepCurrentProgramFocused);
if (nextFocusedProgram != null) {
nextFocusedProgram.getGlobalVisibleRect(mTempRect);
mNextFocusByUpDown = nextFocusedProgram;
@@ -320,8 +317,9 @@ public class ProgramGrid extends VerticalGridView {
mFocusRangeRight = getRightMostFocusablePosition();
mNextFocusByUpDown = null;
// If focus is not a program item, drop focus to the current program when back to the grid
- mKeepCurrentProgramFocused = !(focus instanceof ProgramItemView)
- || GuideUtils.isCurrentProgram((ProgramItemView) focus);
+ mKeepCurrentProgramFocused =
+ !(focus instanceof ProgramItemView)
+ || GuideUtils.isCurrentProgram((ProgramItemView) focus);
}
private int getRightMostFocusablePosition() {
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java
index dd5444e2..5b53f904 100644
--- a/src/com/android/tv/guide/ProgramGuide.java
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -43,14 +43,14 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityManager;
-
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import com.android.tv.ChannelTuner;
-import com.android.tv.Features;
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.util.DurationTimer;
+import com.android.tv.TvFeatures;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.WeakHandler;
+import com.android.tv.common.util.DurationTimer;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.GenreItems;
import com.android.tv.data.ProgramDataManager;
@@ -58,17 +58,16 @@ import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
import com.android.tv.ui.ViewUtils;
+import com.android.tv.ui.hideable.AutoHideScheduler;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-/**
- * The program guide.
- */
-public class ProgramGuide implements ProgramGrid.ChildFocusListener {
+/** The program guide. */
+public class ProgramGuide
+ implements ProgramGrid.ChildFocusListener, AccessibilityStateChangeListener {
private static final String TAG = "ProgramGuide";
private static final boolean DEBUG = false;
@@ -83,8 +82,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
// We clip out the first program entry in ProgramManager, if it does not have enough width.
// In order to prevent from clipping out the current program, this value need be larger than
// or equal to ProgramManager.FIRST_ENTRY_MIN_DURATION.
- private static final long MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME
- = ProgramManager.FIRST_ENTRY_MIN_DURATION;
+ private static final long MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME =
+ ProgramManager.FIRST_ENTRY_MIN_DURATION;
private static final int MSG_PROGRAM_TABLE_FADE_IN_ANIM = 1000;
@@ -103,7 +102,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
private final long mViewPortMillis;
private final int mRowHeight;
private final int mDetailHeight;
- private final int mSelectionRow; // Row that is focused
+ private final int mSelectionRow; // Row that is focused
private final int mTableFadeAnimDuration;
private final int mAnimationDuration;
private final int mDetailPadding;
@@ -145,34 +144,44 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
private final Handler mHandler = new ProgramGuideHandler(this);
private boolean mActive;
- private final Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- hide();
- }
- };
+ private final AutoHideScheduler mAutoHideScheduler;
private final long mShowDurationMillis;
private ViewTreeObserver.OnGlobalLayoutListener mOnLayoutListenerForShow;
private final ProgramManagerListener mProgramManagerListener = new ProgramManagerListener();
- private final Runnable mUpdateTimeIndicator = new Runnable() {
- @Override
- public void run() {
- positionCurrentTimeIndicator();
- mHandler.postAtTime(this,
- Utils.ceilTime(SystemClock.uptimeMillis(), TIME_INDICATOR_UPDATE_FREQUENCY));
- }
- };
-
- public ProgramGuide(MainActivity activity, ChannelTuner channelTuner,
- TvInputManagerHelper tvInputManagerHelper, ChannelDataManager channelDataManager,
- ProgramDataManager programDataManager, @Nullable DvrDataManager dvrDataManager,
- @Nullable DvrScheduleManager dvrScheduleManager, Tracker tracker,
- Runnable preShowRunnable, Runnable postHideRunnable) {
+ private final Runnable mUpdateTimeIndicator =
+ new Runnable() {
+ @Override
+ public void run() {
+ positionCurrentTimeIndicator();
+ mHandler.postAtTime(
+ this,
+ Utils.ceilTime(
+ SystemClock.uptimeMillis(), TIME_INDICATOR_UPDATE_FREQUENCY));
+ }
+ };
+
+ @SuppressWarnings("RestrictTo")
+ public ProgramGuide(
+ MainActivity activity,
+ ChannelTuner channelTuner,
+ TvInputManagerHelper tvInputManagerHelper,
+ ChannelDataManager channelDataManager,
+ ProgramDataManager programDataManager,
+ @Nullable DvrDataManager dvrDataManager,
+ @Nullable DvrScheduleManager dvrScheduleManager,
+ Tracker tracker,
+ Runnable preShowRunnable,
+ Runnable postHideRunnable) {
mActivity = activity;
- mProgramManager = new ProgramManager(tvInputManagerHelper, channelDataManager,
- programDataManager, dvrDataManager, dvrScheduleManager);
+ mProgramManager =
+ new ProgramManager(
+ tvInputManagerHelper,
+ channelDataManager,
+ programDataManager,
+ dvrDataManager,
+ dvrScheduleManager);
mChannelTuner = channelTuner;
mTracker = tracker;
mPreShowRunnable = preShowRunnable;
@@ -185,9 +194,11 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
Point displaySize = new Point();
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
- int gridWidth = displaySize.x
- - res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start)
- - res.getDimensionPixelSize(R.dimen.program_guide_table_header_column_width);
+ int gridWidth =
+ displaySize.x
+ - res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start)
+ - res.getDimensionPixelSize(
+ R.dimen.program_guide_table_header_column_width);
mViewPortMillis = (gridWidth * HOUR_IN_MILLIS) / mWidthPerHour;
mRowHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_item_row_height);
@@ -201,43 +212,49 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding);
mContainer = mActivity.findViewById(R.id.program_guide);
- ViewTreeObserver.OnGlobalFocusChangeListener globalFocusChangeListener
- = new GlobalFocusChangeListener();
+ ViewTreeObserver.OnGlobalFocusChangeListener globalFocusChangeListener =
+ new GlobalFocusChangeListener();
mContainer.getViewTreeObserver().addOnGlobalFocusChangeListener(globalFocusChangeListener);
GenreListAdapter genreListAdapter = new GenreListAdapter(mActivity, mProgramManager, this);
mSidePanel = mContainer.findViewById(R.id.program_guide_side_panel);
- mSidePanelGridView = (VerticalGridView) mContainer.findViewById(
- R.id.program_guide_side_panel_grid_view);
- mSidePanelGridView.getRecycledViewPool().setMaxRecycledViews(
- R.layout.program_guide_side_panel_row,
- res.getInteger(R.integer.max_recycled_view_pool_epg_side_panel_row));
+ mSidePanelGridView =
+ (VerticalGridView) mContainer.findViewById(R.id.program_guide_side_panel_grid_view);
+ mSidePanelGridView
+ .getRecycledViewPool()
+ .setMaxRecycledViews(
+ R.layout.program_guide_side_panel_row,
+ res.getInteger(R.integer.max_recycled_view_pool_epg_side_panel_row));
mSidePanelGridView.setAdapter(genreListAdapter);
mSidePanelGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
- mSidePanelGridView.setWindowAlignmentOffset(mActivity.getResources()
- .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y));
+ mSidePanelGridView.setWindowAlignmentOffset(
+ mActivity
+ .getResources()
+ .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y));
mSidePanelGridView.setWindowAlignmentOffsetPercent(
VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
- if (Features.EPG_SEARCH.isEnabled(mActivity)) {
- mSearchOrb = (SearchOrbView) mContainer.findViewById(
- R.id.program_guide_side_panel_search_orb);
+ if (TvFeatures.EPG_SEARCH.isEnabled(mActivity)) {
+ mSearchOrb =
+ (SearchOrbView)
+ mContainer.findViewById(R.id.program_guide_side_panel_search_orb);
mSearchOrb.setVisibility(View.VISIBLE);
- mSearchOrb.setOnOrbClickedListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- hide();
- mActivity.showProgramGuideSearchFragment();
- }
- });
+ mSearchOrb.setOnOrbClickedListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ hide();
+ mActivity.showProgramGuideSearchFragment();
+ }
+ });
mSidePanelGridView.setOnChildSelectedListener(
new android.support.v17.leanback.widget.OnChildSelectedListener() {
- @Override
- public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) {
- mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f);
- }
- });
+ @Override
+ public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) {
+ mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f);
+ }
+ });
} else {
mSearchOrb = null;
}
@@ -246,134 +263,156 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
mTimelineRow = (TimelineRow) mTable.findViewById(R.id.time_row);
mTimeListAdapter = new TimeListAdapter(res);
- mTimelineRow.getRecycledViewPool().setMaxRecycledViews(
- R.layout.program_guide_table_header_row_item,
- res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item));
+ mTimelineRow
+ .getRecycledViewPool()
+ .setMaxRecycledViews(
+ R.layout.program_guide_table_header_row_item,
+ res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item));
mTimelineRow.setAdapter(mTimeListAdapter);
ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, this);
- programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
- @Override
- public void onChanged() {
- // It is usually called when Genre is changed.
- // Reset selection of ProgramGrid
- resetRowSelection();
- updateGuidePosition();
- }
- });
+ programTableAdapter.registerAdapterDataObserver(
+ new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ // It is usually called when Genre is changed.
+ // Reset selection of ProgramGrid
+ resetRowSelection();
+ updateGuidePosition();
+ }
+ });
mGrid = (ProgramGrid) mTable.findViewById(R.id.grid);
mGrid.initialize(mProgramManager);
- mGrid.getRecycledViewPool().setMaxRecycledViews(
- R.layout.program_guide_table_row,
- res.getInteger(R.integer.max_recycled_view_pool_epg_table_row));
+ mGrid.getRecycledViewPool()
+ .setMaxRecycledViews(
+ R.layout.program_guide_table_row,
+ res.getInteger(R.integer.max_recycled_view_pool_epg_table_row));
mGrid.setAdapter(programTableAdapter);
mGrid.setChildFocusListener(this);
- mGrid.setOnChildSelectedListener(new OnChildSelectedListener() {
- @Override
- public void onChildSelected(ViewGroup parent, View view, int position, long id) {
- if (mIsDuringResetRowSelection) {
- // Ignore if it's during the first resetRowSelection, because onChildSelected
- // will be called again when rows are bound to the program table. if selectRow
- // is called here, mSelectedRow is set and the second selectRow call doesn't
- // work as intended.
- mIsDuringResetRowSelection = false;
- return;
- }
- selectRow(view);
- }
- });
+ mGrid.setOnChildSelectedListener(
+ new OnChildSelectedListener() {
+ @Override
+ public void onChildSelected(
+ ViewGroup parent, View view, int position, long id) {
+ if (mIsDuringResetRowSelection) {
+ // Ignore if it's during the first resetRowSelection, because
+ // onChildSelected
+ // will be called again when rows are bound to the program table. if
+ // selectRow
+ // is called here, mSelectedRow is set and the second selectRow call
+ // doesn't
+ // work as intended.
+ mIsDuringResetRowSelection = false;
+ return;
+ }
+ selectRow(view);
+ }
+ });
mGrid.setFocusScrollStrategy(ProgramGrid.FOCUS_SCROLL_ALIGNED);
mGrid.setWindowAlignmentOffset(mSelectionRow * mRowHeight);
mGrid.setWindowAlignmentOffsetPercent(ProgramGrid.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
mGrid.setItemAlignmentOffset(0);
mGrid.setItemAlignmentOffsetPercent(ProgramGrid.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
- RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- onHorizontalScrolled(dx);
- }
- };
+ RecyclerView.OnScrollListener onScrollListener =
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ onHorizontalScrolled(dx);
+ }
+ };
mTimelineRow.addOnScrollListener(onScrollListener);
mCurrentTimeIndicator = mTable.findViewById(R.id.current_time_indicator);
- mShowAnimatorFull = createAnimator(
- R.animator.program_guide_side_panel_enter_full,
- 0,
- R.animator.program_guide_table_enter_full);
-
- mShowAnimatorPartial = createAnimator(
- R.animator.program_guide_side_panel_enter_partial,
- 0,
- R.animator.program_guide_table_enter_partial);
- mShowAnimatorPartial.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mSidePanelGridView.setVisibility(View.VISIBLE);
- mSidePanelGridView.setAlpha(1.0f);
- }
- });
-
- mHideAnimatorFull = createAnimator(
- R.animator.program_guide_side_panel_exit,
- 0,
- R.animator.program_guide_table_exit);
- mHideAnimatorFull.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mContainer.setVisibility(View.GONE);
- }
- });
- mHideAnimatorPartial = createAnimator(
- R.animator.program_guide_side_panel_exit,
- 0,
- R.animator.program_guide_table_exit);
- mHideAnimatorPartial.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mContainer.setVisibility(View.GONE);
- }
- });
-
- mPartialToFullAnimator = createAnimator(
- R.animator.program_guide_side_panel_hide,
- R.animator.program_guide_side_panel_grid_fade_out,
- R.animator.program_guide_table_partial_to_full);
- mFullToPartialAnimator = createAnimator(
- R.animator.program_guide_side_panel_reveal,
- R.animator.program_guide_side_panel_grid_fade_in,
- R.animator.program_guide_table_full_to_partial);
-
- mProgramTableFadeOutAnimator = AnimatorInflater.loadAnimator(mActivity,
- R.animator.program_guide_table_fade_out);
+ mShowAnimatorFull =
+ createAnimator(
+ R.animator.program_guide_side_panel_enter_full,
+ 0,
+ R.animator.program_guide_table_enter_full);
+
+ mShowAnimatorPartial =
+ createAnimator(
+ R.animator.program_guide_side_panel_enter_partial,
+ 0,
+ R.animator.program_guide_table_enter_partial);
+ mShowAnimatorPartial.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mSidePanelGridView.setVisibility(View.VISIBLE);
+ mSidePanelGridView.setAlpha(1.0f);
+ }
+ });
+
+ mHideAnimatorFull =
+ createAnimator(
+ R.animator.program_guide_side_panel_exit,
+ 0,
+ R.animator.program_guide_table_exit);
+ mHideAnimatorFull.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mContainer.setVisibility(View.GONE);
+ }
+ });
+ mHideAnimatorPartial =
+ createAnimator(
+ R.animator.program_guide_side_panel_exit,
+ 0,
+ R.animator.program_guide_table_exit);
+ mHideAnimatorPartial.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mContainer.setVisibility(View.GONE);
+ }
+ });
+
+ mPartialToFullAnimator =
+ createAnimator(
+ R.animator.program_guide_side_panel_hide,
+ R.animator.program_guide_side_panel_grid_fade_out,
+ R.animator.program_guide_table_partial_to_full);
+ mFullToPartialAnimator =
+ createAnimator(
+ R.animator.program_guide_side_panel_reveal,
+ R.animator.program_guide_side_panel_grid_fade_in,
+ R.animator.program_guide_table_full_to_partial);
+
+ mProgramTableFadeOutAnimator =
+ AnimatorInflater.loadAnimator(mActivity, R.animator.program_guide_table_fade_out);
mProgramTableFadeOutAnimator.setTarget(mTable);
- mProgramTableFadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable) {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
-
- if (!isActive()) {
- return;
- }
- mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId);
- resetTimelineScroll();
- if (!mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) {
- mHandler.sendEmptyMessage(MSG_PROGRAM_TABLE_FADE_IN_ANIM);
- }
- }
- });
- mProgramTableFadeInAnimator = AnimatorInflater.loadAnimator(mActivity,
- R.animator.program_guide_table_fade_in);
+ mProgramTableFadeOutAnimator.addListener(
+ new HardwareLayerAnimatorListenerAdapter(mTable) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+
+ if (!isActive()) {
+ return;
+ }
+ mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId);
+ resetTimelineScroll();
+ if (!mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) {
+ mHandler.sendEmptyMessage(MSG_PROGRAM_TABLE_FADE_IN_ANIM);
+ }
+ }
+ });
+ mProgramTableFadeInAnimator =
+ AnimatorInflater.loadAnimator(mActivity, R.animator.program_guide_table_fade_in);
mProgramTableFadeInAnimator.setTarget(mTable);
mProgramTableFadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable));
mSharedPreference = PreferenceManager.getDefaultSharedPreferences(mActivity);
mAccessibilityManager =
(AccessibilityManager) mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mShowGuidePartial = mAccessibilityManager.isEnabled()
- || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true);
+ mShowGuidePartial =
+ mAccessibilityManager.isEnabled()
+ || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true);
+ mAutoHideScheduler = new AutoHideScheduler(activity, this::hide);
}
@Override
@@ -397,12 +436,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
}
/**
- * Show the program guide. This reveals the side panel, and the program guide table is shown
+ * Show the program guide. This reveals the side panel, and the program guide table is shown
* partially.
*
- * <p>Note: the animation which starts together with ProgramGuide showing animation needs to
- * be initiated in {@code runnableAfterAnimatorReady}. If the animation starts together
- * with show(), the animation may drop some frames.
+ * <p>Note: the animation which starts together with ProgramGuide showing animation needs to be
+ * initiated in {@code runnableAfterAnimatorReady}. If the animation starts together with
+ * show(), the animation may drop some frames.
*/
public void show(final Runnable runnableAfterAnimatorReady) {
if (mContainer.getVisibility() == View.VISIBLE) {
@@ -416,9 +455,10 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
mVisibleDuration.start();
mProgramManager.programGuideVisibilityChanged(true);
- mStartUtcTime = Utils.floorTime(
- System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME,
- HALF_HOUR_IN_MILLIS);
+ mStartUtcTime =
+ Utils.floorTime(
+ System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME,
+ HALF_HOUR_IN_MILLIS);
mProgramManager.updateInitialTimeRange(mStartUtcTime, mStartUtcTime + mViewPortMillis);
mProgramManager.addListener(mProgramManagerListener);
mLastRequestedGenreId = GenreItems.ID_ALL_CHANNELS;
@@ -435,51 +475,60 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
if (DEBUG) {
Log.d(TAG, "show()");
}
- mOnLayoutListenerForShow = new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- mTable.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mSidePanelGridView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mTable.buildLayer();
- mSidePanelGridView.buildLayer();
- mOnLayoutListenerForShow = null;
- mTimelineAnimation = true;
- // Make sure that time indicator update starts after animation is finished.
- startCurrentTimeIndicator(TIME_INDICATOR_UPDATE_FREQUENCY);
- if (DEBUG) {
- mContainer.getViewTreeObserver().addOnDrawListener(
- new ViewTreeObserver.OnDrawListener() {
- long time = System.currentTimeMillis();
- int count = 0;
-
- @Override
- public void onDraw() {
- long curtime = System.currentTimeMillis();
- Log.d(TAG, "onDraw " + count++ + " " + (curtime - time) + "ms");
- time = curtime;
- if (count > 10) {
- mContainer.getViewTreeObserver().removeOnDrawListener(this);
- }
- }
- });
- }
- updateGuidePosition();
- runnableAfterAnimatorReady.run();
- if (mShowGuidePartial) {
- mShowAnimatorPartial.start();
- } else {
- mShowAnimatorFull.start();
- }
- }
- };
+ mOnLayoutListenerForShow =
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mTable.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mSidePanelGridView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mTable.buildLayer();
+ mSidePanelGridView.buildLayer();
+ mOnLayoutListenerForShow = null;
+ mTimelineAnimation = true;
+ // Make sure that time indicator update starts after animation is finished.
+ startCurrentTimeIndicator(TIME_INDICATOR_UPDATE_FREQUENCY);
+ if (DEBUG) {
+ mContainer
+ .getViewTreeObserver()
+ .addOnDrawListener(
+ new ViewTreeObserver.OnDrawListener() {
+ long time = System.currentTimeMillis();
+ int count = 0;
+
+ @Override
+ public void onDraw() {
+ long curtime = System.currentTimeMillis();
+ Log.d(
+ TAG,
+ "onDraw "
+ + count++
+ + " "
+ + (curtime - time)
+ + "ms");
+ time = curtime;
+ if (count > 10) {
+ mContainer
+ .getViewTreeObserver()
+ .removeOnDrawListener(this);
+ }
+ }
+ });
+ }
+ updateGuidePosition();
+ runnableAfterAnimatorReady.run();
+ if (mShowGuidePartial) {
+ mShowAnimatorPartial.start();
+ } else {
+ mShowAnimatorFull.start();
+ }
+ }
+ };
mContainer.getViewTreeObserver().addOnGlobalLayoutListener(mOnLayoutListenerForShow);
scheduleHide();
}
- /**
- * Hide the program guide.
- */
+ /** Hide the program guide. */
public void hide() {
if (!isActive()) {
return;
@@ -516,52 +565,43 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
}
}
- /**
- * Schedules hiding the program guide.
- */
+ /** Schedules hiding the program guide. */
public void scheduleHide() {
- cancelHide();
- mHandler.postDelayed(mHideRunnable, mShowDurationMillis);
+ mAutoHideScheduler.schedule(mShowDurationMillis);
}
- /**
- * Cancels hiding the program guide.
- */
+ /** Cancels hiding the program guide. */
public void cancelHide() {
- mHandler.removeCallbacks(mHideRunnable);
+ mAutoHideScheduler.cancel();
}
- /**
- * Process the {@code KEYCODE_BACK} key event.
- */
+ /** Process the {@code KEYCODE_BACK} key event. */
public void onBackPressed() {
hide();
}
- /**
- * Returns {@code true} if the program guide should process the input events.
- */
+ /** Returns {@code true} if the program guide should process the input events. */
public boolean isActive() {
return mActive;
}
/**
- * Returns {@code true} if the program guide is shown, i.e. showing animation is done and
- * hiding animation is not started yet.
+ * Returns {@code true} if the program guide is shown, i.e. showing animation is done and hiding
+ * animation is not started yet.
*/
public boolean isRunningAnimation() {
- return mShowAnimatorPartial.isStarted() || mShowAnimatorFull.isStarted()
- || mHideAnimatorPartial.isStarted() || mHideAnimatorFull.isStarted();
+ return mShowAnimatorPartial.isStarted()
+ || mShowAnimatorFull.isStarted()
+ || mHideAnimatorPartial.isStarted()
+ || mHideAnimatorFull.isStarted();
}
- /** Returns if program table is in full screen mode. **/
+ /** Returns if program table is in full screen mode. * */
boolean isFull() {
return !mShowGuidePartial;
}
- /**
- * Requests change genre to {@code genreId}.
- */
+ /** Requests change genre to {@code genreId}. */
void requestGenreChange(int genreId) {
if (mLastRequestedGenreId == genreId) {
// When Recycler.onLayout() removes its children to recycle,
@@ -575,15 +615,15 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
// When requestGenreChange is called repeatedly in short time, we keep the fade-out
// state for mTableFadeAnimDuration from now. Without it, we'll see blinks.
mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM);
- mHandler.sendEmptyMessageDelayed(MSG_PROGRAM_TABLE_FADE_IN_ANIM,
- mTableFadeAnimDuration);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_PROGRAM_TABLE_FADE_IN_ANIM, mTableFadeAnimDuration);
return;
}
if (mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) {
mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId);
mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM);
- mHandler.sendEmptyMessageDelayed(MSG_PROGRAM_TABLE_FADE_IN_ANIM,
- mTableFadeAnimDuration);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_PROGRAM_TABLE_FADE_IN_ANIM, mTableFadeAnimDuration);
return;
}
if (mProgramTableFadeInAnimator.isStarted()) {
@@ -593,9 +633,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
mProgramTableFadeOutAnimator.start();
}
- /**
- * Returns the scroll offset of the time line row in pixels.
- */
+ /** Returns the scroll offset of the time line row in pixels. */
int getTimelineRowScrollOffset() {
return mTimelineRow.getScrollOffset();
}
@@ -605,9 +643,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
return mGrid;
}
- /**
- * Gets {@link VerticalGridView} for "genre select" side panel.
- */
+ /** Gets {@link VerticalGridView} for "genre select" side panel. */
VerticalGridView getSidePanel() {
return mSidePanelGridView;
}
@@ -628,9 +664,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
int startPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start);
int topPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_top);
int bottomPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_bottom);
- int tableHeight = res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height)
- + mDetailHeight + mRowHeight * mGrid.getAdapter().getItemCount() + topPadding
- + bottomPadding;
+ int tableHeight =
+ res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height)
+ + mDetailHeight
+ + mRowHeight * mGrid.getAdapter().getItemCount()
+ + topPadding
+ + bottomPadding;
if (tableHeight > screenHeight) {
// EPG height is longer that the screen height.
mTable.setPaddingRelative(startPadding, topPadding, 0, 0);
@@ -645,8 +684,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
}
}
- private Animator createAnimator(int sidePanelAnimResId, int sidePanelGridAnimResId,
- int tableAnimResId) {
+ private Animator createAnimator(
+ int sidePanelAnimResId, int sidePanelGridAnimResId, int tableAnimResId) {
List<Animator> animatorList = new ArrayList<>();
Animator sidePanelAnimator = AnimatorInflater.loadAnimator(mActivity, sidePanelAnimResId);
@@ -654,8 +693,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
animatorList.add(sidePanelAnimator);
if (sidePanelGridAnimResId != 0) {
- Animator sidePanelGridAnimator = AnimatorInflater.loadAnimator(mActivity,
- sidePanelGridAnimResId);
+ Animator sidePanelGridAnimator =
+ AnimatorInflater.loadAnimator(mActivity, sidePanelGridAnimResId);
sidePanelGridAnimator.setTarget(mSidePanelGridView);
sidePanelGridAnimator.addListener(
new HardwareLayerAnimatorListenerAdapter(mSidePanelGridView));
@@ -700,8 +739,9 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
}
private void positionCurrentTimeIndicator() {
- int offset = GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis())
- - mTimelineRow.getScrollOffset();
+ int offset =
+ GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis())
+ - mTimelineRow.getScrollOffset();
if (offset < 0) {
mCurrentTimeIndicator.setVisibility(View.GONE);
} else {
@@ -743,8 +783,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
mSelectedRow = null;
mIsDuringResetRowSelection = true;
mGrid.setSelectedPosition(
- Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()),
- 0));
+ Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()), 0));
mGrid.resetFocusState();
mGrid.onItemSelectionReset();
mIsDuringResetRowSelection = false;
@@ -767,12 +806,13 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
detailView.setVisibility(View.VISIBLE);
final ProgramRow programRow = (ProgramRow) row.findViewById(R.id.row);
- programRow.post(new Runnable() {
- @Override
- public void run() {
- programRow.focusCurrentProgram();
- }
- });
+ programRow.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ programRow.focusCurrentProgram();
+ }
+ });
} else {
animateRowChange(mSelectedRow, row);
}
@@ -799,38 +839,45 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
if (outDetail != null && outDetail.isShown()) {
final View outDetailContent = outDetail.findViewById(R.id.detail_content_full);
- Animator fadeOutAnimator = ObjectAnimator.ofPropertyValuesHolder(outDetailContent,
- PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f),
- PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
- outDetailContent.getTranslationY(), animationPadding));
+ Animator fadeOutAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(
+ outDetailContent,
+ PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f),
+ PropertyValuesHolder.ofFloat(
+ View.TRANSLATION_Y,
+ outDetailContent.getTranslationY(),
+ animationPadding));
fadeOutAnimator.setStartDelay(0);
fadeOutAnimator.setDuration(mAnimationDuration);
fadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(outDetailContent));
- Animator collapseAnimator = ViewUtils
- .createHeightAnimator(outDetail, ViewUtils.getLayoutHeight(outDetail), 0);
+ Animator collapseAnimator =
+ ViewUtils.createHeightAnimator(
+ outDetail, ViewUtils.getLayoutHeight(outDetail), 0);
collapseAnimator.setStartDelay(mAnimationDuration);
collapseAnimator.setDuration(mTableFadeAnimDuration);
- collapseAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animator) {
- outDetailContent.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- outDetailContent.setVisibility(View.VISIBLE);
- }
- });
+ collapseAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ outDetailContent.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ outDetailContent.setVisibility(View.VISIBLE);
+ }
+ });
AnimatorSet outAnimator = new AnimatorSet();
outAnimator.playTogether(fadeOutAnimator, collapseAnimator);
- outAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- mDetailOutAnimator = null;
- }
- });
+ outAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mDetailOutAnimator = null;
+ }
+ });
mDetailOutAnimator = outAnimator;
outAnimator.start();
}
@@ -842,39 +889,49 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
Animator expandAnimator = ViewUtils.createHeightAnimator(inDetail, 0, mDetailHeight);
expandAnimator.setStartDelay(mAnimationDuration);
expandAnimator.setDuration(mTableFadeAnimDuration);
- expandAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animator) {
- inDetailContent.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- inDetailContent.setVisibility(View.VISIBLE);
- inDetailContent.setAlpha(0);
- }
- });
- Animator fadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(inDetailContent,
- PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f),
- PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -animationPadding, 0f));
+ expandAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ inDetailContent.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ inDetailContent.setVisibility(View.VISIBLE);
+ inDetailContent.setAlpha(0);
+ }
+ });
+ Animator fadeInAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(
+ inDetailContent,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f),
+ PropertyValuesHolder.ofFloat(
+ View.TRANSLATION_Y, -animationPadding, 0f));
fadeInAnimator.setDuration(mAnimationDuration);
fadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(inDetailContent));
AnimatorSet inAnimator = new AnimatorSet();
inAnimator.playSequentially(expandAnimator, fadeInAnimator);
- inAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- mDetailInAnimator = null;
- }
- });
+ inAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mDetailInAnimator = null;
+ }
+ });
mDetailInAnimator = inAnimator;
inAnimator.start();
}
}
- private class GlobalFocusChangeListener implements
- ViewTreeObserver.OnGlobalFocusChangeListener {
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ mAutoHideScheduler.onAccessibilityStateChanged(enabled);
+ }
+
+ private class GlobalFocusChangeListener
+ implements ViewTreeObserver.OnGlobalFocusChangeListener {
private static final int UNKNOWN = 0;
private static final int SIDE_PANEL = 1;
private static final int PROGRAM_TABLE = 2;
@@ -912,11 +969,16 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
private class ProgramManagerListener extends ProgramManager.ListenerAdapter {
@Override
public void onTimeRangeUpdated() {
- int scrollOffset = (int) (mWidthPerHour * mProgramManager.getShiftedTime()
- / HOUR_IN_MILLIS);
+ int scrollOffset =
+ (int) (mWidthPerHour * mProgramManager.getShiftedTime() / HOUR_IN_MILLIS);
if (DEBUG) {
- Log.d(TAG, "Horizontal scroll to " + scrollOffset + " pixels ("
- + mProgramManager.getShiftedTime() + " millis)");
+ Log.d(
+ TAG,
+ "Horizontal scroll to "
+ + scrollOffset
+ + " pixels ("
+ + mProgramManager.getShiftedTime()
+ + " millis)");
}
mTimelineRow.scrollTo(scrollOffset, mTimelineAnimation);
}
diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java
index b23d578c..9f379e43 100644
--- a/src/com/android/tv/guide/ProgramItemView.java
+++ b/src/com/android/tv/guide/ProgramItemView.java
@@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Handler;
-import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@@ -35,21 +34,21 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.Clock;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.guide.ProgramManager.TableEntry;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
-
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.TimeUnit;
@@ -60,10 +59,10 @@ public class ProgramItemView extends TextView {
private static final int MAX_PROGRESS = 10000; // From android.widget.ProgressBar.MAX_VALUE
// State indicating the focused program is the current program
- private static final int[] STATE_CURRENT_PROGRAM = { R.attr.state_current_program };
+ private static final int[] STATE_CURRENT_PROGRAM = {R.attr.state_current_program};
// Workaround state in order to not use too much texture memory for RippleDrawable
- private static final int[] STATE_TOO_WIDE = { R.attr.state_program_too_wide };
+ private static final int[] STATE_TOO_WIDE = {R.attr.state_program_too_wide};
private static int sVisibleThreshold;
private static int sItemPadding;
@@ -73,8 +72,10 @@ public class ProgramItemView extends TextView {
private static TextAppearanceSpan sEpisodeTitleStyle;
private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle;
+ private final DvrManager mDvrManager;
+ private final Clock mClock;
+ private final ChannelDataManager mChannelDataManager;
private ProgramGuide mProgramGuide;
- private DvrManager mDvrManager;
private TableEntry mTableEntry;
private int mMaxWidthForRipple;
private int mTextWidth;
@@ -84,96 +85,119 @@ public class ProgramItemView extends TextView {
// as a result of the re-layout (see b/21378855).
private boolean mPreventParentRelayout;
- private static final View.OnClickListener ON_CLICKED = new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- TableEntry entry = ((ProgramItemView) view).mTableEntry;
- if (entry == null) {
- //do nothing
- return;
- }
- ApplicationSingletons singletons = TvApplication.getSingletons(view.getContext());
- Tracker tracker = singletons.getTracker();
- tracker.sendEpgItemClicked();
- final MainActivity tvActivity = (MainActivity) view.getContext();
- final Channel channel = tvActivity.getChannelDataManager().getChannel(entry.channelId);
- if (entry.isCurrentProgram()) {
- view.postDelayed(new Runnable() {
- @Override
- public void run() {
- tvActivity.tuneToChannel(channel);
- tvActivity.hideOverlaysForTune();
+ private static final View.OnClickListener ON_CLICKED =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ TableEntry entry = ((ProgramItemView) view).mTableEntry;
+ Clock clock = ((ProgramItemView) view).mClock;
+ if (entry == null) {
+ // do nothing
+ return;
}
- }, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0
- : view.getResources()
- .getInteger(R.integer.program_guide_ripple_anim_duration));
- } else if (entry.program != null && CommonFeatures.DVR.isEnabled(view.getContext())) {
- DvrManager dvrManager = singletons.getDvrManager();
- if (entry.entryStartUtcMillis > System.currentTimeMillis()
- && dvrManager.isProgramRecordable(entry.program)) {
- if (entry.scheduledRecording == null) {
- DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity,
- channel.getInputId(), new Runnable() {
+ TvSingletons singletons = TvSingletons.getSingletons(view.getContext());
+ Tracker tracker = singletons.getTracker();
+ tracker.sendEpgItemClicked();
+ final MainActivity tvActivity = (MainActivity) view.getContext();
+ final Channel channel =
+ tvActivity.getChannelDataManager().getChannel(entry.channelId);
+ if (entry.isCurrentProgram()) {
+ view.postDelayed(
+ new Runnable() {
@Override
public void run() {
- DvrUiHelper.requestRecordingFutureProgram(tvActivity,
- entry.program, false);
+ tvActivity.tuneToChannel(channel);
+ tvActivity.hideOverlaysForTune();
}
- });
- } else {
- dvrManager.removeScheduledRecording(entry.scheduledRecording);
- String msg = view.getResources().getString(
- R.string.dvr_schedules_deletion_info, entry.program.getTitle());
- ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT);
+ },
+ entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple
+ ? 0
+ : view.getResources()
+ .getInteger(
+ R.integer
+ .program_guide_ripple_anim_duration));
+ } else if (entry.program != null
+ && CommonFeatures.DVR.isEnabled(view.getContext())) {
+ DvrManager dvrManager = singletons.getDvrManager();
+ if (entry.entryStartUtcMillis > clock.currentTimeMillis()
+ && dvrManager.isProgramRecordable(entry.program)) {
+ if (entry.scheduledRecording == null) {
+ DvrUiHelper.checkStorageStatusAndShowErrorMessage(
+ tvActivity,
+ channel.getInputId(),
+ new Runnable() {
+ @Override
+ public void run() {
+ DvrUiHelper.requestRecordingFutureProgram(
+ tvActivity, entry.program, false);
+ }
+ });
+ } else {
+ dvrManager.removeScheduledRecording(entry.scheduledRecording);
+ String msg =
+ view.getResources()
+ .getString(
+ R.string.dvr_schedules_deletion_info,
+ entry.program.getTitle());
+ ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT);
+ }
+ } else {
+ ToastUtils.show(
+ view.getContext(),
+ view.getResources()
+ .getString(R.string.dvr_msg_cannot_record_program),
+ Toast.LENGTH_SHORT);
+ }
}
- } else {
- ToastUtils.show(view.getContext(), view.getResources()
- .getString(R.string.dvr_msg_cannot_record_program), Toast.LENGTH_SHORT);
}
- }
- }
- };
+ };
private static final View.OnFocusChangeListener ON_FOCUS_CHANGED =
new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- if (hasFocus) {
- ((ProgramItemView) view).mUpdateFocus.run();
- } else {
- Handler handler = view.getHandler();
- if (handler != null) {
- handler.removeCallbacks(((ProgramItemView) view).mUpdateFocus);
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (hasFocus) {
+ ((ProgramItemView) view).mUpdateFocus.run();
+ } else {
+ Handler handler = view.getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(((ProgramItemView) view).mUpdateFocus);
+ }
+ }
}
- }
- }
- };
-
- private final Runnable mUpdateFocus = new Runnable() {
- @Override
- public void run() {
- refreshDrawableState();
- TableEntry entry = mTableEntry;
- if (entry == null) {
- //do nothing
- return;
- }
- if (entry.isCurrentProgram()) {
- Drawable background = getBackground();
- if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) {
- // If program guide is not active or is during showing/hiding,
- // the animation is unnecessary, skip it.
- background.jumpToCurrentState();
+ };
+
+ private final Runnable mUpdateFocus =
+ new Runnable() {
+ @Override
+ public void run() {
+ refreshDrawableState();
+ TableEntry entry = mTableEntry;
+ if (entry == null) {
+ // do nothing
+ return;
+ }
+ if (entry.isCurrentProgram()) {
+ Drawable background = getBackground();
+ if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) {
+ // If program guide is not active or is during showing/hiding,
+ // the animation is unnecessary, skip it.
+ background.jumpToCurrentState();
+ }
+ int progress =
+ getProgress(
+ mClock, entry.entryStartUtcMillis, entry.entryEndUtcMillis);
+ setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress);
+ }
+ if (getHandler() != null) {
+ getHandler()
+ .postAtTime(
+ this,
+ Utils.ceilTime(
+ mClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY));
+ }
}
- int progress = getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis);
- setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress);
- }
- if (getHandler() != null) {
- getHandler().postAtTime(this,
- Utils.ceilTime(SystemClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY));
- }
- }
- };
+ };
public ProgramItemView(Context context) {
this(context, null);
@@ -187,7 +211,10 @@ public class ProgramItemView extends TextView {
super(context, attrs, defStyle);
setOnClickListener(ON_CLICKED);
setOnFocusChangeListener(ON_FOCUS_CHANGED);
- mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ TvSingletons singletons = TvSingletons.getSingletons(getContext());
+ mDvrManager = singletons.getDvrManager();
+ mChannelDataManager = singletons.getChannelDataManager();
+ mClock = singletons.getClock();
}
private void initIfNeeded() {
@@ -196,35 +223,46 @@ public class ProgramItemView extends TextView {
}
Resources res = getContext().getResources();
- sVisibleThreshold = res.getDimensionPixelOffset(
- R.dimen.program_guide_table_item_visible_threshold);
+ sVisibleThreshold =
+ res.getDimensionPixelOffset(R.dimen.program_guide_table_item_visible_threshold);
sItemPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_item_padding);
- sCompoundDrawablePadding = res.getDimensionPixelOffset(
- R.dimen.program_guide_table_item_compound_drawable_padding);
-
- ColorStateList programTitleColor = ColorStateList.valueOf(res.getColor(
- R.color.program_guide_table_item_program_title_text_color, null));
- ColorStateList grayedOutProgramTitleColor = res.getColorStateList(
- R.color.program_guide_table_item_grayed_out_program_text_color, null);
- ColorStateList episodeTitleColor = ColorStateList.valueOf(res.getColor(
- R.color.program_guide_table_item_program_episode_title_text_color, null));
- ColorStateList grayedOutEpisodeTitleColor = ColorStateList.valueOf(res.getColor(
- R.color.program_guide_table_item_grayed_out_program_episode_title_text_color,
- null));
- int programTitleSize = res.getDimensionPixelSize(
- R.dimen.program_guide_table_item_program_title_font_size);
- int episodeTitleSize = res.getDimensionPixelSize(
- R.dimen.program_guide_table_item_program_episode_title_font_size);
-
- sProgramTitleStyle = new TextAppearanceSpan(null, 0, programTitleSize, programTitleColor,
- null);
- sGrayedOutProgramTitleStyle = new TextAppearanceSpan(null, 0, programTitleSize,
- grayedOutProgramTitleColor, null);
- sEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor,
- null);
- sGrayedOutEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize,
- grayedOutEpisodeTitleColor, null);
+ sCompoundDrawablePadding =
+ res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_item_compound_drawable_padding);
+
+ ColorStateList programTitleColor =
+ ColorStateList.valueOf(
+ res.getColor(
+ R.color.program_guide_table_item_program_title_text_color, null));
+ ColorStateList grayedOutProgramTitleColor =
+ res.getColorStateList(
+ R.color.program_guide_table_item_grayed_out_program_text_color, null);
+ ColorStateList episodeTitleColor =
+ ColorStateList.valueOf(
+ res.getColor(
+ R.color.program_guide_table_item_program_episode_title_text_color,
+ null));
+ ColorStateList grayedOutEpisodeTitleColor =
+ ColorStateList.valueOf(
+ res.getColor(
+ R.color
+ .program_guide_table_item_grayed_out_program_episode_title_text_color,
+ null));
+ int programTitleSize =
+ res.getDimensionPixelSize(R.dimen.program_guide_table_item_program_title_font_size);
+ int episodeTitleSize =
+ res.getDimensionPixelSize(
+ R.dimen.program_guide_table_item_program_episode_title_font_size);
+
+ sProgramTitleStyle =
+ new TextAppearanceSpan(null, 0, programTitleSize, programTitleColor, null);
+ sGrayedOutProgramTitleStyle =
+ new TextAppearanceSpan(null, 0, programTitleSize, grayedOutProgramTitleColor, null);
+ sEpisodeTitleStyle =
+ new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null);
+ sGrayedOutEpisodeTitleStyle =
+ new TextAppearanceSpan(null, 0, episodeTitleSize, grayedOutEpisodeTitleColor, null);
}
@Override
@@ -236,8 +274,9 @@ public class ProgramItemView extends TextView {
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mTableEntry != null) {
- int states[] = super.onCreateDrawableState(extraSpace
- + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length);
+ int[] states =
+ super.onCreateDrawableState(
+ extraSpace + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length);
if (mTableEntry.isCurrentProgram()) {
mergeDrawableStates(states, STATE_CURRENT_PROGRAM);
}
@@ -254,86 +293,168 @@ public class ProgramItemView extends TextView {
}
@SuppressLint("SwitchIntDef")
- public void setValues(ProgramGuide programGuide, TableEntry entry, int selectedGenreId,
- long fromUtcMillis, long toUtcMillis, String gapTitle) {
+ public void setValues(
+ ProgramGuide programGuide,
+ TableEntry entry,
+ int selectedGenreId,
+ long fromUtcMillis,
+ long toUtcMillis,
+ String gapTitle) {
mProgramGuide = programGuide;
mTableEntry = entry;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.width = entry.getWidth();
- setLayoutParams(layoutParams);
+ if (layoutParams != null) {
+ // There is no layoutParams in the tests so we skip this
+ layoutParams.width = entry.getWidth();
+ setLayoutParams(layoutParams);
+ }
+ String title = mTableEntry.program != null ? mTableEntry.program.getTitle() : null;
+ if (mTableEntry.isGap()) {
+ title = gapTitle;
+ }
+ if (TextUtils.isEmpty(title)) {
+ title = getResources().getString(R.string.program_title_for_no_information);
+ }
+ updateText(selectedGenreId, title);
+ updateIcons();
+ updateContentDescription(title);
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd();
+ // Maximum width for us to use a ripple
+ mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis);
+ }
- String title = entry.program != null ? entry.program.getTitle() : null;
- String episode = entry.program != null ?
- entry.program.getEpisodeDisplayTitle(getContext()) : null;
+ private boolean isEntryWideEnough() {
+ return mTableEntry != null && mTableEntry.getWidth() >= sVisibleThreshold;
+ }
+
+ private void updateText(int selectedGenreId, String title) {
+ if (!isEntryWideEnough()) {
+ setText(null);
+ return;
+ }
+
+ String episode =
+ mTableEntry.program != null
+ ? mTableEntry.program.getEpisodeDisplayTitle(getContext())
+ : null;
TextAppearanceSpan titleStyle = sGrayedOutProgramTitleStyle;
TextAppearanceSpan episodeStyle = sGrayedOutEpisodeTitleStyle;
+ if (mTableEntry.isGap()) {
- if (entry.getWidth() < sVisibleThreshold) {
- setText(null);
+ episode = null;
+ } else if (mTableEntry.hasGenre(selectedGenreId)) {
+ titleStyle = sProgramTitleStyle;
+ episodeStyle = sEpisodeTitleStyle;
+ }
+ SpannableStringBuilder description = new SpannableStringBuilder();
+ description.append(title);
+ if (!TextUtils.isEmpty(episode)) {
+ description.append('\n');
+
+ // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for
+ // all lines. This is a non-printing character so it will not change the horizontal
+ // spacing however it will affect the line height. As we ensure the ZWJ has the same
+ // text style as the title it will make sure the line height is consistent.
+ description.append('\u200D');
+
+ int middle = description.length();
+ description.append(episode);
+
+ description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ description.setSpan(
+ episodeStyle, middle, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
- if (entry.isGap()) {
- title = gapTitle;
- episode = null;
- } else if (entry.hasGenre(selectedGenreId)) {
- titleStyle = sProgramTitleStyle;
- episodeStyle = sEpisodeTitleStyle;
+ description.setSpan(
+ titleStyle, 0, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ setText(description);
+ }
+
+ private void updateIcons() {
+ // Sets recording icons if needed.
+ int iconResId = 0;
+ if (isEntryWideEnough() && mTableEntry.scheduledRecording != null) {
+ if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) {
+ iconResId = R.drawable.ic_warning_white_18dp;
+ } else {
+ switch (mTableEntry.scheduledRecording.getState()) {
+ case ScheduledRecording.STATE_RECORDING_NOT_STARTED:
+ iconResId = R.drawable.ic_scheduled_recording;
+ break;
+ case ScheduledRecording.STATE_RECORDING_IN_PROGRESS:
+ iconResId = R.drawable.ic_recording_program;
+ break;
+ default:
+ // leave the iconResId=0
+ }
}
- if (TextUtils.isEmpty(title)) {
- title = getResources().getString(R.string.program_title_for_no_information);
+ }
+ setCompoundDrawablePadding(iconResId != 0 ? sCompoundDrawablePadding : 0);
+ setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconResId, 0);
+ }
+
+ private void updateContentDescription(String title) {
+ // The content description includes extra information that is displayed on the detail view
+ Resources resources = getResources();
+ String description = title;
+ // TODO(b/73282818): only say channel name when the row changes
+ Channel channel = mChannelDataManager.getChannel(mTableEntry.channelId);
+ if (channel != null) {
+ description = channel.getDisplayNumber() + " " + description;
+ }
+ description +=
+ " "
+ + Utils.getDurationString(
+ getContext(),
+ mClock,
+ mTableEntry.entryStartUtcMillis,
+ mTableEntry.entryEndUtcMillis,
+ true);
+ Program program = mTableEntry.program;
+ if (program != null) {
+ String episodeDescription = program.getEpisodeContentDescription(getContext());
+ if (!TextUtils.isEmpty(episodeDescription)) {
+ description += " " + episodeDescription;
}
- SpannableStringBuilder description = new SpannableStringBuilder();
- description.append(title);
- if (!TextUtils.isEmpty(episode)) {
- description.append('\n');
-
- // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for
- // all lines. This is a non-printing character so it will not change the horizontal
- // spacing however it will affect the line height. As we ensure the ZWJ has the same
- // text style as the title it will make sure the line height is consistent.
- description.append('\u200D');
-
- int middle = description.length();
- description.append(episode);
-
- description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- description.setSpan(episodeStyle, middle, description.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ if (mTableEntry.scheduledRecording != null) {
+ if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) {
+ description +=
+ " " + resources.getString(R.string.dvr_epg_program_recording_conflict);
} else {
- description.setSpan(titleStyle, 0, description.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- setText(description);
-
- // Sets recording icons if needed.
- int iconResId = 0;
- if (mTableEntry.scheduledRecording != null) {
- if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) {
- iconResId = R.drawable.ic_warning_white_18dp;
- } else {
- switch (mTableEntry.scheduledRecording.getState()) {
- case ScheduledRecording.STATE_RECORDING_NOT_STARTED:
- iconResId = R.drawable.ic_scheduled_recording;
- break;
- case ScheduledRecording.STATE_RECORDING_IN_PROGRESS:
- iconResId = R.drawable.ic_recording_program;
- break;
- }
+ switch (mTableEntry.scheduledRecording.getState()) {
+ case ScheduledRecording.STATE_RECORDING_NOT_STARTED:
+ description +=
+ " "
+ + resources.getString(
+ R.string.dvr_epg_program_recording_scheduled);
+ break;
+ case ScheduledRecording.STATE_RECORDING_IN_PROGRESS:
+ description +=
+ " "
+ + resources.getString(
+ R.string.dvr_epg_program_recording_in_progress);
+ break;
+ default:
+ // do nothing
}
}
- setCompoundDrawablePadding(iconResId != 0 ? sCompoundDrawablePadding : 0);
- setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconResId, 0);
}
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd();
- // Maximum width for us to use a ripple
- mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis);
+ if (mTableEntry.isBlocked()) {
+ description += " " + resources.getString(R.string.program_guide_content_locked);
+ } else if (program != null) {
+ String programDescription = program.getDescription();
+ if (!TextUtils.isEmpty(programDescription)) {
+ description += " " + programDescription;
+ }
+ }
+ setContentDescription(description);
}
- /**
- * Update programItemView to handle alignments of text.
- */
+ /** Update programItemView to handle alignments of text. */
public void updateVisibleArea() {
View parentView = ((View) getParent());
if (parentView == null) {
@@ -341,7 +462,7 @@ public class ProgramItemView extends TextView {
}
if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
layoutVisibleArea(parentView.getLeft() - getLeft(), getRight() - parentView.getRight());
- } else {
+ } else {
layoutVisibleArea(getRight() - parentView.getRight(), parentView.getLeft() - getLeft());
}
}
@@ -349,16 +470,14 @@ public class ProgramItemView extends TextView {
/**
* Layout title and episode according to visible area.
*
- * Here's the spec.
- * 1. Don't show text if it's shorter than 48dp.
- * 2. Try showing whole text in visible area by placing and wrapping text,
- * but do not wrap text less than 30min.
- * 3. Episode title is visible only if title isn't multi-line.
+ * <p>Here's the spec. 1. Don't show text if it's shorter than 48dp. 2. Try showing whole text
+ * in visible area by placing and wrapping text, but do not wrap text less than 30min. 3.
+ * Episode title is visible only if title isn't multi-line.
*
* @param startOffset Offset of the start position from the enclosing view's start position.
* @param endOffset Offset of the end position from the enclosing view's end position.
*/
- private void layoutVisibleArea(int startOffset, int endOffset) {
+ private void layoutVisibleArea(int startOffset, int endOffset) {
int width = mTableEntry.getWidth();
int startPadding = Math.max(0, startOffset);
int endPadding = Math.max(0, endOffset);
@@ -388,8 +507,8 @@ public class ProgramItemView extends TextView {
mTableEntry = null;
}
- private static int getProgress(long start, long end) {
- long currentTime = System.currentTimeMillis();
+ private static int getProgress(Clock clock, long start, long end) {
+ long currentTime = clock.currentTimeMillis();
if (currentTime <= start) {
return 0;
} else if (currentTime >= end) {
@@ -417,11 +536,15 @@ public class ProgramItemView extends TextView {
private static int getStateCount(StateListDrawable stateListDrawable) {
try {
- Object stateCount = StateListDrawable.class.getDeclaredMethod("getStateCount")
- .invoke(stateListDrawable);
+ Object stateCount =
+ StateListDrawable.class
+ .getDeclaredMethod("getStateCount")
+ .invoke(stateListDrawable);
return (int) stateCount;
- } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException
- |InvocationTargetException e) {
+ } catch (NoSuchMethodException
+ | IllegalAccessException
+ | IllegalArgumentException
+ | InvocationTargetException e) {
Log.e(TAG, "Failed to call StateListDrawable.getStateCount()", e);
return 0;
}
@@ -429,12 +552,15 @@ public class ProgramItemView extends TextView {
private static Drawable getStateDrawable(StateListDrawable stateListDrawable, int index) {
try {
- Object drawable = StateListDrawable.class
- .getDeclaredMethod("getStateDrawable", Integer.TYPE)
- .invoke(stateListDrawable, index);
+ Object drawable =
+ StateListDrawable.class
+ .getDeclaredMethod("getStateDrawable", Integer.TYPE)
+ .invoke(stateListDrawable, index);
return (Drawable) drawable;
- } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException
- |InvocationTargetException e) {
+ } catch (NoSuchMethodException
+ | IllegalAccessException
+ | IllegalArgumentException
+ | InvocationTargetException e) {
Log.e(TAG, "Failed to call StateListDrawable.getStateDrawable(" + index + ")", e);
return null;
}
diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java
index c1fcdd40..397bacfb 100644
--- a/src/com/android/tv/guide/ProgramListAdapter.java
+++ b/src/com/android/tv/guide/ProgramListAdapter.java
@@ -22,9 +22,8 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener;
import com.android.tv.guide.ProgramManager.TableEntry;
@@ -111,9 +110,14 @@ class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter.Program
Log.d(TAG, "onBind. View = " + itemView + ", Entry = " + entry);
}
ProgramManager programManager = programGuide.getProgramManager();
- ((ProgramItemView) itemView).setValues(programGuide, entry,
- programManager.getSelectedGenreId(), programManager.getFromUtcMillis(),
- programManager.getToUtcMillis(), gapTitle);
+ ((ProgramItemView) itemView)
+ .setValues(
+ programGuide,
+ entry,
+ programManager.getSelectedGenreId(),
+ programManager.getFromUtcMillis(),
+ programManager.getToUtcMillis(),
+ gapTitle);
}
void onUnbind() {
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index 4ec3f77e..3f20a837 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -18,21 +18,20 @@ package com.android.tv.guide;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Log;
-
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.GenreItems;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.DvrScheduleManager.OnConflictStateChangeListener;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -40,9 +39,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-/**
- * Manages the channels and programs for the program guide.
- */
+/** Manages the channels and programs for the program guide. */
@MainThread
public class ProgramManager {
private static final String TAG = "ProgramManager";
@@ -60,7 +57,7 @@ public class ProgramManager {
private final TvInputManagerHelper mTvInputManagerHelper;
private final ChannelDataManager mChannelDataManager;
private final ProgramDataManager mProgramDataManager;
- private final DvrDataManager mDvrDataManager; // Only set if DVR is enabled
+ private final DvrDataManager mDvrDataManager; // Only set if DVR is enabled
private final DvrScheduleManager mDvrScheduleManager;
private long mStartUtcMillis;
@@ -127,51 +124,67 @@ public class ProgramManager {
private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
new DvrDataManager.ScheduledRecordingListener() {
- @Override
- public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
- for (ScheduledRecording schedule : scheduledRecordings) {
- TableEntry oldEntry = getTableEntry(schedule);
- if (oldEntry != null) {
- TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program,
- schedule, oldEntry.entryStartUtcMillis,
- oldEntry.entryEndUtcMillis, oldEntry.isBlocked());
- updateEntry(oldEntry, newEntry);
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ TableEntry oldEntry = getTableEntry(schedule);
+ if (oldEntry != null) {
+ TableEntry newEntry =
+ new TableEntry(
+ oldEntry.channelId,
+ oldEntry.program,
+ schedule,
+ oldEntry.entryStartUtcMillis,
+ oldEntry.entryEndUtcMillis,
+ oldEntry.isBlocked());
+ updateEntry(oldEntry, newEntry);
+ }
+ }
}
- }
- }
- @Override
- public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
- for (ScheduledRecording schedule : scheduledRecordings) {
- TableEntry oldEntry = getTableEntry(schedule);
- if (oldEntry != null) {
- TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, null,
- oldEntry.entryStartUtcMillis, oldEntry.entryEndUtcMillis,
- oldEntry.isBlocked());
- updateEntry(oldEntry, newEntry);
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ TableEntry oldEntry = getTableEntry(schedule);
+ if (oldEntry != null) {
+ TableEntry newEntry =
+ new TableEntry(
+ oldEntry.channelId,
+ oldEntry.program,
+ null,
+ oldEntry.entryStartUtcMillis,
+ oldEntry.entryEndUtcMillis,
+ oldEntry.isBlocked());
+ updateEntry(oldEntry, newEntry);
+ }
+ }
}
- }
- }
- @Override
- public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
- for (ScheduledRecording schedule : scheduledRecordings) {
- TableEntry oldEntry = getTableEntry(schedule);
- if (oldEntry != null) {
- TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program,
- schedule, oldEntry.entryStartUtcMillis,
- oldEntry.entryEndUtcMillis, oldEntry.isBlocked());
- updateEntry(oldEntry, newEntry);
+ @Override
+ public void onScheduledRecordingStatusChanged(
+ ScheduledRecording... scheduledRecordings) {
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ TableEntry oldEntry = getTableEntry(schedule);
+ if (oldEntry != null) {
+ TableEntry newEntry =
+ new TableEntry(
+ oldEntry.channelId,
+ oldEntry.program,
+ schedule,
+ oldEntry.entryStartUtcMillis,
+ oldEntry.entryEndUtcMillis,
+ oldEntry.isBlocked());
+ updateEntry(oldEntry, newEntry);
+ }
+ }
}
- }
- }
- };
+ };
private final OnConflictStateChangeListener mOnConflictStateChangeListener =
new OnConflictStateChangeListener() {
@Override
- public void onConflictStateChange(boolean conflict,
- ScheduledRecording... schedules) {
+ public void onConflictStateChange(
+ boolean conflict, ScheduledRecording... schedules) {
for (ScheduledRecording schedule : schedules) {
TableEntry entry = getTableEntry(schedule);
if (entry != null) {
@@ -181,8 +194,10 @@ public class ProgramManager {
}
};
- public ProgramManager(TvInputManagerHelper tvInputManagerHelper,
- ChannelDataManager channelDataManager, ProgramDataManager programDataManager,
+ public ProgramManager(
+ TvInputManagerHelper tvInputManagerHelper,
+ ChannelDataManager channelDataManager,
+ ProgramDataManager programDataManager,
@Nullable DvrDataManager dvrDataManager,
@Nullable DvrScheduleManager dvrScheduleManager) {
mTvInputManagerHelper = tvInputManagerHelper;
@@ -221,52 +236,39 @@ public class ProgramManager {
}
}
- /**
- * Adds a {@link Listener}.
- */
+ /** Adds a {@link Listener}. */
void addListener(Listener listener) {
mListeners.add(listener);
}
- /**
- * Registers a listener to be invoked when table entries are updated.
- */
+ /** Registers a listener to be invoked when table entries are updated. */
void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) {
mTableEntriesUpdatedListeners.add(listener);
}
- /**
- * Registers a listener to be invoked when a table entry is changed.
- */
+ /** Registers a listener to be invoked when a table entry is changed. */
void addTableEntryChangedListener(TableEntryChangedListener listener) {
mTableEntryChangedListeners.add(listener);
}
- /**
- * Removes a {@link Listener}.
- */
+ /** Removes a {@link Listener}. */
void removeListener(Listener listener) {
mListeners.remove(listener);
}
- /**
- * Removes a previously installed table entries update listener.
- */
+ /** Removes a previously installed table entries update listener. */
void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) {
mTableEntriesUpdatedListeners.remove(listener);
}
- /**
- * Removes a previously installed table entry changed listener.
- */
+ /** Removes a previously installed table entry changed listener. */
void removeTableEntryChangedListener(TableEntryChangedListener listener) {
mTableEntryChangedListeners.remove(listener);
}
/**
- * Resets channel list with given genre.
- * Caller should call {@link #buildGenreFilters()} prior to call this API to make
- * This notifies channel updates to listeners.
+ * Resets channel list with given genre. Caller should call {@link #buildGenreFilters()} prior
+ * to call this API to make This notifies channel updates to listeners.
*/
void resetChannelListWithGenre(int genreId) {
if (genreId == mSelectedGenreId) {
@@ -275,8 +277,14 @@ public class ProgramManager {
mFilteredChannels = mGenreChannelList.get(genreId);
mSelectedGenreId = genreId;
if (DEBUG) {
- Log.d(TAG, "resetChannelListWithGenre: " + GenreItems.getCanonicalGenre(genreId)
- + " has " + mFilteredChannels.size() + " channels out of " + mChannels.size());
+ Log.d(
+ TAG,
+ "resetChannelListWithGenre: "
+ + GenreItems.getCanonicalGenre(genreId)
+ + " has "
+ + mFilteredChannels.size()
+ + " channels out of "
+ + mChannels.size());
}
if (mGenreChannelList.get(mSelectedGenreId) == null) {
throw new IllegalStateException("Genre filter isn't ready.");
@@ -284,9 +292,7 @@ public class ProgramManager {
notifyChannelsUpdated();
}
- /**
- * Update the initial time range to manage. It updates program entries and genre as well.
- */
+ /** Update the initial time range to manage. It updates program entries and genre as well. */
void updateInitialTimeRange(long startUtcMillis, long endUtcMillis) {
mStartUtcMillis = startUtcMillis;
if (endUtcMillis > mEndUtcMillis) {
@@ -298,10 +304,7 @@ public class ProgramManager {
setTimeRange(startUtcMillis, endUtcMillis);
}
-
- /**
- * Shifts the time range by the given time. Also makes ProgramGuide scroll the views.
- */
+ /** Shifts the time range by the given time. Also makes ProgramGuide scroll the views. */
void shiftTime(long timeMillisToScroll) {
long fromUtcMillis = mFromUtcMillis + timeMillisToScroll;
long toUtcMillis = mToUtcMillis + timeMillisToScroll;
@@ -316,23 +319,17 @@ public class ProgramManager {
setTimeRange(fromUtcMillis, toUtcMillis);
}
- /**
- * Returned the scrolled(shifted) time in milliseconds.
- */
+ /** Returned the scrolled(shifted) time in milliseconds. */
long getShiftedTime() {
return mFromUtcMillis - mStartUtcMillis;
}
- /**
- * Returns the start time set by {@link #updateInitialTimeRange}.
- */
+ /** Returns the start time set by {@link #updateInitialTimeRange}. */
long getStartTime() {
return mStartUtcMillis;
}
- /**
- * Returns the program index of the program with {@code entryId} or -1 if not found.
- */
+ /** Returns the program index of the program with {@code entryId} or -1 if not found. */
int getProgramIdIndex(long channelId, long entryId) {
List<TableEntry> entries = mChannelIdEntriesMap.get(channelId);
if (entries != null) {
@@ -345,38 +342,29 @@ public class ProgramManager {
return -1;
}
- /**
- * Returns the program index of the program at {@code time} or -1 if not found.
- */
+ /** Returns the program index of the program at {@code time} or -1 if not found. */
int getProgramIndexAtTime(long channelId, long time) {
List<TableEntry> entries = mChannelIdEntriesMap.get(channelId);
for (int i = 0; i < entries.size(); ++i) {
TableEntry entry = entries.get(i);
- if (entry.entryStartUtcMillis <= time
- && time < entry.entryEndUtcMillis) {
+ if (entry.entryStartUtcMillis <= time && time < entry.entryEndUtcMillis) {
return i;
}
}
return -1;
}
- /**
- * Returns the start time of currently managed time range, in UTC millisecond.
- */
+ /** Returns the start time of currently managed time range, in UTC millisecond. */
long getFromUtcMillis() {
return mFromUtcMillis;
}
- /**
- * Returns the end time of currently managed time range, in UTC millisecond.
- */
+ /** Returns the end time of currently managed time range, in UTC millisecond. */
long getToUtcMillis() {
return mToUtcMillis;
}
- /**
- * Returns the number of the currently managed channels.
- */
+ /** Returns the number of the currently managed channels. */
int getChannelCount() {
return mFilteredChannels.size();
}
@@ -393,15 +381,15 @@ public class ProgramManager {
}
/**
- * Returns the index of provided {@link Channel} within the currently managed channels.
- * Returns -1 if such a channel is not found.
+ * Returns the index of provided {@link Channel} within the currently managed channels. Returns
+ * -1 if such a channel is not found.
*/
int getChannelIndex(Channel channel) {
return mFilteredChannels.indexOf(channel);
}
/**
- * Returns the index of channel with {@code channelId} within the currently managed channels.
+ * Returns the index of channel with {@code channelId} within the currently managed channels.
* Returns -1 if such a channel is not found.
*/
int getChannelIndex(long channelId) {
@@ -425,9 +413,7 @@ public class ProgramManager {
return mChannelIdEntriesMap.get(channelId).get(index);
}
- /**
- * Returns list genre ID's which has a channel.
- */
+ /** Returns list genre ID's which has a channel. */
List<Integer> getFilteredGenreIds() {
return mFilteredGenreIds;
}
@@ -457,15 +443,13 @@ public class ProgramManager {
buildGenreFilters();
}
- /**
- * Updates the table entries without notifying the change.
- */
+ /** Updates the table entries without notifying the change. */
private void updateTableEntriesWithoutNotification(boolean clear) {
if (clear) {
mChannelIdEntriesMap.clear();
}
- boolean parentalControlsEnabled = mTvInputManagerHelper.getParentalControlSettings()
- .isParentalControlsEnabled();
+ boolean parentalControlsEnabled =
+ mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled();
for (Channel channel : mChannels) {
long channelId = channel.getId();
// Inline the updating of the mChannelIdEntriesMap here so we can only call
@@ -475,8 +459,12 @@ public class ProgramManager {
int size = entries.size();
if (DEBUG) {
- Log.d(TAG, "Programs are loaded for channel " + channel.getId()
- + ", loaded size = " + size);
+ Log.d(
+ TAG,
+ "Programs are loaded for channel "
+ + channel.getId()
+ + ", loaded size = "
+ + size);
}
if (size == 0) {
continue;
@@ -496,14 +484,19 @@ public class ProgramManager {
} else {
TableEntry lastEntry = entries.get(entries.size() - 1);
if (mEndUtcMillis > lastEntry.entryEndUtcMillis) {
- entries.add(new TableEntry(channelId, lastEntry.entryEndUtcMillis,
- mEndUtcMillis));
+ entries.add(
+ new TableEntry(
+ channelId, lastEntry.entryEndUtcMillis, mEndUtcMillis));
} else if (lastEntry.entryEndUtcMillis == Long.MAX_VALUE) {
entries.remove(entries.size() - 1);
- entries.add(new TableEntry(lastEntry.channelId, lastEntry.program,
- lastEntry.scheduledRecording,
- lastEntry.entryStartUtcMillis, mEndUtcMillis,
- lastEntry.mIsBlocked));
+ entries.add(
+ new TableEntry(
+ lastEntry.channelId,
+ lastEntry.program,
+ lastEntry.scheduledRecording,
+ lastEntry.entryStartUtcMillis,
+ mEndUtcMillis,
+ lastEntry.mIsBlocked));
}
}
}
@@ -511,11 +504,10 @@ public class ProgramManager {
}
/**
- * Build genre filters based on the current programs.
- * This categories channels by its current program's canonical genres
- * and subsequent @{link resetChannelListWithGenre(int)} calls will reset channel list
- * with built channel list.
- * This is expected to be called whenever program guide is shown.
+ * Build genre filters based on the current programs. This categories channels by its current
+ * program's canonical genres and subsequent @{link resetChannelListWithGenre(int)} calls will
+ * reset channel list with built channel list. This is expected to be called whenever program
+ * guide is shown.
*/
private void buildGenreFilters() {
if (DEBUG) Log.d(TAG, "buildGenreFilters");
@@ -572,9 +564,13 @@ public class ProgramManager {
private void setTimeRange(long fromUtcMillis, long toUtcMillis) {
if (DEBUG) {
- Log.d(TAG, "setTimeRange. {FromTime="
- + Utils.toTimeString(fromUtcMillis) + ", ToTime="
- + Utils.toTimeString(toUtcMillis) + "}");
+ Log.d(
+ TAG,
+ "setTimeRange. {FromTime="
+ + Utils.toTimeString(fromUtcMillis)
+ + ", ToTime="
+ + Utils.toTimeString(toUtcMillis)
+ + "}");
}
if (mFromUtcMillis != fromUtcMillis || mToUtcMillis != toUtcMillis) {
mFromUtcMillis = fromUtcMillis;
@@ -585,8 +581,8 @@ public class ProgramManager {
private List<TableEntry> createProgramEntries(long channelId, boolean parentalControlsEnabled) {
List<TableEntry> entries = new ArrayList<>();
- boolean channelLocked = parentalControlsEnabled
- && mChannelDataManager.getChannel(channelId).isLocked();
+ boolean channelLocked =
+ parentalControlsEnabled && mChannelDataManager.getChannel(channelId).isLocked();
if (channelLocked) {
entries.add(new TableEntry(channelId, mStartUtcMillis, Long.MAX_VALUE, true));
} else {
@@ -597,20 +593,27 @@ public class ProgramManager {
// Dummy program.
continue;
}
- long programStartTime = Math.max(program.getStartTimeUtcMillis(),
- mStartUtcMillis);
+ long programStartTime = Math.max(program.getStartTimeUtcMillis(), mStartUtcMillis);
long programEndTime = program.getEndTimeUtcMillis();
if (programStartTime > lastProgramEndTime) {
// Gap since the last program.
- entries.add(new TableEntry(channelId, lastProgramEndTime,
- programStartTime));
+ entries.add(new TableEntry(channelId, lastProgramEndTime, programStartTime));
lastProgramEndTime = programStartTime;
}
if (programEndTime > lastProgramEndTime) {
- ScheduledRecording scheduledRecording = mDvrDataManager == null ? null
- : mDvrDataManager.getScheduledRecordingForProgramId(program.getId());
- entries.add(new TableEntry(channelId, program, scheduledRecording,
- lastProgramEndTime, programEndTime, false));
+ ScheduledRecording scheduledRecording =
+ mDvrDataManager == null
+ ? null
+ : mDvrDataManager.getScheduledRecordingForProgramId(
+ program.getId());
+ entries.add(
+ new TableEntry(
+ channelId,
+ program,
+ scheduledRecording,
+ lastProgramEndTime,
+ programEndTime,
+ false));
lastProgramEndTime = programEndTime;
}
}
@@ -622,9 +625,15 @@ public class ProgramManager {
// If the first entry's width doesn't have enough width, it is not good to show
// the first entry from UI perspective. So we clip it out.
entries.remove(0);
- entries.set(0, new TableEntry(secondEntry.channelId, secondEntry.program,
- secondEntry.scheduledRecording, mStartUtcMillis,
- secondEntry.entryEndUtcMillis, secondEntry.mIsBlocked));
+ entries.set(
+ 0,
+ new TableEntry(
+ secondEntry.channelId,
+ secondEntry.program,
+ secondEntry.scheduledRecording,
+ mStartUtcMillis,
+ secondEntry.entryEndUtcMillis,
+ secondEntry.mIsBlocked));
}
}
return entries;
@@ -662,8 +671,8 @@ public class ProgramManager {
/**
* Entry for program guide table. An "entry" can be either an actual program or a gap between
- * programs. This is needed for {@link ProgramListAdapter} because
- * {@link android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items.
+ * programs. This is needed for {@link ProgramListAdapter} because {@link
+ * android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items.
*/
static class TableEntry {
/** Channel ID which this entry is included. */
@@ -686,18 +695,27 @@ public class ProgramManager {
this(channelId, null, startUtcMillis, endUtcMillis, false);
}
- private TableEntry(long channelId, long startUtcMillis, long endUtcMillis,
- boolean blocked) {
+ private TableEntry(
+ long channelId, long startUtcMillis, long endUtcMillis, boolean blocked) {
this(channelId, null, null, startUtcMillis, endUtcMillis, blocked);
}
- private TableEntry(long channelId, Program program, long entryStartUtcMillis,
- long entryEndUtcMillis, boolean isBlocked) {
+ private TableEntry(
+ long channelId,
+ Program program,
+ long entryStartUtcMillis,
+ long entryEndUtcMillis,
+ boolean isBlocked) {
this(channelId, program, null, entryStartUtcMillis, entryEndUtcMillis, isBlocked);
}
- private TableEntry(long channelId, Program program, ScheduledRecording scheduledRecording,
- long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) {
+ private TableEntry(
+ long channelId,
+ Program program,
+ ScheduledRecording scheduledRecording,
+ long entryStartUtcMillis,
+ long entryEndUtcMillis,
+ boolean isBlocked) {
this.channelId = channelId;
this.program = program;
this.scheduledRecording = scheduledRecording;
@@ -706,46 +724,34 @@ public class ProgramManager {
mIsBlocked = isBlocked;
}
- /**
- * A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}.
- */
+ /** A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. */
long getId() {
// using a negative entryEndUtcMillis keeps it from conflicting with program Id
return program != null ? program.getId() : -entryEndUtcMillis;
}
- /**
- * Returns true if this is a gap.
- */
+ /** Returns true if this is a gap. */
boolean isGap() {
- return !Program.isValid(program);
+ return !Program.isProgramValid(program);
}
- /**
- * Returns true if this channel is blocked.
- */
+ /** Returns true if this channel is blocked. */
boolean isBlocked() {
return mIsBlocked;
}
- /**
- * Returns true if this program is on the air.
- */
+ /** Returns true if this program is on the air. */
boolean isCurrentProgram() {
long current = System.currentTimeMillis();
return entryStartUtcMillis <= current && entryEndUtcMillis > current;
}
- /**
- * Returns if this program has the genre.
- */
+ /** Returns if this program has the genre. */
boolean hasGenre(int genreId) {
return !isGap() && program.hasGenre(genreId);
}
- /**
- * Returns the width of table entry, in pixels.
- */
+ /** Returns the width of table entry, in pixels. */
int getWidth() {
return GuideUtils.convertMillisToPixel(entryStartUtcMillis, entryEndUtcMillis);
}
@@ -753,17 +759,42 @@ public class ProgramManager {
@Override
public String toString() {
return "TableEntry{"
- + "hashCode=" + hashCode()
- + ", channelId=" + channelId
- + ", program=" + program
- + ", startTime=" + Utils.toTimeString(entryStartUtcMillis)
- + ", endTimeTime=" + Utils.toTimeString(entryEndUtcMillis) + "}";
- }
+ + "hashCode="
+ + hashCode()
+ + ", channelId="
+ + channelId
+ + ", program="
+ + program
+ + ", startTime="
+ + Utils.toTimeString(entryStartUtcMillis)
+ + ", endTimeTime="
+ + Utils.toTimeString(entryEndUtcMillis)
+ + "}";
+ }
+ }
+
+ @VisibleForTesting
+ public static TableEntry createTableEntryForTest(
+ long channelId,
+ Program program,
+ ScheduledRecording scheduledRecording,
+ long entryStartUtcMillis,
+ long entryEndUtcMillis,
+ boolean isBlocked) {
+ return new TableEntry(
+ channelId,
+ program,
+ scheduledRecording,
+ entryStartUtcMillis,
+ entryEndUtcMillis,
+ isBlocked);
}
interface Listener {
void onGenresUpdated();
+
void onChannelsUpdated();
+
void onTimeRangeUpdated();
}
@@ -777,12 +808,12 @@ public class ProgramManager {
static class ListenerAdapter implements Listener {
@Override
- public void onGenresUpdated() { }
+ public void onGenresUpdated() {}
@Override
- public void onChannelsUpdated() { }
+ public void onChannelsUpdated() {}
@Override
- public void onTimeRangeUpdated() { }
+ public void onTimeRangeUpdated() {}
}
}
diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java
index fefc724c..83175bb6 100644
--- a/src/com/android/tv/guide/ProgramRow.java
+++ b/src/com/android/tv/guide/ProgramRow.java
@@ -24,12 +24,9 @@ import android.util.Log;
import android.util.Range;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-
-import com.android.tv.MainActivity;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.guide.ProgramManager.TableEntry;
import com.android.tv.util.Utils;
-
import java.util.concurrent.TimeUnit;
public class ProgramRow extends TimelineGridView {
@@ -47,25 +44,23 @@ public class ProgramRow extends TimelineGridView {
interface ChildFocusListener {
/**
- * Is called after focus is moved. Caller should check if old and new focuses are
- * listener's children.
- * See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}.
+ * Is called after focus is moved. Caller should check if old and new focuses are listener's
+ * children. See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}.
*/
void onChildFocus(View oldFocus, View newFocus);
}
- /**
- * Used only for debugging.
- */
+ /** Used only for debugging. */
private Channel mChannel;
- private final OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- updateChildVisibleArea();
- }
- };
+ private final OnGlobalLayoutListener mLayoutListener =
+ new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ updateChildVisibleArea();
+ }
+ };
public ProgramRow(Context context) {
this(context, null);
@@ -79,9 +74,7 @@ public class ProgramRow extends TimelineGridView {
super(context, attrs, defStyle);
}
- /**
- * Registers a listener focus events occurring on children to the {@code ProgramRow}.
- */
+ /** Registers a listener focus events occurring on children to the {@code ProgramRow}. */
public void setChildFocusListener(ChildFocusListener childFocusListener) {
mChildFocusListener = childFocusListener;
}
@@ -108,9 +101,7 @@ public class ProgramRow extends TimelineGridView {
updateChildVisibleArea();
}
- /**
- * Moves focus to the current program.
- */
+ /** Moves focus to the current program. */
public void focusCurrentProgram() {
View currentProgram = getCurrentProgramView();
if (currentProgram == null) {
@@ -124,13 +115,15 @@ public class ProgramRow extends TimelineGridView {
// Call this API after RTL is resolved. (i.e. View is measured.)
private boolean isDirectionStart(int direction) {
return getLayoutDirection() == LAYOUT_DIRECTION_LTR
- ? direction == View.FOCUS_LEFT : direction == View.FOCUS_RIGHT;
+ ? direction == View.FOCUS_LEFT
+ : direction == View.FOCUS_RIGHT;
}
// Call this API after RTL is resolved. (i.e. View is measured.)
private boolean isDirectionEnd(int direction) {
return getLayoutDirection() == LAYOUT_DIRECTION_LTR
- ? direction == View.FOCUS_RIGHT : direction == View.FOCUS_LEFT;
+ ? direction == View.FOCUS_RIGHT
+ : direction == View.FOCUS_LEFT;
}
@Override
@@ -142,8 +135,8 @@ public class ProgramRow extends TimelineGridView {
if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) {
if (focusedEntry.entryStartUtcMillis < fromMillis) {
// The current entry starts outside of the view; Align or scroll to the left.
- scrollByTime(Math.max(-ONE_HOUR_MILLIS,
- focusedEntry.entryStartUtcMillis - fromMillis));
+ scrollByTime(
+ Math.max(-ONE_HOUR_MILLIS, focusedEntry.entryStartUtcMillis - fromMillis));
return focused;
}
} else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) {
@@ -169,17 +162,19 @@ public class ProgramRow extends TimelineGridView {
TableEntry targetEntry = ((ProgramItemView) target).getTableEntry();
if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) {
- if (targetEntry.entryStartUtcMillis < fromMillis &&
- targetEntry.entryEndUtcMillis < fromMillis + HALF_HOUR_MILLIS) {
+ if (targetEntry.entryStartUtcMillis < fromMillis
+ && targetEntry.entryEndUtcMillis < fromMillis + HALF_HOUR_MILLIS) {
// The target entry starts outside the view; Align or scroll to the left.
- scrollByTime(Math.max(-ONE_HOUR_MILLIS,
- targetEntry.entryStartUtcMillis - fromMillis));
+ scrollByTime(
+ Math.max(-ONE_HOUR_MILLIS, targetEntry.entryStartUtcMillis - fromMillis));
}
} else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) {
if (targetEntry.entryStartUtcMillis > fromMillis + ONE_HOUR_MILLIS + HALF_HOUR_MILLIS) {
// The target entry starts outside the view; Align or scroll to the right.
- scrollByTime(Math.min(ONE_HOUR_MILLIS,
- targetEntry.entryStartUtcMillis - fromMillis - ONE_HOUR_MILLIS));
+ scrollByTime(
+ Math.min(
+ ONE_HOUR_MILLIS,
+ targetEntry.entryStartUtcMillis - fromMillis - ONE_HOUR_MILLIS));
}
}
@@ -188,8 +183,11 @@ public class ProgramRow extends TimelineGridView {
private void scrollByTime(long timeToScroll) {
if (DEBUG) {
- Log.d(TAG, "scrollByTime(timeToScroll="
- + TimeUnit.MILLISECONDS.toMinutes(timeToScroll) + "min)");
+ Log.d(
+ TAG,
+ "scrollByTime(timeToScroll="
+ + TimeUnit.MILLISECONDS.toMinutes(timeToScroll)
+ + "min)");
}
mProgramManager.shiftTime(timeToScroll);
}
@@ -203,12 +201,13 @@ public class ProgramRow extends TimelineGridView {
// The focus is lost due to information loaded. Requests focus immediately.
// (Because this entry is detached after real entries attached, we can't take
// the below approach to resume focus on entry being attached.)
- post(new Runnable() {
- @Override
- public void run() {
- requestFocus();
- }
- });
+ post(
+ new Runnable() {
+ @Override
+ public void run() {
+ requestFocus();
+ }
+ });
} else if (entry.isCurrentProgram()) {
if (DEBUG) Log.d(TAG, "Keep focus to the current program");
// Current program is visible in the guide.
@@ -227,12 +226,13 @@ public class ProgramRow extends TimelineGridView {
TableEntry entry = ((ProgramItemView) child).getTableEntry();
if (entry.isCurrentProgram()) {
mKeepFocusToCurrentProgram = false;
- post(new Runnable() {
- @Override
- public void run() {
- requestFocus();
- }
- });
+ post(
+ new Runnable() {
+ @Override
+ public void run() {
+ requestFocus();
+ }
+ });
}
}
}
@@ -243,8 +243,12 @@ public class ProgramRow extends TimelineGridView {
// Give focus according to the previous focused range
Range<Integer> focusRange = programGrid.getFocusRange();
- View nextFocus = GuideUtils.findNextFocusedProgram(this, focusRange.getLower(),
- focusRange.getUpper(), programGrid.isKeepCurrentProgramFocused());
+ View nextFocus =
+ GuideUtils.findNextFocusedProgram(
+ this,
+ focusRange.getLower(),
+ focusRange.getUpper(),
+ programGrid.isKeepCurrentProgramFocused());
if (nextFocus != null) {
return nextFocus.requestFocus();
@@ -279,30 +283,29 @@ public class ProgramRow extends TimelineGridView {
mChannel = channel;
}
- /**
- * Sets the instance of {@link ProgramGuide}
- */
+ /** Sets the instance of {@link ProgramGuide} */
public void setProgramGuide(ProgramGuide programGuide) {
mProgramGuide = programGuide;
mProgramManager = programGuide.getProgramManager();
}
- /**
- * Resets the scroll with the initial offset {@code scrollOffset}.
- */
+ /** Resets the scroll with the initial offset {@code scrollOffset}. */
public void resetScroll(int scrollOffset) {
- long startTime = GuideUtils.convertPixelToMillis(scrollOffset)
- + mProgramManager.getStartTime();
- int position = mChannel == null ? -1 : mProgramManager.getProgramIndexAtTime(
- mChannel.getId(), startTime);
+ long startTime =
+ GuideUtils.convertPixelToMillis(scrollOffset) + mProgramManager.getStartTime();
+ int position =
+ mChannel == null
+ ? -1
+ : mProgramManager.getProgramIndexAtTime(mChannel.getId(), startTime);
if (position < 0) {
getLayoutManager().scrollToPosition(0);
} else {
TableEntry entry = mProgramManager.getTableEntry(mChannel.getId(), position);
- int offset = GuideUtils.convertMillisToPixel(
- mProgramManager.getStartTime(), entry.entryStartUtcMillis) - scrollOffset;
- ((LinearLayoutManager) getLayoutManager())
- .scrollToPositionWithOffset(position, offset);
+ int offset =
+ GuideUtils.convertMillisToPixel(
+ mProgramManager.getStartTime(), entry.entryStartUtcMillis)
+ - scrollOffset;
+ ((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, offset);
// Workaround to b/31598505. When a program's duration is too long,
// RecyclerView.onScrolled() will not be called after scrollToPositionWithOffset().
// Therefore we have to update children's visible areas by ourselves in this case.
diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java
index 99f853b1..6e7485ac 100644
--- a/src/com/android/tv/guide/ProgramTableAdapter.java
+++ b/src/com/android/tv/guide/ProgramTableAdapter.java
@@ -16,8 +16,6 @@
package com.android.tv.guide;
-import static com.android.tv.util.ImageLoader.ImageLoaderCallback;
-
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -49,32 +47,30 @@ import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeL
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.TvSingletons;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.Program;
import com.android.tv.data.Program.CriticScore;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
-import com.android.tv.util.ImageCache;
-import com.android.tv.util.ImageLoader;
-import com.android.tv.util.ImageLoader.LoadTvInputLogoTask;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+import com.android.tv.util.images.ImageCache;
+import com.android.tv.util.images.ImageLoader;
+import com.android.tv.util.images.ImageLoader.ImageLoaderCallback;
+import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask;
import java.util.ArrayList;
import java.util.List;
-/**
- * Adapts the {@link ProgramListAdapter} list to the body of the program guide table.
- */
+/** Adapts the {@link ProgramListAdapter} list to the body of the program guide table. */
class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.ProgramRowViewHolder>
implements ProgramManager.TableEntryChangedListener {
private static final String TAG = "ProgramTableAdapter";
@@ -118,10 +114,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mContext = context;
mAccessibilityManager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mTvInputManagerHelper = TvApplication.getSingletons(context).getTvInputManagerHelper();
+ mTvInputManagerHelper = TvSingletons.getSingletons(context).getTvInputManagerHelper();
if (CommonFeatures.DVR.isEnabled(context)) {
- mDvrManager = TvApplication.getSingletons(context).getDvrManager();
- mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrManager = TvSingletons.getSingletons(context).getDvrManager();
+ mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
} else {
mDvrManager = null;
mDvrDataManager = null;
@@ -130,58 +126,62 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mProgramManager = programGuide.getProgramManager();
Resources res = context.getResources();
- mChannelLogoWidth = res.getDimensionPixelSize(
- R.dimen.program_guide_table_header_column_channel_logo_width);
- mChannelLogoHeight = res.getDimensionPixelSize(
- R.dimen.program_guide_table_header_column_channel_logo_height);
- mImageWidth = res.getDimensionPixelSize(
- R.dimen.program_guide_table_detail_image_width);
- mImageHeight = res.getDimensionPixelSize(
- R.dimen.program_guide_table_detail_image_height);
- mProgramTitleForNoInformation = res.getString(
- R.string.program_title_for_no_information);
- mProgramTitleForBlockedChannel = res.getString(
- R.string.program_title_for_blocked_channel);
- mChannelTextColor = res.getColor(
- R.color.program_guide_table_header_column_channel_number_text_color, null);
- mChannelBlockedTextColor = res.getColor(
- R.color.program_guide_table_header_column_channel_number_blocked_text_color, null);
- mDetailTextColor = res.getColor(
- R.color.program_guide_table_detail_title_text_color, null);
- mDetailGrayedTextColor = res.getColor(
- R.color.program_guide_table_detail_title_grayed_text_color, null);
+ mChannelLogoWidth =
+ res.getDimensionPixelSize(
+ R.dimen.program_guide_table_header_column_channel_logo_width);
+ mChannelLogoHeight =
+ res.getDimensionPixelSize(
+ R.dimen.program_guide_table_header_column_channel_logo_height);
+ mImageWidth = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_image_width);
+ mImageHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_image_height);
+ mProgramTitleForNoInformation = res.getString(R.string.program_title_for_no_information);
+ mProgramTitleForBlockedChannel = res.getString(R.string.program_title_for_blocked_channel);
+ mChannelTextColor =
+ res.getColor(
+ R.color.program_guide_table_header_column_channel_number_text_color, null);
+ mChannelBlockedTextColor =
+ res.getColor(
+ R.color.program_guide_table_header_column_channel_number_blocked_text_color,
+ null);
+ mDetailTextColor = res.getColor(R.color.program_guide_table_detail_title_text_color, null);
+ mDetailGrayedTextColor =
+ res.getColor(R.color.program_guide_table_detail_title_grayed_text_color, null);
mAnimationDuration =
res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration);
- mDetailPadding = res.getDimensionPixelOffset(
- R.dimen.program_guide_table_detail_padding);
+ mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding);
mProgramRecordableText = res.getString(R.string.dvr_epg_program_recordable);
mRecordingScheduledText = res.getString(R.string.dvr_epg_program_recording_scheduled);
mRecordingConflictText = res.getString(R.string.dvr_epg_program_recording_conflict);
mRecordingFailedText = res.getString(R.string.dvr_epg_program_recording_failed);
mRecordingInProgressText = res.getString(R.string.dvr_epg_program_recording_in_progress);
- mDvrPaddingStartWithTrack = res.getDimensionPixelOffset(
- R.dimen.program_guide_table_detail_dvr_margin_start);
- mDvrPaddingStartWithOutTrack = res.getDimensionPixelOffset(
- R.dimen.program_guide_table_detail_dvr_margin_start_without_track);
-
- int episodeTitleSize = res.getDimensionPixelSize(
- R.dimen.program_guide_table_detail_episode_title_text_size);
- ColorStateList episodeTitleColor = ColorStateList.valueOf(
- res.getColor(R.color.program_guide_table_detail_episode_title_text_color, null));
- mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize,
- episodeTitleColor, null);
+ mDvrPaddingStartWithTrack =
+ res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_dvr_margin_start);
+ mDvrPaddingStartWithOutTrack =
+ res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_detail_dvr_margin_start_without_track);
+
+ int episodeTitleSize =
+ res.getDimensionPixelSize(
+ R.dimen.program_guide_table_detail_episode_title_text_size);
+ ColorStateList episodeTitleColor =
+ ColorStateList.valueOf(
+ res.getColor(
+ R.color.program_guide_table_detail_episode_title_text_color, null));
+ mEpisodeTitleStyle =
+ new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null);
mCriticScoreViews = new ArrayList<>();
mRecycledViewPool = new RecycledViewPool();
- mRecycledViewPool.setMaxRecycledViews(R.layout.program_guide_table_item,
- context.getResources().getInteger(
- R.integer.max_recycled_view_pool_epg_table_item));
- mProgramManager.addListener(new ProgramManager.ListenerAdapter() {
- @Override
- public void onChannelsUpdated() {
- update();
- }
- });
+ mRecycledViewPool.setMaxRecycledViews(
+ R.layout.program_guide_table_item,
+ context.getResources().getInteger(R.integer.max_recycled_view_pool_epg_table_item));
+ mProgramManager.addListener(
+ new ProgramManager.ListenerAdapter() {
+ @Override
+ public void onChannelsUpdated() {
+ update();
+ }
+ });
update();
mProgramManager.addTableEntryChangedListener(this);
}
@@ -193,8 +193,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
}
mProgramListAdapters.clear();
for (int i = 0; i < mProgramManager.getChannelCount(); i++) {
- ProgramListAdapter listAdapter = new ProgramListAdapter(mContext.getResources(),
- mProgramGuide, i);
+ ProgramListAdapter listAdapter =
+ new ProgramListAdapter(mContext.getResources(), mProgramGuide, i);
mProgramManager.addTableEntriesUpdatedListener(listAdapter);
mProgramListAdapters.add(listAdapter);
}
@@ -250,29 +250,31 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
private ProgramManager.TableEntry mSelectedEntry;
private Animator mDetailOutAnimator;
private Animator mDetailInAnimator;
- private final Runnable mDetailInStarter = new Runnable() {
- @Override
- public void run() {
- mProgramRow.removeOnScrollListener(mOnScrollListener);
- if (mDetailInAnimator != null) {
- mDetailInAnimator.start();
- }
- }
- };
- private final Runnable mUpdateDetailViewRunnable = new Runnable() {
- @Override
- public void run() {
- updateDetailView();
- }
- };
+ private final Runnable mDetailInStarter =
+ new Runnable() {
+ @Override
+ public void run() {
+ mProgramRow.removeOnScrollListener(mOnScrollListener);
+ if (mDetailInAnimator != null) {
+ mDetailInAnimator.start();
+ }
+ }
+ };
+ private final Runnable mUpdateDetailViewRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ updateDetailView();
+ }
+ };
private final RecyclerView.OnScrollListener mOnScrollListener =
new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- onHorizontalScrolled();
- }
- };
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ onHorizontalScrolled();
+ }
+ };
private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener =
new ViewTreeObserver.OnGlobalFocusChangeListener() {
@@ -313,8 +315,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
new AccessibilityManager.AccessibilityStateChangeListener() {
@Override
public void onAccessibilityStateChanged(boolean enable) {
- enable &= !TvCommonUtils.isRunningInTest();
- mDetailView.setFocusable(enable);
+ enable &= !CommonUtils.isRunningInTest();
mChannelHeaderView.setFocusable(enable);
}
};
@@ -327,7 +328,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
- mContainer.getViewTreeObserver()
+ mContainer
+ .getViewTreeObserver()
.addOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
@@ -335,7 +337,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
@Override
public void onViewDetachedFromWindow(View v) {
- mContainer.getViewTreeObserver()
+ mContainer
+ .getViewTreeObserver()
.removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
@@ -364,9 +367,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block);
mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo);
- boolean accessibilityEnabled = mAccessibilityManager.isEnabled()
- && !TvCommonUtils.isRunningInTest();
- mDetailView.setFocusable(accessibilityEnabled);
+ boolean accessibilityEnabled =
+ mAccessibilityManager.isEnabled() && !CommonUtils.isRunningInTest();
mChannelHeaderView.setFocusable(accessibilityEnabled);
}
@@ -382,9 +384,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mDetailView.setVisibility(View.GONE);
// The bottom-left of the last channel header view will have a rounded corner.
- mChannelHeaderView.setBackgroundResource((position < mProgramListAdapters.size() - 1)
- ? R.drawable.program_guide_table_header_column_item_background
- : R.drawable.program_guide_table_header_column_last_item_background);
+ mChannelHeaderView.setBackgroundResource(
+ (position < mProgramListAdapters.size() - 1)
+ ? R.drawable.program_guide_table_header_column_item_background
+ : R.drawable.program_guide_table_header_column_last_item_background);
}
private void onBindChannel(Channel channel) {
@@ -411,7 +414,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
} else {
size = R.dimen.program_guide_table_header_column_channel_number_small_font_size;
}
- mChannelNumberView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ mChannelNumberView.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
mChannelNumberView.getContext().getResources().getDimension(size));
mChannelNumberView.setText(displayNumber);
mChannelNumberView.setVisibility(View.VISIBLE);
@@ -429,8 +433,11 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mChannelNameView.setVisibility(View.VISIBLE);
mChannelBlockView.setVisibility(View.GONE);
- mChannel.loadBitmap(itemView.getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
- mChannelLogoWidth, mChannelLogoHeight,
+ mChannel.loadBitmap(
+ itemView.getContext(),
+ Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
+ mChannelLogoWidth,
+ mChannelLogoHeight,
createChannelLogoLoadedCallback(this, channel.getId()));
}
}
@@ -439,7 +446,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
public void onChildFocus(View oldFocus, View newFocus) {
if (newFocus == null) {
return;
- } // When the accessibility service is enabled, focus might be put on channel's header or
+ } // When the accessibility service is enabled, focus might be put on channel's header
+ // or
// detail view, besides program items.
if (newFocus == mChannelHeaderView) {
mSelectedEntry = ((ProgramItemView) mProgramRow.getChildAt(0)).getTableEntry();
@@ -461,7 +469,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
return;
}
- if (Program.isValid(mSelectedEntry.program)) {
+ if (Program.isProgramValid(mSelectedEntry.program)) {
Program program = mSelectedEntry.program;
if (getProgramBlock(program) == null) {
program.prefetchPosterArt(itemView.getContext(), mImageWidth, mImageHeight);
@@ -473,10 +481,12 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
View detailContentView = mDetailView.findViewById(R.id.detail_content);
if (mDetailInAnimator == null) {
- mDetailOutAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView,
- PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
- PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
- 0f, direction * mDetailPadding));
+ mDetailOutAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(
+ detailContentView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
+ PropertyValuesHolder.ofFloat(
+ View.TRANSLATION_X, 0f, direction * mDetailPadding));
mDetailOutAnimator.setDuration(mAnimationDuration);
mDetailOutAnimator.addListener(
new HardwareLayerAnimatorListenerAdapter(detailContentView) {
@@ -501,10 +511,12 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mHandler.postDelayed(mDetailInStarter, mAnimationDuration);
}
- mDetailInAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView,
- PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f),
- PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
- direction * -mDetailPadding, 0f));
+ mDetailInAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(
+ detailContentView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f),
+ PropertyValuesHolder.ofFloat(
+ View.TRANSLATION_X, direction * -mDetailPadding, 0f));
mDetailInAnimator.setDuration(mAnimationDuration);
mDetailInAnimator.addListener(
new HardwareLayerAnimatorListenerAdapter(detailContentView) {
@@ -529,7 +541,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
}
if (DEBUG) Log.d(TAG, "updateDetailView");
mCriticScoresLayout.removeAllViews();
- if (Program.isValid(mSelectedEntry.program)) {
+ if (Program.isProgramValid(mSelectedEntry.program)) {
mTitleView.setTextColor(mDetailTextColor);
Context context = itemView.getContext();
Program program = mSelectedEntry.program;
@@ -538,7 +550,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
updatePosterArt(null);
if (blockedRating == null) {
- program.loadPosterArt(context, mImageWidth, mImageHeight,
+ program.loadPosterArt(
+ context,
+ mImageWidth,
+ mImageHeight,
createProgramPosterArtCallback(this, program));
}
@@ -550,24 +565,35 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
String fullTitle = title + " " + episodeTitle;
SpannableString text = new SpannableString(fullTitle);
- text.setSpan(mEpisodeTitleStyle,
- fullTitle.length() - episodeTitle.length(), fullTitle.length(),
+ text.setSpan(
+ mEpisodeTitleStyle,
+ fullTitle.length() - episodeTitle.length(),
+ fullTitle.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mTitleView.setText(text);
}
- updateTextView(mTimeView, Utils.getDurationString(context,
- program.getStartTimeUtcMillis(),
- program.getEndTimeUtcMillis(), false));
-
- boolean trackMetaDataVisible = updateTextView(mAspectRatioView, Utils
- .getAspectRatioString(program.getVideoWidth(), program.getVideoHeight()));
-
- int videoDefinitionLevel = Utils.getVideoDefinitionLevelFromSize(
- program.getVideoWidth(), program.getVideoHeight());
+ updateTextView(
+ mTimeView,
+ Utils.getDurationString(
+ context,
+ program.getStartTimeUtcMillis(),
+ program.getEndTimeUtcMillis(),
+ false));
+
+ boolean trackMetaDataVisible =
+ updateTextView(
+ mAspectRatioView,
+ Utils.getAspectRatioString(
+ program.getVideoWidth(), program.getVideoHeight()));
+
+ int videoDefinitionLevel =
+ Utils.getVideoDefinitionLevelFromSize(
+ program.getVideoWidth(), program.getVideoHeight());
trackMetaDataVisible |=
- updateTextView(mResolutionView, Utils.getVideoDefinitionLevelString(
- context, videoDefinitionLevel));
+ updateTextView(
+ mResolutionView,
+ Utils.getVideoDefinitionLevelString(context, videoDefinitionLevel));
if (mDvrManager != null && mDvrManager.isProgramRecordable(program)) {
ScheduledRecording scheduledRecording =
@@ -616,7 +642,6 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mDvrIndicator.setVisibility(View.GONE);
}
-
if (blockedRating == null) {
mBlockView.setVisibility(View.GONE);
updateTextView(mDescriptionView, program.getDescription());
@@ -655,8 +680,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
}
private String getBlockedDescription(TvContentRating blockedRating) {
- String name = mTvInputManagerHelper.getContentRatingsManager()
- .getDisplayNameForRating(blockedRating);
+ String name =
+ mTvInputManagerHelper
+ .getContentRatingsManager()
+ .getDisplayNameForRating(blockedRating);
if (TextUtils.isEmpty(name)) {
return mContext.getString(R.string.program_guide_content_locked);
} else {
@@ -691,8 +718,9 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mIsInputLogoVisible = true;
TvInputInfo info = mTvInputManagerHelper.getTvInputInfo(mChannel.getInputId());
if (info != null) {
- LoadTvInputLogoTask task = new LoadTvInputLogoTask(
- itemView.getContext(), ImageCache.getInstance(), info);
+ LoadTvInputLogoTask task =
+ new LoadTvInputLogoTask(
+ itemView.getContext(), ImageCache.getInstance(), info);
ImageLoader.loadBitmap(createTvInputLogoLoadedCallback(info, this), task);
}
}
@@ -735,23 +763,28 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
mInputLogoView.setVisibility(View.VISIBLE);
}
- private void updateCriticScoreView(ProgramRowViewHolder holder, final long programId,
- CriticScore criticScore, View view) {
+ private void updateCriticScoreView(
+ ProgramRowViewHolder holder,
+ final long programId,
+ CriticScore criticScore,
+ View view) {
TextView criticScoreSource = (TextView) view.findViewById(R.id.critic_score_source);
TextView criticScoreText = (TextView) view.findViewById(R.id.critic_score_score);
ImageView criticScoreLogo = (ImageView) view.findViewById(R.id.critic_score_logo);
- //set the appropriate information in the views
+ // set the appropriate information in the views
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
- criticScoreSource.setText(Html.fromHtml(criticScore.source,
- Html.FROM_HTML_MODE_LEGACY));
+ criticScoreSource.setText(
+ Html.fromHtml(criticScore.source, Html.FROM_HTML_MODE_LEGACY));
} else {
criticScoreSource.setText(Html.fromHtml(criticScore.source));
}
criticScoreText.setText(criticScore.score);
criticScoreSource.setVisibility(View.VISIBLE);
criticScoreText.setVisibility(View.VISIBLE);
- ImageLoader.loadBitmap(mContext, criticScore.logoUrl,
+ ImageLoader.loadBitmap(
+ mContext,
+ criticScore.logoUrl,
createCriticScoreLogoCallback(holder, programId, criticScoreLogo));
}
@@ -768,7 +801,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
return new ImageLoaderCallback<ProgramRowViewHolder>(holder) {
@Override
public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logoImage) {
- if (logoImage == null || holder.mSelectedEntry == null
+ if (logoImage == null
+ || holder.mSelectedEntry == null
|| holder.mSelectedEntry.program == null
|| holder.mSelectedEntry.program.getId() != programId) {
logoView.setVisibility(View.GONE);
@@ -785,7 +819,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
return new ImageLoaderCallback<ProgramRowViewHolder>(holder) {
@Override
public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap posterArt) {
- if (posterArt == null || holder.mSelectedEntry == null
+ if (posterArt == null
+ || holder.mSelectedEntry == null
|| holder.mSelectedEntry.program == null) {
return;
}
@@ -803,7 +838,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
return new ImageLoaderCallback<ProgramRowViewHolder>(holder) {
@Override
public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) {
- if (logo == null || holder.mChannel == null
+ if (logo == null
+ || holder.mChannel == null
|| holder.mChannel.getId() != channelId) {
return;
}
@@ -817,8 +853,9 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr
return new ImageLoaderCallback<ProgramRowViewHolder>(holder) {
@Override
public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) {
- if (logo != null && holder.mChannel != null && info.getId()
- .equals(holder.mChannel.getInputId())) {
+ if (logo != null
+ && holder.mChannel != null
+ && info.getId().equals(holder.mChannel.getInputId())) {
holder.updateInputLogoInternal(logo);
}
}
diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java
index d9e96a40..9c10c952 100644
--- a/src/com/android/tv/guide/TimeListAdapter.java
+++ b/src/com/android/tv/guide/TimeListAdapter.java
@@ -16,7 +16,6 @@
package com.android.tv.guide;
-import android.content.Context;
import android.content.res.Resources;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateFormat;
@@ -24,17 +23,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.util.Utils;
-
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
/**
- * Adapts the time range from {@link ProgramManager} to the timeline header row of the program
- * guide table.
+ * Adapts the time range from {@link ProgramManager} to the timeline header row of the program guide
+ * table.
*/
class TimeListAdapter extends RecyclerView.Adapter<TimeListAdapter.TimeViewHolder> {
private static final long TIME_UNIT_MS = TimeUnit.MINUTES.toMillis(30);
@@ -53,8 +50,10 @@ class TimeListAdapter extends RecyclerView.Adapter<TimeListAdapter.TimeViewHolde
TimeListAdapter(Resources res) {
if (sRowHeaderOverlapping == 0) {
- sRowHeaderOverlapping = Math.abs(res.getDimensionPixelOffset(
- R.dimen.program_guide_table_header_row_overlap));
+ sRowHeaderOverlapping =
+ Math.abs(
+ res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_header_row_overlap));
}
Locale locale = res.getConfiguration().locale;
mTimePatternSameDay = DateFormat.getBestDateTimePattern(locale, TIME_PATTERN_SAME_DAY);
diff --git a/src/com/android/tv/guide/TimelineGridView.java b/src/com/android/tv/guide/TimelineGridView.java
index 8af1c8a3..c4922b75 100644
--- a/src/com/android/tv/guide/TimelineGridView.java
+++ b/src/com/android/tv/guide/TimelineGridView.java
@@ -34,14 +34,15 @@ public class TimelineGridView extends RecyclerView {
public TimelineGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) {
- @Override
- public boolean onRequestChildFocus(RecyclerView parent, State state, View child,
- View focused) {
- // This disables the default scroll behavior for focus movement.
- return true;
- }
- });
+ setLayoutManager(
+ new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) {
+ @Override
+ public boolean onRequestChildFocus(
+ RecyclerView parent, State state, View child, View focused) {
+ // This disables the default scroll behavior for focus movement.
+ return true;
+ }
+ });
// RecyclerView is always focusable, however this is not desirable for us, so disable.
// See b/18863217 (ag/634046) for reasons to why RecyclerView is focusable.
diff --git a/src/com/android/tv/guide/TimelineRow.java b/src/com/android/tv/guide/TimelineRow.java
index 3f0c8678..b6a10ab1 100644
--- a/src/com/android/tv/guide/TimelineRow.java
+++ b/src/com/android/tv/guide/TimelineRow.java
@@ -40,19 +40,16 @@ public class TimelineRow extends TimelineGridView {
getLayoutManager().scrollToPosition(0);
}
- /**
- * Returns the current scroll position
- */
+ /** Returns the current scroll position */
public int getScrollOffset() {
return Math.abs(mScrollPosition);
}
- /**
- * Scrolls horizontally to the given position.
- */
+ /** Scrolls horizontally to the given position. */
public void scrollTo(int scrollOffset, boolean smoothScroll) {
- int dx = (scrollOffset - getScrollOffset())
- * (getLayoutDirection() == LAYOUT_DIRECTION_LTR ? 1 : -1);
+ int dx =
+ (scrollOffset - getScrollOffset())
+ * (getLayoutDirection() == LAYOUT_DIRECTION_LTR ? 1 : -1);
if (smoothScroll) {
smoothScrollBy(dx, 0);
} else {
diff --git a/src/com/android/tv/license/LicenseDialogFragment.java b/src/com/android/tv/license/LicenseDialogFragment.java
index b0e09776..74b99dcd 100644
--- a/src/com/android/tv/license/LicenseDialogFragment.java
+++ b/src/com/android/tv/license/LicenseDialogFragment.java
@@ -26,7 +26,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.dialog.SafeDismissDialogFragment;
diff --git a/src/com/android/tv/license/LicenseSideFragment.java b/src/com/android/tv/license/LicenseSideFragment.java
index fd92467c..ff99df2a 100644
--- a/src/com/android/tv/license/LicenseSideFragment.java
+++ b/src/com/android/tv/license/LicenseSideFragment.java
@@ -17,11 +17,9 @@
package com.android.tv.license;
import android.content.Context;
-
import com.android.tv.R;
import com.android.tv.ui.sidepanel.ActionItem;
import com.android.tv.ui.sidepanel.SideFragment;
-
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/license/LicenseUtils.java b/src/com/android/tv/license/LicenseUtils.java
index cf3fe751..1bae0c6a 100644
--- a/src/com/android/tv/license/LicenseUtils.java
+++ b/src/com/android/tv/license/LicenseUtils.java
@@ -17,22 +17,14 @@
package com.android.tv.license;
import android.content.res.AssetManager;
-
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-/**
- * Utilities for showing open source licenses.
- */
+/** Utilities for showing open source licenses. */
public final class LicenseUtils {
- public final static String RATING_SOURCE_FILE =
- "file:///android_asset/rating_sources.html";
+ public static final String RATING_SOURCE_FILE = "file:///android_asset/rating_sources.html";
-
- /**
- * Checks if the rating_attribution.html asset is include in the apk.
- */
+ /** Checks if the rating_attribution.html asset is include in the apk. */
public static boolean hasRatingAttribution(AssetManager am) {
try (InputStream is = am.open("rating_sources.html")) {
return true;
@@ -41,6 +33,5 @@ public final class LicenseUtils {
}
}
- private LicenseUtils() {
- }
+ private LicenseUtils() {}
}
diff --git a/src/com/android/tv/license/Licenses.java b/src/com/android/tv/license/Licenses.java
index 4b8a7ffc..1da91d7a 100644
--- a/src/com/android/tv/license/Licenses.java
+++ b/src/com/android/tv/license/Licenses.java
@@ -18,10 +18,8 @@ package com.android.tv.license;
import android.content.Context;
import android.support.annotation.RawRes;
-
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -36,6 +34,7 @@ import java.util.Collections;
public final class Licenses {
public static final String TAG = "Licenses";
+
public static boolean hasLicenses(Context context) {
return !getTextFromResource(
context.getApplicationContext(), R.raw.third_party_license_metadata, 0, -1)
diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl b/src/com/android/tv/livetv/receiver/GlobalKeyReceiver.java
index ed053790..80ea72f4 100644
--- a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl
+++ b/src/com/android/tv/livetv/receiver/GlobalKeyReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,14 @@
* limitations under the License.
*/
-package com.android.tv.tuner.exoplayer.ffmpeg;
+package com.android.tv.livetv.receiver;
-interface IFfmpegDecoder {
- boolean isAvailable();
- void create();
- void release();
- void resetDecoderState(String mimetype);
- void decode(long timeUs, in byte[] sample);
- byte[] getDecodedSample();
- long getDecodedTimeUs();
- void testSandboxIsolatedProcess();
- void testSandboxMinijail();
-} \ No newline at end of file
+import com.android.tv.receiver.AbstractGlobalKeyReceiver;
+
+/**
+ * Global Key Receiver for LiveTv.
+ *
+ * <p>This is an exported receiver and the name can not change.
+ * Partners that modify LiveTv source should rename this class.
+ */
+public final class GlobalKeyReceiver extends AbstractGlobalKeyReceiver {}
diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java
index 2fd70bfb..3ecd5f5c 100644
--- a/src/com/android/tv/menu/ActionCardView.java
+++ b/src/com/android/tv/menu/ActionCardView.java
@@ -22,12 +22,9 @@ import android.util.Log;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
-
import com.android.tv.R;
-/**
- * A view to render an item of TV options.
- */
+/** A view to render an item of TV options. */
public class ActionCardView extends RelativeLayout implements ItemListRowView.CardView<MenuAction> {
private static final String TAG = MenuView.TAG;
private static final boolean DEBUG = MenuView.DEBUG;
@@ -97,5 +94,5 @@ public class ActionCardView extends RelativeLayout implements ItemListRowView.Ca
}
@Override
- public void onRecycled() { }
+ public void onRecycled() {}
}
diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java
index 94ccd37f..fd93c314 100644
--- a/src/com/android/tv/menu/AppLinkCardView.java
+++ b/src/com/android/tv/menu/AppLinkCardView.java
@@ -33,19 +33,15 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Channel;
-import com.android.tv.util.BitmapUtils;
-import com.android.tv.util.ImageLoader;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.TvInputManagerHelper;
-
+import com.android.tv.util.images.BitmapUtils;
+import com.android.tv.util.images.ImageLoader;
import java.util.Objects;
-/**
- * A view to render an app link card.
- */
+/** A view to render an app link card. */
public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
private static final String TAG = MenuView.TAG;
private static final boolean DEBUG = MenuView.DEBUG;
@@ -88,9 +84,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
mDefaultDrawable = getResources().getDrawable(R.drawable.ic_recent_thumbnail_default, null);
}
- /**
- * Returns the intent which will be started once this card is clicked.
- */
+ /** Returns the intent which will be started once this card is clicked. */
public Intent getIntent() {
return mIntent;
}
@@ -100,13 +94,20 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
Channel newChannel = item.getChannel();
boolean channelChanged = !Objects.equals(mChannel, newChannel);
String previousPosterArtUri = mChannel == null ? null : mChannel.getAppLinkPosterArtUri();
- boolean posterArtChanged = previousPosterArtUri == null
- || newChannel.getAppLinkPosterArtUri() == null
- || !TextUtils.equals(previousPosterArtUri, newChannel.getAppLinkPosterArtUri());
+ boolean posterArtChanged =
+ previousPosterArtUri == null
+ || newChannel.getAppLinkPosterArtUri() == null
+ || !TextUtils.equals(
+ previousPosterArtUri, newChannel.getAppLinkPosterArtUri());
mChannel = newChannel;
if (DEBUG) {
- Log.d(TAG, "onBind(channelName=" + mChannel.getDisplayName() + ", selected=" + selected
- + ")");
+ Log.d(
+ TAG,
+ "onBind(channelName="
+ + mChannel.getDisplayName()
+ + ", selected="
+ + selected
+ + ")");
}
ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId());
if (channelChanged) {
@@ -120,8 +121,8 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
mAppInfoView.setVisibility(VISIBLE);
mAppInfoView.setCompoundDrawablePadding(mIconPadding);
mAppInfoView.setCompoundDrawablesRelative(null, null, null, null);
- appLabel = mTvInputManagerHelper
- .getTvInputApplicationLabel(mChannel.getInputId());
+ appLabel =
+ mTvInputManagerHelper.getTvInputApplicationLabel(mChannel.getInputId());
if (appLabel != null) {
mAppInfoView.setText(appLabel);
} else {
@@ -140,7 +141,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
protected void onPostExecute(CharSequence appLabel) {
mTvInputManagerHelper.setTvInputApplicationLabel(
mLoadTvInputId, appLabel);
- if (mLoadTvInputId != mChannel.getInputId()
+ if (mLoadTvInputId.equals(mChannel.getInputId())
|| !isAttachedToWindow()) {
return;
}
@@ -149,12 +150,17 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) {
- mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON,
- mIconWidth, mIconHeight, createChannelLogoCallback(
+ mChannel.loadBitmap(
+ getContext(),
+ Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON,
+ mIconWidth,
+ mIconHeight,
+ createChannelLogoCallback(
this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON));
} else if (appInfo.icon != 0) {
- Drawable appIcon = mTvInputManagerHelper
- .getTvInputApplicationIcon(mChannel.getInputId());
+ Drawable appIcon =
+ mTvInputManagerHelper.getTvInputApplicationIcon(
+ mChannel.getInputId());
if (appIcon != null) {
BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
@@ -186,11 +192,14 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
}
break;
case Channel.APP_LINK_TYPE_APP:
- appLabel = mTvInputManagerHelper
- .getTvInputApplicationLabel(mChannel.getInputId());
+ appLabel =
+ mTvInputManagerHelper.getTvInputApplicationLabel(mChannel.getInputId());
if (appLabel != null) {
- setText(getContext()
- .getString(R.string.channels_item_app_link_app_launcher, appLabel));
+ setText(
+ getContext()
+ .getString(
+ R.string.channels_item_app_link_app_launcher,
+ appLabel));
} else {
new AsyncTask<Void, Void, CharSequence>() {
private final String mLoadTvInputId = mChannel.getInputId();
@@ -206,15 +215,17 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
@Override
protected void onPostExecute(CharSequence appLabel) {
mTvInputManagerHelper.setTvInputApplicationLabel(
- mLoadTvInputId, appLabel);
+ mLoadTvInputId, appLabel);
if (!mLoadTvInputId.equals(mChannel.getInputId())
- || !isAttachedToWindow()) {
+ || !isAttachedToWindow()) {
return;
}
- setText(getContext()
- .getString(
- R.string.channels_item_app_link_app_launcher,
- appLabel));
+ setText(
+ getContext()
+ .getString(
+ R.string
+ .channels_item_app_link_app_launcher,
+ appLabel));
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -235,9 +246,13 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
mImageView.setImageDrawable(mDefaultDrawable);
mImageView.setForeground(null);
if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) {
- mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART,
- mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel,
- Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART));
+ mChannel.loadBitmap(
+ getContext(),
+ Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART,
+ mCardImageWidth,
+ mCardImageHeight,
+ createChannelLogoCallback(
+ this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART));
} else {
setCardImageWithBanner(appInfo);
}
@@ -265,10 +280,12 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
if (bitmap != null) {
drawable = new BitmapDrawable(getResources(), bitmap);
if (bitmap.getWidth() > bitmap.getHeight()) {
- drawable.setBounds(0, 0, mIconWidth,
- mIconWidth * bitmap.getHeight() / bitmap.getWidth());
+ drawable.setBounds(
+ 0, 0, mIconWidth, mIconWidth * bitmap.getHeight() / bitmap.getWidth());
} else {
- drawable.setBounds(0, 0,
+ drawable.setBounds(
+ 0,
+ 0,
mIconHeight * bitmap.getWidth() / bitmap.getHeight(),
mIconHeight);
}
@@ -303,6 +320,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
private void setCardImageWithBanner(ApplicationInfo appInfo) {
new AsyncTask<Void, Void, Drawable>() {
private String mLoadTvInputId = mChannel.getInputId();
+
@Override
protected Drawable doInBackground(Void... params) {
Drawable banner = null;
@@ -321,7 +339,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
@Override
protected void onPostExecute(Drawable banner) {
- if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) {
+ if (mLoadTvInputId.equals(mChannel.getInputId()) || !isAttachedToWindow()) {
return;
}
if (banner != null) {
@@ -341,6 +359,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
} else {
new AsyncTask<Void, Void, Drawable>() {
private final String mLoadTvInputId = mChannel.getInputId();
+
@Override
protected Drawable doInBackground(Void... params) {
Drawable banner = null;
@@ -373,8 +392,8 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
mImageView.setImageDrawable(mDefaultDrawable);
mImageView.setBackgroundResource(R.color.channel_card);
} else {
- Bitmap bitmap = Bitmap.createBitmap(
- mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888);
+ Bitmap bitmap =
+ Bitmap.createBitmap(mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
banner.setBounds(0, 0, mCardImageWidth, mCardImageHeight);
banner.draw(canvas);
@@ -387,12 +406,19 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
}
private void extractAndSetMetaViewBackgroundColor(Bitmap bitmap) {
- new Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener() {
- @Override
- public void onGenerated(Palette palette) {
- mMetaViewHolder.setBackgroundColor(palette.getDarkVibrantColor(
- getResources().getColor(R.color.channel_card_meta_background, null)));
- }
- });
+ new Palette.Builder(bitmap)
+ .generate(
+ new Palette.PaletteAsyncListener() {
+ @Override
+ public void onGenerated(Palette palette) {
+ mMetaViewHolder.setBackgroundColor(
+ palette.getDarkVibrantColor(
+ getResources()
+ .getColor(
+ R.color
+ .channel_card_meta_background,
+ null)));
+ }
+ });
}
}
diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java
index 4c5e6c78..3a94ebbf 100644
--- a/src/com/android/tv/menu/BaseCardView.java
+++ b/src/com/android/tv/menu/BaseCardView.java
@@ -29,12 +29,9 @@ import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.tv.R;
-/**
- * A base class to render a card.
- */
+/** A base class to render a card. */
public abstract class BaseCardView<T> extends LinearLayout implements ItemListRowView.CardView<T> {
private static final float SCALE_FACTOR_0F = 0f;
private static final float SCALE_FACTOR_1F = 1f;
@@ -49,10 +46,8 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
private final float mExtendedCardHeight;
private final float mTextViewHeight;
private final float mExtendedTextViewHeight;
- @Nullable
- private TextView mTextView;
- @Nullable
- private TextView mTextViewFocused;
+ @Nullable private TextView mTextView;
+ @Nullable private TextView mTextViewFocused;
private final int mCardImageWidth;
private final float mCardHeight;
private boolean mSelected;
@@ -74,27 +69,32 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
setClipToOutline(true);
mFocusAnimDuration = getResources().getInteger(R.integer.menu_focus_anim_duration);
- mFocusTranslationZ = getResources().getDimension(R.dimen.channel_card_elevation_focused)
- - getResources().getDimension(R.dimen.card_elevation_normal);
- mVerticalCardMargin = 2 * (
- getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top)
- + getResources().getDimensionPixelOffset(R.dimen.menu_list_margin_top));
+ mFocusTranslationZ =
+ getResources().getDimension(R.dimen.channel_card_elevation_focused)
+ - getResources().getDimension(R.dimen.card_elevation_normal);
+ mVerticalCardMargin =
+ 2
+ * (getResources().getDimensionPixelOffset(R.dimen.menu_list_padding_top)
+ + getResources()
+ .getDimensionPixelOffset(R.dimen.menu_list_margin_top));
// Ensure the same elevation and focus animation for all subclasses.
setElevation(getResources().getDimension(R.dimen.card_elevation_normal));
mCardCornerRadius = getResources().getDimensionPixelSize(R.dimen.channel_card_round_radius);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius);
- }
- });
+ setOutlineProvider(
+ new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(
+ 0, 0, view.getWidth(), view.getHeight(), mCardCornerRadius);
+ }
+ });
mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height);
- mExtendedCardHeight = getResources().getDimensionPixelSize(
- R.dimen.card_layout_height_extended);
+ mExtendedCardHeight =
+ getResources().getDimensionPixelSize(R.dimen.card_layout_height_extended);
mTextViewHeight = getResources().getDimensionPixelSize(R.dimen.card_meta_layout_height);
- mExtendedTextViewHeight = getResources().getDimensionPixelOffset(
- R.dimen.card_meta_layout_height_extended);
+ mExtendedTextViewHeight =
+ getResources().getDimensionPixelOffset(R.dimen.card_meta_layout_height_extended);
}
@Override
@@ -104,16 +104,14 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
mTextViewFocused = (TextView) findViewById(R.id.card_text_focused);
}
- /**
- * Called when the view is displayed.
- */
+ /** Called when the view is displayed. */
@Override
public void onBind(T item, boolean selected) {
setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
}
@Override
- public void onRecycled() { }
+ public void onRecycled() {}
@Override
public void onSelected() {
@@ -137,9 +135,7 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
}
}
- /**
- * Sets text of this card view.
- */
+ /** Sets text of this card view. */
public void setText(int resId) {
if (mTextResId != resId) {
mTextResId = resId;
@@ -155,9 +151,7 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
}
}
- /**
- * Sets text of this card view.
- */
+ /** Sets text of this card view. */
public void setText(String text) {
if (!TextUtils.equals(text, mTextString)) {
mTextString = text;
@@ -176,8 +170,8 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
private void onTextViewUpdated() {
if (mTextView != null && mTextViewFocused != null) {
mTextViewFocused.measure(
- MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1;
if (mExtendViewOnFocus) {
setTextViewFocusedAlpha(mSelected ? 1f : 0f);
@@ -188,9 +182,7 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
}
- /**
- * Enables or disables text view of this card view.
- */
+ /** Enables or disables text view of this card view. */
public void setTextViewEnabled(boolean enabled) {
if (mTextViewFocused != null) {
mTextViewFocused.setEnabled(enabled);
@@ -200,38 +192,38 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
}
}
- /**
- * Called when the focus animation started.
- */
+ /** Called when the focus animation started. */
protected void onFocusAnimationStart(boolean selected) {
if (mExtendViewOnFocus) {
setTextViewFocusedAlpha(selected ? 1f : 0f);
}
}
- /**
- * Called when the focus animation ended.
- */
+ /** Called when the focus animation ended. */
protected void onFocusAnimationEnd(boolean selected) {
// do nothing.
}
/**
- * Called when the view is bound, or while focus animation is running with a value
- * between {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}.
+ * Called when the view is bound, or while focus animation is running with a value between
+ * {@code SCALE_FACTOR_0F} and {@code SCALE_FACTOR_1F}.
*/
protected void onSetFocusAnimatedValue(float animatedValue) {
- float cardViewHeight = (mExtendViewOnFocus && isFocused())
- ? mExtendedCardHeight : mCardHeight;
+ float cardViewHeight =
+ (mExtendViewOnFocus && isFocused()) ? mExtendedCardHeight : mCardHeight;
float scale = 1f + (mVerticalCardMargin / cardViewHeight) * animatedValue;
setScaleX(scale);
setScaleY(scale);
setTranslationZ(mFocusTranslationZ * animatedValue);
if (mTextView != null && mTextViewFocused != null) {
ViewGroup.LayoutParams params = mTextView.getLayoutParams();
- int height = mExtendViewOnFocus ? Math.round(mTextViewHeight
- + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue)
- : (int) mTextViewHeight;
+ int height =
+ mExtendViewOnFocus
+ ? Math.round(
+ mTextViewHeight
+ + (mExtendedTextViewHeight - mTextViewHeight)
+ * animatedValue)
+ : (int) mTextViewHeight;
if (height != params.height) {
params.height = height;
setTextViewLayoutParams(params);
@@ -252,25 +244,27 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
final boolean selected = targetAnimatedValue == SCALE_FACTOR_1F;
mFocusAnimator = ValueAnimator.ofFloat(mFocusAnimatedValue, targetAnimatedValue);
mFocusAnimator.setDuration(mFocusAnimDuration);
- mFocusAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- setHasTransientState(true);
- onFocusAnimationStart(selected);
- }
+ mFocusAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ setHasTransientState(true);
+ onFocusAnimationStart(selected);
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- setHasTransientState(false);
- onFocusAnimationEnd(selected);
- }
- });
- mFocusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setFocusAnimatedValue((Float) animation.getAnimatedValue());
- }
- });
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setHasTransientState(false);
+ onFocusAnimationEnd(selected);
+ }
+ });
+ mFocusAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setFocusAnimatedValue((Float) animation.getAnimatedValue());
+ }
+ });
mFocusAnimator.start();
}
diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java
index 2ecb6af7..76056ee4 100644
--- a/src/com/android/tv/menu/ChannelCardView.java
+++ b/src/com/android/tv/menu/ChannelCardView.java
@@ -26,19 +26,15 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.parental.ParentalControlSettings;
-import com.android.tv.util.ImageLoader;
-
+import com.android.tv.util.images.ImageLoader;
import java.util.Objects;
-/**
- * A view to render channel card.
- */
+/** A view to render channel card. */
public class ChannelCardView extends BaseCardView<ChannelsRowItem> {
private static final String TAG = MenuView.TAG;
private static final boolean DEBUG = MenuView.DEBUG;
@@ -81,8 +77,13 @@ public class ChannelCardView extends BaseCardView<ChannelsRowItem> {
@Override
public void onBind(ChannelsRowItem item, boolean selected) {
if (DEBUG) {
- Log.d(TAG, "onBind(channelName=" + item.getChannel().getDisplayName() + ", selected="
- + selected + ")");
+ Log.d(
+ TAG,
+ "onBind(channelName="
+ + item.getChannel().getDisplayName()
+ + ", selected="
+ + selected
+ + ")");
}
updateChannel(item);
updateProgram();
@@ -146,7 +147,8 @@ public class ChannelCardView extends BaseCardView<ChannelsRowItem> {
return new ImageLoader.ImageLoaderCallback<ChannelCardView>(cardView) {
@Override
public void onBitmapLoaded(ChannelCardView cardView, @Nullable Bitmap posterArt) {
- if (posterArt == null || cardView.mProgram == null
+ if (posterArt == null
+ || cardView.mProgram == null
|| program.getChannelId() != cardView.mProgram.getChannelId()
|| program.getChannelId() != cardView.mChannel.getId()) {
return;
@@ -160,7 +162,10 @@ public class ChannelCardView extends BaseCardView<ChannelsRowItem> {
if (!TextUtils.equals(mPosterArtUri, posterArtUri)) {
mPosterArtUri = posterArtUri;
if (posterArtUri == null
- || !mProgram.loadPosterArt(getContext(), mCardImageWidth, mCardImageHeight,
+ || !mProgram.loadPosterArt(
+ getContext(),
+ mCardImageWidth,
+ mCardImageHeight,
createProgramPosterArtCallback(this, mProgram))) {
mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
mImageView.setForeground(null);
@@ -172,4 +177,4 @@ public class ChannelCardView extends BaseCardView<ChannelsRowItem> {
mImageView.setImageBitmap(posterArt);
mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient));
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
index bc5d6cfb..9cecb9c0 100644
--- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
+++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
@@ -23,24 +23,21 @@ import android.os.Message;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.util.Log;
-
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
-import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelImpl;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
-
+import com.android.tv.data.api.Channel;
import java.util.List;
-/**
- * A poster image prefetcher to show the program poster art in the Channels row faster.
- */
+/** A poster image prefetcher to show the program poster art in the Channels row faster. */
public class ChannelsPosterPrefetcher {
private static final String TAG = "PosterPrefetcher";
private static final boolean DEBUG = false;
private static final int MSG_PREFETCH_IMAGE = 1000;
- private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500; // 500 milliseconds
+ private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500; // 500 milliseconds
private final ProgramDataManager mProgramDataManager;
private final ChannelsRowAdapter mChannelsAdapter;
@@ -51,23 +48,19 @@ public class ChannelsPosterPrefetcher {
private boolean isCanceled;
- /**
- * Create {@link ChannelsPosterPrefetcher} object with given parameters.
- */
- public ChannelsPosterPrefetcher(Context context, ProgramDataManager programDataManager,
- ChannelsRowAdapter adapter) {
+ /** Create {@link ChannelsPosterPrefetcher} object with given parameters. */
+ public ChannelsPosterPrefetcher(
+ Context context, ProgramDataManager programDataManager, ChannelsRowAdapter adapter) {
mProgramDataManager = programDataManager;
mChannelsAdapter = adapter;
- mPosterArtWidth = context.getResources().getDimensionPixelSize(
- R.dimen.card_image_layout_width);
- mPosterArtHeight = context.getResources().getDimensionPixelSize(
- R.dimen.card_image_layout_height);
+ mPosterArtWidth =
+ context.getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
+ mPosterArtHeight =
+ context.getResources().getDimensionPixelSize(R.dimen.card_image_layout_height);
mContext = context.getApplicationContext();
}
- /**
- * Start prefetching of program poster art of recommendation.
- */
+ /** Start prefetching of program poster art of recommendation. */
public void prefetch() {
SoftPreconditions.checkState(!isCanceled, TAG, "Prefetch called after cancel was called.");
if (isCanceled) {
@@ -79,13 +72,11 @@ public class ChannelsPosterPrefetcher {
* prefetch the intermediate channels. So ignore previous schedule.
*/
mHandler.removeMessages(MSG_PREFETCH_IMAGE);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREFETCH_IMAGE),
- ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_PREFETCH_IMAGE), ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS);
}
- /**
- * Cancels pending and current prefetch requests.
- */
+ /** Cancels pending and current prefetch requests. */
public void cancel() {
isCanceled = true;
mHandler.removeCallbacksAndMessages(null);
@@ -104,11 +95,14 @@ public class ChannelsPosterPrefetcher {
return;
}
Channel channel = item.getChannel();
- if (!Channel.isValid(channel)) {
+ if (!ChannelImpl.isValid(channel)) {
continue;
}
- channel.prefetchImage(mContext, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
- mPosterArtWidth, mPosterArtHeight);
+ channel.prefetchImage(
+ mContext,
+ Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
+ mPosterArtWidth,
+ mPosterArtHeight);
Program program = mProgramDataManager.getCurrentProgram(channel.getId());
if (program != null) {
program.prefetchPosterArt(mContext, mPosterArtWidth, mPosterArtHeight);
@@ -116,8 +110,11 @@ public class ChannelsPosterPrefetcher {
}
}
if (DEBUG) {
- Log.d(TAG, "doPrefetchImages() finished. ImageLoader may still have async tasks for "
- + "channels " + items);
+ Log.d(
+ TAG,
+ "doPrefetchImages() finished. ImageLoader may still have async tasks for "
+ + "channels "
+ + items);
}
}
diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java
index 490d73de..7d03bf2b 100644
--- a/src/com/android/tv/menu/ChannelsRow.java
+++ b/src/com/android/tv/menu/ChannelsRow.java
@@ -17,7 +17,6 @@
package com.android.tv.menu;
import android.content.Context;
-
import com.android.tv.R;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.recommendation.RecentChannelEvaluator;
@@ -26,13 +25,9 @@ import com.android.tv.recommendation.Recommender;
public class ChannelsRow extends ItemListRow {
public static final String ID = ChannelsRow.class.getName();
- /**
- * Minimum count for recent channels.
- */
+ /** Minimum count for recent channels. */
public static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5;
- /**
- * Maximum count for recent channels.
- */
+ /** Maximum count for recent channels. */
public static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10;
private Recommender mTvRecommendation;
@@ -41,25 +36,33 @@ public class ChannelsRow extends ItemListRow {
public ChannelsRow(Context context, Menu menu, ProgramDataManager programDataManager) {
super(context, menu, R.string.menu_title_channels, R.dimen.card_layout_height, null);
- mTvRecommendation = new Recommender(getContext(), new Recommender.Listener() {
- @Override
- public void onRecommenderReady() {
- mChannelsAdapter.update();
- mChannelsPosterPrefetcher.prefetch();
- }
+ mTvRecommendation =
+ new Recommender(
+ getContext(),
+ new Recommender.Listener() {
+ @Override
+ public void onRecommenderReady() {
+ mChannelsAdapter.update();
+ mChannelsPosterPrefetcher.prefetch();
+ }
- @Override
- public void onRecommendationChanged() {
- mChannelsAdapter.update();
- mChannelsPosterPrefetcher.prefetch();
- }
- }, true);
+ @Override
+ public void onRecommendationChanged() {
+ mChannelsAdapter.update();
+ mChannelsPosterPrefetcher.prefetch();
+ }
+ },
+ true);
mTvRecommendation.registerEvaluator(new RecentChannelEvaluator());
- mChannelsAdapter = new ChannelsRowAdapter(context, mTvRecommendation,
- MIN_COUNT_FOR_RECENT_CHANNELS, MAX_COUNT_FOR_RECENT_CHANNELS);
+ mChannelsAdapter =
+ new ChannelsRowAdapter(
+ context,
+ mTvRecommendation,
+ MIN_COUNT_FOR_RECENT_CHANNELS,
+ MAX_COUNT_FOR_RECENT_CHANNELS);
setAdapter(mChannelsAdapter);
- mChannelsPosterPrefetcher = new ChannelsPosterPrefetcher(context, programDataManager,
- mChannelsAdapter);
+ mChannelsPosterPrefetcher =
+ new ChannelsPosterPrefetcher(context, programDataManager, mChannelsAdapter);
}
@Override
@@ -72,9 +75,7 @@ public class ChannelsRow extends ItemListRow {
mChannelsPosterPrefetcher.cancel();
}
- /**
- * Handle the update event of the recent channel.
- */
+ /** Handle the update event of the recent channel. */
@Override
public void onRecentChannelsChanged() {
mChannelsPosterPrefetcher.prefetch();
diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java
index 7ff44ea6..8536ef1f 100644
--- a/src/com/android/tv/menu/ChannelsRowAdapter.java
+++ b/src/com/android/tv/menu/ChannelsRowAdapter.java
@@ -20,25 +20,20 @@ import android.content.Context;
import android.content.Intent;
import android.media.tv.TvInputInfo;
import android.view.View;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelImpl;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.recommendation.Recommender;
-import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
-/**
- * An adapter of the Channels row.
- */
+/** An adapter of the Channels row. */
public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<ChannelsRowItem> {
// There are four special cards: guide, setup, dvr, applink.
private static final int SIZE_OF_VIEW_TYPE = 5;
@@ -50,60 +45,66 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
private final int mMaxCount;
private final int mMinCount;
- private final View.OnClickListener mGuideOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mTracker.sendMenuClicked(R.string.channels_item_program_guide);
- getMainActivity().getOverlayManager().showProgramGuide();
- }
- };
+ private final View.OnClickListener mGuideOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_program_guide);
+ getMainActivity().getOverlayManager().showProgramGuide();
+ }
+ };
- private final View.OnClickListener mSetupOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mTracker.sendMenuClicked(R.string.channels_item_setup);
- getMainActivity().getOverlayManager().showSetupFragment();
- }
- };
+ private final View.OnClickListener mSetupOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_setup);
+ getMainActivity().getOverlayManager().showSetupFragment();
+ }
+ };
- private final View.OnClickListener mDvrOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mTracker.sendMenuClicked(R.string.channels_item_dvr);
- getMainActivity().getOverlayManager().showDvrManager();
- }
- };
+ private final View.OnClickListener mDvrOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_dvr);
+ getMainActivity().getOverlayManager().showDvrManager();
+ }
+ };
- private final View.OnClickListener mAppLinkOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mTracker.sendMenuClicked(R.string.channels_item_app_link);
- Intent intent = ((AppLinkCardView) view).getIntent();
- if (intent != null) {
- getMainActivity().startActivitySafe(intent);
- }
- }
- };
+ private final View.OnClickListener mAppLinkOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_app_link);
+ Intent intent = ((AppLinkCardView) view).getIntent();
+ if (intent != null) {
+ getMainActivity().startActivitySafe(intent);
+ }
+ }
+ };
- private final View.OnClickListener mChannelOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- // Always send the label "Channels" because the channel ID or name or number might be
- // sensitive.
- mTracker.sendMenuClicked(R.string.menu_title_channels);
- getMainActivity().tuneToChannel((Channel) view.getTag());
- getMainActivity().hideOverlaysForTune();
- }
- };
+ private final View.OnClickListener mChannelOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Always send the label "Channels" because the channel ID or name or number
+ // might be
+ // sensitive.
+ mTracker.sendMenuClicked(R.string.menu_title_channels);
+ getMainActivity().tuneToChannel((Channel) view.getTag());
+ getMainActivity().hideOverlaysForTune();
+ }
+ };
- public ChannelsRowAdapter(Context context, Recommender recommender,
- int minCount, int maxCount) {
+ public ChannelsRowAdapter(
+ Context context, Recommender recommender, int minCount, int maxCount) {
super(context);
mContext = context;
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mTracker = appSingletons.getTracker();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mTracker = tvSingletons.getTracker();
if (CommonFeatures.DVR.isEnabled(context)) {
- mDvrDataManager = appSingletons.getDvrDataManager();
+ mDvrDataManager = tvSingletons.getDvrDataManager();
} else {
mDvrDataManager = null;
}
@@ -168,7 +169,7 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
}
if (needToShowAppLinkItem()) {
ChannelsRowItem.APP_LINK_ITEM.setChannel(
- new Channel.Builder(getMainActivity().getCurrentChannel()).build());
+ new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build());
items.add(ChannelsRowItem.APP_LINK_ITEM);
}
for (Channel channel : getRecentChannels()) {
@@ -189,10 +190,11 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
++currentIndex;
}
if (updateItem(needToShowAppLinkItem(), ChannelsRowItem.APP_LINK_ITEM, currentIndex)) {
- if (!getMainActivity().getCurrentChannel()
+ if (!getMainActivity()
+ .getCurrentChannel()
.hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) {
ChannelsRowItem.APP_LINK_ITEM.setChannel(
- new Channel.Builder(getMainActivity().getCurrentChannel()).build());
+ new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build());
notifyItemChanged(currentIndex);
}
++currentIndex;
@@ -213,9 +215,7 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
}
}
- /**
- * Returns {@code true} if the item should be shown.
- */
+ /** Returns {@code true} if the item should be shown. */
private boolean updateItem(boolean needToShow, ChannelsRowItem item, int index) {
List<ChannelsRowItem> items = getItemList();
boolean isItemInList = index < items.size() && item.equals(items.get(index));
@@ -230,14 +230,14 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
}
private boolean needToShowSetupItem() {
- TvInputManagerHelper inputManager =
- TvApplication.getSingletons(mContext).getTvInputManagerHelper();
- return SetupUtils.getInstance(mContext).hasNewInput(inputManager);
+ TvSingletons singletons = TvSingletons.getSingletons(mContext);
+ TvInputManagerHelper inputManager = singletons.getTvInputManagerHelper();
+ return singletons.getSetupUtils().hasNewInput(inputManager);
}
private boolean needToShowDvrItem() {
TvInputManagerHelper inputManager =
- TvApplication.getSingletons(mContext).getTvInputManagerHelper();
+ TvSingletons.getSingletons(mContext).getTvInputManagerHelper();
if (mDvrDataManager != null) {
for (TvInputInfo info : inputManager.getTvInputInfos(true, true)) {
if (info.canRecord()) {
@@ -250,7 +250,7 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
private boolean needToShowAppLinkItem() {
TvInputManagerHelper inputManager =
- TvApplication.getSingletons(mContext).getTvInputManagerHelper();
+ TvSingletons.getSingletons(mContext).getTvInputManagerHelper();
Channel currentChannel = getMainActivity().getCurrentChannel();
return currentChannel != null
&& currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE
@@ -289,8 +289,10 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels
private static boolean addChannelToList(
List<Channel> channelList, Channel channel, long currentChannelId) {
- if (channel == null || channel.getId() == currentChannelId
- || channelList.contains(channel) || !channel.isBrowsable()) {
+ if (channel == null
+ || channel.getId() == currentChannelId
+ || channelList.contains(channel)
+ || !channel.isBrowsable()) {
return false;
}
channelList.add(channel);
diff --git a/src/com/android/tv/menu/ChannelsRowItem.java b/src/com/android/tv/menu/ChannelsRowItem.java
index c35189ec..608bb36e 100644
--- a/src/com/android/tv/menu/ChannelsRowItem.java
+++ b/src/com/android/tv/menu/ChannelsRowItem.java
@@ -17,14 +17,10 @@
package com.android.tv.menu;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
import com.android.tv.R;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
-/**
- * A class for the items in channels row.
- */
+/** A class for the items in channels row. */
public class ChannelsRowItem {
/** The item ID for guide item */
public static final int GUIDE_ITEM_ID = -1;
@@ -62,31 +58,23 @@ public class ChannelsRowItem {
mLayoutId = layoutId;
}
- /**
- * Returns the channel for this item.
- */
+ /** Returns the channel for this item. */
@NonNull
public Channel getChannel() {
return mChannel;
}
- /**
- * Sets the channel.
- */
+ /** Sets the channel. */
public void setChannel(@NonNull Channel channel) {
mChannel = channel;
}
- /**
- * Returns the layout resource ID to represent this item.
- */
+ /** Returns the layout resource ID to represent this item. */
public int getLayoutId() {
return mLayoutId;
}
- /**
- * Returns the unique ID for this item.
- */
+ /** Returns the unique ID for this item. */
public long getItemId() {
return mItemId;
}
@@ -94,8 +82,12 @@ public class ChannelsRowItem {
@Override
public String toString() {
return "ChannelsRowItem{"
- + "itemId=" + mItemId
- + ", layoutId=" + mLayoutId
- + ", channel=" + mChannel + "}";
+ + "itemId="
+ + mItemId
+ + ", layoutId="
+ + mLayoutId
+ + ", channel="
+ + mChannel
+ + "}";
}
}
diff --git a/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java b/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java
index f69d5e86..7da26916 100644
--- a/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java
@@ -17,15 +17,11 @@
package com.android.tv.menu;
import android.content.Context;
-
-import com.android.tv.customization.CustomAction;
-
+import com.android.tv.common.customization.CustomAction;
import java.util.ArrayList;
import java.util.List;
-/**
- * An adapter of options that can accepts customization data.
- */
+/** An adapter of options that can accepts customization data. */
public abstract class CustomizableOptionsRowAdapter extends OptionsRowAdapter {
private final List<CustomAction> mCustomActions;
@@ -54,8 +50,9 @@ public abstract class CustomizableOptionsRowAdapter extends OptionsRowAdapter {
// Type of MenuAction should be unique in the Adapter.
int type = -(i + 1);
CustomAction customAction = mCustomActions.get(i);
- MenuAction action = new MenuAction(
- customAction.getTitle(), type, customAction.getIconDrawable());
+ MenuAction action =
+ new MenuAction(
+ customAction.getTitle(), type, customAction.getIconDrawable());
if (customAction.isFront()) {
actions.add(position++, action);
diff --git a/src/com/android/tv/menu/IMenuView.java b/src/com/android/tv/menu/IMenuView.java
index 87c5d9f6..19ebc739 100644
--- a/src/com/android/tv/menu/IMenuView.java
+++ b/src/com/android/tv/menu/IMenuView.java
@@ -17,33 +17,26 @@
package com.android.tv.menu;
import com.android.tv.menu.Menu.MenuShowReason;
-
import java.util.List;
-/**
- * An base interface for menu view.
- */
+/** An base interface for menu view. */
public interface IMenuView {
- /**
- * Sets menu rows.
- */
+ /** Sets menu rows. */
void setMenuRows(List<MenuRow> menuRows);
/**
* Shows the main menu.
*
- * <p> The inherited class should show the menu and select the row corresponding to
- * {@code rowIdToSelect}. If the menu is already visible, change the current selection to the
- * given row.
+ * <p>The inherited class should show the menu and select the row corresponding to {@code
+ * rowIdToSelect}. If the menu is already visible, change the current selection to the given
+ * row.
*
* @param reason A reason why this is called. See {@link MenuShowReason}.
* @param rowIdToSelect An ID of the row which corresponds to the {@code reason}.
*/
void onShow(@MenuShowReason int reason, String rowIdToSelect, Runnable runnableAfterShow);
- /**
- * Hides the main menu
- */
+ /** Hides the main menu */
void onHide();
/**
@@ -60,8 +53,6 @@ public interface IMenuView {
*/
boolean update(String rowId, boolean menuActive);
- /**
- * Checks if the menu view is visible or not.
- */
+ /** Checks if the menu view is visible or not. */
boolean isVisible();
}
diff --git a/src/com/android/tv/menu/ItemListRow.java b/src/com/android/tv/menu/ItemListRow.java
index faa611fa..2993d085 100644
--- a/src/com/android/tv/menu/ItemListRow.java
+++ b/src/com/android/tv/menu/ItemListRow.java
@@ -17,33 +17,37 @@
package com.android.tv.menu;
import android.content.Context;
-
import com.android.tv.R;
import com.android.tv.menu.ItemListRowView.ItemListAdapter;
/**
- * A menu item which is used to represents the list of the items.
- * A list will be displayed by a HorizontalGridView with cards, so an adapter
- * for the GridView is necessary.
+ * A menu item which is used to represents the list of the items. A list will be displayed by a
+ * HorizontalGridView with cards, so an adapter for the GridView is necessary.
*/
@SuppressWarnings("rawtypes")
public class ItemListRow extends MenuRow {
private ItemListAdapter mAdapter;
- public ItemListRow(Context context, Menu menu, int titleResId, int itemHeightResId,
+ public ItemListRow(
+ Context context,
+ Menu menu,
+ int titleResId,
+ int itemHeightResId,
ItemListAdapter adapter) {
this(context, menu, context.getString(titleResId), itemHeightResId, adapter);
}
- public ItemListRow(Context context, Menu menu, String title, int itemHeightResId,
+ public ItemListRow(
+ Context context,
+ Menu menu,
+ String title,
+ int itemHeightResId,
ItemListAdapter adapter) {
super(context, menu, title, itemHeightResId);
mAdapter = adapter;
}
- /**
- * Returns the adapter.
- */
+ /** Returns the adapter. */
public ItemListAdapter<?> getAdapter() {
return mAdapter;
}
diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java
index cbeee936..7042324d 100644
--- a/src/com/android/tv/menu/ItemListRowView.java
+++ b/src/com/android/tv/menu/ItemListRowView.java
@@ -25,25 +25,24 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.util.ViewCache;
-
import java.util.Collections;
import java.util.List;
-/**
- * A view that shows a title and list view.
- */
+/** A view that shows a title and list view. */
public class ItemListRowView extends MenuRowView implements OnChildSelectedListener {
private static final String TAG = MenuView.TAG;
private static final boolean DEBUG = MenuView.DEBUG;
public interface CardView<T> {
void onBind(T row, boolean selected);
+
void onRecycled();
+
void onSelected();
+
void onDeselected();
}
@@ -115,7 +114,7 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe
}
}
- public static abstract class ItemListAdapter<T>
+ public abstract static class ItemListAdapter<T>
extends RecyclerView.Adapter<ItemListAdapter.MyViewHolder> {
private final MainActivity mMainActivity;
private final LayoutInflater mLayoutInflater;
@@ -129,25 +128,20 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe
}
/**
- * In most cases, implementation should call {@link #setItemList(java.util.List)} with
- * newly update item list.
+ * In most cases, implementation should call {@link #setItemList(java.util.List)} with newly
+ * update item list.
*/
public abstract void update();
- /**
- * Gets layout resource ID. It'll be used in {@link #onCreateViewHolder}.
- */
+ /** Gets layout resource ID. It'll be used in {@link #onCreateViewHolder}. */
protected abstract int getLayoutResId(int viewType);
- /**
- * Releases all the resources which need to be released.
- */
- public void release() {
- }
+ /** Releases all the resources which need to be released. */
+ public void release() {}
/**
- * The initial position of list that will be selected when the main menu appears.
- * By default, the first item is initially selected.
+ * The initial position of list that will be selected when the main menu appears. By
+ * default, the first item is initially selected.
*/
public int getInitialPosition() {
return 0;
@@ -169,8 +163,8 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe
* <p>This sends an item change event, not a structural change event. The items of the same
* positions retain the same identity.
*
- * <p>If there's any structural change and relayout and rebind is needed, call
- * {@link #notifyDataSetChanged} explicitly.
+ * <p>If there's any structural change and relayout and rebind is needed, call {@link
+ * #notifyDataSetChanged} explicitly.
*/
protected void setItemList(List<T> itemList) {
int oldSize = mItemList.size();
@@ -197,24 +191,21 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe
return mItemList.size();
}
- /**
- * Returns the position of the item.
- */
+ /** Returns the position of the item. */
protected int getItemPosition(T item) {
return mItemList.indexOf(item);
}
- /**
- * Returns {@code true} if the item list contains the item, otherwise {@code false}.
- */
+ /** Returns {@code true} if the item list contains the item, otherwise {@code false}. */
protected boolean containsItem(T item) {
return mItemList.contains(item);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = ViewCache.getInstance().getOrCreateView(
- mLayoutInflater, getLayoutResId(viewType), parent);
+ View view =
+ ViewCache.getInstance()
+ .getOrCreateView(mLayoutInflater, getLayoutResId(viewType), parent);
return new MyViewHolder(view);
}
diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java
index e373de61..19a93dbc 100644
--- a/src/com/android/tv/menu/Menu.java
+++ b/src/com/android/tv/menu/Menu.java
@@ -21,27 +21,23 @@ import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
-import android.os.Looper;
-import android.os.Message;
import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.util.Log;
-
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import com.android.tv.ChannelTuner;
import com.android.tv.R;
-import com.android.tv.TvApplication;
import com.android.tv.TvOptionsManager;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
-import com.android.tv.common.TvCommonUtils;
-import com.android.tv.common.WeakHandler;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.DurationTimer;
import com.android.tv.menu.MenuRowFactory.PartnerRow;
import com.android.tv.menu.MenuRowFactory.TvOptionsRow;
import com.android.tv.ui.TunableTvView;
-import com.android.tv.util.DurationTimer;
+import com.android.tv.ui.hideable.AutoHideScheduler;
import com.android.tv.util.ViewCache;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -49,19 +45,25 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-/**
- * A class which controls the menu.
- */
-public class Menu {
+/** A class which controls the menu. */
+public class Menu implements AccessibilityStateChangeListener {
private static final String TAG = "Menu";
private static final boolean DEBUG = false;
@Retention(RetentionPolicy.SOURCE)
- @IntDef({REASON_NONE, REASON_GUIDE, REASON_PLAY_CONTROLS_PLAY, REASON_PLAY_CONTROLS_PAUSE,
- REASON_PLAY_CONTROLS_PLAY_PAUSE, REASON_PLAY_CONTROLS_REWIND,
- REASON_PLAY_CONTROLS_FAST_FORWARD, REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS,
- REASON_PLAY_CONTROLS_JUMP_TO_NEXT})
+ @IntDef({
+ REASON_NONE,
+ REASON_GUIDE,
+ REASON_PLAY_CONTROLS_PLAY,
+ REASON_PLAY_CONTROLS_PAUSE,
+ REASON_PLAY_CONTROLS_PLAY_PAUSE,
+ REASON_PLAY_CONTROLS_REWIND,
+ REASON_PLAY_CONTROLS_FAST_FORWARD,
+ REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS,
+ REASON_PLAY_CONTROLS_JUMP_TO_NEXT
+ })
public @interface MenuShowReason {}
+
public static final int REASON_NONE = 0;
public static final int REASON_GUIDE = 1;
public static final int REASON_PLAY_CONTROLS_PLAY = 2;
@@ -73,6 +75,7 @@ public class Menu {
public static final int REASON_PLAY_CONTROLS_JUMP_TO_NEXT = 8;
private static final List<String> sRowIdListForReason = new ArrayList<>();
+
static {
sRowIdListForReason.add(null); // REASON_NONE
sRowIdListForReason.add(ChannelsRow.ID); // REASON_GUIDE
@@ -86,6 +89,7 @@ public class Menu {
}
private static final Map<Integer, Integer> PRELOAD_VIEW_IDS = new HashMap<>();
+
static {
PRELOAD_VIEW_IDS.put(R.layout.menu_card_guide, 1);
PRELOAD_VIEW_IDS.put(R.layout.menu_card_setup, 1);
@@ -97,15 +101,13 @@ public class Menu {
private static final String SCREEN_NAME = "Menu";
- private static final int MSG_HIDE_MENU = 1000;
-
private final Context mContext;
private final IMenuView mMenuView;
private final Tracker mTracker;
private final DurationTimer mVisibleTimer = new DurationTimer();
private final long mShowDurationMillis;
private final OnMenuVisibilityChangeListener mOnMenuVisibilityChangeListener;
- private final WeakHandler<Menu> mHandler = new MenuWeakHandler(this, Looper.getMainLooper());
+ private final AutoHideScheduler mAutoHideScheduler;
private final MenuUpdater mMenuUpdater;
private final List<MenuRow> mMenuRows = new ArrayList<>();
@@ -116,17 +118,24 @@ public class Menu {
private boolean mAnimationDisabledForTest;
@VisibleForTesting
- Menu(Context context, IMenuView menuView, MenuRowFactory menuRowFactory,
+ Menu(
+ Context context,
+ IMenuView menuView,
+ MenuRowFactory menuRowFactory,
OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) {
this(context, null, null, menuView, menuRowFactory, onMenuVisibilityChangeListener);
}
- public Menu(Context context, TunableTvView tvView, TvOptionsManager optionsManager,
- IMenuView menuView, MenuRowFactory menuRowFactory,
+ public Menu(
+ Context context,
+ TunableTvView tvView,
+ TvOptionsManager optionsManager,
+ IMenuView menuView,
+ MenuRowFactory menuRowFactory,
OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) {
mContext = context;
mMenuView = menuView;
- mTracker = TvApplication.getSingletons(context).getTracker();
+ mTracker = TvSingletons.getSingletons(context).getTracker();
mMenuUpdater = new MenuUpdater(this, tvView, optionsManager);
Resources res = context.getResources();
mShowDurationMillis = res.getInteger(R.integer.menu_show_duration);
@@ -134,12 +143,13 @@ public class Menu {
mShowAnimator = AnimatorInflater.loadAnimator(context, R.animator.menu_enter);
mShowAnimator.setTarget(mMenuView);
mHideAnimator = AnimatorInflater.loadAnimator(context, R.animator.menu_exit);
- mHideAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- hideInternal();
- }
- });
+ mHideAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ hideInternal();
+ }
+ });
mHideAnimator.setTarget(mMenuView);
// Build menu rows
addMenuRow(menuRowFactory.createMenuRow(this, PlayControlsRow.class));
@@ -147,6 +157,7 @@ public class Menu {
addMenuRow(menuRowFactory.createMenuRow(this, PartnerRow.class));
addMenuRow(menuRowFactory.createMenuRow(this, TvOptionsRow.class));
mMenuView.setMenuRows(mMenuRows);
+ mAutoHideScheduler = new AutoHideScheduler(context, () -> hide(true));
}
/**
@@ -163,20 +174,16 @@ public class Menu {
}
}
- /**
- * Call this method to end the lifetime of the menu.
- */
+ /** Call this method to end the lifetime of the menu. */
public void release() {
mMenuUpdater.release();
for (MenuRow row : mMenuRows) {
row.release();
}
- mHandler.removeCallbacksAndMessages(null);
+ mAutoHideScheduler.cancel();
}
- /**
- * Preloads the item view used for the menu.
- */
+ /** Preloads the item view used for the menu. */
public void preloadItemViews() {
HorizontalGridView fakeParent = new HorizontalGridView(mContext);
for (int id : PRELOAD_VIEW_IDS.keySet()) {
@@ -201,20 +208,23 @@ public class Menu {
mOnMenuVisibilityChangeListener.onMenuVisibilityChange(true);
}
String rowIdToSelect = sRowIdListForReason.get(reason);
- mMenuView.onShow(reason, rowIdToSelect, mAnimationDisabledForTest ? null : new Runnable() {
- @Override
- public void run() {
- if (isActive()) {
- mShowAnimator.start();
- }
- }
- });
+ mMenuView.onShow(
+ reason,
+ rowIdToSelect,
+ mAnimationDisabledForTest
+ ? null
+ : new Runnable() {
+ @Override
+ public void run() {
+ if (isActive()) {
+ mShowAnimator.start();
+ }
+ }
+ });
scheduleHide();
}
- /**
- * Closes the menu.
- */
+ /** Closes the menu. */
public void hide(boolean withAnimation) {
if (mShowAnimator.isStarted()) {
mShowAnimator.cancel();
@@ -225,7 +235,7 @@ public class Menu {
if (mAnimationDisabledForTest) {
withAnimation = false;
}
- mHandler.removeMessages(MSG_HIDE_MENU);
+ mAutoHideScheduler.cancel();
if (withAnimation) {
if (!mHideAnimator.isStarted()) {
mHideAnimator.start();
@@ -246,26 +256,21 @@ public class Menu {
}
}
- /**
- * Schedules to hide the menu in some seconds.
- */
+ /** Schedules to hide the menu in some seconds. */
public void scheduleHide() {
- mHandler.removeMessages(MSG_HIDE_MENU);
- if (!mKeepVisible) {
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_MENU, mShowDurationMillis);
- }
+ mAutoHideScheduler.schedule(mShowDurationMillis);
}
/**
- * Called when the caller wants the main menu to be kept visible or not.
- * If {@code keepVisible} is set to {@code true}, the hide schedule doesn't close the main menu,
- * but calling {@link #hide} still hides it.
- * If {@code keepVisible} is set to {@code false}, the hide schedule works as usual.
+ * Called when the caller wants the main menu to be kept visible or not. If {@code keepVisible}
+ * is set to {@code true}, the hide schedule doesn't close the main menu, but calling {@link
+ * #hide} still hides it. If {@code keepVisible} is set to {@code false}, the hide schedule
+ * works as usual.
*/
public void setKeepVisible(boolean keepVisible) {
mKeepVisible = keepVisible;
if (mKeepVisible) {
- mHandler.removeMessages(MSG_HIDE_MENU);
+ mAutoHideScheduler.cancel();
} else if (isActive()) {
scheduleHide();
}
@@ -273,12 +278,10 @@ public class Menu {
@VisibleForTesting
boolean isHideScheduled() {
- return mHandler.hasMessages(MSG_HIDE_MENU);
+ return mAutoHideScheduler.isScheduled();
}
- /**
- * Returns {@code true} if the menu is open and not hiding.
- */
+ /** Returns {@code true} if the menu is open and not hiding. */
public boolean isActive() {
return mMenuView.isVisible() && !mHideAnimator.isStarted();
}
@@ -303,9 +306,7 @@ public class Menu {
return mMenuView.update(rowId, isActive());
}
- /**
- * This method is called when channels are changed.
- */
+ /** This method is called when channels are changed. */
public void onRecentChannelsChanged() {
if (DEBUG) Log.d(TAG, "onRecentChannelsChanged");
for (MenuRow row : mMenuRows) {
@@ -313,42 +314,28 @@ public class Menu {
}
}
- /**
- * This method is called when the stream information is changed.
- */
+ /** This method is called when the stream information is changed. */
public void onStreamInfoChanged() {
if (DEBUG) Log.d(TAG, "update options row in main menu");
mMenuUpdater.onStreamInfoChanged();
}
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ mAutoHideScheduler.onAccessibilityStateChanged(enabled);
+ }
+
@VisibleForTesting
void disableAnimationForTest() {
- if (!TvCommonUtils.isRunningInTest()) {
+ if (!CommonUtils.isRunningInTest()) {
throw new RuntimeException("Animation may only be enabled/disabled during tests.");
}
mAnimationDisabledForTest = true;
}
- /**
- * A listener which receives the notification when the menu is visible/invisible.
- */
- public static abstract class OnMenuVisibilityChangeListener {
- /**
- * Called when the menu becomes visible/invisible.
- */
+ /** A listener which receives the notification when the menu is visible/invisible. */
+ public abstract static class OnMenuVisibilityChangeListener {
+ /** Called when the menu becomes visible/invisible. */
public abstract void onMenuVisibilityChange(boolean visible);
}
-
- private static class MenuWeakHandler extends WeakHandler<Menu> {
- public MenuWeakHandler(Menu menu, Looper mainLooper) {
- super(mainLooper, menu);
- }
-
- @Override
- public void handleMessage(Message msg, @NonNull Menu menu) {
- if (msg.what == MSG_HIDE_MENU) {
- menu.hide(true);
- }
- }
- }
}
diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java
index b4356059..52372535 100644
--- a/src/com/android/tv/menu/MenuAction.java
+++ b/src/com/android/tv/menu/MenuAction.java
@@ -19,37 +19,47 @@ package com.android.tv.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-
import com.android.tv.R;
import com.android.tv.TvOptionsManager;
import com.android.tv.TvOptionsManager.OptionType;
-/**
- * A class to define possible actions from main menu.
- */
+/** A class to define possible actions from main menu. */
public class MenuAction {
// Actions in the TV option row.
public static final MenuAction SELECT_CLOSED_CAPTION_ACTION =
- new MenuAction(R.string.options_item_closed_caption,
+ new MenuAction(
+ R.string.options_item_closed_caption,
TvOptionsManager.OPTION_CLOSED_CAPTIONS,
R.drawable.ic_tvoption_cc);
public static final MenuAction SELECT_DISPLAY_MODE_ACTION =
- new MenuAction(R.string.options_item_display_mode, TvOptionsManager.OPTION_DISPLAY_MODE,
+ new MenuAction(
+ R.string.options_item_display_mode,
+ TvOptionsManager.OPTION_DISPLAY_MODE,
R.drawable.ic_tvoption_aspect);
public static final MenuAction SYSTEMWIDE_PIP_ACTION =
- new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_SYSTEMWIDE_PIP,
+ new MenuAction(
+ R.string.options_item_pip,
+ TvOptionsManager.OPTION_SYSTEMWIDE_PIP,
R.drawable.ic_tvoption_pip);
public static final MenuAction SELECT_AUDIO_LANGUAGE_ACTION =
- new MenuAction(R.string.options_item_multi_audio, TvOptionsManager.OPTION_MULTI_AUDIO,
+ new MenuAction(
+ R.string.options_item_multi_audio,
+ TvOptionsManager.OPTION_MULTI_AUDIO,
R.drawable.ic_tvoption_multi_track);
public static final MenuAction MORE_CHANNELS_ACTION =
- new MenuAction(R.string.options_item_more_channels,
- TvOptionsManager.OPTION_MORE_CHANNELS, R.drawable.ic_store);
+ new MenuAction(
+ R.string.options_item_more_channels,
+ TvOptionsManager.OPTION_MORE_CHANNELS,
+ R.drawable.ic_store);
public static final MenuAction DEV_ACTION =
- new MenuAction(R.string.options_item_developer,
- TvOptionsManager.OPTION_DEVELOPER, R.drawable.ic_developer_mode_tv_white_48dp);
+ new MenuAction(
+ R.string.options_item_developer,
+ TvOptionsManager.OPTION_DEVELOPER,
+ R.drawable.ic_developer_mode_tv_white_48dp);
public static final MenuAction SETTINGS_ACTION =
- new MenuAction(R.string.options_item_settings, TvOptionsManager.OPTION_SETTINGS,
+ new MenuAction(
+ R.string.options_item_settings,
+ TvOptionsManager.OPTION_SETTINGS,
R.drawable.ic_settings);
private final String mActionName;
@@ -60,18 +70,14 @@ public class MenuAction {
private int mDrawableResId;
private boolean mEnabled = true;
- /**
- * Sets the action description. Returns {@code trye} if the description is changed.
- */
+ /** Sets the action description. Returns {@code trye} if the description is changed. */
public static boolean setActionDescription(MenuAction action, String actionDescription) {
String oldDescription = action.mActionDescription;
action.mActionDescription = actionDescription;
return !TextUtils.equals(action.mActionDescription, oldDescription);
}
- /**
- * Enables or disables the action. Returns {@code true} if the value is changed.
- */
+ /** Enables or disables the action. Returns {@code true} if the value is changed. */
public static boolean setEnabled(MenuAction action, boolean enabled) {
boolean changed = action.mEnabled != enabled;
action.mEnabled = enabled;
@@ -105,13 +111,12 @@ public class MenuAction {
return mActionDescription;
}
- @OptionType public int getType() {
+ @OptionType
+ public int getType() {
return mType;
}
- /**
- * Returns Drawable.
- */
+ /** Returns Drawable. */
public Drawable getDrawable(Context context) {
if (mDrawable == null) {
mDrawable = context.getDrawable(mDrawableResId);
diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java
index 173d4004..a600f704 100644
--- a/src/com/android/tv/menu/MenuLayoutManager.java
+++ b/src/com/android/tv/menu/MenuLayoutManager.java
@@ -34,11 +34,9 @@ import android.util.Property;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -47,9 +45,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
-/**
- * A view that represents TV main menu.
- */
+/** A view that represents TV main menu. */
@UiThread
public class MenuLayoutManager {
static final String TAG = "MenuLayoutManager";
@@ -93,24 +89,22 @@ public class MenuLayoutManager {
Resources res = context.getResources();
mRowAlignFromBottom = res.getDimensionPixelOffset(R.dimen.menu_row_align_from_bottom);
mRowContentsPaddingTop = res.getDimensionPixelOffset(R.dimen.menu_row_contents_padding_top);
- mRowContentsPaddingBottomMax = res.getDimensionPixelOffset(
- R.dimen.menu_row_contents_padding_bottom_max);
- mRowTitleTextDescenderHeight = res.getDimensionPixelOffset(
- R.dimen.menu_row_title_text_descender_height);
+ mRowContentsPaddingBottomMax =
+ res.getDimensionPixelOffset(R.dimen.menu_row_contents_padding_bottom_max);
+ mRowTitleTextDescenderHeight =
+ res.getDimensionPixelOffset(R.dimen.menu_row_title_text_descender_height);
mMenuMarginBottomMin = res.getDimensionPixelOffset(R.dimen.menu_margin_bottom_min);
mRowTitleHeight = res.getDimensionPixelSize(R.dimen.menu_row_title_height);
mRowScrollUpAnimationOffset =
res.getDimensionPixelOffset(R.dimen.menu_row_scroll_up_anim_offset);
mRowAnimationDuration = res.getInteger(R.integer.menu_row_selection_anim_duration);
- mOldContentsFadeOutDuration = res.getInteger(
- R.integer.menu_previous_contents_fade_out_duration);
- mCurrentContentsFadeInDuration = res.getInteger(
- R.integer.menu_current_contents_fade_in_duration);
+ mOldContentsFadeOutDuration =
+ res.getInteger(R.integer.menu_previous_contents_fade_out_duration);
+ mCurrentContentsFadeInDuration =
+ res.getInteger(R.integer.menu_current_contents_fade_in_duration);
}
- /**
- * Sets the menu rows and views.
- */
+ /** Sets the menu rows and views. */
public void setMenuRowsAndViews(List<MenuRow> menuRows, List<MenuRowView> menuRowViews) {
mMenuRows.clear();
mMenuRows.addAll(menuRows);
@@ -181,22 +175,54 @@ public class MenuLayoutManager {
for (MenuRowView view : mMenuRowViews) {
View title = view.getChildAt(0);
View contents = view.getChildAt(1);
- Log.d(TAG, prefix + " position=" + position++
- + " rowView={visiblility=" + view.getVisibility()
- + ", alpha=" + view.getAlpha()
- + ", translationY=" + view.getTranslationY()
- + ", left=" + view.getLeft() + ", top=" + view.getTop()
- + ", right=" + view.getRight() + ", bottom=" + view.getBottom()
- + "}, title={visiblility=" + title.getVisibility()
- + ", alpha=" + title.getAlpha()
- + ", translationY=" + title.getTranslationY()
- + ", left=" + title.getLeft() + ", top=" + title.getTop()
- + ", right=" + title.getRight() + ", bottom=" + title.getBottom()
- + "}, contents={visiblility=" + contents.getVisibility()
- + ", alpha=" + contents.getAlpha()
- + ", translationY=" + contents.getTranslationY()
- + ", left=" + contents.getLeft() + ", top=" + contents.getTop()
- + ", right=" + contents.getRight() + ", bottom=" + contents.getBottom()+ "}");
+ Log.d(
+ TAG,
+ prefix
+ + " position="
+ + position++
+ + " rowView={visiblility="
+ + view.getVisibility()
+ + ", alpha="
+ + view.getAlpha()
+ + ", translationY="
+ + view.getTranslationY()
+ + ", left="
+ + view.getLeft()
+ + ", top="
+ + view.getTop()
+ + ", right="
+ + view.getRight()
+ + ", bottom="
+ + view.getBottom()
+ + "}, title={visiblility="
+ + title.getVisibility()
+ + ", alpha="
+ + title.getAlpha()
+ + ", translationY="
+ + title.getTranslationY()
+ + ", left="
+ + title.getLeft()
+ + ", top="
+ + title.getTop()
+ + ", right="
+ + title.getRight()
+ + ", bottom="
+ + title.getBottom()
+ + "}, contents={visiblility="
+ + contents.getVisibility()
+ + ", alpha="
+ + contents.getAlpha()
+ + ", translationY="
+ + contents.getTranslationY()
+ + ", left="
+ + contents.getLeft()
+ + ", top="
+ + contents.getTop()
+ + ", right="
+ + contents.getRight()
+ + ", bottom="
+ + contents.getBottom()
+ + "}");
}
}
@@ -204,14 +230,14 @@ public class MenuLayoutManager {
* Checks if the view will take up space for the layout not.
*
* @param position The index of the menu row view in the list. This is not the index of the view
- * in the screen.
+ * in the screen.
* @param view The menu row view.
* @param rowsToAdd The menu row views to be added in the next layout process.
* @param rowsToRemove The menu row views to be removed in the next layout process.
* @return {@code true} if the view will take up space for the layout, otherwise {@code false}.
*/
- private boolean isVisibleInLayout(int position, MenuRowView view, List<Integer> rowsToAdd,
- List<Integer> rowsToRemove) {
+ private boolean isVisibleInLayout(
+ int position, MenuRowView view, List<Integer> rowsToAdd, List<Integer> rowsToRemove) {
// Checks if the view will be visible or not.
return (view.getVisibility() != View.GONE && !rowsToRemove.contains(position))
|| rowsToAdd.contains(position);
@@ -226,8 +252,8 @@ public class MenuLayoutManager {
* @param bottom The bottom coordinate of the menu view.
*/
private List<Rect> getViewLayouts(int left, int top, int right, int bottom) {
- return getViewLayouts(left, top, right, bottom, Collections.emptyList(),
- Collections.emptyList());
+ return getViewLayouts(
+ left, top, right, bottom, Collections.emptyList(), Collections.emptyList());
}
/**
@@ -247,8 +273,13 @@ public class MenuLayoutManager {
* @param rowsToRemove The menu row views to be removed in the next layout process.
* @return the layout bounds of the menu row views.
*/
- private List<Rect> getViewLayouts(int left, int top, int right, int bottom,
- List<Integer> rowsToAdd, List<Integer> rowsToRemove) {
+ private List<Rect> getViewLayouts(
+ int left,
+ int top,
+ int right,
+ int bottom,
+ List<Integer> rowsToAdd,
+ List<Integer> rowsToRemove) {
// The coordinates should be relative to the parent.
int relativeLeft = 0;
int relateiveRight = right - left;
@@ -262,18 +293,29 @@ public class MenuLayoutManager {
// Calculate for the selected row first.
// The distance between the bottom of the screen and the vertical center of the contents
// should be kept fixed. For more information, please see the redlines.
- int childTop = relativeBottom - mRowAlignFromBottom - rowContentsHeight / 2
- - mRowContentsPaddingTop - rowTitleHeight;
+ int childTop =
+ relativeBottom
+ - mRowAlignFromBottom
+ - rowContentsHeight / 2
+ - mRowContentsPaddingTop
+ - rowTitleHeight;
int childBottom = relativeBottom;
int position = mSelectedPosition + 1;
for (; position < count; ++position) {
// Find and layout the next row to calculate the bottom line of the selected row.
MenuRowView nextView = mMenuRowViews.get(position);
if (isVisibleInLayout(position, nextView, rowsToAdd, rowsToRemove)) {
- int nextTitleTopMax = relativeBottom - mMenuMarginBottomMin - rowTitleHeight
- + mRowTitleTextDescenderHeight;
- int childBottomMax = relativeBottom - mRowAlignFromBottom + rowContentsHeight / 2
- + mRowContentsPaddingBottomMax - rowTitleHeight;
+ int nextTitleTopMax =
+ relativeBottom
+ - mMenuMarginBottomMin
+ - rowTitleHeight
+ + mRowTitleTextDescenderHeight;
+ int childBottomMax =
+ relativeBottom
+ - mRowAlignFromBottom
+ + rowContentsHeight / 2
+ + mRowContentsPaddingBottomMax
+ - rowTitleHeight;
childBottom = Math.min(nextTitleTopMax, childBottomMax);
layouts.add(new Rect(relativeLeft, childBottom, relateiveRight, relativeBottom));
break;
@@ -309,19 +351,22 @@ public class MenuLayoutManager {
return layouts;
}
- /**
- * Move the current selection to the given {@code position}.
- */
+ /** Move the current selection to the given {@code position}. */
public void setSelectedPosition(int position) {
if (DEBUG) {
- Log.d(TAG, "setSelectedPosition(position=" + position + ") {previousPosition="
- + mSelectedPosition + "}");
+ Log.d(
+ TAG,
+ "setSelectedPosition(position="
+ + position
+ + ") {previousPosition="
+ + mSelectedPosition
+ + "}");
}
if (mSelectedPosition == position) {
return;
}
boolean indexValid = Utils.isIndexValid(mMenuRowViews, position);
- SoftPreconditions.checkArgument(indexValid, TAG, "position " + position);
+ SoftPreconditions.checkArgument(indexValid, TAG, "position %s ", position);
if (!indexValid) {
return;
}
@@ -347,13 +392,18 @@ public class MenuLayoutManager {
}
/**
- * Move the current selection to the given {@code position} with animation.
- * The animation specification is included in http://b/21069476
+ * Move the current selection to the given {@code position} with animation. The animation
+ * specification is included in http://b/21069476
*/
public void setSelectedPositionSmooth(final int position) {
if (DEBUG) {
- Log.d(TAG, "setSelectedPositionSmooth(position=" + position + ") {previousPosition="
- + mSelectedPosition + "}");
+ Log.d(
+ TAG,
+ "setSelectedPositionSmooth(position="
+ + position
+ + ") {previousPosition="
+ + mSelectedPosition
+ + "}");
}
if (mMenuView.getVisibility() != View.VISIBLE) {
setSelectedPosition(position);
@@ -363,13 +413,13 @@ public class MenuLayoutManager {
return;
}
boolean oldIndexValid = Utils.isIndexValid(mMenuRowViews, mSelectedPosition);
- SoftPreconditions
- .checkState(oldIndexValid, TAG, "No previous selection: " + mSelectedPosition);
+ SoftPreconditions.checkState(
+ oldIndexValid, TAG, "No previous selection: " + mSelectedPosition);
if (!oldIndexValid) {
return;
}
boolean newIndexValid = Utils.isIndexValid(mMenuRowViews, position);
- SoftPreconditions.checkArgument(newIndexValid, TAG, "position " + position);
+ SoftPreconditions.checkArgument(newIndexValid, TAG, "position %s", position);
if (!newIndexValid) {
return;
}
@@ -415,8 +465,7 @@ public class MenuLayoutManager {
mMenuView.requestFocus();
if (mTempTitleViewForOld == null) {
// Initialize here because we don't know when the views are inflated.
- mTempTitleViewForOld =
- (TextView) mMenuView.findViewById(R.id.temp_title_for_old);
+ mTempTitleViewForOld = (TextView) mMenuView.findViewById(R.id.temp_title_for_old);
mTempTitleViewForCurrent =
(TextView) mMenuView.findViewById(R.id.temp_title_for_current);
}
@@ -425,16 +474,21 @@ public class MenuLayoutManager {
mPropertyValuesAfterAnimation.clear();
List<Animator> animators = new ArrayList<>();
boolean scrollDown = position > oldPosition;
- List<Rect> layouts = getViewLayouts(mMenuView.getLeft(), mMenuView.getTop(),
- mMenuView.getRight(), mMenuView.getBottom());
+ List<Rect> layouts =
+ getViewLayouts(
+ mMenuView.getLeft(),
+ mMenuView.getTop(),
+ mMenuView.getRight(),
+ mMenuView.getBottom());
// Old row.
MenuRow oldRow = mMenuRows.get(oldPosition);
final MenuRowView oldView = mMenuRowViews.get(oldPosition);
View oldContentsView = oldView.getContentsView();
// Old contents view.
- animators.add(createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn)
- .setDuration(mOldContentsFadeOutDuration));
+ animators.add(
+ createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn)
+ .setDuration(mOldContentsFadeOutDuration));
final TextView oldTitleView = oldView.getTitleView();
setTempTitleView(mTempTitleViewForOld, oldTitleView);
Rect oldLayoutRect = layouts.get(oldPosition);
@@ -444,20 +498,36 @@ public class MenuLayoutManager {
// This case is not included in the animation specification.
mTempTitleViewForOld.setScaleX(1.0f);
mTempTitleViewForOld.setScaleY(1.0f);
- animators.add(createAlphaAnimator(mTempTitleViewForOld, 0.0f,
- oldView.getTitleViewAlphaDeselected(), mFastOutLinearIn));
+ animators.add(
+ createAlphaAnimator(
+ mTempTitleViewForOld,
+ 0.0f,
+ oldView.getTitleViewAlphaDeselected(),
+ mFastOutLinearIn));
int offset = oldLayoutRect.top - mTempTitleViewForOld.getTop();
- animators.add(createTranslationYAnimator(mTempTitleViewForOld,
- offset + mRowScrollUpAnimationOffset, offset));
+ animators.add(
+ createTranslationYAnimator(
+ mTempTitleViewForOld,
+ offset + mRowScrollUpAnimationOffset,
+ offset));
} else {
- animators.add(createScaleXAnimator(mTempTitleViewForOld,
- oldView.getTitleViewScaleSelected(), 1.0f));
- animators.add(createScaleYAnimator(mTempTitleViewForOld,
- oldView.getTitleViewScaleSelected(), 1.0f));
- animators.add(createAlphaAnimator(mTempTitleViewForOld, oldTitleView.getAlpha(),
- oldView.getTitleViewAlphaDeselected(), mLinearOutSlowIn));
- animators.add(createTranslationYAnimator(mTempTitleViewForOld, 0,
- oldLayoutRect.top - mTempTitleViewForOld.getTop()));
+ animators.add(
+ createScaleXAnimator(
+ mTempTitleViewForOld, oldView.getTitleViewScaleSelected(), 1.0f));
+ animators.add(
+ createScaleYAnimator(
+ mTempTitleViewForOld, oldView.getTitleViewScaleSelected(), 1.0f));
+ animators.add(
+ createAlphaAnimator(
+ mTempTitleViewForOld,
+ oldTitleView.getAlpha(),
+ oldView.getTitleViewAlphaDeselected(),
+ mLinearOutSlowIn));
+ animators.add(
+ createTranslationYAnimator(
+ mTempTitleViewForOld,
+ 0,
+ oldLayoutRect.top - mTempTitleViewForOld.getTop()));
}
oldTitleView.setAlpha(oldView.getTitleViewAlphaDeselected());
oldTitleView.setVisibility(View.INVISIBLE);
@@ -471,23 +541,30 @@ public class MenuLayoutManager {
// The maximum is to the top of the start position of mTempTitleViewForOld.
int distanceCurrentTitle = currentLayoutRect.top - currentView.getTop();
int distance = Math.max(mRowScrollUpAnimationOffset, distanceCurrentTitle);
- int distanceToTopOfSecondTitle = oldLayoutRect.top - mRowScrollUpAnimationOffset
- - oldView.getTop();
- animators.add(createTranslationYAnimator(oldTitleView, 0.0f,
- Math.min(distance, distanceToTopOfSecondTitle)));
- animators.add(createAlphaAnimator(oldTitleView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn)
- .setDuration(mOldContentsFadeOutDuration));
- animators.add(createScaleXAnimator(oldTitleView,
- oldView.getTitleViewScaleSelected(), 1.0f));
- animators.add(createScaleYAnimator(oldTitleView,
- oldView.getTitleViewScaleSelected(), 1.0f));
+ int distanceToTopOfSecondTitle =
+ oldLayoutRect.top - mRowScrollUpAnimationOffset - oldView.getTop();
+ animators.add(
+ createTranslationYAnimator(
+ oldTitleView, 0.0f, Math.min(distance, distanceToTopOfSecondTitle)));
+ animators.add(
+ createAlphaAnimator(oldTitleView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn)
+ .setDuration(mOldContentsFadeOutDuration));
+ animators.add(
+ createScaleXAnimator(oldTitleView, oldView.getTitleViewScaleSelected(), 1.0f));
+ animators.add(
+ createScaleYAnimator(oldTitleView, oldView.getTitleViewScaleSelected(), 1.0f));
mTempTitleViewForOld.setScaleX(1.0f);
mTempTitleViewForOld.setScaleY(1.0f);
- animators.add(createAlphaAnimator(mTempTitleViewForOld, 0.0f,
- oldView.getTitleViewAlphaDeselected(), mFastOutLinearIn));
+ animators.add(
+ createAlphaAnimator(
+ mTempTitleViewForOld,
+ 0.0f,
+ oldView.getTitleViewAlphaDeselected(),
+ mFastOutLinearIn));
int offset = oldLayoutRect.top - mTempTitleViewForOld.getTop();
- animators.add(createTranslationYAnimator(mTempTitleViewForOld,
- offset - mRowScrollUpAnimationOffset, offset));
+ animators.add(
+ createTranslationYAnimator(
+ mTempTitleViewForOld, offset - mRowScrollUpAnimationOffset, offset));
}
// Current row.
Rect currentLayoutRect = new Rect(layouts.get(position));
@@ -502,29 +579,40 @@ public class MenuLayoutManager {
// The maximum is to the top of the end position of mTempTitleViewForCurrent.
int distanceOldTitle = oldView.getTop() - oldLayoutRect.top;
int distance = Math.max(mRowScrollUpAnimationOffset, distanceOldTitle);
- int distanceTopOfSecondTitle = currentView.getTop() - mRowScrollUpAnimationOffset
- - currentLayoutRect.top;
- animators.add(createTranslationYAnimator(currentTitleView,
- Math.min(distance, distanceTopOfSecondTitle), 0.0f));
+ int distanceTopOfSecondTitle =
+ currentView.getTop() - mRowScrollUpAnimationOffset - currentLayoutRect.top;
+ animators.add(
+ createTranslationYAnimator(
+ currentTitleView, Math.min(distance, distanceTopOfSecondTitle), 0.0f));
currentView.setTop(currentLayoutRect.top);
- ObjectAnimator animator = createAlphaAnimator(currentTitleView, 0.0f, 1.0f,
- mFastOutLinearIn).setDuration(mCurrentContentsFadeInDuration);
+ ObjectAnimator animator =
+ createAlphaAnimator(currentTitleView, 0.0f, 1.0f, mFastOutLinearIn)
+ .setDuration(mCurrentContentsFadeInDuration);
animator.setStartDelay(mOldContentsFadeOutDuration);
currentTitleView.setAlpha(0.0f);
animators.add(animator);
- animators.add(createScaleXAnimator(currentTitleView, 1.0f,
- currentView.getTitleViewScaleSelected()));
- animators.add(createScaleYAnimator(currentTitleView, 1.0f,
- currentView.getTitleViewScaleSelected()));
- animators.add(createTranslationYAnimator(mTempTitleViewForCurrent, 0.0f,
- -mRowScrollUpAnimationOffset));
- animators.add(createAlphaAnimator(mTempTitleViewForCurrent,
- currentView.getTitleViewAlphaDeselected(), 0, mLinearOutSlowIn));
+ animators.add(
+ createScaleXAnimator(
+ currentTitleView, 1.0f, currentView.getTitleViewScaleSelected()));
+ animators.add(
+ createScaleYAnimator(
+ currentTitleView, 1.0f, currentView.getTitleViewScaleSelected()));
+ animators.add(
+ createTranslationYAnimator(
+ mTempTitleViewForCurrent, 0.0f, -mRowScrollUpAnimationOffset));
+ animators.add(
+ createAlphaAnimator(
+ mTempTitleViewForCurrent,
+ currentView.getTitleViewAlphaDeselected(),
+ 0,
+ mLinearOutSlowIn));
// Current contents view.
- animators.add(createTranslationYAnimator(currentContentsView,
- mRowScrollUpAnimationOffset, 0.0f));
- animator = createAlphaAnimator(currentContentsView, 0.0f, 1.0f, mFastOutLinearIn)
- .setDuration(mCurrentContentsFadeInDuration);
+ animators.add(
+ createTranslationYAnimator(
+ currentContentsView, mRowScrollUpAnimationOffset, 0.0f));
+ animator =
+ createAlphaAnimator(currentContentsView, 0.0f, 1.0f, mFastOutLinearIn)
+ .setDuration(mCurrentContentsFadeInDuration);
animator.setStartDelay(mOldContentsFadeOutDuration);
animators.add(animator);
} else {
@@ -532,17 +620,27 @@ public class MenuLayoutManager {
// Current title view.
int currentViewOffset = currentLayoutRect.top - currentView.getTop();
animators.add(createTranslationYAnimator(currentTitleView, 0, currentViewOffset));
- animators.add(createAlphaAnimator(currentTitleView,
- currentView.getTitleViewAlphaDeselected(), 1.0f, mFastOutSlowIn));
- animators.add(createScaleXAnimator(currentTitleView, 1.0f,
- currentView.getTitleViewScaleSelected()));
- animators.add(createScaleYAnimator(currentTitleView, 1.0f,
- currentView.getTitleViewScaleSelected()));
+ animators.add(
+ createAlphaAnimator(
+ currentTitleView,
+ currentView.getTitleViewAlphaDeselected(),
+ 1.0f,
+ mFastOutSlowIn));
+ animators.add(
+ createScaleXAnimator(
+ currentTitleView, 1.0f, currentView.getTitleViewScaleSelected()));
+ animators.add(
+ createScaleYAnimator(
+ currentTitleView, 1.0f, currentView.getTitleViewScaleSelected()));
// Current contents view.
- animators.add(createTranslationYAnimator(currentContentsView,
- currentViewOffset - mRowScrollUpAnimationOffset, currentViewOffset));
- ObjectAnimator animator = createAlphaAnimator(currentContentsView, 0.0f, 1.0f,
- mFastOutLinearIn).setDuration(mCurrentContentsFadeInDuration);
+ animators.add(
+ createTranslationYAnimator(
+ currentContentsView,
+ currentViewOffset - mRowScrollUpAnimationOffset,
+ currentViewOffset));
+ ObjectAnimator animator =
+ createAlphaAnimator(currentContentsView, 0.0f, 1.0f, mFastOutLinearIn)
+ .setDuration(mCurrentContentsFadeInDuration);
animator.setStartDelay(mOldContentsFadeOutDuration);
animators.add(animator);
}
@@ -553,9 +651,13 @@ public class MenuLayoutManager {
if (nextPosition != INVALID_POSITION) {
MenuRowView nextView = mMenuRowViews.get(nextPosition);
Rect nextLayoutRect = layouts.get(nextPosition);
- animators.add(createTranslationYAnimator(nextView,
- nextLayoutRect.top + mRowScrollUpAnimationOffset - nextView.getTop(),
- nextLayoutRect.top - nextView.getTop()));
+ animators.add(
+ createTranslationYAnimator(
+ nextView,
+ nextLayoutRect.top
+ + mRowScrollUpAnimationOffset
+ - nextView.getTop(),
+ nextLayoutRect.top - nextView.getTop()));
animators.add(createAlphaAnimator(nextView, 0.0f, 1.0f, mFastOutLinearIn));
}
} else {
@@ -563,15 +665,22 @@ public class MenuLayoutManager {
if (nextPosition != INVALID_POSITION) {
MenuRowView nextView = mMenuRowViews.get(nextPosition);
animators.add(createTranslationYAnimator(nextView, 0, mRowScrollUpAnimationOffset));
- animators.add(createAlphaAnimator(nextView,
- nextView.getTitleViewAlphaDeselected(), 0.0f, 1.0f, mLinearOutSlowIn));
+ animators.add(
+ createAlphaAnimator(
+ nextView,
+ nextView.getTitleViewAlphaDeselected(),
+ 0.0f,
+ 1.0f,
+ mLinearOutSlowIn));
}
}
// Other rows.
int count = mMenuRowViews.size();
for (int i = 0; i < count; ++i) {
MenuRowView view = mMenuRowViews.get(i);
- if (view.getVisibility() == View.VISIBLE && i != oldPosition && i != position
+ if (view.getVisibility() == View.VISIBLE
+ && i != oldPosition
+ && i != position
&& i != nextPosition) {
Rect rect = layouts.get(i);
animators.add(createTranslationYAnimator(view, 0, rect.top - view.getTop()));
@@ -582,51 +691,62 @@ public class MenuLayoutManager {
propertyValuesAfterAnimation.addAll(mPropertyValuesAfterAnimation);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(animators);
- mAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- if (DEBUG) dumpChildren("onRowAnimationEndBefore");
- mAnimatorSet = null;
- // The property values which are different from the end values and need to be
- // changed after the animation are set here.
- // e.g. setting translationY to 0, alpha of the contents view to 1.
- for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) {
- holder.property.set(holder.view, holder.value);
- }
- oldView.onDeselected();
- currentView.onSelected(true);
- mTempTitleViewForOld.setVisibility(View.GONE);
- mTempTitleViewForCurrent.setVisibility(View.GONE);
- layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(),
- mMenuView.getBottom());
- if (DEBUG) dumpChildren("onRowAnimationEndAfter");
-
- MenuRow currentRow = mMenuRows.get(position);
- if (currentRow.hideTitleWhenSelected()) {
- View titleView = mMenuRowViews.get(position).getTitleView();
- mTitleFadeOutAnimator = createAlphaAnimator(titleView, titleView.getAlpha(),
- 0.0f, mLinearOutSlowIn);
- mTitleFadeOutAnimator.setStartDelay(TITLE_SHOW_DURATION_BEFORE_HIDDEN_MS);
- mTitleFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCanceled;
-
- @Override
- public void onAnimationCancel(Animator animator) {
- mCanceled = true;
+ mAnimatorSet.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (DEBUG) dumpChildren("onRowAnimationEndBefore");
+ mAnimatorSet = null;
+ // The property values which are different from the end values and need to
+ // be
+ // changed after the animation are set here.
+ // e.g. setting translationY to 0, alpha of the contents view to 1.
+ for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) {
+ holder.property.set(holder.view, holder.value);
}
-
- @Override
- public void onAnimationEnd(Animator animator) {
- mTitleFadeOutAnimator = null;
- if (!mCanceled) {
- mMenuRowViews.get(position).onSelected(false);
- }
+ oldView.onDeselected();
+ currentView.onSelected(true);
+ mTempTitleViewForOld.setVisibility(View.GONE);
+ mTempTitleViewForCurrent.setVisibility(View.GONE);
+ layout(
+ mMenuView.getLeft(),
+ mMenuView.getTop(),
+ mMenuView.getRight(),
+ mMenuView.getBottom());
+ if (DEBUG) dumpChildren("onRowAnimationEndAfter");
+
+ MenuRow currentRow = mMenuRows.get(position);
+ if (currentRow.hideTitleWhenSelected()) {
+ View titleView = mMenuRowViews.get(position).getTitleView();
+ mTitleFadeOutAnimator =
+ createAlphaAnimator(
+ titleView,
+ titleView.getAlpha(),
+ 0.0f,
+ mLinearOutSlowIn);
+ mTitleFadeOutAnimator.setStartDelay(
+ TITLE_SHOW_DURATION_BEFORE_HIDDEN_MS);
+ mTitleFadeOutAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ private boolean mCanceled;
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mTitleFadeOutAnimator = null;
+ if (!mCanceled) {
+ mMenuRowViews.get(position).onSelected(false);
+ }
+ }
+ });
+ mTitleFadeOutAnimator.start();
}
- });
- mTitleFadeOutAnimator.start();
- }
- }
- });
+ }
+ });
mAnimatorSet.start();
if (DEBUG) dumpChildren("startedRowAnimation()");
}
@@ -661,7 +781,8 @@ public class MenuLayoutManager {
if (mMenuView.getVisibility() != View.VISIBLE) {
int count = mMenuRowViews.size();
for (int i = 0; i < count; ++i) {
- mMenuRowViews.get(i)
+ mMenuRowViews
+ .get(i)
.setVisibility(mMenuRows.get(i).isVisible() ? View.VISIBLE : View.GONE);
}
return;
@@ -674,8 +795,8 @@ public class MenuLayoutManager {
for (int i = mSelectedPosition - 1; i >= 0; --i) {
MenuRow row = mMenuRows.get(i);
MenuRowView view = mMenuRowViews.get(i);
- if (row.isVisible() && (view.getVisibility() == View.GONE
- || mRemovingRowViews.contains(i))) {
+ if (row.isVisible()
+ && (view.getVisibility() == View.GONE || mRemovingRowViews.contains(i))) {
// Removing rows are still VISIBLE.
addedRowViews.add(i);
++added;
@@ -691,8 +812,8 @@ public class MenuLayoutManager {
for (int i = mSelectedPosition + 1; i < count; ++i) {
MenuRow row = mMenuRows.get(i);
MenuRowView view = mMenuRowViews.get(i);
- if (row.isVisible() && (view.getVisibility() == View.GONE
- || mRemovingRowViews.contains(i))) {
+ if (row.isVisible()
+ && (view.getVisibility() == View.GONE || mRemovingRowViews.contains(i))) {
// Removing rows are still VISIBLE.
addedRowViews.add(i);
++added;
@@ -717,8 +838,14 @@ public class MenuLayoutManager {
}
mPropertyValuesAfterAnimation.clear();
List<Animator> animators = new ArrayList<>();
- List<Rect> layouts = getViewLayouts(mMenuView.getLeft(), mMenuView.getTop(),
- mMenuView.getRight(), mMenuView.getBottom(), addedRowViews, removedRowViews);
+ List<Rect> layouts =
+ getViewLayouts(
+ mMenuView.getLeft(),
+ mMenuView.getTop(),
+ mMenuView.getRight(),
+ mMenuView.getBottom(),
+ addedRowViews,
+ removedRowViews);
for (int position : addedRowViews) {
MenuRowView view = mMenuRowViews.get(position);
view.setVisibility(View.VISIBLE);
@@ -728,7 +855,8 @@ public class MenuLayoutManager {
view.layout(rect.left, rect.top, rect.right, rect.bottom);
View titleView = view.getTitleView();
MarginLayoutParams params = (MarginLayoutParams) titleView.getLayoutParams();
- titleView.layout(view.getPaddingLeft() + params.leftMargin,
+ titleView.layout(
+ view.getPaddingLeft() + params.leftMargin,
view.getPaddingTop() + params.topMargin,
rect.right - rect.left - view.getPaddingRight() - params.rightMargin,
rect.bottom - rect.top - view.getPaddingBottom() - params.bottomMargin);
@@ -749,23 +877,28 @@ public class MenuLayoutManager {
mRemovingRowViews.addAll(removedRowViews);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(animators);
- mAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimatorSet = null;
- // The property values which are different from the end values and need to be
- // changed after the animation are set here.
- // e.g. setting translationY to 0, alpha of the contents view to 1.
- for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) {
- holder.property.set(holder.view, holder.value);
- }
- for (int position : mRemovingRowViews) {
- mMenuRowViews.get(position).setVisibility(View.GONE);
- }
- layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(),
- mMenuView.getBottom());
- }
- });
+ mAnimatorSet.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatorSet = null;
+ // The property values which are different from the end values and need to
+ // be
+ // changed after the animation are set here.
+ // e.g. setting translationY to 0, alpha of the contents view to 1.
+ for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) {
+ holder.property.set(holder.view, holder.value);
+ }
+ for (int position : mRemovingRowViews) {
+ mMenuRowViews.get(position).setVisibility(View.GONE);
+ }
+ layout(
+ mMenuView.getLeft(),
+ mMenuView.getTop(),
+ mMenuView.getRight(),
+ mMenuView.getBottom());
+ }
+ });
mAnimatorSet.start();
if (DEBUG) dumpChildren("onMenuRowUpdated()");
}
@@ -778,16 +911,16 @@ public class MenuLayoutManager {
return animator;
}
- private ObjectAnimator createAlphaAnimator(View view, float from, float to,
- TimeInterpolator interpolator) {
+ private ObjectAnimator createAlphaAnimator(
+ View view, float from, float to, TimeInterpolator interpolator) {
ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, from, to);
animator.setDuration(mRowAnimationDuration);
animator.setInterpolator(interpolator);
return animator;
}
- private ObjectAnimator createAlphaAnimator(View view, float from, float to, float end,
- TimeInterpolator interpolator) {
+ private ObjectAnimator createAlphaAnimator(
+ View view, float from, float to, float end, TimeInterpolator interpolator) {
ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, from, to);
animator.setDuration(mRowAnimationDuration);
animator.setInterpolator(interpolator);
@@ -809,9 +942,7 @@ public class MenuLayoutManager {
return animator;
}
- /**
- * Returns the current position.
- */
+ /** Returns the current position. */
public int getSelectedPosition() {
return mSelectedPosition;
}
@@ -828,15 +959,10 @@ public class MenuLayoutManager {
}
}
- /**
- * Called when the menu becomes visible.
- */
- public void onMenuShow() {
- }
+ /** Called when the menu becomes visible. */
+ public void onMenuShow() {}
- /**
- * Called when the menu becomes hidden.
- */
+ /** Called when the menu becomes hidden. */
public void onMenuHide() {
if (mAnimatorSet != null) {
mAnimatorSet.end();
diff --git a/src/com/android/tv/menu/MenuRow.java b/src/com/android/tv/menu/MenuRow.java
index 47804f11..8dc12bad 100644
--- a/src/com/android/tv/menu/MenuRow.java
+++ b/src/com/android/tv/menu/MenuRow.java
@@ -17,13 +17,11 @@
package com.android.tv.menu;
import android.content.Context;
-import android.view.View;
/**
- * A base class of the item which will be displayed in the main menu.
- * It contains the data such as title to represent a row.
- * This is an abstract class and the sub-class could have it's own data for
- * the row.
+ * A base class of the item which will be displayed in the main menu. It contains the data such as
+ * title to represent a row. This is an abstract class and the sub-class could have it's own data
+ * for the row.
*/
public abstract class MenuRow {
private final Context mContext;
@@ -45,86 +43,60 @@ public abstract class MenuRow {
mHeight = context.getResources().getDimensionPixelSize(heightResId);
}
- /**
- * Returns the context.
- */
+ /** Returns the context. */
protected Context getContext() {
return mContext;
}
- /**
- * Returns the menu object.
- */
+ /** Returns the menu object. */
public Menu getMenu() {
return mMenu;
}
- /**
- * Returns the title of this row.
- */
+ /** Returns the title of this row. */
public String getTitle() {
return mTitle;
}
- /**
- * Returns the height of this row.
- */
+ /** Returns the height of this row. */
public int getHeight() {
return mHeight;
}
- /**
- * Sets the menu row view.
- */
+ /** Sets the menu row view. */
public void setMenuRowView(MenuRowView menuRowView) {
mMenuRowView = menuRowView;
}
- /**
- * Returns the menu row view.
- */
+ /** Returns the menu row view. */
protected MenuRowView getMenuRowView() {
return mMenuRowView;
}
- /**
- * Updates the contents in this row.
- * This method is called only by the menu when necessary.
- */
- abstract public void update();
+ /** Updates the contents in this row. This method is called only by the menu when necessary. */
+ public abstract void update();
- /**
- * Indicates whether this row is shown in the menu.
- */
+ /** Indicates whether this row is shown in the menu. */
public boolean isVisible() {
return true;
}
/**
- * Releases all the resources which need to be released.
- * This method is called when the main menu is not available any more.
+ * Releases all the resources which need to be released. This method is called when the main
+ * menu is not available any more.
*/
- public void release() {
- }
+ public void release() {}
- /**
- * Returns the ID of the layout resource for this row.
- */
- abstract public int getLayoutResId();
+ /** Returns the ID of the layout resource for this row. */
+ public abstract int getLayoutResId();
- /**
- * Returns the ID of this row. This ID is used to select the row in the main menu.
- */
- abstract public String getId();
+ /** Returns the ID of this row. This ID is used to select the row in the main menu. */
+ public abstract String getId();
- /**
- * This method is called when recent channels are changed.
- */
- public void onRecentChannelsChanged() { }
+ /** This method is called when recent channels are changed. */
+ public void onRecentChannelsChanged() {}
- /**
- * Returns whether to hide the title when the row is selected.
- */
+ /** Returns whether to hide the title when the row is selected. */
public boolean hideTitleWhenSelected() {
return false;
}
diff --git a/src/com/android/tv/menu/MenuRowFactory.java b/src/com/android/tv/menu/MenuRowFactory.java
index 570cfb8f..048d725d 100644
--- a/src/com/android/tv/menu/MenuRowFactory.java
+++ b/src/com/android/tv/menu/MenuRowFactory.java
@@ -19,80 +19,76 @@ package com.android.tv.menu;
import android.content.Context;
import android.support.annotation.Nullable;
import android.text.TextUtils;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.customization.CustomAction;
-import com.android.tv.customization.TvCustomizationManager;
+import com.android.tv.common.customization.CustomAction;
+import com.android.tv.common.customization.CustomizationManager;
import com.android.tv.ui.TunableTvView;
-
import java.util.List;
-/**
- * A factory class to create menu rows.
- */
+/** A factory class to create menu rows. */
public class MenuRowFactory {
private final MainActivity mMainActivity;
private final TunableTvView mTvView;
- private final TvCustomizationManager mTvCustomizationManager;
+ private final CustomizationManager mCustomizationManager;
- /**
- * A constructor.
- */
+ /** A constructor. */
public MenuRowFactory(MainActivity mainActivity, TunableTvView tvView) {
mMainActivity = mainActivity;
mTvView = tvView;
- mTvCustomizationManager = new TvCustomizationManager(mainActivity);
- mTvCustomizationManager.initialize();
+ mCustomizationManager = new CustomizationManager(mainActivity);
+ mCustomizationManager.initialize();
}
- /**
- * Creates an object corresponding to the given {@code key}.
- */
+ /** Creates an object corresponding to the given {@code key}. */
@Nullable
public MenuRow createMenuRow(Menu menu, Class<?> key) {
if (PlayControlsRow.class.equals(key)) {
- return new PlayControlsRow(mMainActivity, mTvView, menu,
- mMainActivity.getTimeShiftManager());
+ return new PlayControlsRow(
+ mMainActivity, mTvView, menu, mMainActivity.getTimeShiftManager());
} else if (ChannelsRow.class.equals(key)) {
return new ChannelsRow(mMainActivity, menu, mMainActivity.getProgramDataManager());
} else if (PartnerRow.class.equals(key)) {
- List<CustomAction> customActions = mTvCustomizationManager.getCustomActions(
- TvCustomizationManager.ID_PARTNER_ROW);
- String title = mTvCustomizationManager.getPartnerRowTitle();
+ List<CustomAction> customActions =
+ mCustomizationManager.getCustomActions(CustomizationManager.ID_PARTNER_ROW);
+ String title = mCustomizationManager.getPartnerRowTitle();
if (customActions != null && !TextUtils.isEmpty(title)) {
return new PartnerRow(mMainActivity, menu, title, customActions);
}
return null;
} else if (TvOptionsRow.class.equals(key)) {
- return new TvOptionsRow(mMainActivity, menu, mTvCustomizationManager
- .getCustomActions(TvCustomizationManager.ID_OPTIONS_ROW));
+ return new TvOptionsRow(
+ mMainActivity,
+ menu,
+ mCustomizationManager.getCustomActions(CustomizationManager.ID_OPTIONS_ROW));
}
return null;
}
- /**
- * A menu row which represents the TV options row.
- */
+ /** A menu row which represents the TV options row. */
public static class TvOptionsRow extends ItemListRow {
- /**
- * The ID of the row.
- */
+ /** The ID of the row. */
public static final String ID = TvOptionsRow.class.getName();
private TvOptionsRow(Context context, Menu menu, List<CustomAction> customActions) {
- super(context, menu, R.string.menu_title_options, R.dimen.action_card_height,
+ super(
+ context,
+ menu,
+ R.string.menu_title_options,
+ R.dimen.action_card_height,
new TvOptionsRowAdapter(context, customActions));
}
}
- /**
- * A menu row which represents the partner row.
- */
+ /** A menu row which represents the partner row. */
public static class PartnerRow extends ItemListRow {
- private PartnerRow(Context context, Menu menu, String title,
- List<CustomAction> customActions) {
- super(context, menu, title, R.dimen.action_card_height,
+ private PartnerRow(
+ Context context, Menu menu, String title, List<CustomAction> customActions) {
+ super(
+ context,
+ menu,
+ title,
+ R.dimen.action_card_height,
new PartnerOptionsRowAdapter(context, customActions));
}
}
diff --git a/src/com/android/tv/menu/MenuRowView.java b/src/com/android/tv/menu/MenuRowView.java
index 97dea29a..a064f352 100644
--- a/src/com/android/tv/menu/MenuRowView.java
+++ b/src/com/android/tv/menu/MenuRowView.java
@@ -27,7 +27,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.menu.Menu.MenuShowReason;
@@ -46,25 +45,23 @@ public abstract class MenuRowView extends LinearLayout {
* reset when the menu is popped up.
*/
private View mLastFocusView;
+
private MenuRow mRow;
- private final OnFocusChangeListener mOnFocusChangeListener = new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- onChildFocusChange(v, hasFocus);
- }
- };
+ private final OnFocusChangeListener mOnFocusChangeListener =
+ new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ onChildFocusChange(v, hasFocus);
+ }
+ };
- /**
- * Returns the alpha value of the title view when it's deselected.
- */
+ /** Returns the alpha value of the title view when it's deselected. */
public float getTitleViewAlphaDeselected() {
return mTitleViewAlphaDeselected;
}
- /**
- * Returns the scale value of the title view when it's selected.
- */
+ /** Returns the scale value of the title view when it's selected. */
public float getTitleViewScaleSelected() {
return mTitleViewScaleSelected;
}
@@ -125,26 +122,22 @@ public abstract class MenuRowView extends LinearLayout {
}
}
- abstract protected int getContentsViewId();
+ protected abstract int getContentsViewId();
- /**
- * Returns the title view.
- */
+ /** Returns the title view. */
public final TextView getTitleView() {
return mTitleView;
}
- /**
- * Returns the contents view.
- */
+ /** Returns the contents view. */
public final View getContentsView() {
return mContentsView;
}
/**
- * Initialize this view. e.g. Set the initial selection.
- * This method is called when the main menu is visible.
- * Subclass of {@link MenuRowView} should override this to set correct mLastFocusView.
+ * Initialize this view. e.g. Set the initial selection. This method is called when the main
+ * menu is visible. Subclass of {@link MenuRowView} should override this to set correct
+ * mLastFocusView.
*
* @param reason A reason why this is initialized. See {@link MenuShowReason}
*/
@@ -177,17 +170,17 @@ public abstract class MenuRowView extends LinearLayout {
}
/**
- * Sets the view which needs to have focus when this row appears.
- * Subclasses should call this in {@link #initialize} if needed.
+ * Sets the view which needs to have focus when this row appears. Subclasses should call this in
+ * {@link #initialize} if needed.
*/
protected void setInitialFocusView(@NonNull View v) {
mLastFocusView = v;
}
/**
- * Called when the focus of a child view is changed.
- * The inherited class should override this method instead of calling
- * {@link android.view.View#setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
+ * Called when the focus of a child view is changed. The inherited class should override this
+ * method instead of calling {@link
+ * android.view.View#setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
*/
protected void onChildFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
@@ -195,9 +188,7 @@ public abstract class MenuRowView extends LinearLayout {
}
}
- /**
- * Returns the ID of row object bound to this view.
- */
+ /** Returns the ID of row object bound to this view. */
public String getRowId() {
return mRow == null ? null : mRow.getId();
}
@@ -206,7 +197,7 @@ public abstract class MenuRowView extends LinearLayout {
* Called when this row is selected.
*
* @param showTitle If {@code true}, the title is not hidden immediately after the row is
- * selected even though hideTitleWhenSelected() is {@code true}.
+ * selected even though hideTitleWhenSelected() is {@code true}.
*/
public void onSelected(boolean showTitle) {
if (mRow.hideTitleWhenSelected() && !showTitle) {
@@ -225,9 +216,7 @@ public abstract class MenuRowView extends LinearLayout {
mLastFocusView = lastFocusView;
}
- /**
- * Called when this row is deselected.
- */
+ /** Called when this row is deselected. */
public void onDeselected() {
mTitleView.setVisibility(VISIBLE);
mTitleView.setAlpha(mTitleViewAlphaDeselected);
@@ -236,9 +225,7 @@ public abstract class MenuRowView extends LinearLayout {
mContentsView.setVisibility(GONE);
}
- /**
- * Returns the preferred height of the contents view. The top/bottom padding is excluded.
- */
+ /** Returns the preferred height of the contents view. The top/bottom padding is excluded. */
public int getPreferredContentsHeight() {
return mRow.getHeight();
}
diff --git a/src/com/android/tv/menu/MenuUpdater.java b/src/com/android/tv/menu/MenuUpdater.java
index 18416c85..5d277824 100644
--- a/src/com/android/tv/menu/MenuUpdater.java
+++ b/src/com/android/tv/menu/MenuUpdater.java
@@ -17,12 +17,11 @@
package com.android.tv.menu;
import android.support.annotation.Nullable;
-
import com.android.tv.ChannelTuner;
import com.android.tv.TvOptionsManager;
import com.android.tv.TvOptionsManager.OptionChangedListener;
import com.android.tv.TvOptionsManager.OptionType;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.menu.MenuRowFactory.TvOptionsRow;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener;
@@ -39,49 +38,52 @@ public class MenuUpdater {
@Nullable private final TvOptionsManager mOptionsManager;
private ChannelTuner mChannelTuner;
- private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() {
- @Override
- public void onLoadFinished() {}
+ private final ChannelTuner.Listener mChannelTunerListener =
+ new ChannelTuner.Listener() {
+ @Override
+ public void onLoadFinished() {}
- @Override
- public void onBrowsableChannelListChanged() {
- mMenu.update(ChannelsRow.ID);
- }
+ @Override
+ public void onBrowsableChannelListChanged() {
+ mMenu.update(ChannelsRow.ID);
+ }
- @Override
- public void onCurrentChannelUnavailable(Channel channel) {}
+ @Override
+ public void onCurrentChannelUnavailable(Channel channel) {}
- @Override
- public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
- mMenu.update(ChannelsRow.ID);
- }
- };
- private final OptionChangedListener mOptionChangeListener = new OptionChangedListener() {
- @Override
- public void onOptionChanged(@OptionType int optionType, String newString) {
- mMenu.update(TvOptionsRow.ID);
- }
- };
+ @Override
+ public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
+ mMenu.update(ChannelsRow.ID);
+ }
+ };
+ private final OptionChangedListener mOptionChangeListener =
+ new OptionChangedListener() {
+ @Override
+ public void onOptionChanged(@OptionType int optionType, String newString) {
+ mMenu.update(TvOptionsRow.ID);
+ }
+ };
public MenuUpdater(Menu menu, TunableTvView tvView, TvOptionsManager optionsManager) {
mMenu = menu;
mTvView = tvView;
mOptionsManager = optionsManager;
if (mTvView != null) {
- mTvView.setOnScreenBlockedListener(new OnScreenBlockingChangedListener() {
- @Override
- public void onScreenBlockingChanged(boolean blocked) {
- mMenu.update(PlayControlsRow.ID);
- }
- });
+ mTvView.setOnScreenBlockedListener(
+ new OnScreenBlockingChangedListener() {
+ @Override
+ public void onScreenBlockingChanged(boolean blocked) {
+ mMenu.update(PlayControlsRow.ID);
+ }
+ });
}
if (mOptionsManager != null) {
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS,
- mOptionChangeListener);
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE,
- mOptionChangeListener);
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO,
- mOptionChangeListener);
+ mOptionsManager.setOptionChangedListener(
+ TvOptionsManager.OPTION_CLOSED_CAPTIONS, mOptionChangeListener);
+ mOptionsManager.setOptionChangedListener(
+ TvOptionsManager.OPTION_DISPLAY_MODE, mOptionChangeListener);
+ mOptionsManager.setOptionChangedListener(
+ TvOptionsManager.OPTION_MULTI_AUDIO, mOptionChangeListener);
}
}
@@ -98,16 +100,12 @@ public class MenuUpdater {
}
}
- /**
- * Called when the stream information changes.
- */
+ /** Called when the stream information changes. */
public void onStreamInfoChanged() {
mMenu.update(TvOptionsRow.ID);
}
- /**
- * Called at the end of the menu's lifetime.
- */
+ /** Called at the end of the menu's lifetime. */
public void release() {
if (mChannelTuner != null) {
mChannelTuner.removeListener(mChannelTunerListener);
diff --git a/src/com/android/tv/menu/MenuView.java b/src/com/android/tv/menu/MenuView.java
index ee0b036e..f5fec000 100644
--- a/src/com/android/tv/menu/MenuView.java
+++ b/src/com/android/tv/menu/MenuView.java
@@ -26,15 +26,11 @@ import android.view.ViewParent;
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.FrameLayout;
-
import com.android.tv.menu.Menu.MenuShowReason;
-
import java.util.ArrayList;
import java.util.List;
-/**
- * A view that represents TV main menu.
- */
+/** A view that represents TV main menu. */
public class MenuView extends FrameLayout implements IMenuView {
static final String TAG = MenuView.class.getSimpleName();
static final boolean DEBUG = false;
@@ -60,20 +56,25 @@ public class MenuView extends FrameLayout implements IMenuView {
mLayoutInflater = LayoutInflater.from(context);
// Set hardware layer type for smooth animation of lots of views.
setLayerType(LAYER_TYPE_HARDWARE, null);
- getViewTreeObserver().addOnGlobalFocusChangeListener(new OnGlobalFocusChangeListener() {
- @Override
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- MenuRowView newParent = getParentMenuRowView(newFocus);
- if (newParent != null) {
- if (DEBUG) Log.d(TAG, "Focus changed to " + newParent);
- // When the row is selected, the row view itself has the focus because the row
- // is collapsed. To make the child of the row have the focus, requestFocus()
- // should be called again after the row is expanded. It's done in
- // setSelectedPosition().
- setSelectedPositionSmooth(mMenuRowViews.indexOf(newParent));
- }
- }
- });
+ getViewTreeObserver()
+ .addOnGlobalFocusChangeListener(
+ new OnGlobalFocusChangeListener() {
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ MenuRowView newParent = getParentMenuRowView(newFocus);
+ if (newParent != null) {
+ if (DEBUG) Log.d(TAG, "Focus changed to " + newParent);
+ // When the row is selected, the row view itself has the focus
+ // because the row
+ // is collapsed. To make the child of the row have the focus,
+ // requestFocus()
+ // should be called again after the row is expanded. It's done
+ // in
+ // setSelectedPosition().
+ setSelectedPositionSmooth(mMenuRowViews.indexOf(newParent));
+ }
+ }
+ });
mLayoutManager = new MenuLayoutManager(context, this);
}
@@ -102,8 +103,8 @@ public class MenuView extends FrameLayout implements IMenuView {
}
@Override
- public void onShow(@MenuShowReason int reason, String rowIdToSelect,
- final Runnable runnableAfterShow) {
+ public void onShow(
+ @MenuShowReason int reason, String rowIdToSelect, final Runnable runnableAfterShow) {
if (DEBUG) {
Log.d(TAG, "onShow(reason=" + reason + ", rowIdToSelect=" + rowIdToSelect + ")");
}
@@ -132,15 +133,18 @@ public class MenuView extends FrameLayout implements IMenuView {
// Make the selected row have the focus.
requestFocus();
if (runnableAfterShow != null) {
- getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- // Start show animation after layout finishes for smooth animation because the
- // layout can take long time.
- runnableAfterShow.run();
- }
- });
+ getViewTreeObserver()
+ .addOnGlobalLayoutListener(
+ new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ // Start show animation after layout finishes for smooth
+ // animation because the
+ // layout can take long time.
+ runnableAfterShow.run();
+ }
+ });
}
mLayoutManager.onMenuShow();
}
diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java
index dd6194a1..ceffe861 100644
--- a/src/com/android/tv/menu/OptionsRowAdapter.java
+++ b/src/com/android/tv/menu/OptionsRowAdapter.java
@@ -18,11 +18,9 @@ package com.android.tv.menu;
import android.content.Context;
import android.view.View;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
-
import java.util.List;
/*
@@ -33,33 +31,33 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<
protected final Tracker mTracker;
private List<MenuAction> mActionList;
- private final View.OnClickListener mMenuActionOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- final MenuAction action = (MenuAction) view.getTag();
- view.post(new Runnable() {
+ private final View.OnClickListener mMenuActionOnClickListener =
+ new View.OnClickListener() {
@Override
- public void run() {
- int resId = action.getActionNameResId();
- if (resId == 0) {
- mTracker.sendMenuClicked(CUSTOM_ACTION_LABEL);
- } else {
- mTracker.sendMenuClicked(resId);
- }
- executeAction(action.getType());
+ public void onClick(View view) {
+ final MenuAction action = (MenuAction) view.getTag();
+ view.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ int resId = action.getActionNameResId();
+ if (resId == 0) {
+ mTracker.sendMenuClicked(CUSTOM_ACTION_LABEL);
+ } else {
+ mTracker.sendMenuClicked(resId);
+ }
+ executeAction(action.getType());
+ }
+ });
}
- });
- }
- };
+ };
public OptionsRowAdapter(Context context) {
super(context);
- mTracker = TvApplication.getSingletons(context).getTracker();
+ mTracker = TvSingletons.getSingletons(context).getTracker();
}
- /**
- * Update action list and its content.
- */
+ /** Update action list and its content. */
@Override
public void update() {
if (mActionList == null) {
@@ -76,13 +74,14 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<
}
protected abstract List<MenuAction> createActions();
+
protected abstract void updateActions();
+
protected abstract void executeAction(int type);
/**
- * Gets the action at the given position.
- * Note that action at the position may differ from returned by {@link #createActions}.
- * See {@link CustomizableOptionsRowAdapter}
+ * Gets the action at the given position. Note that action at the position may differ from
+ * returned by {@link #createActions}. See {@link CustomizableOptionsRowAdapter}
*/
protected MenuAction getAction(int position) {
return mActionList.get(position);
diff --git a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java
index c8249a4c..9676fe4d 100644
--- a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java
@@ -17,9 +17,7 @@
package com.android.tv.menu;
import android.content.Context;
-
-import com.android.tv.customization.CustomAction;
-
+import com.android.tv.common.customization.CustomAction;
import java.util.Collections;
import java.util.List;
@@ -34,8 +32,7 @@ public class PartnerOptionsRowAdapter extends CustomizableOptionsRowAdapter {
}
@Override
- protected void executeBaseAction(int option) {
- }
+ protected void executeBaseAction(int option) {}
@Override
protected void updateActions() {
diff --git a/src/com/android/tv/menu/PlayControlsButton.java b/src/com/android/tv/menu/PlayControlsButton.java
index 77715f28..ac3292a3 100644
--- a/src/com/android/tv/menu/PlayControlsButton.java
+++ b/src/com/android/tv/menu/PlayControlsButton.java
@@ -25,7 +25,6 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.android.tv.R;
public class PlayControlsButton extends FrameLayout {
@@ -54,8 +53,8 @@ public class PlayControlsButton extends FrameLayout {
this(context, attrs, defStyleAttr, 0);
}
- public PlayControlsButton(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
+ public PlayControlsButton(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
inflate(context, R.layout.play_controls_button, this);
mButton = (ImageView) findViewById(R.id.button);
@@ -66,9 +65,7 @@ public class PlayControlsButton extends FrameLayout {
mIconFocusedColor = mIconColor;
}
- /**
- * Sets the resource ID of the image to be displayed in the center of this control.
- */
+ /** Sets the resource ID of the image to be displayed in the center of this control. */
public void setImageResId(int imageResId) {
int newTintColor = hasFocus() ? mIconFocusedColor : mIconColor;
if (mImageResourceId != imageResId) {
@@ -87,40 +84,39 @@ public class PlayControlsButton extends FrameLayout {
mIcon.getDrawable().setTint(tintColor);
}
- /**
- * Sets an action which is to be run when the button is clicked.
- */
+ /** Sets an action which is to be run when the button is clicked. */
public void setAction(final Runnable clickAction) {
- mButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- clickAction.run();
- }
- });
+ mButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ clickAction.run();
+ }
+ });
}
- /**
- * Sets the icon's color should change to when the button is on focus.
- */
+ /** Sets the icon's color should change to when the button is on focus. */
public void setFocusedIconColor(int color) {
final ValueAnimator valueAnimator = ValueAnimator.ofArgb(mIconColor, color);
- valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(final ValueAnimator animator) {
- mIcon.getDrawable().setTint((int) animator.getAnimatedValue());
- }
- });
+ valueAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(final ValueAnimator animator) {
+ mIcon.getDrawable().setTint((int) animator.getAnimatedValue());
+ }
+ });
valueAnimator.setDuration(mFocusAnimationTimeMs);
- mButton.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- valueAnimator.start();
- } else {
- valueAnimator.reverse();
- }
- }
- });
+ mButton.setOnFocusChangeListener(
+ new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ valueAnimator.start();
+ } else {
+ valueAnimator.reverse();
+ }
+ }
+ });
mIconFocusedColor = color;
}
diff --git a/src/com/android/tv/menu/PlayControlsRow.java b/src/com/android/tv/menu/PlayControlsRow.java
index a60ff153..f19bd2b0 100644
--- a/src/com/android/tv/menu/PlayControlsRow.java
+++ b/src/com/android/tv/menu/PlayControlsRow.java
@@ -17,7 +17,6 @@
package com.android.tv.menu;
import android.content.Context;
-
import com.android.tv.R;
import com.android.tv.TimeShiftManager;
import com.android.tv.ui.TunableTvView;
@@ -28,8 +27,8 @@ public class PlayControlsRow extends MenuRow {
private final TunableTvView mTvView;
private final TimeShiftManager mTimeShiftManager;
- public PlayControlsRow(Context context, TunableTvView tvView, Menu menu,
- TimeShiftManager timeShiftManager) {
+ public PlayControlsRow(
+ Context context, TunableTvView tvView, Menu menu, TimeShiftManager timeShiftManager) {
super(context, menu, R.string.menu_title_play_controls, R.dimen.play_controls_height);
mTvView = tvView;
mTimeShiftManager = timeShiftManager;
@@ -45,16 +44,12 @@ public class PlayControlsRow extends MenuRow {
return R.layout.play_controls;
}
- /**
- * Returns TV view.
- */
+ /** Returns TV view. */
public TunableTvView getTvView() {
return mTvView;
}
- /**
- * Returns an instance of {@link TimeShiftManager}.
- */
+ /** Returns an instance of {@link TimeShiftManager}. */
public TimeShiftManager getTimeShiftManager() {
return mTimeShiftManager;
}
diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java
index 4d766788..496d1969 100644
--- a/src/com/android/tv/menu/PlayControlsRowView.java
+++ b/src/com/android/tv/menu/PlayControlsRowView.java
@@ -24,16 +24,15 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TimeShiftManager;
import com.android.tv.TimeShiftManager.TimeShiftActionId;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
@@ -79,27 +78,29 @@ public class PlayControlsRowView extends MenuRowView {
private final String mUnavailableMessage;
- private final ScheduledRecordingListener mScheduledRecordingListener
- = new ScheduledRecordingListener() {
- @Override
- public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { }
-
- @Override
- public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { }
-
- @Override
- public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
- Channel currentChannel = mMainActivity.getCurrentChannel();
- if (currentChannel != null && isShown()) {
- for (ScheduledRecording schedule : scheduledRecordings) {
- if (schedule.getChannelId() == currentChannel.getId()) {
- updateRecordButton();
- break;
+ private final ScheduledRecordingListener mScheduledRecordingListener =
+ new ScheduledRecordingListener() {
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {}
+
+ @Override
+ public void onScheduledRecordingRemoved(
+ ScheduledRecording... scheduledRecordings) {}
+
+ @Override
+ public void onScheduledRecordingStatusChanged(
+ ScheduledRecording... scheduledRecordings) {
+ Channel currentChannel = mMainActivity.getCurrentChannel();
+ if (currentChannel != null && isShown()) {
+ for (ScheduledRecording schedule : scheduledRecordings) {
+ if (schedule.getChannelId() == currentChannel.getId()) {
+ updateRecordButton();
+ break;
+ }
+ }
}
}
- }
- }
- };
+ };
public PlayControlsRowView(Context context) {
this(context, null);
@@ -113,22 +114,21 @@ public class PlayControlsRowView extends MenuRowView {
this(context, attrs, defStyleAttr, 0);
}
- public PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
+ public PlayControlsRowView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = context.getResources();
mTimeIndicatorLeftMargin =
- - res.getDimensionPixelSize(R.dimen.play_controls_time_indicator_width) / 2;
- mTimeTextLeftMargin =
- - res.getDimensionPixelOffset(R.dimen.play_controls_time_width) / 2;
+ -res.getDimensionPixelSize(R.dimen.play_controls_time_indicator_width) / 2;
+ mTimeTextLeftMargin = -res.getDimensionPixelOffset(R.dimen.play_controls_time_width) / 2;
mTimelineWidth = res.getDimensionPixelSize(R.dimen.play_controls_width);
mTimeFormat = DateFormat.getTimeFormat(context);
mNormalButtonMargin = res.getDimensionPixelSize(R.dimen.play_controls_button_normal_margin);
mCompactButtonMargin =
res.getDimensionPixelSize(R.dimen.play_controls_button_compact_margin);
if (CommonFeatures.DVR.isEnabled(context)) {
- mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager();
- mDvrManager = TvApplication.getSingletons(context).getDvrManager();
+ mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
+ mDvrManager = TvSingletons.getSingletons(context).getDvrManager();
} else {
mDvrDataManager = null;
mDvrManager = null;
@@ -154,7 +154,6 @@ public class PlayControlsRowView extends MenuRowView {
}
});
}
-
}
}
@@ -181,104 +180,141 @@ public class PlayControlsRowView extends MenuRowView {
mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time);
mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time);
- initializeButton(mJumpPreviousButton, R.drawable.lb_ic_skip_previous,
- R.string.play_controls_description_skip_previous, null, new Runnable() {
- @Override
- public void run() {
- if (mTimeShiftManager.isAvailable()) {
- mTimeShiftManager.jumpToPrevious();
- updateControls(true);
- }
- }
- });
- initializeButton(mRewindButton, R.drawable.lb_ic_fast_rewind,
- R.string.play_controls_description_fast_rewind, null, new Runnable() {
- @Override
- public void run() {
- if (mTimeShiftManager.isAvailable()) {
- mTimeShiftManager.rewind();
- updateButtons();
- }
- }
- });
- initializeButton(mPlayPauseButton, R.drawable.lb_ic_play,
- R.string.play_controls_description_play_pause, null, new Runnable() {
- @Override
- public void run() {
- if (mTimeShiftManager.isAvailable()) {
- mTimeShiftManager.togglePlayPause();
- updateButtons();
- }
- }
- });
- initializeButton(mFastForwardButton, R.drawable.lb_ic_fast_forward,
- R.string.play_controls_description_fast_forward, null, new Runnable() {
- @Override
- public void run() {
- if (mTimeShiftManager.isAvailable()) {
- mTimeShiftManager.fastForward();
- updateButtons();
- }
- }
- });
- initializeButton(mJumpNextButton, R.drawable.lb_ic_skip_next,
- R.string.play_controls_description_skip_next, null, new Runnable() {
- @Override
- public void run() {
- if (mTimeShiftManager.isAvailable()) {
- mTimeShiftManager.jumpToNext();
- updateControls(true);
- }
- }
- });
- int color = getResources().getColor(R.color.play_controls_recording_icon_color_on_focus,
- null);
- initializeButton(mRecordButton, R.drawable.ic_record_start, R.string
- .channels_item_record_start, color, new Runnable() {
- @Override
- public void run() {
- onRecordButtonClicked();
- }
- });
+ initializeButton(
+ mJumpPreviousButton,
+ R.drawable.lb_ic_skip_previous,
+ R.string.play_controls_description_skip_previous,
+ null,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.jumpToPrevious();
+ updateControls(true);
+ }
+ }
+ });
+ initializeButton(
+ mRewindButton,
+ R.drawable.lb_ic_fast_rewind,
+ R.string.play_controls_description_fast_rewind,
+ null,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.rewind();
+ updateButtons();
+ }
+ }
+ });
+ initializeButton(
+ mPlayPauseButton,
+ R.drawable.lb_ic_play,
+ R.string.play_controls_description_play_pause,
+ null,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.togglePlayPause();
+ updateButtons();
+ }
+ }
+ });
+ initializeButton(
+ mFastForwardButton,
+ R.drawable.lb_ic_fast_forward,
+ R.string.play_controls_description_fast_forward,
+ null,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.fastForward();
+ updateButtons();
+ }
+ }
+ });
+ initializeButton(
+ mJumpNextButton,
+ R.drawable.lb_ic_skip_next,
+ R.string.play_controls_description_skip_next,
+ null,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.jumpToNext();
+ updateControls(true);
+ }
+ }
+ });
+ int color =
+ getResources().getColor(R.color.play_controls_recording_icon_color_on_focus, null);
+ initializeButton(
+ mRecordButton,
+ R.drawable.ic_record_start,
+ R.string.channels_item_record_start,
+ color,
+ new Runnable() {
+ @Override
+ public void run() {
+ onRecordButtonClicked();
+ }
+ });
}
private boolean isCurrentChannelRecording() {
Channel currentChannel = mMainActivity.getCurrentChannel();
- return currentChannel != null && mDvrManager != null
+ return currentChannel != null
+ && mDvrManager != null
&& mDvrManager.getCurrentRecording(currentChannel.getId()) != null;
}
private void onRecordButtonClicked() {
boolean isRecording = isCurrentChannelRecording();
Channel currentChannel = mMainActivity.getCurrentChannel();
- TvApplication.getSingletons(getContext()).getTracker().sendMenuClicked(isRecording ?
- R.string.channels_item_record_start : R.string.channels_item_record_stop);
+ TvSingletons.getSingletons(getContext())
+ .getTracker()
+ .sendMenuClicked(
+ isRecording
+ ? R.string.channels_item_record_start
+ : R.string.channels_item_record_stop);
if (!isRecording) {
if (!(mDvrManager != null && mDvrManager.isChannelRecordable(currentChannel))) {
- Toast.makeText(mMainActivity, R.string.dvr_msg_cannot_record_channel,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ mMainActivity,
+ R.string.dvr_msg_cannot_record_channel,
+ Toast.LENGTH_SHORT)
+ .show();
} else {
- Program program = TvApplication.getSingletons(mMainActivity).getProgramDataManager()
- .getCurrentProgram(currentChannel.getId());
- DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity,
- currentChannel.getInputId(), new Runnable() {
+ Program program =
+ TvSingletons.getSingletons(mMainActivity)
+ .getProgramDataManager()
+ .getCurrentProgram(currentChannel.getId());
+ DvrUiHelper.checkStorageStatusAndShowErrorMessage(
+ mMainActivity,
+ currentChannel.getInputId(),
+ new Runnable() {
@Override
public void run() {
- DvrUiHelper.requestRecordingCurrentProgram(mMainActivity,
- currentChannel, program, true);
+ DvrUiHelper.requestRecordingCurrentProgram(
+ mMainActivity, currentChannel, program, true);
}
});
}
} else if (currentChannel != null) {
- DvrUiHelper.showStopRecordingDialog(mMainActivity, currentChannel.getId(),
+ DvrUiHelper.showStopRecordingDialog(
+ mMainActivity,
+ currentChannel.getId(),
DvrStopRecordingFragment.REASON_USER_STOP,
new HalfSizedDialogFragment.OnActionClickListener() {
@Override
public void onActionClick(long actionId) {
if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
ScheduledRecording currentRecording =
- mDvrManager.getCurrentRecording(
- currentChannel.getId());
+ mDvrManager.getCurrentRecording(currentChannel.getId());
if (currentRecording != null) {
mDvrManager.stopRecording(currentRecording);
}
@@ -288,8 +324,12 @@ public class PlayControlsRowView extends MenuRowView {
}
}
- private void initializeButton(PlayControlsButton button, int imageResId,
- int descriptionId, Integer focusedIconColor, Runnable clickAction) {
+ private void initializeButton(
+ PlayControlsButton button,
+ int imageResId,
+ int descriptionId,
+ Integer focusedIconColor,
+ Runnable clickAction) {
button.setImageResId(imageResId);
button.setAction(clickAction);
if (focusedIconColor != null) {
@@ -305,69 +345,77 @@ public class PlayControlsRowView extends MenuRowView {
PlayControlsRow playControlsRow = (PlayControlsRow) row;
mTvView = playControlsRow.getTvView();
mTimeShiftManager = playControlsRow.getTimeShiftManager();
- mTimeShiftManager.setListener(new TimeShiftManager.Listener() {
- @Override
- public void onAvailabilityChanged() {
- updateMenuVisibility();
- PlayControlsRowView.this.updateAll(false);
- }
+ mTimeShiftManager.setListener(
+ new TimeShiftManager.Listener() {
+ @Override
+ public void onAvailabilityChanged() {
+ updateMenuVisibility();
+ PlayControlsRowView.this.updateAll(false);
+ }
- @Override
- public void onPlayStatusChanged(int status) {
- updateMenuVisibility();
- if (mTimeShiftManager.isAvailable()) {
- updateControls(false);
- }
- }
+ @Override
+ public void onPlayStatusChanged(int status) {
+ updateMenuVisibility();
+ if (mTimeShiftManager.isAvailable()) {
+ updateControls(false);
+ }
+ }
- @Override
- public void onRecordTimeRangeChanged() {
- if (mTimeShiftManager.isAvailable()) {
- updateControls(false);
- }
- }
+ @Override
+ public void onRecordTimeRangeChanged() {
+ if (mTimeShiftManager.isAvailable()) {
+ updateControls(false);
+ }
+ }
- @Override
- public void onCurrentPositionChanged() {
- if (mTimeShiftManager.isAvailable()) {
- initializeTimeline();
- updateControls(false);
- }
- }
+ @Override
+ public void onCurrentPositionChanged() {
+ if (mTimeShiftManager.isAvailable()) {
+ initializeTimeline();
+ updateControls(false);
+ }
+ }
- @Override
- public void onProgramInfoChanged() {
- if (mTimeShiftManager.isAvailable()) {
- initializeTimeline();
- updateControls(false);
- }
- }
+ @Override
+ public void onProgramInfoChanged() {
+ if (mTimeShiftManager.isAvailable()) {
+ initializeTimeline();
+ updateControls(false);
+ }
+ }
- @Override
- public void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled) {
- // Move focus to the play/pause button when the PREVIOUS, NEXT, REWIND or
- // FAST_FORWARD button is clicked and the button becomes disabled.
- // No need to update the UI here because the UI will be updated by other callbacks.
- if (!enabled &&
- ((actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS
- && mJumpPreviousButton.hasFocus())
- || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND
- && mRewindButton.hasFocus())
- || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD
- && mFastForwardButton.hasFocus())
- || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
- && mJumpNextButton.hasFocus()))) {
- mPlayPauseButton.requestFocus();
- }
- }
- });
+ @Override
+ public void onActionEnabledChanged(
+ @TimeShiftActionId int actionId, boolean enabled) {
+ // Move focus to the play/pause button when the PREVIOUS, NEXT, REWIND or
+ // FAST_FORWARD button is clicked and the button becomes disabled.
+ // No need to update the UI here because the UI will be updated by other
+ // callbacks.
+ if (!enabled
+ && ((actionId
+ == TimeShiftManager
+ .TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS
+ && mJumpPreviousButton.hasFocus())
+ || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND
+ && mRewindButton.hasFocus())
+ || (actionId
+ == TimeShiftManager
+ .TIME_SHIFT_ACTION_ID_FAST_FORWARD
+ && mFastForwardButton.hasFocus())
+ || (actionId
+ == TimeShiftManager
+ .TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
+ && mJumpNextButton.hasFocus()))) {
+ mPlayPauseButton.requestFocus();
+ }
+ }
+ });
// force update to initialize everything
updateAll(true);
}
private void initializeTimeline() {
- Program program = mTimeShiftManager.getProgramAt(
- mTimeShiftManager.getCurrentPositionMs());
+ Program program = mTimeShiftManager.getProgramAt(mTimeShiftManager.getCurrentPositionMs());
mProgramStartTimeMs = program.getStartTimeUtcMillis();
mProgramEndTimeMs = program.getEndTimeUtcMillis();
mProgress.setMax(mProgramEndTimeMs - mProgramStartTimeMs);
@@ -441,16 +489,17 @@ public class PlayControlsRowView extends MenuRowView {
// Focus may be changed in another message if requestFocus is called in this message.
// After the focus is actually changed, hideRippleAnimation should run
// to reflect the result of the focus change. To be sure, hideRippleAnimation is posted.
- post(new Runnable() {
- @Override
- public void run() {
- mJumpPreviousButton.hideRippleAnimation();
- mRewindButton.hideRippleAnimation();
- mPlayPauseButton.hideRippleAnimation();
- mFastForwardButton.hideRippleAnimation();
- mJumpNextButton.hideRippleAnimation();
- }
- });
+ post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mJumpPreviousButton.hideRippleAnimation();
+ mRewindButton.hideRippleAnimation();
+ mPlayPauseButton.hideRippleAnimation();
+ mFastForwardButton.hideRippleAnimation();
+ mJumpNextButton.hideRippleAnimation();
+ }
+ });
}
@Override
@@ -465,9 +514,7 @@ public class PlayControlsRowView extends MenuRowView {
}
}
- /**
- * Updates the view contents. It is called from the PlayControlsRow.
- */
+ /** Updates the view contents. It is called from the PlayControlsRow. */
public void update() {
updateAll(false);
}
@@ -516,13 +563,22 @@ public class PlayControlsRowView extends MenuRowView {
private void updateProgress() {
if (isEnabled()) {
- long progressStartTimeMs = Math.min(mProgramEndTimeMs,
- Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs()));
- long currentPlayingTimeMs = Math.min(mProgramEndTimeMs,
- Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs()));
- long progressEndTimeMs = Math.min(mProgramEndTimeMs,
- Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs()));
- mProgress.setProgressRange(progressStartTimeMs - mProgramStartTimeMs,
+ long progressStartTimeMs =
+ Math.min(
+ mProgramEndTimeMs,
+ Math.max(
+ mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs()));
+ long currentPlayingTimeMs =
+ Math.min(
+ mProgramEndTimeMs,
+ Math.max(
+ mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs()));
+ long progressEndTimeMs =
+ Math.min(
+ mProgramEndTimeMs,
+ Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs()));
+ mProgress.setProgressRange(
+ progressStartTimeMs - mProgramStartTimeMs,
progressEndTimeMs - mProgramStartTimeMs);
mProgress.setProgress(currentPlayingTimeMs - mProgramStartTimeMs);
} else {
@@ -560,21 +616,24 @@ public class PlayControlsRowView extends MenuRowView {
if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PAUSED) {
mPlayPauseButton.setImageResId(R.drawable.lb_ic_play);
- mPlayPauseButton.setEnabled(mTimeShiftManager.isActionEnabled(
- TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY));
+ mPlayPauseButton.setEnabled(
+ mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY));
} else {
mPlayPauseButton.setImageResId(R.drawable.lb_ic_pause);
- mPlayPauseButton.setEnabled(mTimeShiftManager.isActionEnabled(
- TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE));
+ mPlayPauseButton.setEnabled(
+ mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE));
}
- mJumpPreviousButton.setEnabled(mTimeShiftManager.isActionEnabled(
- TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS));
- mRewindButton.setEnabled(mTimeShiftManager.isActionEnabled(
- TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND));
- mFastForwardButton.setEnabled(mTimeShiftManager.isActionEnabled(
- TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD));
- mJumpNextButton.setEnabled(mTimeShiftManager.isActionEnabled(
- TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT));
+ mJumpPreviousButton.setEnabled(
+ mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS));
+ mRewindButton.setEnabled(
+ mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND));
+ mFastForwardButton.setEnabled(
+ mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD));
+ mJumpNextButton.setEnabled(
+ mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT));
mJumpPreviousButton.setVisibility(VISIBLE);
mJumpNextButton.setVisibility(VISIBLE);
updateButtonMargin();
@@ -590,8 +649,11 @@ public class PlayControlsRowView extends MenuRowView {
if (mTimeShiftManager.getDisplayedPlaySpeed() == TimeShiftManager.PLAY_SPEED_1X) {
button.setLabel(null);
} else {
- button.setLabel(getResources().getString(R.string.play_controls_speed,
- mTimeShiftManager.getDisplayedPlaySpeed()));
+ button.setLabel(
+ getResources()
+ .getString(
+ R.string.play_controls_speed,
+ mTimeShiftManager.getDisplayedPlaySpeed()));
}
}
@@ -618,12 +680,13 @@ public class PlayControlsRowView extends MenuRowView {
}
private void updateButtonMargin() {
- int numOfVisibleButtons = (mJumpPreviousButton.getVisibility() == View.VISIBLE ? 1 : 0)
- + (mRewindButton.getVisibility() == View.VISIBLE ? 1 : 0)
- + (mPlayPauseButton.getVisibility() == View.VISIBLE ? 1 : 0)
- + (mFastForwardButton.getVisibility() == View.VISIBLE ? 1 : 0)
- + (mJumpNextButton.getVisibility() == View.VISIBLE ? 1 : 0)
- + (mRecordButton.getVisibility() == View.VISIBLE ? 1 : 0);
+ int numOfVisibleButtons =
+ (mJumpPreviousButton.getVisibility() == View.VISIBLE ? 1 : 0)
+ + (mRewindButton.getVisibility() == View.VISIBLE ? 1 : 0)
+ + (mPlayPauseButton.getVisibility() == View.VISIBLE ? 1 : 0)
+ + (mFastForwardButton.getVisibility() == View.VISIBLE ? 1 : 0)
+ + (mJumpNextButton.getVisibility() == View.VISIBLE ? 1 : 0)
+ + (mRecordButton.getVisibility() == View.VISIBLE ? 1 : 0);
boolean useCompactLayout = numOfVisibleButtons > NORMAL_WIDTH_MAX_BUTTON_COUNT;
if (mUseCompactLayout == useCompactLayout) {
return;
diff --git a/src/com/android/tv/menu/PlaybackProgressBar.java b/src/com/android/tv/menu/PlaybackProgressBar.java
index e8061bc6..398afe13 100644
--- a/src/com/android/tv/menu/PlaybackProgressBar.java
+++ b/src/com/android/tv/menu/PlaybackProgressBar.java
@@ -24,12 +24,9 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.View;
-
import com.android.tv.R;
-/**
- * A progress bar control which has two progresses which start in the middle of the control.
- */
+/** A progress bar control which has two progresses which start in the middle of the control. */
public class PlaybackProgressBar extends View {
private final LayerDrawable mProgressDrawable;
private final Drawable mPrimaryDrawable;
@@ -51,11 +48,12 @@ public class PlaybackProgressBar extends View {
this(context, attrs, defStyleAttr, 0);
}
- public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
+ public PlaybackProgressBar(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.PlaybackProgressBar, defStyleAttr, defStyleRes);
+ TypedArray a =
+ context.obtainStyledAttributes(
+ attrs, R.styleable.PlaybackProgressBar, defStyleAttr, defStyleRes);
mProgressDrawable =
(LayerDrawable) a.getDrawable(R.styleable.PlaybackProgressBar_progressDrawable);
mPrimaryDrawable = mProgressDrawable.findDrawableByLayerId(android.R.id.progress);
@@ -98,9 +96,7 @@ public class PlaybackProgressBar extends View {
}
}
- /**
- * Sets the start and end position of the progress.
- */
+ /** Sets the start and end position of the progress. */
public void setProgressRange(long start, long end) {
start = constrain(start, 0, mMax);
end = constrain(end, start, mMax);
@@ -112,9 +108,7 @@ public class PlaybackProgressBar extends View {
}
}
- /**
- * Sets the progress position.
- */
+ /** Sets the progress position. */
public void setProgress(long progress) {
progress = constrain(progress, mProgressStart, mProgressEnd);
if (progress != mProgress) {
diff --git a/src/com/android/tv/menu/SimpleCardView.java b/src/com/android/tv/menu/SimpleCardView.java
index fc5192da..e8ecdc72 100644
--- a/src/com/android/tv/menu/SimpleCardView.java
+++ b/src/com/android/tv/menu/SimpleCardView.java
@@ -19,9 +19,7 @@ package com.android.tv.menu;
import android.content.Context;
import android.util.AttributeSet;
-/**
- * A view to render a guide card.
- */
+/** A view to render a guide card. */
public class SimpleCardView extends BaseCardView<ChannelsRowItem> {
public SimpleCardView(Context context) {
diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java
index 6e035f22..55affb59 100644
--- a/src/com/android/tv/menu/TvOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java
@@ -19,18 +19,16 @@ package com.android.tv.menu;
import android.content.Context;
import android.media.tv.TvTrackInfo;
import android.support.annotation.VisibleForTesting;
-
-import com.android.tv.Features;
+import com.android.tv.TvFeatures;
import com.android.tv.TvOptionsManager;
-import com.android.tv.customization.CustomAction;
+import com.android.tv.common.customization.CustomAction;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.data.DisplayMode;
import com.android.tv.ui.TvViewUiManager;
import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
import com.android.tv.ui.sidepanel.DeveloperOptionFragment;
import com.android.tv.ui.sidepanel.DisplayModeFragment;
import com.android.tv.ui.sidepanel.MultiAudioFragment;
-import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
@@ -47,12 +45,12 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
List<MenuAction> actionList = new ArrayList<>();
actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION);
- if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) {
+ if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) {
actionList.add(MenuAction.SYSTEMWIDE_PIP_ACTION);
}
actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
actionList.add(MenuAction.MORE_CHANNELS_ACTION);
- if (Utils.isDeveloper()) {
+ if (CommonUtils.isDeveloper()) {
actionList.add(MenuAction.DEV_ACTION);
}
actionList.add(MenuAction.SETTINGS_ACTION);
@@ -87,7 +85,8 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
private boolean updatePipAction() {
if (containsItem(MenuAction.SYSTEMWIDE_PIP_ACTION)) {
- return MenuAction.setEnabled(MenuAction.SYSTEMWIDE_PIP_ACTION,
+ return MenuAction.setEnabled(
+ MenuAction.SYSTEMWIDE_PIP_ACTION,
!getMainActivity().isScreenBlockedByResourceConflictOrParentalControl());
}
return false;
@@ -103,41 +102,50 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
private boolean updateDisplayModeAction() {
TvViewUiManager uiManager = getMainActivity().getTvViewUiManager();
- boolean enabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL)
- || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM);
+ boolean enabled =
+ uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL)
+ || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM);
// Use "|" operator for non-short-circuit evaluation.
return MenuAction.setEnabled(MenuAction.SELECT_DISPLAY_MODE_ACTION, enabled)
| updateActionDescription(MenuAction.SELECT_DISPLAY_MODE_ACTION);
}
private boolean updateActionDescription(MenuAction action) {
- return MenuAction.setActionDescription(action,
- getMainActivity().getTvOptionsManager().getOptionString(action.getType()));
+ return MenuAction.setActionDescription(
+ action, getMainActivity().getTvOptionsManager().getOptionString(action.getType()));
}
@Override
protected void executeBaseAction(int type) {
switch (type) {
case TvOptionsManager.OPTION_CLOSED_CAPTIONS:
- getMainActivity().getOverlayManager().getSideFragmentManager()
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
.show(new ClosedCaptionFragment());
break;
case TvOptionsManager.OPTION_DISPLAY_MODE:
- getMainActivity().getOverlayManager().getSideFragmentManager()
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
.show(new DisplayModeFragment());
break;
case TvOptionsManager.OPTION_SYSTEMWIDE_PIP:
getMainActivity().enterPictureInPictureMode();
break;
case TvOptionsManager.OPTION_MULTI_AUDIO:
- getMainActivity().getOverlayManager().getSideFragmentManager()
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
.show(new MultiAudioFragment());
break;
case TvOptionsManager.OPTION_MORE_CHANNELS:
getMainActivity().showMerchantCollection();
break;
case TvOptionsManager.OPTION_DEVELOPER:
- getMainActivity().getOverlayManager().getSideFragmentManager()
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
.show(new DeveloperOptionFragment());
break;
case TvOptionsManager.OPTION_SETTINGS:
@@ -145,4 +153,4 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
break;
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/onboarding/NewSourcesFragment.java b/src/com/android/tv/onboarding/NewSourcesFragment.java
index 8509b50c..f3b077a7 100644
--- a/src/com/android/tv/onboarding/NewSourcesFragment.java
+++ b/src/com/android/tv/onboarding/NewSourcesFragment.java
@@ -23,28 +23,17 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.ui.setup.SetupActionHelper;
-import com.android.tv.util.SetupUtils;
-/**
- * A fragment for new channel source info/setup.
- */
+/** A fragment for new channel source info/setup. */
public class NewSourcesFragment extends Fragment {
- /**
- * The action category.
- */
- public static final String ACTION_CATEOGRY =
- "com.android.tv.onboarding.NewSourcesFragment";
- /**
- * An action to show the setup screen.
- */
+ /** The action category. */
+ public static final String ACTION_CATEOGRY = "com.android.tv.onboarding.NewSourcesFragment";
+ /** An action to show the setup screen. */
public static final int ACTION_SETUP = 1;
- /**
- * An action to close this fragment.
- */
+ /** An action to close this fragment. */
public static final int ACTION_SKIP = 2;
public NewSourcesFragment() {
@@ -57,19 +46,20 @@ public class NewSourcesFragment extends Fragment {
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_new_sources, container, false);
initializeButton(view.findViewById(R.id.setup), ACTION_SETUP);
initializeButton(view.findViewById(R.id.skip), ACTION_SKIP);
- SetupUtils.getInstance(getActivity()).markAllInputsRecognized(TvApplication
- .getSingletons(getActivity()).getTvInputManagerHelper());
+ TvSingletons singletons = TvSingletons.getSingletons(getActivity());
+ singletons.getSetupUtils().markAllInputsRecognized(singletons.getTvInputManagerHelper());
view.requestFocus();
return view;
}
private void initializeButton(View view, int actionId) {
- view.setOnClickListener(SetupActionHelper.createOnClickListenerForAction(this,
- ACTION_CATEOGRY, actionId, null));
+ view.setOnClickListener(
+ SetupActionHelper.createOnClickListenerForAction(
+ this, ACTION_CATEOGRY, actionId, null));
}
}
diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java
index 45205c4c..a1cf9de1 100644
--- a/src/com/android/tv/onboarding/OnboardingActivity.java
+++ b/src/com/android/tv/onboarding/OnboardingActivity.java
@@ -26,17 +26,15 @@ import android.media.tv.TvInputInfo;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.widget.Toast;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
import com.android.tv.SetupPassthroughActivity;
-import com.android.tv.TvApplication;
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.TvSingletons;
import com.android.tv.common.ui.setup.SetupActivity;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.util.OnboardingUtils;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
@@ -51,29 +49,31 @@ public class OnboardingActivity extends SetupActivity {
private ChannelDataManager mChannelDataManager;
private TvInputManagerHelper mInputManager;
- private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- mChannelDataManager.removeListener(this);
- SetupUtils.getInstance(OnboardingActivity.this).markNewChannelsBrowsable();
- }
+ private SetupUtils mSetupUtils;
+ private final ChannelDataManager.Listener mChannelListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ mChannelDataManager.removeListener(this);
+ mSetupUtils.markNewChannelsBrowsable();
+ }
- @Override
- public void onChannelListUpdated() { }
+ @Override
+ public void onChannelListUpdated() {}
- @Override
- public void onChannelBrowsableChanged() { }
- };
+ @Override
+ public void onChannelBrowsableChanged() {}
+ };
/**
* Returns an intent to start {@link OnboardingActivity}.
*
* @param context context to create an intent. Should not be {@code null}.
* @param intentAfterCompletion intent which will be used to start a new activity when this
- * activity finishes. Should not be {@code null}.
+ * activity finishes. Should not be {@code null}.
*/
- public static Intent buildIntent(@NonNull Context context,
- @NonNull Intent intentAfterCompletion) {
+ public static Intent buildIntent(
+ @NonNull Context context, @NonNull Intent intentAfterCompletion) {
return new Intent(context, OnboardingActivity.class)
.putExtra(OnboardingActivity.KEY_INTENT_AFTER_COMPLETION, intentAfterCompletion);
}
@@ -81,19 +81,21 @@ public class OnboardingActivity extends SetupActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ApplicationSingletons singletons = TvApplication.getSingletons(this);
+ TvSingletons singletons = TvSingletons.getSingletons(this);
mInputManager = singletons.getTvInputManagerHelper();
+ mSetupUtils = singletons.getSetupUtils();
if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) {
mChannelDataManager = singletons.getChannelDataManager();
// Make the channels of the new inputs which have been setup outside Live TV
// browsable.
if (mChannelDataManager.isDbLoadFinished()) {
- SetupUtils.getInstance(this).markNewChannelsBrowsable();
+ mSetupUtils.markNewChannelsBrowsable();
} else {
mChannelDataManager.addListener(mChannelListener);
}
} else {
- requestPermissions(new String[] {PermissionUtils.PERMISSION_READ_TV_LISTINGS},
+ requestPermissions(
+ new String[] {PermissionUtils.PERMISSION_READ_TV_LISTINGS},
PERMISSIONS_REQUEST_READ_TV_LISTINGS);
}
}
@@ -109,32 +111,35 @@ public class OnboardingActivity extends SetupActivity {
@Override
protected Fragment onCreateInitialFragment() {
if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) {
- return OnboardingUtils.isFirstRunWithCurrentVersion(this) ? new WelcomeFragment()
+ return OnboardingUtils.isFirstRunWithCurrentVersion(this)
+ ? new WelcomeFragment()
: new SetupSourcesFragment();
}
return null;
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
finish();
- Intent intentForNextActivity = getIntent().getParcelableExtra(
- KEY_INTENT_AFTER_COMPLETION);
+ Intent intentForNextActivity =
+ getIntent().getParcelableExtra(KEY_INTENT_AFTER_COMPLETION);
startActivity(buildIntent(this, intentForNextActivity));
} else {
- Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
- Toast.LENGTH_LONG).show();
+ Toast.makeText(
+ this,
+ R.string.msg_read_tv_listing_permission_denied,
+ Toast.LENGTH_LONG)
+ .show();
finish();
}
}
}
private void finishActivity() {
- Intent intentForNextActivity = getIntent().getParcelableExtra(
- KEY_INTENT_AFTER_COMPLETION);
+ Intent intentForNextActivity = getIntent().getParcelableExtra(KEY_INTENT_AFTER_COMPLETION);
if (intentForNextActivity != null) {
startActivity(intentForNextActivity);
}
@@ -142,12 +147,14 @@ public class OnboardingActivity extends SetupActivity {
}
private void showMerchantCollection() {
- executeActionWithDelay(new Runnable() {
- @Override
- public void run() {
- startActivity(OnboardingUtils.ONLINE_STORE_INTENT);
- }
- }, SHOW_RIPPLE_DURATION_MS);
+ executeActionWithDelay(
+ new Runnable() {
+ @Override
+ public void run() {
+ startActivity(OnboardingUtils.ONLINE_STORE_INTENT);
+ }
+ },
+ SHOW_RIPPLE_DURATION_MS);
}
@Override
@@ -167,42 +174,55 @@ public class OnboardingActivity extends SetupActivity {
case SetupSourcesFragment.ACTION_ONLINE_STORE:
showMerchantCollection();
return true;
- case SetupSourcesFragment.ACTION_SETUP_INPUT: {
- String inputId = params.getString(
- SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- Intent intent = TvCommonUtils.createSetupIntent(input);
- if (intent == null) {
- Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT)
- .show();
+ case SetupSourcesFragment.ACTION_SETUP_INPUT:
+ {
+ String inputId =
+ params.getString(
+ SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
+ TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+ Intent intent = CommonUtils.createSetupIntent(input);
+ if (intent == null) {
+ Toast.makeText(
+ this,
+ R.string.msg_no_setup_activity,
+ Toast.LENGTH_SHORT)
+ .show();
+ return true;
+ }
+ // Even though other app can handle the intent, the setup launched by
+ // Live
+ // channels should go through Live channels SetupPassthroughActivity.
+ intent.setComponent(
+ new ComponentName(this, SetupPassthroughActivity.class));
+ try {
+ // Now we know that the user intends to set up this input. Grant
+ // permission for writing EPG data.
+ SetupUtils.grantEpgPermission(
+ this, input.getServiceInfo().packageName);
+ startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(
+ this,
+ getString(
+ R.string.msg_unable_to_start_setup_activity,
+ input.loadLabel(this)),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
return true;
}
- // Even though other app can handle the intent, the setup launched by Live
- // channels should go through Live channels SetupPassthroughActivity.
- intent.setComponent(new ComponentName(this,
- SetupPassthroughActivity.class));
- try {
- // Now we know that the user intends to set up this input. Grant
- // permission for writing EPG data.
- SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName);
- startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this,
- getString(R.string.msg_unable_to_start_setup_activity,
- input.loadLabel(this)), Toast.LENGTH_SHORT).show();
- }
- return true;
- }
- case SetupMultiPaneFragment.ACTION_DONE: {
- ChannelDataManager manager = TvApplication.getSingletons(
- OnboardingActivity.this).getChannelDataManager();
- if (manager.getChannelCount() == 0) {
- finish();
- } else {
- finishActivity();
+ case SetupMultiPaneFragment.ACTION_DONE:
+ {
+ ChannelDataManager manager =
+ TvSingletons.getSingletons(OnboardingActivity.this)
+ .getChannelDataManager();
+ if (manager.getChannelCount() == 0) {
+ finish();
+ } else {
+ finishActivity();
+ }
+ return true;
}
- return true;
- }
}
break;
}
diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java
index f56daec5..f032f622 100644
--- a/src/com/android/tv/onboarding/SetupSourcesFragment.java
+++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java
@@ -30,41 +30,30 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.TvInputNewComparator;
-import com.android.tv.tuner.TunerInputController;
import com.android.tv.ui.GuidedActionsStylistWithDivider;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-/**
- * A fragment for channel source info/setup.
- */
+/** A fragment for channel source info/setup. */
public class SetupSourcesFragment extends SetupMultiPaneFragment {
- /**
- * The action category for the actions which is fired from this fragment.
- */
- public static final String ACTION_CATEGORY =
- "com.android.tv.onboarding.SetupSourcesFragment";
- /**
- * An action to open the merchant collection.
- */
+ /** The action category for the actions which is fired from this fragment. */
+ public static final String ACTION_CATEGORY = "com.android.tv.onboarding.SetupSourcesFragment";
+ /** An action to open the merchant collection. */
public static final int ACTION_ONLINE_STORE = 1;
/**
* An action to show the setup activity of TV input.
- * <p>
- * This action is not added to the action list. This is sent outside of the fragment.
- * Use {@link #ACTION_PARAM_KEY_INPUT_ID} to get the input ID from the parameter.
+ *
+ * <p>This action is not added to the action list. This is sent outside of the fragment. Use
+ * {@link #ACTION_PARAM_KEY_INPUT_ID} to get the input ID from the parameter.
*/
public static final int ACTION_SETUP_INPUT = 2;
@@ -77,10 +66,10 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
private static final String SETUP_TRACKER_LABEL = "Setup fragment";
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
- TvApplication.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL);
+ TvSingletons.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL);
return view;
}
@@ -130,82 +119,87 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
private int mPendingAction = PENDING_ACTION_NONE;
- private final TvInputCallback mInputCallback = new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- handleInputChanged();
- }
+ private final TvInputCallback mInputCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ handleInputChanged();
+ }
- @Override
- public void onInputRemoved(String inputId) {
- handleInputChanged();
- }
+ @Override
+ public void onInputRemoved(String inputId) {
+ handleInputChanged();
+ }
- @Override
- public void onInputUpdated(String inputId) {
- handleInputChanged();
- }
+ @Override
+ public void onInputUpdated(String inputId) {
+ handleInputChanged();
+ }
- @Override
- public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
- handleInputChanged();
- }
+ @Override
+ public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
+ handleInputChanged();
+ }
- private void handleInputChanged() {
- // The actions created while enter transition is running will not be included in the
- // fragment transition.
- if (mParentFragment.isEnterTransitionRunning()) {
- mPendingAction = PENDING_ACTION_INPUT_CHANGED;
- return;
- }
- buildInputs();
- updateActions();
- }
- };
+ private void handleInputChanged() {
+ // The actions created while enter transition is running will not be
+ // included in the
+ // fragment transition.
+ if (mParentFragment.isEnterTransitionRunning()) {
+ mPendingAction = PENDING_ACTION_INPUT_CHANGED;
+ return;
+ }
+ buildInputs();
+ updateActions();
+ }
+ };
- private final ChannelDataManager.Listener mChannelDataManagerListener
- = new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- handleChannelChanged();
- }
+ private final ChannelDataManager.Listener mChannelDataManagerListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ handleChannelChanged();
+ }
- @Override
- public void onChannelListUpdated() {
- handleChannelChanged();
- }
+ @Override
+ public void onChannelListUpdated() {
+ handleChannelChanged();
+ }
- @Override
- public void onChannelBrowsableChanged() {
- handleChannelChanged();
- }
+ @Override
+ public void onChannelBrowsableChanged() {
+ handleChannelChanged();
+ }
- private void handleChannelChanged() {
- // The actions created while enter transition is running will not be included in the
- // fragment transition.
- if (mParentFragment.isEnterTransitionRunning()) {
- if (mPendingAction != PENDING_ACTION_INPUT_CHANGED) {
- mPendingAction = PENDING_ACTION_CHANNEL_CHANGED;
+ private void handleChannelChanged() {
+ // The actions created while enter transition is running will not be
+ // included in the
+ // fragment transition.
+ if (mParentFragment.isEnterTransitionRunning()) {
+ if (mPendingAction != PENDING_ACTION_INPUT_CHANGED) {
+ mPendingAction = PENDING_ACTION_CHANNEL_CHANGED;
+ }
+ return;
+ }
+ updateActions();
}
- return;
- }
- updateActions();
- }
- };
+ };
@Override
public void onCreate(Bundle savedInstanceState) {
Context context = getActivity();
- ApplicationSingletons app = TvApplication.getSingletons(context);
- mInputManager = app.getTvInputManagerHelper();
- mChannelDataManager = app.getChannelDataManager();
- mSetupUtils = SetupUtils.getInstance(context);
+ TvSingletons singletons = TvSingletons.getSingletons(context);
+ mInputManager = singletons.getTvInputManagerHelper();
+ mChannelDataManager = singletons.getChannelDataManager();
+ mSetupUtils = singletons.getSetupUtils();
buildInputs();
mInputManager.addCallback(mInputCallback);
mChannelDataManager.addListener(mChannelDataManagerListener);
super.onCreate(savedInstanceState);
mParentFragment = (SetupSourcesFragment) getParentFragment();
- TunerInputController.executeNetworkTunerDiscoveryAsyncTask(getContext());
+ singletons
+ .getTunerInputController()
+ .executeNetworkTunerDiscoveryAsyncTask(getContext());
}
@Override
@@ -229,8 +223,8 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
}
@Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
+ public void onCreateActions(
+ @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
createActionsInternal(actions);
}
@@ -274,24 +268,26 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
int position = 0;
if (mDoneInputStartIndex > 0) {
// Need a "New" category
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_HEADER)
- .title(null)
- .description(getString(R.string.setup_category_new))
- .focusable(false)
- .infoOnly(true)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_HEADER)
+ .title(null)
+ .description(getString(R.string.setup_category_new))
+ .focusable(false)
+ .infoOnly(true)
+ .build());
}
for (int i = 0; i < mInputs.size(); ++i) {
if (i == mDoneInputStartIndex) {
++position;
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_HEADER)
- .title(null)
- .description(getString(R.string.setup_category_done))
- .focusable(false)
- .infoOnly(true)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_HEADER)
+ .title(null)
+ .description(getString(R.string.setup_category_done))
+ .focusable(false)
+ .infoOnly(true)
+ .build());
}
TvInputInfo input = mInputs.get(i);
String inputId = input.getId();
@@ -301,8 +297,12 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
if (channelCount == 0) {
description = getString(R.string.setup_input_no_channels);
} else {
- description = getResources().getQuantityString(
- R.plurals.setup_input_channels, channelCount, channelCount);
+ description =
+ getResources()
+ .getQuantityString(
+ R.plurals.setup_input_channels,
+ channelCount,
+ channelCount);
}
} else if (i >= mKnownInputStartIndex) {
description = getString(R.string.setup_input_setup_now);
@@ -313,11 +313,12 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
if (input.getId().equals(mNewlyAddedInputId)) {
newPosition = position;
}
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_INPUT_START + i)
- .title(input.loadLabel(getActivity()).toString())
- .description(description)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_INPUT_START + i)
+ .title(input.loadLabel(getActivity()).toString())
+ .description(description)
+ .build());
}
if (mInputs.size() > 0) {
// Divider
@@ -326,12 +327,13 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
}
// online store action
++position;
- actions.add(new GuidedAction.Builder(getActivity())
- .id(ACTION_ONLINE_STORE)
- .title(getString(R.string.setup_store_action_title))
- .description(getString(R.string.setup_store_action_description))
- .icon(R.drawable.ic_store)
- .build());
+ actions.add(
+ new GuidedAction.Builder(getActivity())
+ .id(ACTION_ONLINE_STORE)
+ .title(getString(R.string.setup_store_action_title))
+ .description(getString(R.string.setup_store_action_description))
+ .icon(R.drawable.ic_store)
+ .build());
if (newPosition != -1) {
VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView();
@@ -367,6 +369,7 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
case PENDING_ACTION_CHANNEL_CHANGED:
updateActions();
break;
+ default: // fall out
}
mPendingAction = PENDING_ACTION_NONE;
}
@@ -382,17 +385,19 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
if (descriptionView != null) {
if (action.getId() == ACTION_HEADER) {
descriptionView.setAlpha(ALPHA_CATEGORY);
- descriptionView.setTextColor(getResources().getColor(R.color.setup_category,
- null));
- descriptionView.setTypeface(Typeface.create(
- getString(R.string.condensed_font), 0));
+ descriptionView.setTextColor(
+ getResources().getColor(R.color.setup_category, null));
+ descriptionView.setTypeface(
+ Typeface.create(getString(R.string.condensed_font), 0));
} else {
descriptionView.setAlpha(ALPHA_INPUT_DESCRIPTION);
- descriptionView.setTextColor(getResources().getColor(
- R.color.common_setup_input_description, null));
+ descriptionView.setTextColor(
+ getResources()
+ .getColor(R.color.common_setup_input_description, null));
descriptionView.setTypeface(Typeface.create(getString(R.string.font), 0));
}
}
+ setAccessibilityDelegate(vh, action);
}
}
}
diff --git a/src/com/android/tv/onboarding/WelcomeFragment.java b/src/com/android/tv/onboarding/WelcomeFragment.java
index f12233e9..8c119a8a 100644
--- a/src/com/android/tv/onboarding/WelcomeFragment.java
+++ b/src/com/android/tv/onboarding/WelcomeFragment.java
@@ -16,33 +16,37 @@
package com.android.tv.onboarding;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v17.leanback.app.OnboardingFragment;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
import android.widget.ImageView;
-
+import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.common.ui.setup.SetupActionHelper;
import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
-
import java.util.ArrayList;
import java.util.List;
-/**
- * A fragment for the onboarding welcome screen.
- */
+/** A fragment for the onboarding welcome screen. */
public class WelcomeFragment extends OnboardingFragment {
- public static final String ACTION_CATEGORY = "comgoogle.android.tv.onboarding.WelcomeFragment";
+ public static final String ACTION_CATEGORY = "com.android.tv.onboarding.WelcomeFragment";
public static final int ACTION_NEXT = 1;
private static final long START_DELAY_CLOUD_MS = 33;
@@ -56,523 +60,523 @@ public class WelcomeFragment extends OnboardingFragment {
// TODO: Use animator list xml.
private static final int[] TV_FRAMES_1_START = {
- R.drawable.tv_1a_01,
- R.drawable.tv_1a_02,
- R.drawable.tv_1a_03,
- R.drawable.tv_1a_04,
- R.drawable.tv_1a_05,
- R.drawable.tv_1a_06,
- R.drawable.tv_1a_07,
- R.drawable.tv_1a_08,
- R.drawable.tv_1a_09,
- R.drawable.tv_1a_10,
- R.drawable.tv_1a_11,
- R.drawable.tv_1a_12,
- R.drawable.tv_1a_13,
- R.drawable.tv_1a_14,
- R.drawable.tv_1a_15,
- R.drawable.tv_1a_16,
- R.drawable.tv_1a_17,
- R.drawable.tv_1a_18,
- R.drawable.tv_1a_19,
- R.drawable.tv_1a_20
+ R.drawable.tv_1a_01,
+ R.drawable.tv_1a_02,
+ R.drawable.tv_1a_03,
+ R.drawable.tv_1a_04,
+ R.drawable.tv_1a_05,
+ R.drawable.tv_1a_06,
+ R.drawable.tv_1a_07,
+ R.drawable.tv_1a_08,
+ R.drawable.tv_1a_09,
+ R.drawable.tv_1a_10,
+ R.drawable.tv_1a_11,
+ R.drawable.tv_1a_12,
+ R.drawable.tv_1a_13,
+ R.drawable.tv_1a_14,
+ R.drawable.tv_1a_15,
+ R.drawable.tv_1a_16,
+ R.drawable.tv_1a_17,
+ R.drawable.tv_1a_18,
+ R.drawable.tv_1a_19,
+ R.drawable.tv_1a_20
};
private static final int[] TV_FRAMES_1_END = {
- R.drawable.tv_1b_01,
- R.drawable.tv_1b_02,
- R.drawable.tv_1b_03,
- R.drawable.tv_1b_04,
- R.drawable.tv_1b_05,
- R.drawable.tv_1b_06,
- R.drawable.tv_1b_07,
- R.drawable.tv_1b_08,
- R.drawable.tv_1b_09,
- R.drawable.tv_1b_10,
- R.drawable.tv_1b_11
+ R.drawable.tv_1b_01,
+ R.drawable.tv_1b_02,
+ R.drawable.tv_1b_03,
+ R.drawable.tv_1b_04,
+ R.drawable.tv_1b_05,
+ R.drawable.tv_1b_06,
+ R.drawable.tv_1b_07,
+ R.drawable.tv_1b_08,
+ R.drawable.tv_1b_09,
+ R.drawable.tv_1b_10,
+ R.drawable.tv_1b_11
};
private static final int[] TV_FRAMES_2_START = {
- R.drawable.tv_5a_0,
- R.drawable.tv_5a_1,
- R.drawable.tv_5a_2,
- R.drawable.tv_5a_3,
- R.drawable.tv_5a_4,
- R.drawable.tv_5a_5,
- R.drawable.tv_5a_6,
- R.drawable.tv_5a_7,
- R.drawable.tv_5a_8,
- R.drawable.tv_5a_9,
- R.drawable.tv_5a_10,
- R.drawable.tv_5a_11,
- R.drawable.tv_5a_12,
- R.drawable.tv_5a_13,
- R.drawable.tv_5a_14,
- R.drawable.tv_5a_15,
- R.drawable.tv_5a_16,
- R.drawable.tv_5a_17,
- R.drawable.tv_5a_18,
- R.drawable.tv_5a_19,
- R.drawable.tv_5a_20,
- R.drawable.tv_5a_21,
- R.drawable.tv_5a_22,
- R.drawable.tv_5a_23,
- R.drawable.tv_5a_24,
- R.drawable.tv_5a_25,
- R.drawable.tv_5a_26,
- R.drawable.tv_5a_27,
- R.drawable.tv_5a_28,
- R.drawable.tv_5a_29,
- R.drawable.tv_5a_30,
- R.drawable.tv_5a_31,
- R.drawable.tv_5a_32,
- R.drawable.tv_5a_33,
- R.drawable.tv_5a_34,
- R.drawable.tv_5a_35,
- R.drawable.tv_5a_36,
- R.drawable.tv_5a_37,
- R.drawable.tv_5a_38,
- R.drawable.tv_5a_39,
- R.drawable.tv_5a_40,
- R.drawable.tv_5a_41,
- R.drawable.tv_5a_42,
- R.drawable.tv_5a_43,
- R.drawable.tv_5a_44,
- R.drawable.tv_5a_45,
- R.drawable.tv_5a_46,
- R.drawable.tv_5a_47,
- R.drawable.tv_5a_48,
- R.drawable.tv_5a_49,
- R.drawable.tv_5a_50,
- R.drawable.tv_5a_51,
- R.drawable.tv_5a_52,
- R.drawable.tv_5a_53,
- R.drawable.tv_5a_54,
- R.drawable.tv_5a_55,
- R.drawable.tv_5a_56,
- R.drawable.tv_5a_57,
- R.drawable.tv_5a_58,
- R.drawable.tv_5a_59,
- R.drawable.tv_5a_60,
- R.drawable.tv_5a_61,
- R.drawable.tv_5a_62,
- R.drawable.tv_5a_63,
- R.drawable.tv_5a_64,
- R.drawable.tv_5a_65,
- R.drawable.tv_5a_66,
- R.drawable.tv_5a_67,
- R.drawable.tv_5a_68,
- R.drawable.tv_5a_69,
- R.drawable.tv_5a_70,
- R.drawable.tv_5a_71,
- R.drawable.tv_5a_72,
- R.drawable.tv_5a_73,
- R.drawable.tv_5a_74,
- R.drawable.tv_5a_75,
- R.drawable.tv_5a_76,
- R.drawable.tv_5a_77,
- R.drawable.tv_5a_78,
- R.drawable.tv_5a_79,
- R.drawable.tv_5a_80,
- R.drawable.tv_5a_81,
- R.drawable.tv_5a_82,
- R.drawable.tv_5a_83,
- R.drawable.tv_5a_84,
- R.drawable.tv_5a_85,
- R.drawable.tv_5a_86,
- R.drawable.tv_5a_87,
- R.drawable.tv_5a_88,
- R.drawable.tv_5a_89,
- R.drawable.tv_5a_90,
- R.drawable.tv_5a_91,
- R.drawable.tv_5a_92,
- R.drawable.tv_5a_93,
- R.drawable.tv_5a_94,
- R.drawable.tv_5a_95,
- R.drawable.tv_5a_96,
- R.drawable.tv_5a_97,
- R.drawable.tv_5a_98,
- R.drawable.tv_5a_99,
- R.drawable.tv_5a_100,
- R.drawable.tv_5a_101,
- R.drawable.tv_5a_102,
- R.drawable.tv_5a_103,
- R.drawable.tv_5a_104,
- R.drawable.tv_5a_105,
- R.drawable.tv_5a_106,
- R.drawable.tv_5a_107,
- R.drawable.tv_5a_108,
- R.drawable.tv_5a_109,
- R.drawable.tv_5a_110,
- R.drawable.tv_5a_111,
- R.drawable.tv_5a_112,
- R.drawable.tv_5a_113,
- R.drawable.tv_5a_114,
- R.drawable.tv_5a_115,
- R.drawable.tv_5a_116,
- R.drawable.tv_5a_117,
- R.drawable.tv_5a_118,
- R.drawable.tv_5a_119,
- R.drawable.tv_5a_120,
- R.drawable.tv_5a_121,
- R.drawable.tv_5a_122,
- R.drawable.tv_5a_123,
- R.drawable.tv_5a_124,
- R.drawable.tv_5a_125,
- R.drawable.tv_5a_126,
- R.drawable.tv_5a_127,
- R.drawable.tv_5a_128,
- R.drawable.tv_5a_129,
- R.drawable.tv_5a_130,
- R.drawable.tv_5a_131,
- R.drawable.tv_5a_132,
- R.drawable.tv_5a_133,
- R.drawable.tv_5a_134,
- R.drawable.tv_5a_135,
- R.drawable.tv_5a_136,
- R.drawable.tv_5a_137,
- R.drawable.tv_5a_138,
- R.drawable.tv_5a_139,
- R.drawable.tv_5a_140,
- R.drawable.tv_5a_141,
- R.drawable.tv_5a_142,
- R.drawable.tv_5a_143,
- R.drawable.tv_5a_144,
- R.drawable.tv_5a_145,
- R.drawable.tv_5a_146,
- R.drawable.tv_5a_147,
- R.drawable.tv_5a_148,
- R.drawable.tv_5a_149,
- R.drawable.tv_5a_150,
- R.drawable.tv_5a_151,
- R.drawable.tv_5a_152,
- R.drawable.tv_5a_153,
- R.drawable.tv_5a_154,
- R.drawable.tv_5a_155,
- R.drawable.tv_5a_156,
- R.drawable.tv_5a_157,
- R.drawable.tv_5a_158,
- R.drawable.tv_5a_159,
- R.drawable.tv_5a_160,
- R.drawable.tv_5a_161,
- R.drawable.tv_5a_162,
- R.drawable.tv_5a_163,
- R.drawable.tv_5a_164,
- R.drawable.tv_5a_165,
- R.drawable.tv_5a_166,
- R.drawable.tv_5a_167,
- R.drawable.tv_5a_168,
- R.drawable.tv_5a_169,
- R.drawable.tv_5a_170,
- R.drawable.tv_5a_171,
- R.drawable.tv_5a_172,
- R.drawable.tv_5a_173,
- R.drawable.tv_5a_174,
- R.drawable.tv_5a_175,
- R.drawable.tv_5a_176,
- R.drawable.tv_5a_177,
- R.drawable.tv_5a_178,
- R.drawable.tv_5a_179,
- R.drawable.tv_5a_180,
- R.drawable.tv_5a_181,
- R.drawable.tv_5a_182,
- R.drawable.tv_5a_183,
- R.drawable.tv_5a_184,
- R.drawable.tv_5a_185,
- R.drawable.tv_5a_186,
- R.drawable.tv_5a_187,
- R.drawable.tv_5a_188,
- R.drawable.tv_5a_189,
- R.drawable.tv_5a_190,
- R.drawable.tv_5a_191,
- R.drawable.tv_5a_192,
- R.drawable.tv_5a_193,
- R.drawable.tv_5a_194,
- R.drawable.tv_5a_195,
- R.drawable.tv_5a_196,
- R.drawable.tv_5a_197,
- R.drawable.tv_5a_198,
- R.drawable.tv_5a_199,
- R.drawable.tv_5a_200,
- R.drawable.tv_5a_201,
- R.drawable.tv_5a_202,
- R.drawable.tv_5a_203,
- R.drawable.tv_5a_204,
- R.drawable.tv_5a_205,
- R.drawable.tv_5a_206,
- R.drawable.tv_5a_207,
- R.drawable.tv_5a_208,
- R.drawable.tv_5a_209,
- R.drawable.tv_5a_210,
- R.drawable.tv_5a_211,
- R.drawable.tv_5a_212,
- R.drawable.tv_5a_213,
- R.drawable.tv_5a_214,
- R.drawable.tv_5a_215,
- R.drawable.tv_5a_216,
- R.drawable.tv_5a_217,
- R.drawable.tv_5a_218,
- R.drawable.tv_5a_219,
- R.drawable.tv_5a_220,
- R.drawable.tv_5a_221,
- R.drawable.tv_5a_222,
- R.drawable.tv_5a_223,
- R.drawable.tv_5a_224
+ R.drawable.tv_5a_0,
+ R.drawable.tv_5a_1,
+ R.drawable.tv_5a_2,
+ R.drawable.tv_5a_3,
+ R.drawable.tv_5a_4,
+ R.drawable.tv_5a_5,
+ R.drawable.tv_5a_6,
+ R.drawable.tv_5a_7,
+ R.drawable.tv_5a_8,
+ R.drawable.tv_5a_9,
+ R.drawable.tv_5a_10,
+ R.drawable.tv_5a_11,
+ R.drawable.tv_5a_12,
+ R.drawable.tv_5a_13,
+ R.drawable.tv_5a_14,
+ R.drawable.tv_5a_15,
+ R.drawable.tv_5a_16,
+ R.drawable.tv_5a_17,
+ R.drawable.tv_5a_18,
+ R.drawable.tv_5a_19,
+ R.drawable.tv_5a_20,
+ R.drawable.tv_5a_21,
+ R.drawable.tv_5a_22,
+ R.drawable.tv_5a_23,
+ R.drawable.tv_5a_24,
+ R.drawable.tv_5a_25,
+ R.drawable.tv_5a_26,
+ R.drawable.tv_5a_27,
+ R.drawable.tv_5a_28,
+ R.drawable.tv_5a_29,
+ R.drawable.tv_5a_30,
+ R.drawable.tv_5a_31,
+ R.drawable.tv_5a_32,
+ R.drawable.tv_5a_33,
+ R.drawable.tv_5a_34,
+ R.drawable.tv_5a_35,
+ R.drawable.tv_5a_36,
+ R.drawable.tv_5a_37,
+ R.drawable.tv_5a_38,
+ R.drawable.tv_5a_39,
+ R.drawable.tv_5a_40,
+ R.drawable.tv_5a_41,
+ R.drawable.tv_5a_42,
+ R.drawable.tv_5a_43,
+ R.drawable.tv_5a_44,
+ R.drawable.tv_5a_45,
+ R.drawable.tv_5a_46,
+ R.drawable.tv_5a_47,
+ R.drawable.tv_5a_48,
+ R.drawable.tv_5a_49,
+ R.drawable.tv_5a_50,
+ R.drawable.tv_5a_51,
+ R.drawable.tv_5a_52,
+ R.drawable.tv_5a_53,
+ R.drawable.tv_5a_54,
+ R.drawable.tv_5a_55,
+ R.drawable.tv_5a_56,
+ R.drawable.tv_5a_57,
+ R.drawable.tv_5a_58,
+ R.drawable.tv_5a_59,
+ R.drawable.tv_5a_60,
+ R.drawable.tv_5a_61,
+ R.drawable.tv_5a_62,
+ R.drawable.tv_5a_63,
+ R.drawable.tv_5a_64,
+ R.drawable.tv_5a_65,
+ R.drawable.tv_5a_66,
+ R.drawable.tv_5a_67,
+ R.drawable.tv_5a_68,
+ R.drawable.tv_5a_69,
+ R.drawable.tv_5a_70,
+ R.drawable.tv_5a_71,
+ R.drawable.tv_5a_72,
+ R.drawable.tv_5a_73,
+ R.drawable.tv_5a_74,
+ R.drawable.tv_5a_75,
+ R.drawable.tv_5a_76,
+ R.drawable.tv_5a_77,
+ R.drawable.tv_5a_78,
+ R.drawable.tv_5a_79,
+ R.drawable.tv_5a_80,
+ R.drawable.tv_5a_81,
+ R.drawable.tv_5a_82,
+ R.drawable.tv_5a_83,
+ R.drawable.tv_5a_84,
+ R.drawable.tv_5a_85,
+ R.drawable.tv_5a_86,
+ R.drawable.tv_5a_87,
+ R.drawable.tv_5a_88,
+ R.drawable.tv_5a_89,
+ R.drawable.tv_5a_90,
+ R.drawable.tv_5a_91,
+ R.drawable.tv_5a_92,
+ R.drawable.tv_5a_93,
+ R.drawable.tv_5a_94,
+ R.drawable.tv_5a_95,
+ R.drawable.tv_5a_96,
+ R.drawable.tv_5a_97,
+ R.drawable.tv_5a_98,
+ R.drawable.tv_5a_99,
+ R.drawable.tv_5a_100,
+ R.drawable.tv_5a_101,
+ R.drawable.tv_5a_102,
+ R.drawable.tv_5a_103,
+ R.drawable.tv_5a_104,
+ R.drawable.tv_5a_105,
+ R.drawable.tv_5a_106,
+ R.drawable.tv_5a_107,
+ R.drawable.tv_5a_108,
+ R.drawable.tv_5a_109,
+ R.drawable.tv_5a_110,
+ R.drawable.tv_5a_111,
+ R.drawable.tv_5a_112,
+ R.drawable.tv_5a_113,
+ R.drawable.tv_5a_114,
+ R.drawable.tv_5a_115,
+ R.drawable.tv_5a_116,
+ R.drawable.tv_5a_117,
+ R.drawable.tv_5a_118,
+ R.drawable.tv_5a_119,
+ R.drawable.tv_5a_120,
+ R.drawable.tv_5a_121,
+ R.drawable.tv_5a_122,
+ R.drawable.tv_5a_123,
+ R.drawable.tv_5a_124,
+ R.drawable.tv_5a_125,
+ R.drawable.tv_5a_126,
+ R.drawable.tv_5a_127,
+ R.drawable.tv_5a_128,
+ R.drawable.tv_5a_129,
+ R.drawable.tv_5a_130,
+ R.drawable.tv_5a_131,
+ R.drawable.tv_5a_132,
+ R.drawable.tv_5a_133,
+ R.drawable.tv_5a_134,
+ R.drawable.tv_5a_135,
+ R.drawable.tv_5a_136,
+ R.drawable.tv_5a_137,
+ R.drawable.tv_5a_138,
+ R.drawable.tv_5a_139,
+ R.drawable.tv_5a_140,
+ R.drawable.tv_5a_141,
+ R.drawable.tv_5a_142,
+ R.drawable.tv_5a_143,
+ R.drawable.tv_5a_144,
+ R.drawable.tv_5a_145,
+ R.drawable.tv_5a_146,
+ R.drawable.tv_5a_147,
+ R.drawable.tv_5a_148,
+ R.drawable.tv_5a_149,
+ R.drawable.tv_5a_150,
+ R.drawable.tv_5a_151,
+ R.drawable.tv_5a_152,
+ R.drawable.tv_5a_153,
+ R.drawable.tv_5a_154,
+ R.drawable.tv_5a_155,
+ R.drawable.tv_5a_156,
+ R.drawable.tv_5a_157,
+ R.drawable.tv_5a_158,
+ R.drawable.tv_5a_159,
+ R.drawable.tv_5a_160,
+ R.drawable.tv_5a_161,
+ R.drawable.tv_5a_162,
+ R.drawable.tv_5a_163,
+ R.drawable.tv_5a_164,
+ R.drawable.tv_5a_165,
+ R.drawable.tv_5a_166,
+ R.drawable.tv_5a_167,
+ R.drawable.tv_5a_168,
+ R.drawable.tv_5a_169,
+ R.drawable.tv_5a_170,
+ R.drawable.tv_5a_171,
+ R.drawable.tv_5a_172,
+ R.drawable.tv_5a_173,
+ R.drawable.tv_5a_174,
+ R.drawable.tv_5a_175,
+ R.drawable.tv_5a_176,
+ R.drawable.tv_5a_177,
+ R.drawable.tv_5a_178,
+ R.drawable.tv_5a_179,
+ R.drawable.tv_5a_180,
+ R.drawable.tv_5a_181,
+ R.drawable.tv_5a_182,
+ R.drawable.tv_5a_183,
+ R.drawable.tv_5a_184,
+ R.drawable.tv_5a_185,
+ R.drawable.tv_5a_186,
+ R.drawable.tv_5a_187,
+ R.drawable.tv_5a_188,
+ R.drawable.tv_5a_189,
+ R.drawable.tv_5a_190,
+ R.drawable.tv_5a_191,
+ R.drawable.tv_5a_192,
+ R.drawable.tv_5a_193,
+ R.drawable.tv_5a_194,
+ R.drawable.tv_5a_195,
+ R.drawable.tv_5a_196,
+ R.drawable.tv_5a_197,
+ R.drawable.tv_5a_198,
+ R.drawable.tv_5a_199,
+ R.drawable.tv_5a_200,
+ R.drawable.tv_5a_201,
+ R.drawable.tv_5a_202,
+ R.drawable.tv_5a_203,
+ R.drawable.tv_5a_204,
+ R.drawable.tv_5a_205,
+ R.drawable.tv_5a_206,
+ R.drawable.tv_5a_207,
+ R.drawable.tv_5a_208,
+ R.drawable.tv_5a_209,
+ R.drawable.tv_5a_210,
+ R.drawable.tv_5a_211,
+ R.drawable.tv_5a_212,
+ R.drawable.tv_5a_213,
+ R.drawable.tv_5a_214,
+ R.drawable.tv_5a_215,
+ R.drawable.tv_5a_216,
+ R.drawable.tv_5a_217,
+ R.drawable.tv_5a_218,
+ R.drawable.tv_5a_219,
+ R.drawable.tv_5a_220,
+ R.drawable.tv_5a_221,
+ R.drawable.tv_5a_222,
+ R.drawable.tv_5a_223,
+ R.drawable.tv_5a_224
};
private static final int[] TV_FRAMES_3_BLUE_ARROW = {
- R.drawable.arrow_blue_00,
- R.drawable.arrow_blue_01,
- R.drawable.arrow_blue_02,
- R.drawable.arrow_blue_03,
- R.drawable.arrow_blue_04,
- R.drawable.arrow_blue_05,
- R.drawable.arrow_blue_06,
- R.drawable.arrow_blue_07,
- R.drawable.arrow_blue_08,
- R.drawable.arrow_blue_09,
- R.drawable.arrow_blue_10,
- R.drawable.arrow_blue_11,
- R.drawable.arrow_blue_12,
- R.drawable.arrow_blue_13,
- R.drawable.arrow_blue_14,
- R.drawable.arrow_blue_15,
- R.drawable.arrow_blue_16,
- R.drawable.arrow_blue_17,
- R.drawable.arrow_blue_18,
- R.drawable.arrow_blue_19,
- R.drawable.arrow_blue_20,
- R.drawable.arrow_blue_21,
- R.drawable.arrow_blue_22,
- R.drawable.arrow_blue_23,
- R.drawable.arrow_blue_24,
- R.drawable.arrow_blue_25,
- R.drawable.arrow_blue_26,
- R.drawable.arrow_blue_27,
- R.drawable.arrow_blue_28,
- R.drawable.arrow_blue_29,
- R.drawable.arrow_blue_30,
- R.drawable.arrow_blue_31,
- R.drawable.arrow_blue_32,
- R.drawable.arrow_blue_33,
- R.drawable.arrow_blue_34,
- R.drawable.arrow_blue_35,
- R.drawable.arrow_blue_36,
- R.drawable.arrow_blue_37,
- R.drawable.arrow_blue_38,
- R.drawable.arrow_blue_39,
- R.drawable.arrow_blue_40,
- R.drawable.arrow_blue_41,
- R.drawable.arrow_blue_42,
- R.drawable.arrow_blue_43,
- R.drawable.arrow_blue_44,
- R.drawable.arrow_blue_45,
- R.drawable.arrow_blue_46,
- R.drawable.arrow_blue_47,
- R.drawable.arrow_blue_48,
- R.drawable.arrow_blue_49,
- R.drawable.arrow_blue_50,
- R.drawable.arrow_blue_51,
- R.drawable.arrow_blue_52,
- R.drawable.arrow_blue_53,
- R.drawable.arrow_blue_54,
- R.drawable.arrow_blue_55,
- R.drawable.arrow_blue_56,
- R.drawable.arrow_blue_57,
- R.drawable.arrow_blue_58,
- R.drawable.arrow_blue_59,
- R.drawable.arrow_blue_60
+ R.drawable.arrow_blue_00,
+ R.drawable.arrow_blue_01,
+ R.drawable.arrow_blue_02,
+ R.drawable.arrow_blue_03,
+ R.drawable.arrow_blue_04,
+ R.drawable.arrow_blue_05,
+ R.drawable.arrow_blue_06,
+ R.drawable.arrow_blue_07,
+ R.drawable.arrow_blue_08,
+ R.drawable.arrow_blue_09,
+ R.drawable.arrow_blue_10,
+ R.drawable.arrow_blue_11,
+ R.drawable.arrow_blue_12,
+ R.drawable.arrow_blue_13,
+ R.drawable.arrow_blue_14,
+ R.drawable.arrow_blue_15,
+ R.drawable.arrow_blue_16,
+ R.drawable.arrow_blue_17,
+ R.drawable.arrow_blue_18,
+ R.drawable.arrow_blue_19,
+ R.drawable.arrow_blue_20,
+ R.drawable.arrow_blue_21,
+ R.drawable.arrow_blue_22,
+ R.drawable.arrow_blue_23,
+ R.drawable.arrow_blue_24,
+ R.drawable.arrow_blue_25,
+ R.drawable.arrow_blue_26,
+ R.drawable.arrow_blue_27,
+ R.drawable.arrow_blue_28,
+ R.drawable.arrow_blue_29,
+ R.drawable.arrow_blue_30,
+ R.drawable.arrow_blue_31,
+ R.drawable.arrow_blue_32,
+ R.drawable.arrow_blue_33,
+ R.drawable.arrow_blue_34,
+ R.drawable.arrow_blue_35,
+ R.drawable.arrow_blue_36,
+ R.drawable.arrow_blue_37,
+ R.drawable.arrow_blue_38,
+ R.drawable.arrow_blue_39,
+ R.drawable.arrow_blue_40,
+ R.drawable.arrow_blue_41,
+ R.drawable.arrow_blue_42,
+ R.drawable.arrow_blue_43,
+ R.drawable.arrow_blue_44,
+ R.drawable.arrow_blue_45,
+ R.drawable.arrow_blue_46,
+ R.drawable.arrow_blue_47,
+ R.drawable.arrow_blue_48,
+ R.drawable.arrow_blue_49,
+ R.drawable.arrow_blue_50,
+ R.drawable.arrow_blue_51,
+ R.drawable.arrow_blue_52,
+ R.drawable.arrow_blue_53,
+ R.drawable.arrow_blue_54,
+ R.drawable.arrow_blue_55,
+ R.drawable.arrow_blue_56,
+ R.drawable.arrow_blue_57,
+ R.drawable.arrow_blue_58,
+ R.drawable.arrow_blue_59,
+ R.drawable.arrow_blue_60
};
private static final int[] TV_FRAMES_3_BLUE_START = {
- R.drawable.tv_2a_01,
- R.drawable.tv_2a_02,
- R.drawable.tv_2a_03,
- R.drawable.tv_2a_04,
- R.drawable.tv_2a_05,
- R.drawable.tv_2a_06,
- R.drawable.tv_2a_07,
- R.drawable.tv_2a_08,
- R.drawable.tv_2a_09,
- R.drawable.tv_2a_10,
- R.drawable.tv_2a_11,
- R.drawable.tv_2a_12,
- R.drawable.tv_2a_13,
- R.drawable.tv_2a_14,
- R.drawable.tv_2a_15,
- R.drawable.tv_2a_16,
- R.drawable.tv_2a_17,
- R.drawable.tv_2a_18,
- R.drawable.tv_2a_19
+ R.drawable.tv_2a_01,
+ R.drawable.tv_2a_02,
+ R.drawable.tv_2a_03,
+ R.drawable.tv_2a_04,
+ R.drawable.tv_2a_05,
+ R.drawable.tv_2a_06,
+ R.drawable.tv_2a_07,
+ R.drawable.tv_2a_08,
+ R.drawable.tv_2a_09,
+ R.drawable.tv_2a_10,
+ R.drawable.tv_2a_11,
+ R.drawable.tv_2a_12,
+ R.drawable.tv_2a_13,
+ R.drawable.tv_2a_14,
+ R.drawable.tv_2a_15,
+ R.drawable.tv_2a_16,
+ R.drawable.tv_2a_17,
+ R.drawable.tv_2a_18,
+ R.drawable.tv_2a_19
};
private static final int[] TV_FRAMES_3_BLUE_END = {
- R.drawable.tv_2b_01,
- R.drawable.tv_2b_02,
- R.drawable.tv_2b_03,
- R.drawable.tv_2b_04,
- R.drawable.tv_2b_05,
- R.drawable.tv_2b_06,
- R.drawable.tv_2b_07,
- R.drawable.tv_2b_08,
- R.drawable.tv_2b_09,
- R.drawable.tv_2b_10,
- R.drawable.tv_2b_11,
- R.drawable.tv_2b_12,
- R.drawable.tv_2b_13,
- R.drawable.tv_2b_14,
- R.drawable.tv_2b_15,
- R.drawable.tv_2b_16,
- R.drawable.tv_2b_17,
- R.drawable.tv_2b_18,
- R.drawable.tv_2b_19
+ R.drawable.tv_2b_01,
+ R.drawable.tv_2b_02,
+ R.drawable.tv_2b_03,
+ R.drawable.tv_2b_04,
+ R.drawable.tv_2b_05,
+ R.drawable.tv_2b_06,
+ R.drawable.tv_2b_07,
+ R.drawable.tv_2b_08,
+ R.drawable.tv_2b_09,
+ R.drawable.tv_2b_10,
+ R.drawable.tv_2b_11,
+ R.drawable.tv_2b_12,
+ R.drawable.tv_2b_13,
+ R.drawable.tv_2b_14,
+ R.drawable.tv_2b_15,
+ R.drawable.tv_2b_16,
+ R.drawable.tv_2b_17,
+ R.drawable.tv_2b_18,
+ R.drawable.tv_2b_19
};
private static final int[] TV_FRAMES_3_ORANGE_ARROW = {
- R.drawable.arrow_orange_180,
- R.drawable.arrow_orange_181,
- R.drawable.arrow_orange_182,
- R.drawable.arrow_orange_183,
- R.drawable.arrow_orange_184,
- R.drawable.arrow_orange_185,
- R.drawable.arrow_orange_186,
- R.drawable.arrow_orange_187,
- R.drawable.arrow_orange_188,
- R.drawable.arrow_orange_189,
- R.drawable.arrow_orange_190,
- R.drawable.arrow_orange_191,
- R.drawable.arrow_orange_192,
- R.drawable.arrow_orange_193,
- R.drawable.arrow_orange_194,
- R.drawable.arrow_orange_195,
- R.drawable.arrow_orange_196,
- R.drawable.arrow_orange_197,
- R.drawable.arrow_orange_198,
- R.drawable.arrow_orange_199,
- R.drawable.arrow_orange_200,
- R.drawable.arrow_orange_201,
- R.drawable.arrow_orange_202,
- R.drawable.arrow_orange_203,
- R.drawable.arrow_orange_204,
- R.drawable.arrow_orange_205,
- R.drawable.arrow_orange_206,
- R.drawable.arrow_orange_207,
- R.drawable.arrow_orange_208,
- R.drawable.arrow_orange_209,
- R.drawable.arrow_orange_210,
- R.drawable.arrow_orange_211,
- R.drawable.arrow_orange_212,
- R.drawable.arrow_orange_213,
- R.drawable.arrow_orange_214,
- R.drawable.arrow_orange_215,
- R.drawable.arrow_orange_216,
- R.drawable.arrow_orange_217,
- R.drawable.arrow_orange_218,
- R.drawable.arrow_orange_219,
- R.drawable.arrow_orange_220,
- R.drawable.arrow_orange_221,
- R.drawable.arrow_orange_222,
- R.drawable.arrow_orange_223,
- R.drawable.arrow_orange_224,
- R.drawable.arrow_orange_225,
- R.drawable.arrow_orange_226,
- R.drawable.arrow_orange_227,
- R.drawable.arrow_orange_228,
- R.drawable.arrow_orange_229,
- R.drawable.arrow_orange_230,
- R.drawable.arrow_orange_231,
- R.drawable.arrow_orange_232,
- R.drawable.arrow_orange_233,
- R.drawable.arrow_orange_234,
- R.drawable.arrow_orange_235,
- R.drawable.arrow_orange_236,
- R.drawable.arrow_orange_237,
- R.drawable.arrow_orange_238,
- R.drawable.arrow_orange_239,
- R.drawable.arrow_orange_240
+ R.drawable.arrow_orange_180,
+ R.drawable.arrow_orange_181,
+ R.drawable.arrow_orange_182,
+ R.drawable.arrow_orange_183,
+ R.drawable.arrow_orange_184,
+ R.drawable.arrow_orange_185,
+ R.drawable.arrow_orange_186,
+ R.drawable.arrow_orange_187,
+ R.drawable.arrow_orange_188,
+ R.drawable.arrow_orange_189,
+ R.drawable.arrow_orange_190,
+ R.drawable.arrow_orange_191,
+ R.drawable.arrow_orange_192,
+ R.drawable.arrow_orange_193,
+ R.drawable.arrow_orange_194,
+ R.drawable.arrow_orange_195,
+ R.drawable.arrow_orange_196,
+ R.drawable.arrow_orange_197,
+ R.drawable.arrow_orange_198,
+ R.drawable.arrow_orange_199,
+ R.drawable.arrow_orange_200,
+ R.drawable.arrow_orange_201,
+ R.drawable.arrow_orange_202,
+ R.drawable.arrow_orange_203,
+ R.drawable.arrow_orange_204,
+ R.drawable.arrow_orange_205,
+ R.drawable.arrow_orange_206,
+ R.drawable.arrow_orange_207,
+ R.drawable.arrow_orange_208,
+ R.drawable.arrow_orange_209,
+ R.drawable.arrow_orange_210,
+ R.drawable.arrow_orange_211,
+ R.drawable.arrow_orange_212,
+ R.drawable.arrow_orange_213,
+ R.drawable.arrow_orange_214,
+ R.drawable.arrow_orange_215,
+ R.drawable.arrow_orange_216,
+ R.drawable.arrow_orange_217,
+ R.drawable.arrow_orange_218,
+ R.drawable.arrow_orange_219,
+ R.drawable.arrow_orange_220,
+ R.drawable.arrow_orange_221,
+ R.drawable.arrow_orange_222,
+ R.drawable.arrow_orange_223,
+ R.drawable.arrow_orange_224,
+ R.drawable.arrow_orange_225,
+ R.drawable.arrow_orange_226,
+ R.drawable.arrow_orange_227,
+ R.drawable.arrow_orange_228,
+ R.drawable.arrow_orange_229,
+ R.drawable.arrow_orange_230,
+ R.drawable.arrow_orange_231,
+ R.drawable.arrow_orange_232,
+ R.drawable.arrow_orange_233,
+ R.drawable.arrow_orange_234,
+ R.drawable.arrow_orange_235,
+ R.drawable.arrow_orange_236,
+ R.drawable.arrow_orange_237,
+ R.drawable.arrow_orange_238,
+ R.drawable.arrow_orange_239,
+ R.drawable.arrow_orange_240
};
private static final int[] TV_FRAMES_3_ORANGE_START = {
- R.drawable.tv_2c_01,
- R.drawable.tv_2c_02,
- R.drawable.tv_2c_03,
- R.drawable.tv_2c_04,
- R.drawable.tv_2c_05,
- R.drawable.tv_2c_06,
- R.drawable.tv_2c_07,
- R.drawable.tv_2c_08,
- R.drawable.tv_2c_09,
- R.drawable.tv_2c_10,
- R.drawable.tv_2c_11,
- R.drawable.tv_2c_12,
- R.drawable.tv_2c_13,
- R.drawable.tv_2c_14,
- R.drawable.tv_2c_15,
- R.drawable.tv_2c_16
+ R.drawable.tv_2c_01,
+ R.drawable.tv_2c_02,
+ R.drawable.tv_2c_03,
+ R.drawable.tv_2c_04,
+ R.drawable.tv_2c_05,
+ R.drawable.tv_2c_06,
+ R.drawable.tv_2c_07,
+ R.drawable.tv_2c_08,
+ R.drawable.tv_2c_09,
+ R.drawable.tv_2c_10,
+ R.drawable.tv_2c_11,
+ R.drawable.tv_2c_12,
+ R.drawable.tv_2c_13,
+ R.drawable.tv_2c_14,
+ R.drawable.tv_2c_15,
+ R.drawable.tv_2c_16
};
private static final int[] TV_FRAMES_4_START = {
- R.drawable.tv_3a_01,
- R.drawable.tv_3a_02,
- R.drawable.tv_3a_03,
- R.drawable.tv_3a_04,
- R.drawable.tv_3a_05,
- R.drawable.tv_3a_06,
- R.drawable.tv_3a_07,
- R.drawable.tv_3a_08,
- R.drawable.tv_3a_09,
- R.drawable.tv_3a_10,
- R.drawable.tv_3a_11,
- R.drawable.tv_3a_12,
- R.drawable.tv_3a_13,
- R.drawable.tv_3a_14,
- R.drawable.tv_3a_15,
- R.drawable.tv_3a_16,
- R.drawable.tv_3a_17,
- R.drawable.tv_3b_75,
- R.drawable.tv_3b_76,
- R.drawable.tv_3b_77,
- R.drawable.tv_3b_78,
- R.drawable.tv_3b_79,
- R.drawable.tv_3b_80,
- R.drawable.tv_3b_81,
- R.drawable.tv_3b_82,
- R.drawable.tv_3b_83,
- R.drawable.tv_3b_84,
- R.drawable.tv_3b_85,
- R.drawable.tv_3b_86,
- R.drawable.tv_3b_87,
- R.drawable.tv_3b_88,
- R.drawable.tv_3b_89,
- R.drawable.tv_3b_90,
- R.drawable.tv_3b_91,
- R.drawable.tv_3b_92,
- R.drawable.tv_3b_93,
- R.drawable.tv_3b_94,
- R.drawable.tv_3b_95,
- R.drawable.tv_3b_96,
- R.drawable.tv_3b_97,
- R.drawable.tv_3b_98,
- R.drawable.tv_3b_99,
- R.drawable.tv_3b_100,
- R.drawable.tv_3b_101,
- R.drawable.tv_3b_102,
- R.drawable.tv_3b_103,
- R.drawable.tv_3b_104,
- R.drawable.tv_3b_105,
- R.drawable.tv_3b_106,
- R.drawable.tv_3b_107,
- R.drawable.tv_3b_108,
- R.drawable.tv_3b_109,
- R.drawable.tv_3b_110,
- R.drawable.tv_3b_111,
- R.drawable.tv_3b_112,
- R.drawable.tv_3b_113,
- R.drawable.tv_3b_114,
- R.drawable.tv_3b_115,
- R.drawable.tv_3b_116,
- R.drawable.tv_3b_117,
- R.drawable.tv_3b_118
+ R.drawable.tv_3a_01,
+ R.drawable.tv_3a_02,
+ R.drawable.tv_3a_03,
+ R.drawable.tv_3a_04,
+ R.drawable.tv_3a_05,
+ R.drawable.tv_3a_06,
+ R.drawable.tv_3a_07,
+ R.drawable.tv_3a_08,
+ R.drawable.tv_3a_09,
+ R.drawable.tv_3a_10,
+ R.drawable.tv_3a_11,
+ R.drawable.tv_3a_12,
+ R.drawable.tv_3a_13,
+ R.drawable.tv_3a_14,
+ R.drawable.tv_3a_15,
+ R.drawable.tv_3a_16,
+ R.drawable.tv_3a_17,
+ R.drawable.tv_3b_75,
+ R.drawable.tv_3b_76,
+ R.drawable.tv_3b_77,
+ R.drawable.tv_3b_78,
+ R.drawable.tv_3b_79,
+ R.drawable.tv_3b_80,
+ R.drawable.tv_3b_81,
+ R.drawable.tv_3b_82,
+ R.drawable.tv_3b_83,
+ R.drawable.tv_3b_84,
+ R.drawable.tv_3b_85,
+ R.drawable.tv_3b_86,
+ R.drawable.tv_3b_87,
+ R.drawable.tv_3b_88,
+ R.drawable.tv_3b_89,
+ R.drawable.tv_3b_90,
+ R.drawable.tv_3b_91,
+ R.drawable.tv_3b_92,
+ R.drawable.tv_3b_93,
+ R.drawable.tv_3b_94,
+ R.drawable.tv_3b_95,
+ R.drawable.tv_3b_96,
+ R.drawable.tv_3b_97,
+ R.drawable.tv_3b_98,
+ R.drawable.tv_3b_99,
+ R.drawable.tv_3b_100,
+ R.drawable.tv_3b_101,
+ R.drawable.tv_3b_102,
+ R.drawable.tv_3b_103,
+ R.drawable.tv_3b_104,
+ R.drawable.tv_3b_105,
+ R.drawable.tv_3b_106,
+ R.drawable.tv_3b_107,
+ R.drawable.tv_3b_108,
+ R.drawable.tv_3b_109,
+ R.drawable.tv_3b_110,
+ R.drawable.tv_3b_111,
+ R.drawable.tv_3b_112,
+ R.drawable.tv_3b_113,
+ R.drawable.tv_3b_114,
+ R.drawable.tv_3b_115,
+ R.drawable.tv_3b_116,
+ R.drawable.tv_3b_117,
+ R.drawable.tv_3b_118
};
private String[] mPageTitles;
@@ -581,13 +585,21 @@ public class WelcomeFragment extends OnboardingFragment {
private ImageView mTvContentView;
private ImageView mArrowView;
+ private TextView mTitleView;
+ private Button mStartButton;
+ private View mPagingIndicator;
+
private Animator mAnimator;
+ private boolean mLogoAnimationFinished;
+ private boolean mTitleChanged;
+
public WelcomeFragment() {
- setExitTransition(new SetupAnimationHelper.TransitionBuilder()
- .setSlideEdge(Gravity.START)
- .setParentIdsForDelay(new int[]{R.id.onboarding_fragment_root})
- .build());
+ setExitTransition(
+ new SetupAnimationHelper.TransitionBuilder()
+ .setSlideEdge(Gravity.START)
+ .setParentIdsForDelay(new int[] {R.id.onboarding_fragment_root})
+ .build());
}
@Override
@@ -605,11 +617,97 @@ public class WelcomeFragment extends OnboardingFragment {
@Nullable
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
setLogoResourceId(R.drawable.splash_logo);
- if (savedInstanceState != null) {
+ mTitleView = view.findViewById(android.support.v17.leanback.R.id.title);
+ mPagingIndicator = view.findViewById(android.support.v17.leanback.R.id.page_indicator);
+ mStartButton = view.findViewById(android.support.v17.leanback.R.id.button_start);
+
+ mStartButton.setAccessibilityDelegate(
+ new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityEvent(
+ View host, AccessibilityEvent event) {
+ int type = event.getEventType();
+ if (type == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ || type == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
+ if (!mTitleChanged || mTitleView.isAccessibilityFocused()) {
+ // Skip the event before the title is accessibility focused to avoid
+ // race
+ // conditions
+ return;
+ }
+ }
+ super.onInitializeAccessibilityEvent(host, event);
+ }
+ });
+
+ mPagingIndicator.setAccessibilityDelegate(
+ new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityEvent(
+ View host, AccessibilityEvent event) {
+ int type = event.getEventType();
+ if (type == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ || type == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
+ if (!mTitleChanged || mTitleView.isAccessibilityFocused()) {
+ // Skip the event before the title is accessibility focused to avoid
+ // race
+ // conditions
+ return;
+ }
+ }
+ super.onInitializeAccessibilityEvent(host, event);
+ }
+ });
+
+ mTitleView.setAccessibilityDelegate(
+ new AccessibilityDelegate() {
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
+ if (!mTitleChanged || mTitleView.isAccessibilityFocused()) {
+ // Skip the event before the title is accessibility focused to avoid
+ // race
+ // conditions
+ return false;
+ }
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
+
+ mTitleView.addTextChangedListener(
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ mTitleChanged = false;
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (!mTitleView.isAccessibilityFocused()) {
+ mTitleView.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ } else {
+ mTitleView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ }
+ mTitleChanged = true;
+ }
+ });
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (savedInstanceState != null && mLogoAnimationFinished) {
switch (getCurrentPageIndex()) {
case 0:
mTvContentView.setImageResource(
@@ -631,7 +729,6 @@ public class WelcomeFragment extends OnboardingFragment {
break;
}
}
- return view;
}
@Override
@@ -640,29 +737,38 @@ public class WelcomeFragment extends OnboardingFragment {
}
@Override
+ protected void onLogoAnimationFinished() {
+ super.onLogoAnimationFinished();
+ mLogoAnimationFinished = true;
+ }
+
+ @Override
protected Animator onCreateEnterAnimation() {
List<Animator> animators = new ArrayList<>();
// Cloud 1
View view = getActivity().findViewById(R.id.cloud1);
view.setAlpha(0);
- Animator animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.onboarding_welcome_cloud_enter);
+ Animator animator =
+ AnimatorInflater.loadAnimator(
+ getActivity(), R.animator.onboarding_welcome_cloud_enter);
animator.setStartDelay(START_DELAY_CLOUD_MS);
animator.setTarget(view);
animators.add(animator);
// Cloud 2
view = getActivity().findViewById(R.id.cloud2);
view.setAlpha(0);
- animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.onboarding_welcome_cloud_enter);
+ animator =
+ AnimatorInflater.loadAnimator(
+ getActivity(), R.animator.onboarding_welcome_cloud_enter);
animator.setStartDelay(START_DELAY_CLOUD_MS);
animator.setTarget(view);
animators.add(animator);
// TV container
view = getActivity().findViewById(R.id.tv_container);
view.setAlpha(0);
- animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.onboarding_welcome_tv_enter);
+ animator =
+ AnimatorInflater.loadAnimator(
+ getActivity(), R.animator.onboarding_welcome_tv_enter);
animator.setStartDelay(START_DELAY_TV_MS);
animator.setTarget(view);
animators.add(animator);
@@ -675,8 +781,9 @@ public class WelcomeFragment extends OnboardingFragment {
// Shadow
view = getActivity().findViewById(R.id.shadow);
view.setAlpha(0);
- animator = AnimatorInflater.loadAnimator(getActivity(),
- R.animator.onboarding_welcome_shadow_enter);
+ animator =
+ AnimatorInflater.loadAnimator(
+ getActivity(), R.animator.onboarding_welcome_shadow_enter);
animator.setStartDelay(START_DELAY_SHADOW_MS);
animator.setTarget(view);
animators.add(animator);
@@ -702,8 +809,9 @@ public class WelcomeFragment extends OnboardingFragment {
@Nullable
@Override
protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container) {
- mArrowView = (ImageView) inflater.inflate(R.layout.onboarding_welcome_foreground, container,
- false);
+ mArrowView =
+ (ImageView)
+ inflater.inflate(R.layout.onboarding_welcome_foreground, container, false);
return mArrowView;
}
@@ -732,65 +840,74 @@ public class WelcomeFragment extends OnboardingFragment {
if (mAnimator != null) {
mAnimator.cancel();
}
+ mTitleChanged = false;
mArrowView.setVisibility(View.GONE);
// TV screen hiding animator.
- Animator hideAnimator = previousPage == 0
- ? SetupAnimationHelper.createFrameAnimator(mTvContentView, TV_FRAMES_1_END)
- : SetupAnimationHelper.createFadeOutAnimator(mTvContentView,
- VIDEO_FADE_OUT_DURATION_MS, true);
+ Animator hideAnimator =
+ previousPage == 0
+ ? SetupAnimationHelper.createFrameAnimator(mTvContentView, TV_FRAMES_1_END)
+ : SetupAnimationHelper.createFadeOutAnimator(
+ mTvContentView, VIDEO_FADE_OUT_DURATION_MS, true);
// TV screen showing animator.
AnimatorSet animatorSet = new AnimatorSet();
int firstFrame;
switch (newPage) {
case 0:
- animatorSet.playSequentially(hideAnimator,
- SetupAnimationHelper.createFrameAnimator(mTvContentView,
- TV_FRAMES_1_START));
+ animatorSet.playSequentially(
+ hideAnimator,
+ SetupAnimationHelper.createFrameAnimator(
+ mTvContentView, TV_FRAMES_1_START));
firstFrame = TV_FRAMES_1_START[0];
break;
case 1:
- animatorSet.playSequentially(hideAnimator,
- SetupAnimationHelper.createFrameAnimator(mTvContentView,
- TV_FRAMES_2_START));
+ animatorSet.playSequentially(
+ hideAnimator,
+ SetupAnimationHelper.createFrameAnimator(
+ mTvContentView, TV_FRAMES_2_START));
firstFrame = TV_FRAMES_2_START[0];
break;
case 2:
mArrowView.setVisibility(View.VISIBLE);
- animatorSet.playSequentially(hideAnimator,
- SetupAnimationHelper.createFrameAnimator(mArrowView,
- TV_FRAMES_3_BLUE_ARROW),
- SetupAnimationHelper.createFrameAnimator(mTvContentView,
- TV_FRAMES_3_BLUE_START),
- SetupAnimationHelper.createFrameAnimatorWithDelay(mTvContentView,
- TV_FRAMES_3_BLUE_END, BLUE_SCREEN_HOLD_DURATION_MS),
- SetupAnimationHelper.createFrameAnimator(mArrowView,
- TV_FRAMES_3_ORANGE_ARROW),
- SetupAnimationHelper.createFrameAnimator(mTvContentView,
- TV_FRAMES_3_ORANGE_START));
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mArrowView.setImageResource(TV_FRAMES_3_BLUE_ARROW[0]);
- }
- });
+ animatorSet.playSequentially(
+ hideAnimator,
+ SetupAnimationHelper.createFrameAnimator(
+ mArrowView, TV_FRAMES_3_BLUE_ARROW),
+ SetupAnimationHelper.createFrameAnimator(
+ mTvContentView, TV_FRAMES_3_BLUE_START),
+ SetupAnimationHelper.createFrameAnimatorWithDelay(
+ mTvContentView, TV_FRAMES_3_BLUE_END, BLUE_SCREEN_HOLD_DURATION_MS),
+ SetupAnimationHelper.createFrameAnimator(
+ mArrowView, TV_FRAMES_3_ORANGE_ARROW),
+ SetupAnimationHelper.createFrameAnimator(
+ mTvContentView, TV_FRAMES_3_ORANGE_START));
+ animatorSet.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mArrowView.setImageResource(TV_FRAMES_3_BLUE_ARROW[0]);
+ }
+ });
firstFrame = TV_FRAMES_3_BLUE_START[0];
break;
case 3:
default:
- animatorSet.playSequentially(hideAnimator,
- SetupAnimationHelper.createFrameAnimator(mTvContentView,
- TV_FRAMES_4_START));
+ animatorSet.playSequentially(
+ hideAnimator,
+ SetupAnimationHelper.createFrameAnimator(
+ mTvContentView, TV_FRAMES_4_START));
firstFrame = TV_FRAMES_4_START[0];
break;
}
final int firstImageResource = firstFrame;
- hideAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Shows the first frame of show animation when the hide animator is canceled.
- mTvContentView.setImageResource(firstImageResource);
- }
- });
+ hideAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Shows the first frame of show animation when the hide animator is
+ // canceled.
+ mTvContentView.setImageResource(firstImageResource);
+ }
+ });
mAnimator = SetupAnimationHelper.applyAnimationTimeScale(animatorSet);
mAnimator.start();
}
diff --git a/src/com/android/tv/parental/ContentRatingLevelPolicy.java b/src/com/android/tv/parental/ContentRatingLevelPolicy.java
index 9bd15423..5b0a6cf5 100644
--- a/src/com/android/tv/parental/ContentRatingLevelPolicy.java
+++ b/src/com/android/tv/parental/ContentRatingLevelPolicy.java
@@ -17,12 +17,10 @@
package com.android.tv.parental;
import android.media.tv.TvContentRating;
-
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvSettings.ContentRatingLevel;
-
import java.util.HashSet;
import java.util.Set;
@@ -31,10 +29,11 @@ public class ContentRatingLevelPolicy {
private static final int AGE_THRESHOLD_FOR_LEVEL_MEDIUM = 12;
private static final int AGE_THRESHOLD_FOR_LEVEL_LOW = -1; // Highest age for each rating system
- private ContentRatingLevelPolicy() { }
+ private ContentRatingLevelPolicy() {}
public static Set<TvContentRating> getRatingsForLevel(
- ParentalControlSettings settings, ContentRatingsManager manager,
+ ParentalControlSettings settings,
+ ContentRatingsManager manager,
@ContentRatingLevel int level) {
if (level == TvSettings.CONTENT_RATING_LEVEL_NONE) {
return new HashSet<>();
@@ -64,14 +63,17 @@ public class ContentRatingLevelPolicy {
if (rating.getAgeHint() < ageLimit) {
continue;
}
- TvContentRating tvContentRating = TvContentRating.createRating(
- contentRatingSystem.getDomain(), contentRatingSystem.getName(),
- rating.getName());
+ TvContentRating tvContentRating =
+ TvContentRating.createRating(
+ contentRatingSystem.getDomain(),
+ contentRatingSystem.getName(),
+ rating.getName());
ratings.add(tvContentRating);
for (SubRating subRating : rating.getSubRatings()) {
- tvContentRating = TvContentRating.createRating(
- contentRatingSystem.getDomain(), contentRatingSystem.getName(),
- rating.getName(), subRating.getName());
+ tvContentRating =
+ TvContentRating.createRating(
+ contentRatingSystem.getDomain(), contentRatingSystem.getName(),
+ rating.getName(), subRating.getName());
ratings.add(tvContentRating);
}
}
diff --git a/src/com/android/tv/parental/ContentRatingSystem.java b/src/com/android/tv/parental/ContentRatingSystem.java
index 5672b793..600aaca1 100644
--- a/src/com/android/tv/parental/ContentRatingSystem.java
+++ b/src/com/android/tv/parental/ContentRatingSystem.java
@@ -20,9 +20,7 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.tv.TvContentRating;
import android.text.TextUtils;
-
import com.android.tv.R;
-
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -86,7 +84,7 @@ public class ContentRatingSystem {
return mDomain + DELIMITER + mName;
}
- public String getName(){
+ public String getName() {
return mName;
}
@@ -94,19 +92,19 @@ public class ContentRatingSystem {
return mDomain;
}
- public String getTitle(){
+ public String getTitle() {
return mTitle;
}
- public String getDescription(){
+ public String getDescription() {
return mDescription;
}
- public List<String> getCountries(){
+ public List<String> getCountries() {
return mCountries;
}
- public List<Rating> getRatings(){
+ public List<Rating> getRatings() {
return mRatings;
}
@@ -119,11 +117,11 @@ public class ContentRatingSystem {
return null;
}
- public List<SubRating> getSubRatings(){
+ public List<SubRating> getSubRatings() {
return mSubRatings;
}
- public List<Order> getOrders(){
+ public List<Order> getOrders() {
return mOrders;
}
@@ -139,9 +137,7 @@ public class ContentRatingSystem {
return mIsCustom;
}
- /**
- * Returns true if the ratings is owned by this content rating system.
- */
+ /** Returns true if the ratings is owned by this content rating system. */
public boolean ownsRating(TvContentRating rating) {
return mDomain.equals(rating.getDomain()) && mName.equals(rating.getRatingSystem());
}
@@ -161,9 +157,16 @@ public class ContentRatingSystem {
}
private ContentRatingSystem(
- String name, String domain, String title, String description, List<String> countries,
- String displayName, List<Rating> ratings, List<SubRating> subRatings,
- List<Order> orders, boolean isCustom) {
+ String name,
+ String domain,
+ String title,
+ String description,
+ List<String> countries,
+ String displayName,
+ List<Rating> ratings,
+ List<SubRating> subRatings,
+ List<Order> orders,
+ boolean isCustom) {
mName = name;
mDomain = domain;
mTitle = title;
@@ -298,8 +301,8 @@ public class ContentRatingSystem {
}
}
if (!used) {
- throw new IllegalArgumentException("Subrating " + subRating.getName() +
- " isn't used by any rating");
+ throw new IllegalArgumentException(
+ "Subrating " + subRating.getName() + " isn't used by any rating");
}
}
@@ -310,8 +313,17 @@ public class ContentRatingSystem {
}
}
- return new ContentRatingSystem(mName, mDomain, mTitle, mDescription, mCountries,
- displayName, ratings, subRatings, orders, mIsCustom);
+ return new ContentRatingSystem(
+ mName,
+ mDomain,
+ mTitle,
+ mDescription,
+ mCountries,
+ displayName,
+ ratings,
+ subRatings,
+ orders,
+ mIsCustom);
}
}
@@ -347,8 +359,13 @@ public class ContentRatingSystem {
return mSubRatings;
}
- private Rating(String name, String title, String description, Drawable icon,
- int contentAgeHint, List<SubRating> subRatings) {
+ private Rating(
+ String name,
+ String title,
+ String description,
+ Drawable icon,
+ int contentAgeHint,
+ List<SubRating> subRatings) {
mName = name;
mTitle = title;
mDescription = description;
@@ -365,8 +382,7 @@ public class ContentRatingSystem {
private int mContentAgeHint = -1;
private final List<String> mSubRatingNames = new ArrayList<>();
- public Builder() {
- }
+ public Builder() {}
public void setName(String name) {
mName = name;
@@ -400,8 +416,8 @@ public class ContentRatingSystem {
throw new IllegalArgumentException("Invalid subrating for rating " + mName);
}
if (mContentAgeHint < 0) {
- throw new IllegalArgumentException("Rating " + mName + " should define " +
- "non-negative contentAgeHint");
+ throw new IllegalArgumentException(
+ "Rating " + mName + " should define " + "non-negative contentAgeHint");
}
List<SubRating> subRatings = new ArrayList<>();
@@ -415,12 +431,11 @@ public class ContentRatingSystem {
}
}
if (!found) {
- throw new IllegalArgumentException("Unknown subrating name " + subRatingId +
- " in rating " + mName);
+ throw new IllegalArgumentException(
+ "Unknown subrating name " + subRatingId + " in rating " + mName);
}
}
- return new Rating(
- mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings);
+ return new Rating(mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings);
}
}
}
@@ -460,8 +475,7 @@ public class ContentRatingSystem {
private String mDescription;
private Drawable mIcon;
- public Builder() {
- }
+ public Builder() {}
public void setName(String name) {
mName = name;
@@ -500,8 +514,8 @@ public class ContentRatingSystem {
}
/**
- * Returns index of the rating in this order.
- * Returns -1 if this order doesn't contain the rating.
+ * Returns index of the rating in this order. Returns -1 if this order doesn't contain the
+ * rating.
*/
public int getRatingIndex(Rating rating) {
for (int i = 0; i < mRatingOrder.size(); i++) {
@@ -515,8 +529,7 @@ public class ContentRatingSystem {
public static class Builder {
private final List<String> mRatingNames = new ArrayList<>();
- public Builder() {
- }
+ public Builder() {}
private Order build(List<Rating> ratings) {
List<Rating> ratingOrder = new ArrayList<>();
@@ -531,8 +544,8 @@ public class ContentRatingSystem {
}
if (!found) {
- throw new IllegalArgumentException("Unknown rating " + ratingName +
- " in rating-order tag");
+ throw new IllegalArgumentException(
+ "Unknown rating " + ratingName + " in rating-order tag");
}
}
diff --git a/src/com/android/tv/parental/ContentRatingsManager.java b/src/com/android/tv/parental/ContentRatingsManager.java
index c3bb8c0f..32a1325b 100644
--- a/src/com/android/tv/parental/ContentRatingsManager.java
+++ b/src/com/android/tv/parental/ContentRatingsManager.java
@@ -19,14 +19,12 @@ package com.android.tv.parental;
import android.content.Context;
import android.media.tv.TvContentRating;
import android.media.tv.TvContentRatingSystemInfo;
-import android.media.tv.TvInputManager;
import android.support.annotation.Nullable;
import android.text.TextUtils;
-
import com.android.tv.R;
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
-
+import com.android.tv.util.TvInputManagerHelper;
import java.util.ArrayList;
import java.util.List;
@@ -34,19 +32,19 @@ public class ContentRatingsManager {
private final List<ContentRatingSystem> mContentRatingSystems = new ArrayList<>();
private final Context mContext;
+ private final TvInputManagerHelper.TvInputManagerInterface mTvInputManager;
- public ContentRatingsManager(Context context) {
+ public ContentRatingsManager(
+ Context context, TvInputManagerHelper.TvInputManagerInterface tvInputManager) {
mContext = context;
+ this.mTvInputManager = tvInputManager;
}
public void update() {
mContentRatingSystems.clear();
-
- TvInputManager manager =
- (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
ContentRatingsParser parser = new ContentRatingsParser(mContext);
- List<TvContentRatingSystemInfo> infos = manager.getTvContentRatingSystemList();
+ List<TvContentRatingSystemInfo> infos = mTvInputManager.getTvContentRatingSystemList();
for (TvContentRatingSystemInfo info : infos) {
List<ContentRatingSystem> list = parser.parse(info);
if (list != null) {
@@ -55,9 +53,7 @@ public class ContentRatingsManager {
}
}
- /**
- * Returns the content rating system with the give ID.
- */
+ /** Returns the content rating system with the give ID. */
@Nullable
public ContentRatingSystem getContentRatingSystem(String contentRatingSystemId) {
for (ContentRatingSystem ratingSystem : mContentRatingSystems) {
@@ -68,9 +64,7 @@ public class ContentRatingsManager {
return null;
}
- /**
- * Returns a new list of all content rating systems defined.
- */
+ /** Returns a new list of all content rating systems defined. */
public List<ContentRatingSystem> getContentRatingSystems() {
return new ArrayList<>(mContentRatingSystems);
}
@@ -118,8 +112,10 @@ public class ContentRatingsManager {
private List<SubRating> getSubRatings(Rating rating, TvContentRating canonicalRating) {
List<SubRating> subRatings = new ArrayList<>();
- if (rating == null || rating.getSubRatings() == null
- || canonicalRating == null || canonicalRating.getSubRatings() == null) {
+ if (rating == null
+ || rating.getSubRatings() == null
+ || canonicalRating == null
+ || canonicalRating.getSubRatings() == null) {
return subRatings;
}
for (String subRatingString : canonicalRating.getSubRatings()) {
diff --git a/src/com/android/tv/parental/ContentRatingsParser.java b/src/com/android/tv/parental/ContentRatingsParser.java
index d9f62473..14df88ea 100644
--- a/src/com/android/tv/parental/ContentRatingsParser.java
+++ b/src/com/android/tv/parental/ContentRatingsParser.java
@@ -24,18 +24,16 @@ import android.content.res.XmlResourceParser;
import android.media.tv.TvContentRatingSystemInfo;
import android.net.Uri;
import android.util.Log;
-
import com.android.tv.parental.ContentRatingSystem.Order;
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+/** Parses Content Ratings */
public class ContentRatingsParser {
private static final String TAG = "ContentRatingsParser";
private static final boolean DEBUG = false;
@@ -74,8 +72,8 @@ public class ContentRatingsParser {
try {
String packageName = uri.getAuthority();
int resId = (int) ContentUris.parseId(uri);
- try (XmlResourceParser parser = mContext.getPackageManager()
- .getXml(packageName, resId, null)) {
+ try (XmlResourceParser parser =
+ mContext.getPackageManager().getXml(packageName, resId, null)) {
if (parser == null) {
throw new IllegalArgumentException("Cannot get XML with URI " + uri);
}
@@ -90,8 +88,8 @@ public class ContentRatingsParser {
return ratingSystems;
}
- private List<ContentRatingSystem> parse(XmlResourceParser parser, String domain,
- boolean isCustom)
+ private List<ContentRatingSystem> parse(
+ XmlResourceParser parser, String domain, boolean isCustom)
throws XmlPullParserException, IOException {
try {
mResources = mContext.getPackageManager().getResourcesForApplication(domain);
@@ -112,7 +110,9 @@ public class ContentRatingsParser {
int eventType = parser.getEventType();
assertEquals(eventType, XmlPullParser.START_TAG, "Malformed XML: Not a valid XML file");
- assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITIONS,
+ assertEquals(
+ parser.getName(),
+ TAG_RATING_SYSTEM_DEFINITIONS,
"Malformed XML: Should start with tag " + TAG_RATING_SYSTEM_DEFINITIONS);
boolean hasVersionAttr = false;
@@ -124,8 +124,10 @@ public class ContentRatingsParser {
}
}
if (!hasVersionAttr) {
- throw new XmlPullParserException("Malformed XML: Should contains a version attribute"
- + " in " + TAG_RATING_SYSTEM_DEFINITIONS);
+ throw new XmlPullParserException(
+ "Malformed XML: Should contains a version attribute"
+ + " in "
+ + TAG_RATING_SYSTEM_DEFINITIONS);
}
List<ContentRatingSystem> ratingSystems = new ArrayList<>();
@@ -135,25 +137,29 @@ public class ContentRatingsParser {
if (TAG_RATING_SYSTEM_DEFINITION.equals(parser.getName())) {
ratingSystems.add(parseRatingSystemDefinition(parser, domain, isCustom));
} else {
- checkVersion("Malformed XML: Should contains " +
- TAG_RATING_SYSTEM_DEFINITION);
+ checkVersion(
+ "Malformed XML: Should contains " + TAG_RATING_SYSTEM_DEFINITION);
}
break;
case XmlPullParser.END_TAG:
if (TAG_RATING_SYSTEM_DEFINITIONS.equals(parser.getName())) {
eventType = parser.next();
- assertEquals(eventType, XmlPullParser.END_DOCUMENT,
- "Malformed XML: Should end with tag " +
- TAG_RATING_SYSTEM_DEFINITIONS);
+ assertEquals(
+ eventType,
+ XmlPullParser.END_DOCUMENT,
+ "Malformed XML: Should end with tag "
+ + TAG_RATING_SYSTEM_DEFINITIONS);
return ratingSystems;
} else {
- checkVersion("Malformed XML: Should end with tag " +
- TAG_RATING_SYSTEM_DEFINITIONS);
+ checkVersion(
+ "Malformed XML: Should end with tag "
+ + TAG_RATING_SYSTEM_DEFINITIONS);
}
}
}
- throw new XmlPullParserException(TAG_RATING_SYSTEM_DEFINITIONS +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_RATING_SYSTEM_DEFINITIONS
+ + " section is incomplete or section ending tag is missing");
}
private static void assertEquals(int a, int b, String msg) throws XmlPullParserException {
@@ -174,8 +180,9 @@ public class ContentRatingsParser {
}
}
- private ContentRatingSystem parseRatingSystemDefinition(XmlResourceParser parser, String domain,
- boolean isCustom) throws XmlPullParserException, IOException {
+ private ContentRatingSystem parseRatingSystemDefinition(
+ XmlResourceParser parser, String domain, boolean isCustom)
+ throws XmlPullParserException, IOException {
ContentRatingSystem.Builder builder = new ContentRatingSystem.Builder(mContext);
builder.setDomain(domain);
@@ -198,13 +205,17 @@ public class ContentRatingsParser {
mResources.getString(parser.getAttributeResourceValue(i, 0)));
break;
default:
- checkVersion("Malformed XML: Unknown attribute " + attr + " in " +
- TAG_RATING_SYSTEM_DEFINITION);
+ checkVersion(
+ "Malformed XML: Unknown attribute "
+ + attr
+ + " in "
+ + TAG_RATING_SYSTEM_DEFINITION);
}
}
while (parser.next() != XmlPullParser.END_DOCUMENT) {
- switch (parser.getEventType()) {
+ int eventType = parser.getEventType();
+ switch (eventType) {
case XmlPullParser.START_TAG:
String tag = parser.getName();
switch (tag) {
@@ -218,8 +229,11 @@ public class ContentRatingsParser {
builder.addOrderBuilder(parseOrder(parser));
break;
default:
- checkVersion("Malformed XML: Unknown tag " + tag + " in " +
- TAG_RATING_SYSTEM_DEFINITION);
+ checkVersion(
+ "Malformed XML: Unknown tag "
+ + tag
+ + " in "
+ + TAG_RATING_SYSTEM_DEFINITION);
}
break;
case XmlPullParser.END_TAG:
@@ -227,13 +241,21 @@ public class ContentRatingsParser {
builder.setIsCustom(isCustom);
return builder.build();
} else {
- checkVersion("Malformed XML: Tag mismatch for " +
- TAG_RATING_SYSTEM_DEFINITION);
+ checkVersion(
+ "Malformed XML: Tag mismatch for " + TAG_RATING_SYSTEM_DEFINITION);
}
+ break;
+ default:
+ checkVersion(
+ "Malformed XML: Unknown event type "
+ + eventType
+ + " in "
+ + TAG_RATING_SYSTEM_DEFINITION);
}
}
- throw new XmlPullParserException(TAG_RATING_SYSTEM_DEFINITION +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_RATING_SYSTEM_DEFINITION
+ + " section is incomplete or section ending tag is missing");
}
private Rating.Builder parseRatingDefinition(XmlResourceParser parser)
@@ -265,14 +287,19 @@ public class ContentRatingsParser {
}
if (contentAgeHint < 0) {
- throw new XmlPullParserException("Malformed XML: " + ATTR_CONTENT_AGE_HINT +
- " should be a non-negative number");
+ throw new XmlPullParserException(
+ "Malformed XML: "
+ + ATTR_CONTENT_AGE_HINT
+ + " should be a non-negative number");
}
builder.setContentAgeHint(contentAgeHint);
break;
default:
- checkVersion("Malformed XML: Unknown attribute " + attr + " in " +
- TAG_RATING_DEFINITION);
+ checkVersion(
+ "Malformed XML: Unknown attribute "
+ + attr
+ + " in "
+ + TAG_RATING_DEFINITION);
}
}
@@ -282,8 +309,11 @@ public class ContentRatingsParser {
if (TAG_SUB_RATING.equals(parser.getName())) {
builder = parseSubRating(parser, builder);
} else {
- checkVersion(("Malformed XML: Only " + TAG_SUB_RATING + " is allowed in " +
- TAG_RATING_DEFINITION));
+ checkVersion(
+ ("Malformed XML: Only "
+ + TAG_SUB_RATING
+ + " is allowed in "
+ + TAG_RATING_DEFINITION));
}
break;
case XmlPullParser.END_TAG:
@@ -294,8 +324,8 @@ public class ContentRatingsParser {
}
}
}
- throw new XmlPullParserException(TAG_RATING_DEFINITION +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_RATING_DEFINITION + " section is incomplete or section ending tag is missing");
}
private SubRating.Builder parseSubRatingDefinition(XmlResourceParser parser)
@@ -320,8 +350,11 @@ public class ContentRatingsParser {
mResources.getDrawable(parser.getAttributeResourceValue(i, 0), null));
break;
default:
- checkVersion("Malformed XML: Unknown attribute " + attr + " in " +
- TAG_SUB_RATING_DEFINITION);
+ checkVersion(
+ "Malformed XML: Unknown attribute "
+ + attr
+ + " in "
+ + TAG_SUB_RATING_DEFINITION);
}
}
@@ -331,23 +364,26 @@ public class ContentRatingsParser {
if (TAG_SUB_RATING_DEFINITION.equals(parser.getName())) {
return builder;
} else {
- checkVersion("Malformed XML: " + TAG_SUB_RATING_DEFINITION +
- " isn't closed");
+ checkVersion(
+ "Malformed XML: " + TAG_SUB_RATING_DEFINITION + " isn't closed");
}
break;
default:
checkVersion("Malformed XML: " + TAG_SUB_RATING_DEFINITION + " has child");
}
}
- throw new XmlPullParserException(TAG_SUB_RATING_DEFINITION +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_SUB_RATING_DEFINITION
+ + " section is incomplete or section ending tag is missing");
}
private Order.Builder parseOrder(XmlResourceParser parser)
throws XmlPullParserException, IOException {
Order.Builder builder = new Order.Builder();
- assertEquals(parser.getAttributeCount(), 0,
+ assertEquals(
+ parser.getAttributeCount(),
+ 0,
"Malformed XML: Attribute isn't allowed in " + TAG_RATING_ORDER);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
@@ -355,19 +391,24 @@ public class ContentRatingsParser {
case XmlPullParser.START_TAG:
if (TAG_RATING.equals(parser.getName())) {
builder = parseRating(parser, builder);
- } else {
- checkVersion("Malformed XML: Only " + TAG_RATING + " is allowed in " +
- TAG_RATING_ORDER);
+ } else {
+ checkVersion(
+ "Malformed XML: Only "
+ + TAG_RATING
+ + " is allowed in "
+ + TAG_RATING_ORDER);
}
break;
case XmlPullParser.END_TAG:
- assertEquals(parser.getName(), TAG_RATING_ORDER,
+ assertEquals(
+ parser.getName(),
+ TAG_RATING_ORDER,
"Malformed XML: Tag mismatch for " + TAG_RATING_ORDER);
return builder;
}
}
- throw new XmlPullParserException(TAG_RATING_ORDER +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_RATING_ORDER + " section is incomplete or section ending tag is missing");
}
private Order.Builder parseRating(XmlResourceParser parser, Order.Builder builder)
@@ -379,8 +420,11 @@ public class ContentRatingsParser {
builder.addRatingName(parser.getAttributeValue(i));
break;
default:
- checkVersion("Malformed XML: " + TAG_RATING_ORDER + " should only contain "
- + ATTR_NAME);
+ checkVersion(
+ "Malformed XML: "
+ + TAG_RATING_ORDER
+ + " should only contain "
+ + ATTR_NAME);
}
}
@@ -393,8 +437,8 @@ public class ContentRatingsParser {
}
}
}
- throw new XmlPullParserException(TAG_RATING +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_RATING + " section is incomplete or section ending tag is missing");
}
private Rating.Builder parseSubRating(XmlResourceParser parser, Rating.Builder builder)
@@ -406,8 +450,11 @@ public class ContentRatingsParser {
builder.addSubRatingName(parser.getAttributeValue(i));
break;
default:
- checkVersion("Malformed XML: " + TAG_SUB_RATING + " should only contain " +
- ATTR_NAME);
+ checkVersion(
+ "Malformed XML: "
+ + TAG_SUB_RATING
+ + " should only contain "
+ + ATTR_NAME);
}
}
@@ -420,8 +467,8 @@ public class ContentRatingsParser {
}
}
}
- throw new XmlPullParserException(TAG_SUB_RATING +
- " section is incomplete or section ending tag is missing");
+ throw new XmlPullParserException(
+ TAG_SUB_RATING + " section is incomplete or section ending tag is missing");
}
// Title might be a resource id or a string value. Try loading as an id first, then use the
diff --git a/src/com/android/tv/parental/ParentalControlSettings.java b/src/com/android/tv/parental/ParentalControlSettings.java
index 2471c565..db1f0a4d 100644
--- a/src/com/android/tv/parental/ParentalControlSettings.java
+++ b/src/com/android/tv/parental/ParentalControlSettings.java
@@ -19,30 +19,22 @@ package com.android.tv.parental;
import android.content.Context;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputManager;
-
-import com.android.tv.experiments.Experiments;
+import com.android.tv.common.experiments.Experiments;
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvSettings.ContentRatingLevel;
-
import java.util.HashSet;
import java.util.Set;
public class ParentalControlSettings {
- /**
- * The rating and all of its sub-ratings are blocked.
- */
+ /** The rating and all of its sub-ratings are blocked. */
public static final int RATING_BLOCKED = 0;
- /**
- * The rating is blocked but not all of its sub-ratings are blocked.
- */
+ /** The rating is blocked but not all of its sub-ratings are blocked. */
public static final int RATING_BLOCKED_PARTIAL = 1;
- /**
- * The rating is not blocked.
- */
+ /** The rating is not blocked. */
public static final int RATING_NOT_BLOCKED = 2;
private final Context mContext;
@@ -65,8 +57,10 @@ public class ParentalControlSettings {
mTvInputManager.setParentalControlsEnabled(enabled);
}
- public void setContentRatingSystemEnabled(ContentRatingsManager manager,
- ContentRatingSystem contentRatingSystem, boolean enabled) {
+ public void setContentRatingSystemEnabled(
+ ContentRatingsManager manager,
+ ContentRatingSystem contentRatingSystem,
+ boolean enabled) {
if (enabled) {
TvSettings.addContentRatingSystem(mContext, contentRatingSystem.getId());
@@ -118,8 +112,8 @@ public class ParentalControlSettings {
}
}
- public void setContentRatingLevel(ContentRatingsManager manager,
- @ContentRatingLevel int level) {
+ public void setContentRatingLevel(
+ ContentRatingsManager manager, @ContentRatingLevel int level) {
@ContentRatingLevel int currentLevel = getContentRatingLevel();
if (level == currentLevel) {
return;
@@ -167,18 +161,17 @@ public class ParentalControlSettings {
/**
* Sets the blocked status of a given content rating.
- * <p>
- * Note that a call to this method automatically changes the current rating level to
- * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
- * </p>
+ *
+ * <p>Note that a call to this method automatically changes the current rating level to {@code
+ * TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
*
* @param contentRatingSystem The content rating system where the given rating belongs.
* @param rating The content rating to set.
* @return {@code true} if changed, {@code false} otherwise.
* @see #setSubRatingBlocked
*/
- public boolean setRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating,
- boolean blocked) {
+ public boolean setRatingBlocked(
+ ContentRatingSystem contentRatingSystem, Rating rating, boolean blocked) {
return setRatingBlockedInternal(contentRatingSystem, rating, null, blocked);
}
@@ -225,10 +218,9 @@ public class ParentalControlSettings {
/**
* Sets the blocked status of a given content sub-rating.
- * <p>
- * Note that a call to this method automatically changes the current rating level to
- * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
- * </p>
+ *
+ * <p>Note that a call to this method automatically changes the current rating level to {@code
+ * TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
*
* @param contentRatingSystem The content rating system where the given rating belongs.
* @param rating The content rating associated with the given sub-rating.
@@ -236,8 +228,11 @@ public class ParentalControlSettings {
* @return {@code true} if changed, {@code false} otherwise.
* @see #setRatingBlocked
*/
- public boolean setSubRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating,
- SubRating subRating, boolean blocked) {
+ public boolean setSubRatingBlocked(
+ ContentRatingSystem contentRatingSystem,
+ Rating rating,
+ SubRating subRating,
+ boolean blocked) {
return setRatingBlockedInternal(contentRatingSystem, rating, subRating, blocked);
}
@@ -249,16 +244,20 @@ public class ParentalControlSettings {
* @param subRating The content sub-rating to check.
* @return {@code true} if blocked, {@code false} otherwise.
*/
- public boolean isSubRatingEnabled(ContentRatingSystem contentRatingSystem, Rating rating,
- SubRating subRating) {
+ public boolean isSubRatingEnabled(
+ ContentRatingSystem contentRatingSystem, Rating rating, SubRating subRating) {
return mRatings.contains(toTvContentRating(contentRatingSystem, rating, subRating));
}
- private boolean setRatingBlockedInternal(ContentRatingSystem contentRatingSystem, Rating rating,
- SubRating subRating, boolean blocked) {
- TvContentRating tvContentRating = (subRating == null)
- ? toTvContentRating(contentRatingSystem, rating)
- : toTvContentRating(contentRatingSystem, rating, subRating);
+ private boolean setRatingBlockedInternal(
+ ContentRatingSystem contentRatingSystem,
+ Rating rating,
+ SubRating subRating,
+ boolean blocked) {
+ TvContentRating tvContentRating =
+ (subRating == null)
+ ? toTvContentRating(contentRatingSystem, rating)
+ : toTvContentRating(contentRatingSystem, rating, subRating);
boolean changed;
if (blocked) {
changed = mRatings.add(tvContentRating);
@@ -280,8 +279,8 @@ public class ParentalControlSettings {
}
/**
- * Returns the blocked status of a given rating. The status can be one of the followings:
- * {@link #RATING_BLOCKED}, {@link #RATING_BLOCKED_PARTIAL} and {@link #RATING_NOT_BLOCKED}
+ * Returns the blocked status of a given rating. The status can be one of the followings: {@link
+ * #RATING_BLOCKED}, {@link #RATING_BLOCKED_PARTIAL} and {@link #RATING_NOT_BLOCKED}
*/
public int getBlockedStatus(ContentRatingSystem contentRatingSystem, Rating rating) {
if (isRatingBlocked(contentRatingSystem, rating)) {
@@ -295,15 +294,18 @@ public class ParentalControlSettings {
return RATING_NOT_BLOCKED;
}
- private TvContentRating toTvContentRating(ContentRatingSystem contentRatingSystem,
- Rating rating) {
- return TvContentRating.createRating(contentRatingSystem.getDomain(),
- contentRatingSystem.getName(), rating.getName());
+ private TvContentRating toTvContentRating(
+ ContentRatingSystem contentRatingSystem, Rating rating) {
+ return TvContentRating.createRating(
+ contentRatingSystem.getDomain(), contentRatingSystem.getName(), rating.getName());
}
- private TvContentRating toTvContentRating(ContentRatingSystem contentRatingSystem,
- Rating rating, SubRating subRating) {
- return TvContentRating.createRating(contentRatingSystem.getDomain(),
- contentRatingSystem.getName(), rating.getName(), subRating.getName());
+ private TvContentRating toTvContentRating(
+ ContentRatingSystem contentRatingSystem, Rating rating, SubRating subRating) {
+ return TvContentRating.createRating(
+ contentRatingSystem.getDomain(),
+ contentRatingSystem.getName(),
+ rating.getName(),
+ subRating.getName());
}
}
diff --git a/src/com/android/tv/perf/EventNames.java b/src/com/android/tv/perf/EventNames.java
index 6026897b..54745f3b 100644
--- a/src/com/android/tv/perf/EventNames.java
+++ b/src/com/android/tv/perf/EventNames.java
@@ -16,12 +16,11 @@
package com.android.tv.perf;
-import android.support.annotation.StringDef;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.support.annotation.StringDef;
import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
/**
* Constants for performance event names.
*
@@ -47,8 +46,8 @@ public final class EventNames {
public static final String MAIN_ACTIVITY_ONSTART = "MainActivity.onStart";
public static final String MAIN_ACTIVITY_ONRESUME = "MainActivity.onResume";
/**
- * Event name for query running time of on-device search in
- * {@link com.android.tv.search.LocalSearchProvider}.
+ * Event name for query running time of on-device search in {@link
+ * com.android.tv.search.LocalSearchProvider}.
*/
public static final String ON_DEVICE_SEARCH = "OnDeviceSearch";
diff --git a/src/com/android/tv/perf/PerformanceMonitor.java b/src/com/android/tv/perf/PerformanceMonitor.java
index 40368b41..111aa851 100644
--- a/src/com/android/tv/perf/PerformanceMonitor.java
+++ b/src/com/android/tv/perf/PerformanceMonitor.java
@@ -16,10 +16,10 @@
package com.android.tv.perf;
-import android.content.Context;
-
import static com.android.tv.perf.EventNames.EventName;
+import android.content.Context;
+
/** Measures Performance. */
public interface PerformanceMonitor {
@@ -62,7 +62,6 @@ public interface PerformanceMonitor {
*/
TimerEvent startTimer();
-
/**
* Stops timer for a specific event and records the timer duration. passing a null TimerEvent
* will cause this operation to be skipped.
diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/AbstractGlobalKeyReceiver.java
index cc8e76c4..f88bd8a8 100644
--- a/src/com/android/tv/receiver/GlobalKeyReceiver.java
+++ b/src/com/android/tv/receiver/AbstractGlobalKeyReceiver.java
@@ -23,15 +23,14 @@ import android.os.AsyncTask;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
-
+import com.android.tv.Starter;
import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
-/**
- * Handles global keys.
- */
-public class GlobalKeyReceiver extends BroadcastReceiver {
+/** Handles global keys. */
+public abstract class AbstractGlobalKeyReceiver extends BroadcastReceiver {
private static final boolean DEBUG = false;
- private static final String TAG = "GlobalKeyReceiver";
+ private static final String TAG = "AbstractGlobalKeyReceiver";
private static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
// Settings.Secure.USER_SETUP_COMPLETE is hidden.
@@ -42,11 +41,11 @@ public class GlobalKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) {
+ if (!TvSingletons.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) {
Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
return;
}
- TvApplication.setCurrentRunningProcess(context, true);
+ Starter.start(context);
Context appContext = context.getApplicationContext();
if (DEBUG) Log.d(TAG, "onReceive: " + intent);
if (sUserSetupComplete) {
@@ -55,8 +54,11 @@ public class GlobalKeyReceiver extends BroadcastReceiver {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
- return Settings.Secure.getInt(appContext.getContentResolver(),
- SETTINGS_USER_SETUP_COMPLETE, 0) != 0;
+ return Settings.Secure.getInt(
+ appContext.getContentResolver(),
+ SETTINGS_USER_SETUP_COMPLETE,
+ 0)
+ != 0;
}
@Override
@@ -82,6 +84,9 @@ public class GlobalKeyReceiver extends BroadcastReceiver {
// Workaround for b/23947504, the same key event may be sent twice, filter it.
sLastEventTime = eventTime;
switch (keyCode) {
+ case KeyEvent.KEYCODE_DVR:
+ ((TvApplication) appContext).handleDvrKey();
+ break;
case KeyEvent.KEYCODE_GUIDE:
((TvApplication) appContext).handleGuideKey();
break;
diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
index 313b2dfa..3fb66245 100644
--- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -24,17 +24,15 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.Tracker;
-import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.common.util.SharedPreferencesUtils;
/**
* Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities to Google
- * Analytics and listeners. Call {@link #register} to start receiving notifications, and
- * {@link #unregister} to stop.
+ * Analytics and listeners. Call {@link #register} to start receiving notifications, and {@link
+ * #unregister} to stop.
*/
public final class AudioCapabilitiesReceiver {
private static final String SETTINGS_KEY_AC3_PASSTHRU_REPORTED = "ac3_passthrough_reported";
@@ -50,8 +48,7 @@ public final class AudioCapabilitiesReceiver {
private final Context mContext;
private final Analytics mAnalytics;
private final Tracker mTracker;
- @Nullable
- private final OnAc3PassthroughCapabilityChangeListener mListener;
+ @Nullable private final OnAc3PassthroughCapabilityChangeListener mListener;
private final BroadcastReceiver mReceiver = new HdmiAudioPlugBroadcastReceiver();
/**
@@ -60,12 +57,12 @@ public final class AudioCapabilitiesReceiver {
* @param context context for registering to receive broadcasts
* @param listener listener which receives AC3 passthrough capability change notification
*/
- public AudioCapabilitiesReceiver(@NonNull Context context,
- @Nullable OnAc3PassthroughCapabilityChangeListener listener) {
+ public AudioCapabilitiesReceiver(
+ @NonNull Context context, @Nullable OnAc3PassthroughCapabilityChangeListener listener) {
mContext = context;
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mAnalytics = appSingletons.getAnalytics();
- mTracker = appSingletons.getTracker();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mAnalytics = tvSingletons.getAnalytics();
+ mTracker = tvSingletons.getTracker();
mListener = listener;
}
@@ -121,8 +118,8 @@ public final class AudioCapabilitiesReceiver {
}
private SharedPreferences getSharedPreferences() {
- return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES,
- Context.MODE_PRIVATE);
+ return mContext.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE);
}
private boolean getBoolean(String key, boolean def) {
@@ -141,13 +138,9 @@ public final class AudioCapabilitiesReceiver {
getSharedPreferences().edit().putInt(key, val).apply();
}
- /**
- * Listener notified when AC3 passthrough capability changes.
- */
+ /** Listener notified when AC3 passthrough capability changes. */
public interface OnAc3PassthroughCapabilityChangeListener {
- /**
- * Called when the AC3 passthrough capability changes.
- */
+ /** Called when the AC3 passthrough capability changes. */
void onAc3PassthroughCapabilityChange(boolean capability);
}
}
diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java
index 369e7d54..d8528bb5 100644
--- a/src/com/android/tv/receiver/BootCompletedReceiver.java
+++ b/src/com/android/tv/receiver/BootCompletedReceiver.java
@@ -23,10 +23,10 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
-
-import com.android.tv.Features;
+import com.android.tv.Starter;
import com.android.tv.TvActivity;
-import com.android.tv.TvApplication;
+import com.android.tv.TvFeatures;
+import com.android.tv.TvSingletons;
import com.android.tv.dvr.recorder.DvrRecordingService;
import com.android.tv.dvr.recorder.RecordingScheduler;
import com.android.tv.recommendation.ChannelPreviewUpdater;
@@ -38,11 +38,12 @@ import com.android.tv.util.SetupUtils;
* Boot completed receiver for TV app.
*
* <p>It's used to
+ *
* <ul>
- * <li>start the {@link NotificationService} for recommendation</li>
- * <li>grant permission to the TIS's </li>
- * <li>enable {@link TvActivity} if necessary</li>
- * <li>start the {@link DvrRecordingService} </li>
+ * <li>start the {@link NotificationService} for recommendation
+ * <li>grant permission to the TIS's
+ * <li>enable {@link TvActivity} if necessary
+ * <li>start the {@link DvrRecordingService}
* </ul>
*/
public class BootCompletedReceiver extends BroadcastReceiver {
@@ -51,12 +52,12 @@ public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) {
+ if (!TvSingletons.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) {
Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
return;
}
if (DEBUG) Log.d(TAG, "boot completed " + intent);
- TvApplication.setCurrentRunningProcess(context, true);
+ Starter.start(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ChannelPreviewUpdater.getInstance(context).updatePreviewDataForChannelsImmediately();
@@ -69,7 +70,7 @@ public class BootCompletedReceiver extends BroadcastReceiver {
// Grant permission to already set up packages after the system has finished booting.
SetupUtils.grantEpgPermissionToSetUpPackages(context);
- if (Features.UNHIDE.isEnabled(context)) {
+ if (TvFeatures.UNHIDE.isEnabled(context)) {
if (OnboardingUtils.isFirstBoot(context)) {
// Enable the application if this is the first "unhide" feature is enabled just in
// case when the app has been disabled before.
@@ -77,14 +78,14 @@ public class BootCompletedReceiver extends BroadcastReceiver {
ComponentName name = new ComponentName(context, TvActivity.class);
if (pm.getComponentEnabledSetting(name)
!= PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
- pm.setComponentEnabledSetting(name,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ pm.setComponentEnabledSetting(
+ name, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
}
OnboardingUtils.setFirstBootCompleted(context);
}
}
- RecordingScheduler scheduler = TvApplication.getSingletons(context).getRecordingScheduler();
+ RecordingScheduler scheduler = TvSingletons.getSingletons(context).getRecordingScheduler();
if (scheduler != null) {
scheduler.updateAndStartServiceIfNeeded();
}
diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java
index 124172f0..07f5d6be 100644
--- a/src/com/android/tv/receiver/PackageIntentsReceiver.java
+++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java
@@ -21,24 +21,24 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
-
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
+import com.android.tv.TvFeatures;
+import com.android.tv.TvSingletons;
import com.android.tv.util.Partner;
+import com.google.android.tv.partner.support.EpgContract;
-/**
- * A class for handling the broadcast intents from PackageManager.
- */
+/** A class for handling the broadcast intents from PackageManager. */
public class PackageIntentsReceiver extends BroadcastReceiver {
private static final String TAG = "PackageIntentsReceiver";
@Override
public void onReceive(Context context, Intent intent) {
- if (!TvApplication.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) {
+ if (!TvSingletons.getSingletons(context).getTvInputManagerHelper().hasTvInputManager()) {
Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
return;
}
- TvApplication.setCurrentRunningProcess(context, true);
- ((TvApplication) context.getApplicationContext()).handleInputCountChanged();
+ Starter.start(context);
+ ((TvSingletons) context.getApplicationContext()).handleInputCountChanged();
Uri uri = intent.getData();
final String packageName = (uri != null ? uri.getSchemeSpecificPart() : null);
diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java
index 2709ebe1..410b8252 100644
--- a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java
+++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java
@@ -28,16 +28,14 @@ import android.support.annotation.RequiresApi;
import android.support.media.tv.TvContractCompat;
import android.text.TextUtils;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
+import com.android.tv.Starter;
+import com.android.tv.TvSingletons;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.PreviewProgramContent;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -48,23 +46,19 @@ import java.util.concurrent.TimeUnit;
@RequiresApi(Build.VERSION_CODES.O)
public class ChannelPreviewUpdater {
private static final String TAG = "ChannelPreviewUpdater";
- // STOPSHIP: set it to false.
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final int UPATE_PREVIEW_PROGRAMS_JOB_ID = 1000001;
private static final long ROUTINE_INTERVAL_MS = TimeUnit.MINUTES.toMillis(10);
// The left time of a program should meet the threshold so that it could be recommended.
- private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS =
- TimeUnit.MINUTES.toMillis(10);
- private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90%
+ private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = TimeUnit.MINUTES.toMillis(10);
+ private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90%
private static final int RECOMMENDATION_COUNT = 6;
private static final int MIN_COUNT_TO_ADD_ROW = 4;
private static ChannelPreviewUpdater sChannelPreviewUpdater;
- /**
- * Creates and returns the {@link ChannelPreviewUpdater}.
- */
+ /** Creates and returns the {@link ChannelPreviewUpdater}. */
public static ChannelPreviewUpdater getInstance(Context context) {
if (sChannelPreviewUpdater == null) {
sChannelPreviewUpdater = new ChannelPreviewUpdater(context.getApplicationContext());
@@ -82,21 +76,22 @@ public class ChannelPreviewUpdater {
private boolean mNeedUpdateAfterRecommenderReady = false;
- private Recommender.Listener mRecommenderListener = new Recommender.Listener() {
- @Override
- public void onRecommenderReady() {
- if (mNeedUpdateAfterRecommenderReady) {
- if (DEBUG) Log.d(TAG, "Recommender is ready");
- updatePreviewDataForChannelsImmediately();
- mNeedUpdateAfterRecommenderReady = false;
- }
- }
+ private Recommender.Listener mRecommenderListener =
+ new Recommender.Listener() {
+ @Override
+ public void onRecommenderReady() {
+ if (mNeedUpdateAfterRecommenderReady) {
+ if (DEBUG) Log.d(TAG, "Recommender is ready");
+ updatePreviewDataForChannelsImmediately();
+ mNeedUpdateAfterRecommenderReady = false;
+ }
+ }
- @Override
- public void onRecommendationChanged() {
- updatePreviewDataForChannelsImmediately();
- }
- };
+ @Override
+ public void onRecommendationChanged() {
+ updatePreviewDataForChannelsImmediately();
+ }
+ };
private ChannelPreviewUpdater(Context context) {
mContext = context;
@@ -104,15 +99,13 @@ public class ChannelPreviewUpdater {
mRecommender.registerEvaluator(new RandomEvaluator(), 0.1, 0.1);
mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5);
mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0);
- ApplicationSingletons appSingleton = TvApplication.getSingletons(context);
- mPreviewDataManager = appSingleton.getPreviewDataManager();
- mParentalControlSettings = appSingleton.getTvInputManagerHelper()
- .getParentalControlSettings();
+ TvSingletons tvSingleton = TvSingletons.getSingletons(context);
+ mPreviewDataManager = tvSingleton.getPreviewDataManager();
+ mParentalControlSettings =
+ tvSingleton.getTvInputManagerHelper().getParentalControlSettings();
}
- /**
- * Starts the routine service for updating the preview programs.
- */
+ /** Starts the routine service for updating the preview programs. */
public void startRoutineService() {
JobScheduler jobScheduler =
(JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
@@ -120,11 +113,13 @@ public class ChannelPreviewUpdater {
if (DEBUG) Log.d(TAG, "UPDATE_PREVIEW_JOB already exists");
return;
}
- JobInfo job = new JobInfo.Builder(UPATE_PREVIEW_PROGRAMS_JOB_ID,
- new ComponentName(mContext, ChannelPreviewUpdateService.class))
- .setPeriodic(ROUTINE_INTERVAL_MS)
- .setPersisted(true)
- .build();
+ JobInfo job =
+ new JobInfo.Builder(
+ UPATE_PREVIEW_PROGRAMS_JOB_ID,
+ new ComponentName(mContext, ChannelPreviewUpdateService.class))
+ .setPeriodic(ROUTINE_INTERVAL_MS)
+ .setPersisted(true)
+ .build();
if (jobScheduler.schedule(job) < 0) {
Log.i(TAG, "JobScheduler failed to schedule the job");
}
@@ -138,9 +133,7 @@ public class ChannelPreviewUpdater {
updatePreviewDataForChannelsImmediately();
}
- /**
- * Updates the preview programs table.
- */
+ /** Updates the preview programs table. */
public void updatePreviewDataForChannelsImmediately() {
if (!mRecommender.isReady()) {
mNeedUpdateAfterRecommenderReady = true;
@@ -148,16 +141,17 @@ public class ChannelPreviewUpdater {
}
if (!mPreviewDataManager.isLoadFinished()) {
- mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() {
- @Override
- public void onPreviewDataLoadFinished() {
- mPreviewDataManager.removeListener(this);
- updatePreviewDataForChannels();
- }
+ mPreviewDataManager.addListener(
+ new PreviewDataManager.PreviewDataListener() {
+ @Override
+ public void onPreviewDataLoadFinished() {
+ mPreviewDataManager.removeListener(this);
+ updatePreviewDataForChannels();
+ }
- @Override
- public void onPreviewDataUpdateFinished() { }
- });
+ @Override
+ public void onPreviewDataUpdateFinished() {}
+ });
return;
}
updatePreviewDataForChannels();
@@ -225,8 +219,9 @@ public class ChannelPreviewUpdater {
}
private void updatePreviewDataForChannelsInternal(Set<Program> programs) {
- long defaultPreviewChannelId = mPreviewDataManager.getPreviewChannelId(
- PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL);
+ long defaultPreviewChannelId =
+ mPreviewDataManager.getPreviewChannelId(
+ PreviewDataManager.TYPE_DEFAULT_PREVIEW_CHANNEL);
if (defaultPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID) {
// Only create if there is enough programs
if (programs.size() > MIN_COUNT_TO_ADD_ROW) {
@@ -248,7 +243,8 @@ public class ChannelPreviewUpdater {
});
}
} else {
- updatePreviewProgramsForPreviewChannel(defaultPreviewChannelId,
+ updatePreviewProgramsForPreviewChannel(
+ defaultPreviewChannelId,
generatePreviewProgramContentsFromPrograms(defaultPreviewChannelId, programs));
}
}
@@ -266,39 +262,38 @@ public class ChannelPreviewUpdater {
return result;
}
- private void updatePreviewProgramsForPreviewChannel(long previewChannelId,
- Set<PreviewProgramContent> previewProgramContents) {
- PreviewDataManager.PreviewDataListener previewDataListener
- = new PreviewDataManager.PreviewDataListener() {
- @Override
- public void onPreviewDataLoadFinished() { }
-
- @Override
- public void onPreviewDataUpdateFinished() {
- mPreviewDataManager.removeListener(this);
- if (mJobService != null && mJobParams != null) {
- if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService");
- mJobService.jobFinished(mJobParams, false);
- mJobService = null;
- mJobParams = null;
- } else {
- if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService");
- }
- }
- };
+ private void updatePreviewProgramsForPreviewChannel(
+ long previewChannelId, Set<PreviewProgramContent> previewProgramContents) {
+ PreviewDataManager.PreviewDataListener previewDataListener =
+ new PreviewDataManager.PreviewDataListener() {
+ @Override
+ public void onPreviewDataLoadFinished() {}
+
+ @Override
+ public void onPreviewDataUpdateFinished() {
+ mPreviewDataManager.removeListener(this);
+ if (mJobService != null && mJobParams != null) {
+ if (DEBUG) Log.d(TAG, "UpdateAsyncTask.onPostExecute with JobService");
+ mJobService.jobFinished(mJobParams, false);
+ mJobService = null;
+ mJobParams = null;
+ } else {
+ if (DEBUG)
+ Log.d(TAG, "UpdateAsyncTask.onPostExecute without JobService");
+ }
+ }
+ };
mPreviewDataManager.updatePreviewProgramsForChannel(
previewChannelId, previewProgramContents, previewDataListener);
}
- /**
- * Job to execute the update of preview programs.
- */
+ /** Job to execute the update of preview programs. */
public static class ChannelPreviewUpdateService extends JobService {
private ChannelPreviewUpdater mChannelPreviewUpdater;
@Override
public void onCreate() {
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
if (DEBUG) Log.d(TAG, "ChannelPreviewUpdateService.onCreate");
mChannelPreviewUpdater = ChannelPreviewUpdater.getInstance(this);
}
diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java
index 26f0fbf0..c7a7cb37 100644
--- a/src/com/android/tv/recommendation/ChannelRecord.java
+++ b/src/com/android/tv/recommendation/ChannelRecord.java
@@ -17,14 +17,12 @@
package com.android.tv.recommendation;
import android.content.Context;
+import android.support.annotation.GuardedBy;
import android.support.annotation.VisibleForTesting;
-
-import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
+import com.android.tv.TvSingletons;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
-import com.android.tv.util.Utils;
-
+import com.android.tv.data.api.Channel;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -32,7 +30,10 @@ public class ChannelRecord {
// TODO: decide the value for max history size.
@VisibleForTesting static final int MAX_HISTORY_SIZE = 100;
private final Context mContext;
+
+ @GuardedBy("this")
private final Deque<WatchedProgram> mWatchHistory;
+
private Program mCurrentProgram;
private Channel mChannel;
private long mTotalWatchDurationMs;
@@ -62,7 +63,7 @@ public class ChannelRecord {
mInputRemoved = removed;
}
- public long getLastWatchEndTimeMs() {
+ public synchronized long getLastWatchEndTimeMs() {
WatchedProgram p = mWatchHistory.peekLast();
return (p == null) ? 0 : p.getWatchEndTimeMs();
}
@@ -71,7 +72,7 @@ public class ChannelRecord {
long time = System.currentTimeMillis();
if (mCurrentProgram == null || mCurrentProgram.getEndTimeUtcMillis() < time) {
ProgramDataManager manager =
- TvApplication.getSingletons(mContext).getProgramDataManager();
+ TvSingletons.getSingletons(mContext).getProgramDataManager();
mCurrentProgram = manager.getCurrentProgram(mChannel.getId());
}
return mCurrentProgram;
@@ -81,11 +82,11 @@ public class ChannelRecord {
return mTotalWatchDurationMs;
}
- public final WatchedProgram[] getWatchHistory() {
+ public final synchronized WatchedProgram[] getWatchHistory() {
return mWatchHistory.toArray(new WatchedProgram[mWatchHistory.size()]);
}
- public void logWatchHistory(WatchedProgram p) {
+ public synchronized void logWatchHistory(WatchedProgram p) {
mWatchHistory.offer(p);
mTotalWatchDurationMs += p.getWatchedDurationMs();
if (mWatchHistory.size() > MAX_HISTORY_SIZE) {
diff --git a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java
index 9a6de7e2..8b0a3502 100644
--- a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java
+++ b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java
@@ -19,7 +19,7 @@ package com.android.tv.recommendation;
import java.util.List;
public class FavoriteChannelEvaluator extends Recommender.Evaluator {
- private static final long MIN_WATCH_PERIOD_MS = 1000 * 60 * 60 * 24; // 1 day
+ private static final long MIN_WATCH_PERIOD_MS = 1000 * 60 * 60 * 24; // 1 day
// When there is no watch history, use the current time as a default value.
private long mEarliestWatchStartTimeMs = System.currentTimeMillis();
@@ -46,7 +46,6 @@ public class FavoriteChannelEvaluator extends Recommender.Evaluator {
}
long watchPeriodMs = System.currentTimeMillis() - mEarliestWatchStartTimeMs;
- return (double) cr.getTotalWatchDurationMs() /
- Math.max(watchPeriodMs, MIN_WATCH_PERIOD_MS);
+ return (double) cr.getTotalWatchDurationMs() / Math.max(watchPeriodMs, MIN_WATCH_PERIOD_MS);
}
}
diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java
index a44eca41..f40a0862 100644
--- a/src/com/android/tv/recommendation/NotificationService.java
+++ b/src/com/android/tv/recommendation/NotificationService.java
@@ -40,42 +40,39 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseLongArray;
import android.view.View;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.MainActivityWrapper.OnCurrentChannelChangeListener;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.Starter;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.CommonConstants;
import com.android.tv.common.WeakHandler;
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-import com.android.tv.util.BitmapUtils;
-import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
-import com.android.tv.util.ImageLoader;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
+import com.android.tv.util.images.BitmapUtils;
+import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo;
+import com.android.tv.util.images.ImageLoader;
import java.util.ArrayList;
import java.util.List;
-/**
- * A local service for notify recommendation at home launcher.
- */
-public class NotificationService extends Service implements Recommender.Listener,
- OnCurrentChannelChangeListener {
+/** A local service for notify recommendation at home launcher. */
+public class NotificationService extends Service
+ implements Recommender.Listener, OnCurrentChannelChangeListener {
private static final String TAG = "NotificationService";
private static final boolean DEBUG = false;
public static final String ACTION_SHOW_RECOMMENDATION =
- "com.android.tv.notification.ACTION_SHOW_RECOMMENDATION";
+ CommonConstants.BASE_PACKAGE + ".notification.ACTION_SHOW_RECOMMENDATION";
public static final String ACTION_HIDE_RECOMMENDATION =
- "com.android.tv.notification.ACTION_HIDE_RECOMMENDATION";
+ CommonConstants.BASE_PACKAGE + ".notification.ACTION_HIDE_RECOMMENDATION";
/**
- * Recommendation intent has an extra data for the recommendation type. It'll be also
- * sent to a TV input as a tune parameter.
+ * Recommendation intent has an extra data for the recommendation type. It'll be also sent to a
+ * TV input as a tune parameter.
*/
public static final String TUNE_PARAMS_RECOMMENDATION_TYPE =
- "com.android.tv.recommendation_type";
+ CommonConstants.BASE_PACKAGE + ".recommendation_type";
private static final String TYPE_RANDOM_RECOMMENDATION = "random";
private static final String TYPE_ROUTINE_WATCH_RECOMMENDATION = "routine_watch";
@@ -92,9 +89,9 @@ public class NotificationService extends Service implements Recommender.Listener
private static final int MSG_UPDATE_RECOMMENDATION = 1002;
private static final int MSG_HIDE_RECOMMENDATION = 1003;
- private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min
- private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min
- private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90%
+ private static final long RECOMMENDATION_RETRY_TIME_MS = 5 * 60 * 1000; // 5 min
+ private static final long RECOMMENDATION_THRESHOLD_LEFT_TIME_MS = 10 * 60 * 1000; // 10 min
+ private static final int RECOMMENDATION_THRESHOLD_PROGRESS = 90; // 90%
private static final int MAX_PROGRAM_UPDATE_COUNT = 20;
private TvInputManagerHelper mTvInputManagerHelper;
@@ -126,17 +123,17 @@ public class NotificationService extends Service implements Recommender.Listener
@Override
public void onCreate() {
if (DEBUG) Log.d(TAG, "onCreate");
- TvApplication.setCurrentRunningProcess(this, true);
+ Starter.start(this);
super.onCreate();
mCurrentNotificationCount = 0;
mNotificationChannels = new long[NOTIFICATION_COUNT];
for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
mNotificationChannels[i] = Channel.INVALID_ID;
}
- mNotificationCardMaxWidth = getResources().getDimensionPixelSize(
- R.dimen.notif_card_img_max_width);
- mNotificationCardHeight = getResources().getDimensionPixelSize(
- R.dimen.notif_card_img_height);
+ mNotificationCardMaxWidth =
+ getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
+ mNotificationCardHeight =
+ getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
mCardImageMaxWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
mCardImageMinWidth = getResources().getDimensionPixelSize(R.dimen.notif_card_img_min_width);
@@ -150,17 +147,17 @@ public class NotificationService extends Service implements Recommender.Listener
getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_bottom);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- ApplicationSingletons appSingletons = TvApplication.getSingletons(this);
- mTvInputManagerHelper = appSingletons.getTvInputManagerHelper();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(this);
+ mTvInputManagerHelper = tvSingletons.getTvInputManagerHelper();
mHandlerThread = new HandlerThread("tv notification");
mHandlerThread.start();
mHandler = new NotificationHandler(mHandlerThread.getLooper(), this);
mHandler.sendEmptyMessage(MSG_INITIALIZE_RECOMMENDER);
// Just called for early initialization.
- appSingletons.getChannelDataManager();
- appSingletons.getProgramDataManager();
- appSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this);
+ tvSingletons.getChannelDataManager();
+ tvSingletons.getProgramDataManager();
+ tvSingletons.getMainActivityWrapper().addOnCurrentChannelChangeListener(this);
}
@UiThread
@@ -178,8 +175,8 @@ public class NotificationService extends Service implements Recommender.Listener
mRecommender.registerEvaluator(new RandomEvaluator());
} else if (TYPE_ROUTINE_WATCH_RECOMMENDATION.equals(mRecommendationType)) {
mRecommender.registerEvaluator(new RoutineWatchEvaluator());
- } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION
- .equals(mRecommendationType)) {
+ } else if (TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION.equals(
+ mRecommendationType)) {
mRecommender.registerEvaluator(new FavoriteChannelEvaluator(), 0.5, 0.5);
mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0);
} else {
@@ -189,6 +186,9 @@ public class NotificationService extends Service implements Recommender.Listener
}
private void handleShowRecommendation() {
+ if (mRecommender == null) {
+ return;
+ }
if (!mRecommender.isReady()) {
mShowRecommendationAfterRecommenderReady = true;
} else {
@@ -197,13 +197,16 @@ public class NotificationService extends Service implements Recommender.Listener
}
private void handleUpdateRecommendation(int notificationId, Channel channel) {
- if (mNotificationChannels[notificationId] == Channel.INVALID_ID || !sendNotification(
- channel.getId(), notificationId)) {
+ if (mNotificationChannels[notificationId] == Channel.INVALID_ID
+ || !sendNotification(channel.getId(), notificationId)) {
changeRecommendation(notificationId);
}
}
private void handleHideRecommendation() {
+ if (mRecommender == null) {
+ return;
+ }
if (!mRecommender.isReady()) {
mShowRecommendationAfterRecommenderReady = false;
} else {
@@ -213,7 +216,8 @@ public class NotificationService extends Service implements Recommender.Listener
@Override
public void onDestroy() {
- TvApplication.getSingletons(this).getMainActivityWrapper()
+ TvSingletons.getSingletons(this)
+ .getMainActivityWrapper()
.removeOnCurrentChannelChangeListener(this);
if (mRecommender != null) {
mRecommender.release();
@@ -316,7 +320,7 @@ public class NotificationService extends Service implements Recommender.Listener
}
for (Channel c : channels) {
if (!isNotifiedChannel(c.getId())) {
- if(sendNotification(c.getId(), notificationId)) {
+ if (sendNotification(c.getId(), notificationId)) {
return;
}
}
@@ -334,13 +338,13 @@ public class NotificationService extends Service implements Recommender.Listener
}
private void hideAllRecommendation() {
- for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
- if (mNotificationChannels[i] != Channel.INVALID_ID) {
- mNotificationChannels[i] = Channel.INVALID_ID;
- mNotificationManager.cancel(NOTIFY_TAG, i);
- }
- }
- mCurrentNotificationCount = 0;
+ for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
+ if (mNotificationChannels[i] != Channel.INVALID_ID) {
+ mNotificationChannels[i] = Channel.INVALID_ID;
+ mNotificationManager.cancel(NOTIFY_TAG, i);
+ }
+ }
+ mCurrentNotificationCount = 0;
}
private boolean sendNotification(final long channelId, final int notificationId) {
@@ -350,8 +354,13 @@ public class NotificationService extends Service implements Recommender.Listener
}
final Channel channel = cr.getChannel();
if (DEBUG) {
- Log.d(TAG, "sendNotification (channelName=" + channel.getDisplayName() + " notifyId="
- + notificationId + ")");
+ Log.d(
+ TAG,
+ "sendNotification (channelName="
+ + channel.getDisplayName()
+ + " notifyId="
+ + notificationId
+ + ")");
}
// TODO: Move some checking logic into TvRecommendation.
@@ -363,17 +372,18 @@ public class NotificationService extends Service implements Recommender.Listener
if (inputInfo == null) {
return false;
}
- final String inputDisplayName = inputInfo.loadLabel(this).toString();
final Program program = Utils.getCurrentProgram(this, channel.getId());
if (program == null) {
return false;
}
- final long programDurationMs = program.getEndTimeUtcMillis()
- - program.getStartTimeUtcMillis();
+ final long programDurationMs =
+ program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis();
long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis();
- final int programProgress = (programDurationMs <= 0) ? -1
- : 100 - (int) (programLeftTimsMs * 100 / programDurationMs);
+ final int programProgress =
+ (programDurationMs <= 0)
+ ? -1
+ : 100 - (int) (programLeftTimsMs * 100 / programDurationMs);
// We recommend those programs that meet the condition only.
if (programProgress >= RECOMMENDATION_THRESHOLD_PROGRESS
@@ -382,19 +392,24 @@ public class NotificationService extends Service implements Recommender.Listener
}
// We don't trust TIS to provide us with proper sized image
- ScaledBitmapInfo posterArtBitmapInfo = BitmapUtils.decodeSampledBitmapFromUriString(this,
- program.getPosterArtUri(), (int) mNotificationCardMaxWidth,
- (int) mNotificationCardHeight);
+ ScaledBitmapInfo posterArtBitmapInfo =
+ BitmapUtils.decodeSampledBitmapFromUriString(
+ this,
+ program.getPosterArtUri(),
+ (int) mNotificationCardMaxWidth,
+ (int) mNotificationCardHeight);
if (posterArtBitmapInfo == null) {
Log.e(TAG, "Failed to decode poster image for " + program.getPosterArtUri());
return false;
}
final Bitmap posterArtBitmap = posterArtBitmapInfo.bitmap;
- channel.loadBitmap(this, Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoMaxWidth,
+ channel.loadBitmap(
+ this,
+ Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
+ mChannelLogoMaxWidth,
mChannelLogoMaxHeight,
- createChannelLogoCallback(this, notificationId, inputDisplayName, channel, program,
- posterArtBitmap));
+ createChannelLogoCallback(this, notificationId, channel, program, posterArtBitmap));
if (mNotificationChannels[notificationId] == Channel.INVALID_ID) {
++mCurrentNotificationCount;
@@ -404,62 +419,77 @@ public class NotificationService extends Service implements Recommender.Listener
return true;
}
- @NonNull
- private static ImageLoader.ImageLoaderCallback<NotificationService> createChannelLogoCallback(
- NotificationService service, final int notificationId, final String inputDisplayName,
- final Channel channel, final Program program, final Bitmap posterArtBitmap) {
- return new ImageLoader.ImageLoaderCallback<NotificationService>(service) {
- @Override
- public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) {
- service.sendNotification(notificationId, channelLogo, channel, posterArtBitmap,
- program, inputDisplayName);
- }
- };
- }
-
- private void sendNotification(int notificationId, Bitmap channelLogo, Channel channel,
- Bitmap posterArtBitmap, Program program, String inputDisplayName) {
- final long programDurationMs = program.getEndTimeUtcMillis() - program
- .getStartTimeUtcMillis();
+ private void sendNotification(
+ int notificationId,
+ Bitmap channelLogo,
+ Channel channel,
+ Bitmap posterArtBitmap,
+ Program program) {
+ final long programDurationMs =
+ program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis();
long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis();
- final int programProgress = (programDurationMs <= 0) ? -1
- : 100 - (int) (programLeftTimsMs * 100 / programDurationMs);
+ final int programProgress =
+ (programDurationMs <= 0)
+ ? -1
+ : 100 - (int) (programLeftTimsMs * 100 / programDurationMs);
Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri());
intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0);
// This callback will run on the main thread.
- Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap
- : overlayChannelLogo(channelLogo, posterArtBitmap);
+ Bitmap largeIconBitmap =
+ (channelLogo == null)
+ ? posterArtBitmap
+ : overlayChannelLogo(channelLogo, posterArtBitmap);
String channelDisplayName = channel.getDisplayName();
- Notification notification = new Notification.Builder(this)
- .setContentIntent(notificationIntent)
- .setContentTitle(program.getTitle())
- .setContentText(TextUtils.isEmpty(channelDisplayName) ? channel.getDisplayNumber()
- : channelDisplayName)
- .setContentInfo(channelDisplayName)
- .setAutoCancel(true).setLargeIcon(largeIconBitmap)
- .setSmallIcon(R.drawable.ic_launcher_s)
- .setCategory(Notification.CATEGORY_RECOMMENDATION)
- .setProgress((programProgress > 0) ? 100 : 0, programProgress, false)
- .setSortKey(mRecommender.getChannelSortKey(channel.getId()))
- .build();
+ Notification notification =
+ new Notification.Builder(this)
+ .setContentIntent(notificationIntent)
+ .setContentTitle(program.getTitle())
+ .setContentText(
+ TextUtils.isEmpty(channelDisplayName)
+ ? channel.getDisplayNumber()
+ : channelDisplayName)
+ .setContentInfo(channelDisplayName)
+ .setAutoCancel(true)
+ .setLargeIcon(largeIconBitmap)
+ .setSmallIcon(R.drawable.ic_launcher_s)
+ .setCategory(Notification.CATEGORY_RECOMMENDATION)
+ .setProgress((programProgress > 0) ? 100 : 0, programProgress, false)
+ .setSortKey(mRecommender.getChannelSortKey(channel.getId()))
+ .build();
notification.color = getResources().getColor(R.color.recommendation_card_background, null);
if (!TextUtils.isEmpty(program.getThumbnailUri())) {
- notification.extras
- .putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri());
+ notification.extras.putString(
+ Notification.EXTRA_BACKGROUND_IMAGE_URI, program.getThumbnailUri());
}
mNotificationManager.notify(NOTIFY_TAG, notificationId, notification);
Message msg = mHandler.obtainMessage(MSG_UPDATE_RECOMMENDATION, notificationId, 0, channel);
mHandler.sendMessageDelayed(msg, programDurationMs / MAX_PROGRAM_UPDATE_COUNT);
}
+ @NonNull
+ private static ImageLoader.ImageLoaderCallback<NotificationService> createChannelLogoCallback(
+ NotificationService service,
+ final int notificationId,
+ final Channel channel,
+ final Program program,
+ final Bitmap posterArtBitmap) {
+ return new ImageLoader.ImageLoaderCallback<NotificationService>(service) {
+ @Override
+ public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) {
+ service.sendNotification(
+ notificationId, channelLogo, channel, posterArtBitmap, program);
+ }
+ };
+ }
+
private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) {
- Bitmap result = BitmapUtils.getScaledMutableBitmap(
- background, Integer.MAX_VALUE, mCardImageHeight);
- Bitmap scaledLogo = BitmapUtils.scaleBitmap(
- logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight);
+ Bitmap result =
+ BitmapUtils.getScaledMutableBitmap(background, Integer.MAX_VALUE, mCardImageHeight);
+ Bitmap scaledLogo =
+ BitmapUtils.scaleBitmap(logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight);
Canvas canvas;
try {
canvas = new Canvas(result);
@@ -524,27 +554,32 @@ public class NotificationService extends Service implements Recommender.Listener
@Override
public void handleMessage(Message msg, @NonNull NotificationService notificationService) {
switch (msg.what) {
- case MSG_INITIALIZE_RECOMMENDER: {
- notificationService.handleInitializeRecommender();
- break;
- }
- case MSG_SHOW_RECOMMENDATION: {
- notificationService.handleShowRecommendation();
- break;
- }
- case MSG_UPDATE_RECOMMENDATION: {
- int notificationId = msg.arg1;
- Channel channel = ((Channel) msg.obj);
- notificationService.handleUpdateRecommendation(notificationId, channel);
- break;
- }
- case MSG_HIDE_RECOMMENDATION: {
- notificationService.handleHideRecommendation();
- break;
- }
- default: {
- super.handleMessage(msg);
- }
+ case MSG_INITIALIZE_RECOMMENDER:
+ {
+ notificationService.handleInitializeRecommender();
+ break;
+ }
+ case MSG_SHOW_RECOMMENDATION:
+ {
+ notificationService.handleShowRecommendation();
+ break;
+ }
+ case MSG_UPDATE_RECOMMENDATION:
+ {
+ int notificationId = msg.arg1;
+ Channel channel = ((Channel) msg.obj);
+ notificationService.handleUpdateRecommendation(notificationId, channel);
+ break;
+ }
+ case MSG_HIDE_RECOMMENDATION:
+ {
+ notificationService.handleHideRecommendation();
+ break;
+ }
+ default:
+ {
+ super.handleMessage(msg);
+ }
}
}
}
diff --git a/src/com/android/tv/recommendation/RecentChannelEvaluator.java b/src/com/android/tv/recommendation/RecentChannelEvaluator.java
index e724f4ce..f4c4877d 100644
--- a/src/com/android/tv/recommendation/RecentChannelEvaluator.java
+++ b/src/com/android/tv/recommendation/RecentChannelEvaluator.java
@@ -51,9 +51,12 @@ public class RecentChannelEvaluator extends Recommender.Evaluator {
if (watchDuration < WATCH_DURATION_MS_LOWER_BOUND) {
watchDurationScore = MAX_SCORE_FOR_LOWER_BOUND;
} else if (watchDuration < WATCH_DURATION_MS_UPPER_BOUND) {
- watchDurationScore = (watchDuration - WATCH_DURATION_MS_LOWER_BOUND)
- / (WATCH_DURATION_MS_UPPER_BOUND - WATCH_DURATION_MS_LOWER_BOUND)
- * (1 - MAX_SCORE_FOR_LOWER_BOUND) + MAX_SCORE_FOR_LOWER_BOUND;
+ watchDurationScore =
+ (watchDuration - WATCH_DURATION_MS_LOWER_BOUND)
+ / (WATCH_DURATION_MS_UPPER_BOUND
+ - WATCH_DURATION_MS_LOWER_BOUND)
+ * (1 - MAX_SCORE_FOR_LOWER_BOUND)
+ + MAX_SCORE_FOR_LOWER_BOUND;
} else {
watchDurationScore = 1.0;
}
@@ -61,4 +64,4 @@ public class RecentChannelEvaluator extends Recommender.Evaluator {
}
return (maxScore > 0.0) ? maxScore : NOT_RECOMMENDED;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java
index dc148ec8..649920fb 100644
--- a/src/com/android/tv/recommendation/RecommendationDataManager.java
+++ b/src/com/android/tv/recommendation/RecommendationDataManager.java
@@ -33,16 +33,14 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.WeakHandler;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.data.WatchedHistoryManager;
-import com.android.tv.util.PermissionUtils;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.TvUriMatcher;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -52,6 +50,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+/** Manages teh data need to make recommendations. */
public class RecommendationDataManager implements WatchedHistoryManager.Listener {
private static final int MSG_START = 1000;
private static final int MSG_STOP = 1001;
@@ -84,40 +83,38 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Handler mMainHandler;
- @Nullable
- private WatchedHistoryManager mWatchedHistoryManager;
+ @Nullable private WatchedHistoryManager mWatchedHistoryManager;
private final ChannelDataManager mChannelDataManager;
private final ChannelDataManager.Listener mChannelDataListener =
new ChannelDataManager.Listener() {
- @Override
- @MainThread
- public void onLoadFinished() {
- updateChannelData();
- }
+ @Override
+ @MainThread
+ public void onLoadFinished() {
+ updateChannelData();
+ }
- @Override
- @MainThread
- public void onChannelListUpdated() {
- updateChannelData();
- }
+ @Override
+ @MainThread
+ public void onChannelListUpdated() {
+ updateChannelData();
+ }
- @Override
- @MainThread
- public void onChannelBrowsableChanged() {
- updateChannelData();
- }
- };
+ @Override
+ @MainThread
+ public void onChannelBrowsableChanged() {
+ updateChannelData();
+ }
+ };
// For thread safety, this variable is handled only on main thread.
private final List<Listener> mListeners = new ArrayList<>();
/**
- * Gets instance of RecommendationDataManager, and adds a {@link Listener}.
- * The listener methods will be called in the same thread as its caller of the method.
- * Note that {@link #release(Listener)} should be called when this manager is not needed
- * any more.
+ * Gets instance of RecommendationDataManager, and adds a {@link Listener}. The listener methods
+ * will be called in the same thread as its caller of the method. Note that {@link
+ * #release(Listener)} should be called when this manager is not needed any more.
*/
- public synchronized static RecommendationDataManager acquireManager(
+ public static synchronized RecommendationDataManager acquireManager(
Context context, @NonNull Listener listener) {
if (sManager == null) {
sManager = new RecommendationDataManager(context);
@@ -129,7 +126,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
private final TvInputCallback mInternalCallback =
new TvInputCallback() {
@Override
- public void onInputStateChanged(String inputId, int state) { }
+ public void onInputStateChanged(String inputId, int state) {}
@Override
public void onInputAdded(String inputId) {
@@ -144,8 +141,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
for (ChannelRecord channelRecord : mChannelRecordMap.values()) {
if (channelRecord.getChannel().getInputId().equals(inputId)) {
channelRecord.setInputRemoved(false);
- mAvailableChannelRecordMap.put(channelRecord.getChannel().getId(),
- channelRecord);
+ mAvailableChannelRecordMap.put(
+ channelRecord.getChannel().getId(), channelRecord);
channelRecordMapChanged = true;
}
}
@@ -179,7 +176,7 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
}
@Override
- public void onInputUpdated(String inputId) { }
+ public void onInputUpdated(String inputId) {}
};
private RecommendationDataManager(Context context) {
@@ -189,48 +186,44 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
mHandler = new RecommendationHandler(mHandlerThread.getLooper(), this);
mMainHandler = new RecommendationMainHandler(Looper.getMainLooper(), this);
mContentObserver = new RecommendationContentObserver(mHandler);
- mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager();
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- start();
- }
- });
+ mChannelDataManager = TvSingletons.getSingletons(mContext).getChannelDataManager();
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ start();
+ }
+ });
}
/**
- * Removes the {@link Listener}, and releases RecommendationDataManager
- * if there are no listeners remained.
+ * Removes the {@link Listener}, and releases RecommendationDataManager if there are no
+ * listeners remained.
*/
public void release(@NonNull final Listener listener) {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- removeListener(listener);
- if (mListeners.size() == 0) {
- stop();
- }
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ removeListener(listener);
+ if (mListeners.size() == 0) {
+ stop();
+ }
+ }
+ });
}
- /**
- * Returns a {@link ChannelRecord} corresponds to the channel ID {@code ChannelId}.
- */
+ /** Returns a {@link ChannelRecord} corresponds to the channel ID {@code ChannelId}. */
public ChannelRecord getChannelRecord(long channelId) {
return mAvailableChannelRecordMap.get(channelId);
}
- /**
- * Returns the number of channels registered in ChannelRecord map.
- */
+ /** Returns the number of channels registered in ChannelRecord map. */
public int getChannelRecordCount() {
return mAvailableChannelRecordMap.size();
}
- /**
- * Returns a Collection of ChannelRecords.
- */
+ /** Returns a Collection of ChannelRecords. */
public Collection<ChannelRecord> getChannelRecords() {
return Collections.unmodifiableCollection(mAvailableChannelRecordMap.values());
}
@@ -264,12 +257,13 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
}
private void addListener(Listener listener) {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- mListeners.add(listener);
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ mListeners.add(listener);
+ }
+ });
}
@MainThread
@@ -286,10 +280,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
mWatchedHistoryManager.setListener(this);
mWatchedHistoryManager.start();
} else {
- mContext.getContentResolver().registerContentObserver(
- TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver);
- mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY,
- TvContract.WatchedPrograms.CONTENT_URI)
+ mContext.getContentResolver()
+ .registerContentObserver(
+ TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver);
+ mHandler.obtainMessage(
+ MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)
.sendToTarget();
}
mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
@@ -333,7 +328,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
}
}
}
- if (isChannelRecordMapChanged && mChannelRecordMapLoaded
+ if (isChannelRecordMapChanged
+ && mChannelRecordMapLoaded
&& !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) {
mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED);
}
@@ -356,14 +352,15 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
final ChannelRecord channelRecord =
updateChannelRecordFromWatchedProgram(watchedProgram);
if (mChannelRecordMapLoaded && channelRecord != null) {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- for (Listener l : mListeners) {
- l.onNewWatchLog(channelRecord);
- }
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ for (Listener l : mListeners) {
+ l.onNewWatchLog(channelRecord);
+ }
+ }
+ });
}
}
if (!mChannelRecordMapLoaded) {
@@ -374,96 +371,99 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
private WatchedProgram convertFromWatchedHistoryManagerRecords(
WatchedHistoryManager.WatchedRecord watchedRecord) {
long endTime = watchedRecord.watchedStartTime + watchedRecord.duration;
- Program program = new Program.Builder()
- .setChannelId(watchedRecord.channelId)
- .setTitle("")
- .setStartTimeUtcMillis(watchedRecord.watchedStartTime)
- .setEndTimeUtcMillis(endTime)
- .build();
+ Program program =
+ new Program.Builder()
+ .setChannelId(watchedRecord.channelId)
+ .setTitle("")
+ .setStartTimeUtcMillis(watchedRecord.watchedStartTime)
+ .setEndTimeUtcMillis(endTime)
+ .build();
return new WatchedProgram(program, watchedRecord.watchedStartTime, endTime);
}
@Override
public void onLoadFinished() {
- for (WatchedHistoryManager.WatchedRecord record
- : mWatchedHistoryManager.getWatchedHistory()) {
- updateChannelRecordFromWatchedProgram(
- convertFromWatchedHistoryManagerRecords(record));
+ for (WatchedHistoryManager.WatchedRecord record :
+ mWatchedHistoryManager.getWatchedHistory()) {
+ updateChannelRecordFromWatchedProgram(convertFromWatchedHistoryManagerRecords(record));
}
mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED);
}
@Override
public void onNewRecordAdded(WatchedHistoryManager.WatchedRecord watchedRecord) {
- final ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram(
- convertFromWatchedHistoryManagerRecords(watchedRecord));
+ final ChannelRecord channelRecord =
+ updateChannelRecordFromWatchedProgram(
+ convertFromWatchedHistoryManagerRecords(watchedRecord));
if (mChannelRecordMapLoaded && channelRecord != null) {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- for (Listener l : mListeners) {
- l.onNewWatchLog(channelRecord);
- }
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ for (Listener l : mListeners) {
+ l.onNewWatchLog(channelRecord);
+ }
+ }
+ });
}
}
private WatchedProgram createWatchedProgramFromWatchedProgramCursor(Cursor cursor) {
// Have to initiate the indexes of WatchedProgram Columns.
if (mIndexWatchChannelId == -1) {
- mIndexWatchChannelId = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_CHANNEL_ID);
- mIndexProgramTitle = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_TITLE);
- mIndexProgramStartTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS);
- mIndexProgramEndTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
- mIndexWatchStartTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
- mIndexWatchEndTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
+ mIndexWatchChannelId =
+ cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID);
+ mIndexProgramTitle = cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_TITLE);
+ mIndexProgramStartTime =
+ cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS);
+ mIndexProgramEndTime =
+ cursor.getColumnIndex(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
+ mIndexWatchStartTime =
+ cursor.getColumnIndex(
+ TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
+ mIndexWatchEndTime =
+ cursor.getColumnIndex(
+ TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
}
- Program program = new Program.Builder()
- .setChannelId(cursor.getLong(mIndexWatchChannelId))
- .setTitle(cursor.getString(mIndexProgramTitle))
- .setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime))
- .setEndTimeUtcMillis(cursor.getLong(mIndexProgramEndTime))
- .build();
+ Program program =
+ new Program.Builder()
+ .setChannelId(cursor.getLong(mIndexWatchChannelId))
+ .setTitle(cursor.getString(mIndexProgramTitle))
+ .setStartTimeUtcMillis(cursor.getLong(mIndexProgramStartTime))
+ .setEndTimeUtcMillis(cursor.getLong(mIndexProgramEndTime))
+ .build();
- return new WatchedProgram(program,
- cursor.getLong(mIndexWatchStartTime),
- cursor.getLong(mIndexWatchEndTime));
+ return new WatchedProgram(
+ program, cursor.getLong(mIndexWatchStartTime), cursor.getLong(mIndexWatchEndTime));
}
private void onNotifyChannelRecordMapLoaded() {
mChannelRecordMapLoaded = true;
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- for (Listener l : mListeners) {
- l.onChannelRecordLoaded();
- }
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ for (Listener l : mListeners) {
+ l.onChannelRecordLoaded();
+ }
+ }
+ });
}
private void onNotifyChannelRecordMapChanged() {
- runOnMainThread(new Runnable() {
- @Override
- public void run() {
- for (Listener l : mListeners) {
- l.onChannelRecordChanged();
- }
- }
- });
+ runOnMainThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ for (Listener l : mListeners) {
+ l.onChannelRecordChanged();
+ }
+ }
+ });
}
- /**
- * Returns true if ChannelRecords are added into mChannelRecordMap or removed from it.
- */
+ /** Returns true if ChannelRecords are added into mChannelRecordMap or removed from it. */
private boolean updateChannelRecordMapFromChannel(Channel channel) {
if (!channel.isBrowsable()) {
mChannelRecordMap.remove(channel.getId());
@@ -507,8 +507,8 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
public void onChange(final boolean selfChange, final Uri uri) {
switch (TvUriMatcher.match(uri)) {
case TvUriMatcher.MATCH_WATCHED_PROGRAM_ID:
- if (!mHandler.hasMessages(MSG_UPDATE_WATCH_HISTORY,
- TvContract.WatchedPrograms.CONTENT_URI)) {
+ if (!mHandler.hasMessages(
+ MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)) {
mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, uri).sendToTarget();
}
break;
@@ -524,15 +524,11 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
}
}
- /**
- * A listener interface to receive notification about the recommendation data.
- *
- * @MainThread
- */
+ /** A listener interface to receive notification about the recommendation data. @MainThread */
public interface Listener {
/**
- * Called when loading channel record map from database is finished.
- * It will be called after RecommendationDataManager.start() is finished.
+ * Called when loading channel record map from database is finished. It will be called after
+ * RecommendationDataManager.start() is finished.
*
* <p>Note that this method is called on the main thread.
*/
@@ -601,6 +597,6 @@ public class RecommendationDataManager implements WatchedHistoryManager.Listener
}
@Override
- protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) { }
+ protected void handleMessage(Message msg, @NonNull RecommendationDataManager referent) {}
}
}
diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java
index 82c2893d..f350799f 100644
--- a/src/com/android/tv/recommendation/Recommender.java
+++ b/src/com/android/tv/recommendation/Recommender.java
@@ -20,9 +20,7 @@ import android.content.Context;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Pair;
-
-import com.android.tv.data.Channel;
-
+import com.android.tv.data.api.Channel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -35,8 +33,7 @@ import java.util.concurrent.TimeUnit;
public class Recommender implements RecommendationDataManager.Listener {
private static final String TAG = "Recommender";
- @VisibleForTesting
- static final String INVALID_CHANNEL_SORT_KEY = "INVALID";
+ @VisibleForTesting static final String INVALID_CHANNEL_SORT_KEY = "INVALID";
private static final long MINIMUM_RECOMMENDATION_UPDATE_PERIOD = TimeUnit.MINUTES.toMillis(5);
private static final Comparator<Pair<Channel, Double>> mChannelScoreComparator =
new Comparator<Pair<Channel, Double>>() {
@@ -69,7 +66,9 @@ public class Recommender implements RecommendationDataManager.Listener {
}
@VisibleForTesting
- Recommender(Listener listener, boolean includeRecommendedOnly,
+ Recommender(
+ Listener listener,
+ boolean includeRecommendedOnly,
RecommendationDataManager dataManager) {
mListener = listener;
mIncludeRecommendedOnly = includeRecommendedOnly;
@@ -85,16 +84,16 @@ public class Recommender implements RecommendationDataManager.Listener {
}
public void registerEvaluator(Evaluator evaluator) {
- registerEvaluator(evaluator,
- EvaluatorWrapper.DEFAULT_BASE_SCORE, EvaluatorWrapper.DEFAULT_WEIGHT);
+ registerEvaluator(
+ evaluator, EvaluatorWrapper.DEFAULT_BASE_SCORE, EvaluatorWrapper.DEFAULT_WEIGHT);
}
/**
* Register the evaluator used in recommendation.
*
- * The range of evaluated scores by this evaluator will be between {@code baseScore} and
+ * <p>The range of evaluated scores by this evaluator will be between {@code baseScore} and
* {@code baseScore} + {@code weight} (inclusive).
-
+ *
* @param evaluator The evaluator to register inside this recommender.
* @param baseScore Base(Minimum) score of the score evaluated by {@code evaluator}.
* @param weight Weight value to rearrange the score evaluated by {@code evaluator}.
@@ -108,13 +107,13 @@ public class Recommender implements RecommendationDataManager.Listener {
}
/**
- * Return the channel list of recommendation up to {@code n} or the number of channels.
- * During the evaluation, this method updates the channel sort key of recommended channels.
+ * Return the channel list of recommendation up to {@code n} or the number of channels. During
+ * the evaluation, this method updates the channel sort key of recommended channels.
*
* @param size The number of channels that might be recommended.
- * @return Top {@code size} channels recommended sorted by score in descending order. If
- * {@code size} is bigger than the number of channels, the number of results could
- * be less than {@code size}.
+ * @return Top {@code size} channels recommended sorted by score in descending order. If {@code
+ * size} is bigger than the number of channels, the number of results could be less than
+ * {@code size}.
*/
public List<Channel> recommendChannels(int size) {
List<Pair<Channel, Double>> records = new ArrayList<>();
@@ -154,7 +153,7 @@ public class Recommender implements RecommendationDataManager.Listener {
*
* @param channelId The channel ID to retrieve the {@link Channel} object for.
* @return the {@link Channel} object for the given channel ID, {@code null} if such a channel
- * is not found.
+ * is not found.
*/
public Channel getChannel(long channelId) {
ChannelRecord record = mDataManager.getChannelRecord(channelId);
@@ -172,10 +171,10 @@ public class Recommender implements RecommendationDataManager.Listener {
}
/**
- * Returns the sort key of a given channel Id. Sort key is determined in
- * {@link #recommendChannels()} and getChannelSortKey must be called after that.
+ * Returns the sort key of a given channel Id. Sort key is determined in {@link
+ * #recommendChannels()} and getChannelSortKey must be called after that.
*
- * If getChannelSortKey was called before evaluating the channels or trying to get sort key
+ * <p>If getChannelSortKey was called before evaluating the channels or trying to get sort key
* of non-recommended channel, it returns {@link #INVALID_CHANNEL_SORT_KEY}.
*/
public String getChannelSortKey(long channelId) {
@@ -231,27 +230,25 @@ public class Recommender implements RecommendationDataManager.Listener {
mLastRecommendationUpdatedTimeUtcMillis = newUpdatedTimeMs;
}
- public static abstract class Evaluator {
+ public abstract static class Evaluator {
public static final double NOT_RECOMMENDED = -1.0;
private Recommender mRecommender;
protected Evaluator() {}
- protected void onChannelRecordListChanged(List<ChannelRecord> channelRecords) {
- }
+ protected void onChannelRecordListChanged(List<ChannelRecord> channelRecords) {}
/**
* This will be called when a new watch log comes into WatchedPrograms table.
*
* @param channelRecord The channel record corresponds to the new watch log.
*/
- protected void onNewWatchLog(ChannelRecord channelRecord) {
- }
+ protected void onNewWatchLog(ChannelRecord channelRecord) {}
/**
- * The implementation should return the recommendation score for the given channel ID.
- * The return value should be in the range of [0.0, 1.0] or NOT_RECOMMENDED for denoting
- * that it gives up to calculate the score for the channel.
+ * The implementation should return the recommendation score for the given channel ID. The
+ * return value should be in the range of [0.0, 1.0] or NOT_RECOMMENDED for denoting that it
+ * gives up to calculate the score for the channel.
*
* @param channelId The channel ID which will be evaluated by this recommender.
* @return The recommendation score
@@ -278,8 +275,8 @@ public class Recommender implements RecommendationDataManager.Listener {
// this value.
private final double mWeight;
- public EvaluatorWrapper(Recommender recommender, Evaluator evaluator,
- double baseScore, double weight) {
+ public EvaluatorWrapper(
+ Recommender recommender, Evaluator evaluator, double baseScore, double weight) {
mEvaluator = evaluator;
evaluator.setRecommender(recommender);
mBaseScore = baseScore;
@@ -287,27 +284,27 @@ public class Recommender implements RecommendationDataManager.Listener {
}
/**
- * This returns the scaled score for the given channel ID based on the returned value
- * of evaluateChannel().
+ * This returns the scaled score for the given channel ID based on the returned value of
+ * evaluateChannel().
*
* @param channelId The channel ID which will be evaluated by the recommender.
* @return Returns the scaled score (mBaseScore + score * mWeight) when evaluateChannel() is
- * in the range of [0.0, 1.0]. If evaluateChannel() returns NOT_RECOMMENDED or any
- * negative numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more
- * than 1.0, it returns (mBaseScore + mWeight).
+ * in the range of [0.0, 1.0]. If evaluateChannel() returns NOT_RECOMMENDED or any
+ * negative numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more than
+ * 1.0, it returns (mBaseScore + mWeight).
*/
private double getScaledEvaluatorScore(long channelId) {
double score = mEvaluator.evaluateChannel(channelId);
if (score < 0.0) {
if (score != Evaluator.NOT_RECOMMENDED) {
- Log.w(TAG, "Unexpected score (" + score + ") from the recommender"
- + mEvaluator);
+ Log.w(
+ TAG,
+ "Unexpected score (" + score + ") from the recommender" + mEvaluator);
}
// If the recommender gives up to calculate the score, return 0.0
return Evaluator.NOT_RECOMMENDED;
} else if (score > 1.0) {
- Log.w(TAG, "Unexpected score (" + score + ") from the recommender"
- + mEvaluator);
+ Log.w(TAG, "Unexpected score (" + score + ") from the recommender" + mEvaluator);
score = 1.0;
}
return mBaseScore + score * mWeight;
@@ -323,14 +320,10 @@ public class Recommender implements RecommendationDataManager.Listener {
}
public interface Listener {
- /**
- * Called after channel record map is loaded.
- */
+ /** Called after channel record map is loaded. */
void onRecommenderReady();
- /**
- * Called when the recommendation changes.
- */
+ /** Called when the recommendation changes. */
void onRecommendationChanged();
}
}
diff --git a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java
index ad55afb7..edc23c53 100644
--- a/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java
+++ b/src/com/android/tv/recommendation/RecordedProgramPreviewUpdater.java
@@ -21,39 +21,32 @@ import android.os.Build;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.PreviewProgramContent;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.data.RecordedProgram;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-/**
- * Class to update the preview data for {@link RecordedProgram}
- */
+/** Class to update the preview data for {@link RecordedProgram} */
@RequiresApi(Build.VERSION_CODES.O)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
public class RecordedProgramPreviewUpdater {
private static final String TAG = "RecordedProgramPreviewUpdater";
- // STOPSHIP: set it to false.
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final int RECOMMENDATION_COUNT = 6;
private static RecordedProgramPreviewUpdater sRecordedProgramPreviewUpdater;
- /**
- * Creates and returns the {@link RecordedProgramPreviewUpdater}.
- */
+ /** Creates and returns the {@link RecordedProgramPreviewUpdater}. */
public static RecordedProgramPreviewUpdater getInstance(Context context) {
if (sRecordedProgramPreviewUpdater == null) {
- sRecordedProgramPreviewUpdater
- = new RecordedProgramPreviewUpdater(context.getApplicationContext());
+ sRecordedProgramPreviewUpdater =
+ new RecordedProgramPreviewUpdater(context.getApplicationContext());
}
return sRecordedProgramPreviewUpdater;
}
@@ -64,56 +57,56 @@ public class RecordedProgramPreviewUpdater {
private RecordedProgramPreviewUpdater(Context context) {
mContext = context.getApplicationContext();
- ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext);
- mPreviewDataManager = applicationSingletons.getPreviewDataManager();
- mDvrDataManager = applicationSingletons.getDvrDataManager();
- mDvrDataManager.addRecordedProgramListener(new DvrDataManager.RecordedProgramListener() {
- @Override
- public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
- if (DEBUG) Log.d(TAG, "Add new preview recorded programs");
- updatePreviewDataForRecordedPrograms();
- }
+ TvSingletons tvSingletons = TvSingletons.getSingletons(mContext);
+ mPreviewDataManager = tvSingletons.getPreviewDataManager();
+ mDvrDataManager = tvSingletons.getDvrDataManager();
+ mDvrDataManager.addRecordedProgramListener(
+ new DvrDataManager.RecordedProgramListener() {
+ @Override
+ public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
+ if (DEBUG) Log.d(TAG, "Add new preview recorded programs");
+ updatePreviewDataForRecordedPrograms();
+ }
- @Override
- public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
- if (DEBUG) Log.d(TAG, "Update preview recorded programs");
- updatePreviewDataForRecordedPrograms();
- }
+ @Override
+ public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
+ if (DEBUG) Log.d(TAG, "Update preview recorded programs");
+ updatePreviewDataForRecordedPrograms();
+ }
- @Override
- public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
- if (DEBUG) Log.d(TAG, "Delete preview recorded programs");
- updatePreviewDataForRecordedPrograms();
- }
- });
+ @Override
+ public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
+ if (DEBUG) Log.d(TAG, "Delete preview recorded programs");
+ updatePreviewDataForRecordedPrograms();
+ }
+ });
}
- /**
- * Updates the preview data for recorded programs.
- */
+ /** Updates the preview data for recorded programs. */
public void updatePreviewDataForRecordedPrograms() {
if (!mPreviewDataManager.isLoadFinished()) {
- mPreviewDataManager.addListener(new PreviewDataManager.PreviewDataListener() {
- @Override
- public void onPreviewDataLoadFinished() {
- mPreviewDataManager.removeListener(this);
- updatePreviewDataForRecordedPrograms();
- }
+ mPreviewDataManager.addListener(
+ new PreviewDataManager.PreviewDataListener() {
+ @Override
+ public void onPreviewDataLoadFinished() {
+ mPreviewDataManager.removeListener(this);
+ updatePreviewDataForRecordedPrograms();
+ }
- @Override
- public void onPreviewDataUpdateFinished() { }
- });
+ @Override
+ public void onPreviewDataUpdateFinished() {}
+ });
return;
}
if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
mDvrDataManager.addRecordedProgramLoadFinishedListener(
new DvrDataManager.OnRecordedProgramLoadFinishedListener() {
- @Override
- public void onRecordedProgramLoadFinished() {
- mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
- updatePreviewDataForRecordedPrograms();
- }
- });
+ @Override
+ public void onRecordedProgramLoadFinished() {
+ mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
+ updatePreviewDataForRecordedPrograms();
+ }
+ });
return;
}
updatePreviewDataForRecordedProgramsInternal();
@@ -121,15 +114,18 @@ public class RecordedProgramPreviewUpdater {
private void updatePreviewDataForRecordedProgramsInternal() {
Set<RecordedProgram> recordedPrograms = generateRecommendationRecordedPrograms();
- Long recordedPreviewChannelId = mPreviewDataManager.getPreviewChannelId(
- PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL);
+ Long recordedPreviewChannelId =
+ mPreviewDataManager.getPreviewChannelId(
+ PreviewDataManager.TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL);
if (recordedPreviewChannelId == PreviewDataManager.INVALID_PREVIEW_CHANNEL_ID
&& !recordedPrograms.isEmpty()) {
createPreviewChannelForRecordedPrograms();
} else {
- mPreviewDataManager.updatePreviewProgramsForChannel(recordedPreviewChannelId,
+ mPreviewDataManager.updatePreviewProgramsForChannel(
+ recordedPreviewChannelId,
generatePreviewProgramContentsFromRecordedPrograms(
- recordedPreviewChannelId, recordedPrograms), null);
+ recordedPreviewChannelId, recordedPrograms),
+ null);
}
}
@@ -168,8 +164,9 @@ public class RecordedProgramPreviewUpdater {
long previewChannelId, Set<RecordedProgram> recordedPrograms) {
Set<PreviewProgramContent> result = new HashSet<>();
for (RecordedProgram recordedProgram : recordedPrograms) {
- result.add(PreviewProgramContent.createFromRecordedProgram(mContext, previewChannelId,
- recordedProgram));
+ result.add(
+ PreviewProgramContent.createFromRecordedProgram(
+ mContext, previewChannelId, recordedProgram));
}
return result;
}
diff --git a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
index 5ff7cae9..9240682a 100644
--- a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
+++ b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
@@ -19,9 +19,7 @@ package com.android.tv.recommendation;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
-
import com.android.tv.data.Program;
-
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Calendar;
@@ -32,8 +30,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
// TODO: test and refine constant values in WatchedProgramRecommender in order to
// improve the performance of this recommender.
private static final double REQUIRED_MIN_SCORE = 0.15;
- @VisibleForTesting
- static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7;
+ @VisibleForTesting static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7;
private static final double TITLE_MATCH_WEIGHT = 0.5;
private static final double TIME_MATCH_WEIGHT = 1 - TITLE_MATCH_WEIGHT;
private static final long DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(14);
@@ -57,8 +54,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
}
Program watchedProgram = watchHistory[watchHistory.length - 1].getProgram();
- long startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis()
- - watchedProgram.getStartTimeUtcMillis();
+ long startTimeDiffMsWithCurrentProgram =
+ currentProgram.getStartTimeUtcMillis() - watchedProgram.getStartTimeUtcMillis();
if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) {
return NOT_RECOMMENDED;
}
@@ -70,42 +67,48 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
== watchHistory[i].getProgram().getStartTimeUtcMillis()) {
watchedDurationMs += watchHistory[i].getWatchedDurationMs();
} else {
- double score = calculateRoutineWatchScore(
- currentProgram, watchedProgram, watchedDurationMs);
+ double score =
+ calculateRoutineWatchScore(
+ currentProgram, watchedProgram, watchedDurationMs);
if (score >= REQUIRED_MIN_SCORE && score > maxScore) {
maxScore = score;
}
watchedProgram = watchHistory[i].getProgram();
watchedDurationMs = watchHistory[i].getWatchedDurationMs();
- startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis()
- - watchedProgram.getStartTimeUtcMillis();
+ startTimeDiffMsWithCurrentProgram =
+ currentProgram.getStartTimeUtcMillis()
+ - watchedProgram.getStartTimeUtcMillis();
if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) {
return maxScore;
}
}
}
- double score = calculateRoutineWatchScore(
- currentProgram, watchedProgram, watchedDurationMs);
+ double score =
+ calculateRoutineWatchScore(currentProgram, watchedProgram, watchedDurationMs);
if (score >= REQUIRED_MIN_SCORE && score > maxScore) {
maxScore = score;
}
return maxScore;
}
- private static double calculateRoutineWatchScore(Program currentProgram, Program watchedProgram,
- long watchedDurationMs) {
+ private static double calculateRoutineWatchScore(
+ Program currentProgram, Program watchedProgram, long watchedDurationMs) {
double timeMatchScore = calculateTimeMatchScore(currentProgram, watchedProgram);
- double titleMatchScore = calculateTitleMatchScore(
- currentProgram.getTitle(), watchedProgram.getTitle());
+ double titleMatchScore =
+ calculateTitleMatchScore(currentProgram.getTitle(), watchedProgram.getTitle());
double watchDurationScore = calculateWatchDurationScore(watchedProgram, watchedDurationMs);
- long diffMs = currentProgram.getStartTimeUtcMillis()
- - watchedProgram.getStartTimeUtcMillis();
- double multiplierForOldProgram = (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM)
- ? 1.0 - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0)
- / (MAX_DIFF_MS_FOR_OLD_PROGRAM - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM)
- : 0.0;
+ long diffMs =
+ currentProgram.getStartTimeUtcMillis() - watchedProgram.getStartTimeUtcMillis();
+ double multiplierForOldProgram =
+ (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM)
+ ? 1.0
+ - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0)
+ / (MAX_DIFF_MS_FOR_OLD_PROGRAM
+ - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM)
+ : 0.0;
return (titleMatchScore * TITLE_MATCH_WEIGHT + timeMatchScore * TIME_MATCH_WEIGHT)
- * watchDurationScore * multiplierForOldProgram;
+ * watchDurationScore
+ * multiplierForOldProgram;
}
@VisibleForTesting
@@ -118,8 +121,7 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
if (wordList1.isEmpty() || wordList2.isEmpty()) {
return 0;
}
- int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength(
- wordList1, wordList2);
+ int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength(wordList1, wordList2);
// F-measure score
double precision = (double) maxMatchedWordSeqLen / wordList1.size();
@@ -128,8 +130,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
}
@VisibleForTesting
- static int calculateMaximumMatchedWordSequenceLength(List<String> toSearchWords,
- List<String> toMatchWords) {
+ static int calculateMaximumMatchedWordSequenceLength(
+ List<String> toSearchWords, List<String> toMatchWords) {
int[] matchedWordSeqLen = new int[toMatchWords.size()];
int maxMatchedWordSeqLen = 0;
for (String word : toSearchWords) {
@@ -170,14 +172,20 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
boolean sameDay = false;
// Handle cases like (00:00 - 02:00) - (01:00 - 03:00) or (22:00 - 25:00) - (23:00 - 26:00).
- double score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec)
- - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec));
+ double score =
+ Math.max(
+ 0,
+ Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec)
+ - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec));
if (score > 0) {
sameDay = (t1.weekDay == t2.weekDay);
} else if (t1.dayChanged != t2.dayChanged) {
// To handle cases like t1 : (00:00 - 01:00) and t2 : (23:00 - 25:00).
- score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60)
- - t1.startTimeOfDayInSec);
+ score =
+ Math.max(
+ 0,
+ Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60)
+ - t1.startTimeOfDayInSec);
// Same day if next day of t2's start day equals to t1's start day. (1 <= weekDay <= 7)
sameDay = (t1.weekDay == ((t2.weekDay % 7) + 1));
}
@@ -206,7 +214,8 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
BreakIterator boundary = BreakIterator.getWordInstance();
boundary.setText(text);
int start = boundary.first();
- for (int end = boundary.next(); end != BreakIterator.DONE;
+ for (int end = boundary.next();
+ end != BreakIterator.DONE;
start = end, end = boundary.next()) {
String word = text.substring(start, end);
if (Character.isLetterOrDigit(word.charAt(0))) {
@@ -233,15 +242,20 @@ public class RoutineWatchEvaluator extends Recommender.Evaluator {
time.setTimeInMillis(p.getEndTimeUtcMillis());
boolean dayChanged = (weekDay != time.get(Calendar.DAY_OF_WEEK));
// Set maximum program duration time to 12 hours.
- int endTimeOfDayInSec = startTimeOfDayInSec +
- (int) Math.min(p.getEndTimeUtcMillis() - p.getStartTimeUtcMillis(),
- TimeUnit.HOURS.toMillis(12)) / 1000;
+ int endTimeOfDayInSec =
+ startTimeOfDayInSec
+ + (int)
+ Math.min(
+ p.getEndTimeUtcMillis()
+ - p.getStartTimeUtcMillis(),
+ TimeUnit.HOURS.toMillis(12))
+ / 1000;
return new ProgramTime(startTimeOfDayInSec, endTimeOfDayInSec, weekDay, dayChanged);
}
- private ProgramTime(int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay,
- boolean dayChanged) {
+ private ProgramTime(
+ int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay, boolean dayChanged) {
this.startTimeOfDayInSec = startTimeOfDayInSec;
this.endTimeOfDayInSec = endTimeOfDayInSec;
this.weekDay = weekDay;
diff --git a/src/com/android/tv/search/AutoValue_LocalSearchProvider_SearchResult.java b/src/com/android/tv/search/AutoValue_LocalSearchProvider_SearchResult.java
new file mode 100644
index 00000000..528096dd
--- /dev/null
+++ b/src/com/android/tv/search/AutoValue_LocalSearchProvider_SearchResult.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+
+
+package com.android.tv.search;
+
+import android.support.annotation.Nullable;
+
+/**
+ * Hand copy of generated Autovalue class.
+ *
+ * TODO get autovalue working
+ */
+
+final class AutoValue_LocalSearchProvider_SearchResult extends LocalSearchProvider.SearchResult {
+
+ private final long channelId;
+ private final String channelNumber;
+ private final String title;
+ private final String description;
+ private final String imageUri;
+ private final String intentAction;
+ private final String intentData;
+ private final String intentExtraData;
+ private final String contentType;
+ private final boolean isLive;
+ private final int videoWidth;
+ private final int videoHeight;
+ private final long duration;
+ private final int progressPercentage;
+
+ private AutoValue_LocalSearchProvider_SearchResult(
+ long channelId,
+ @Nullable String channelNumber,
+ @Nullable String title,
+ @Nullable String description,
+ @Nullable String imageUri,
+ @Nullable String intentAction,
+ @Nullable String intentData,
+ @Nullable String intentExtraData,
+ @Nullable String contentType,
+ boolean isLive,
+ int videoWidth,
+ int videoHeight,
+ long duration,
+ int progressPercentage) {
+ this.channelId = channelId;
+ this.channelNumber = channelNumber;
+ this.title = title;
+ this.description = description;
+ this.imageUri = imageUri;
+ this.intentAction = intentAction;
+ this.intentData = intentData;
+ this.intentExtraData = intentExtraData;
+ this.contentType = contentType;
+ this.isLive = isLive;
+ this.videoWidth = videoWidth;
+ this.videoHeight = videoHeight;
+ this.duration = duration;
+ this.progressPercentage = progressPercentage;
+ }
+
+ @Override
+ long getChannelId() {
+ return channelId;
+ }
+
+ @Nullable
+ @Override
+ String getChannelNumber() {
+ return channelNumber;
+ }
+
+ @Nullable
+ @Override
+ String getTitle() {
+ return title;
+ }
+
+ @Nullable
+ @Override
+ String getDescription() {
+ return description;
+ }
+
+ @Nullable
+ @Override
+ String getImageUri() {
+ return imageUri;
+ }
+
+ @Nullable
+ @Override
+ String getIntentAction() {
+ return intentAction;
+ }
+
+ @Nullable
+ @Override
+ String getIntentData() {
+ return intentData;
+ }
+
+ @Nullable
+ @Override
+ String getIntentExtraData() {
+ return intentExtraData;
+ }
+
+ @Nullable
+ @Override
+ String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ boolean getIsLive() {
+ return isLive;
+ }
+
+ @Override
+ int getVideoWidth() {
+ return videoWidth;
+ }
+
+ @Override
+ int getVideoHeight() {
+ return videoHeight;
+ }
+
+ @Override
+ long getDuration() {
+ return duration;
+ }
+
+ @Override
+ int getProgressPercentage() {
+ return progressPercentage;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchResult{"
+ + "channelId=" + channelId + ", "
+ + "channelNumber=" + channelNumber + ", "
+ + "title=" + title + ", "
+ + "description=" + description + ", "
+ + "imageUri=" + imageUri + ", "
+ + "intentAction=" + intentAction + ", "
+ + "intentData=" + intentData + ", "
+ + "intentExtraData=" + intentExtraData + ", "
+ + "contentType=" + contentType + ", "
+ + "isLive=" + isLive + ", "
+ + "videoWidth=" + videoWidth + ", "
+ + "videoHeight=" + videoHeight + ", "
+ + "duration=" + duration + ", "
+ + "progressPercentage=" + progressPercentage
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof LocalSearchProvider.SearchResult) {
+ LocalSearchProvider.SearchResult that = (LocalSearchProvider.SearchResult) o;
+ return (this.channelId == that.getChannelId())
+ && ((this.channelNumber == null) ? (that.getChannelNumber() == null) : this.channelNumber.equals(that.getChannelNumber()))
+ && ((this.title == null) ? (that.getTitle() == null) : this.title.equals(that.getTitle()))
+ && ((this.description == null) ? (that.getDescription() == null) : this.description.equals(that.getDescription()))
+ && ((this.imageUri == null) ? (that.getImageUri() == null) : this.imageUri.equals(that.getImageUri()))
+ && ((this.intentAction == null) ? (that.getIntentAction() == null) : this.intentAction.equals(that.getIntentAction()))
+ && ((this.intentData == null) ? (that.getIntentData() == null) : this.intentData.equals(that.getIntentData()))
+ && ((this.intentExtraData == null) ? (that.getIntentExtraData() == null) : this.intentExtraData.equals(that.getIntentExtraData()))
+ && ((this.contentType == null) ? (that.getContentType() == null) : this.contentType.equals(that.getContentType()))
+ && (this.isLive == that.getIsLive())
+ && (this.videoWidth == that.getVideoWidth())
+ && (this.videoHeight == that.getVideoHeight())
+ && (this.duration == that.getDuration())
+ && (this.progressPercentage == that.getProgressPercentage());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int h$ = 1;
+ h$ *= 1000003;
+ h$ ^= (int) ((channelId >>> 32) ^ channelId);
+ h$ *= 1000003;
+ h$ ^= (channelNumber == null) ? 0 : channelNumber.hashCode();
+ h$ *= 1000003;
+ h$ ^= (title == null) ? 0 : title.hashCode();
+ h$ *= 1000003;
+ h$ ^= (description == null) ? 0 : description.hashCode();
+ h$ *= 1000003;
+ h$ ^= (imageUri == null) ? 0 : imageUri.hashCode();
+ h$ *= 1000003;
+ h$ ^= (intentAction == null) ? 0 : intentAction.hashCode();
+ h$ *= 1000003;
+ h$ ^= (intentData == null) ? 0 : intentData.hashCode();
+ h$ *= 1000003;
+ h$ ^= (intentExtraData == null) ? 0 : intentExtraData.hashCode();
+ h$ *= 1000003;
+ h$ ^= (contentType == null) ? 0 : contentType.hashCode();
+ h$ *= 1000003;
+ h$ ^= isLive ? 1231 : 1237;
+ h$ *= 1000003;
+ h$ ^= videoWidth;
+ h$ *= 1000003;
+ h$ ^= videoHeight;
+ h$ *= 1000003;
+ h$ ^= (int) ((duration >>> 32) ^ duration);
+ h$ *= 1000003;
+ h$ ^= progressPercentage;
+ return h$;
+ }
+
+ static final class Builder extends LocalSearchProvider.SearchResult.Builder {
+ private Long channelId;
+ private String channelNumber;
+ private String title;
+ private String description;
+ private String imageUri;
+ private String intentAction;
+ private String intentData;
+ private String intentExtraData;
+ private String contentType;
+ private Boolean isLive;
+ private Integer videoWidth;
+ private Integer videoHeight;
+ private Long duration;
+ private Integer progressPercentage;
+ Builder() {
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setChannelId(long channelId) {
+ this.channelId = channelId;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setChannelNumber(@Nullable String channelNumber) {
+ this.channelNumber = channelNumber;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setTitle(@Nullable String title) {
+ this.title = title;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setImageUri(@Nullable String imageUri) {
+ this.imageUri = imageUri;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setIntentAction(@Nullable String intentAction) {
+ this.intentAction = intentAction;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setIntentData(@Nullable String intentData) {
+ this.intentData = intentData;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setIntentExtraData(@Nullable String intentExtraData) {
+ this.intentExtraData = intentExtraData;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setContentType(@Nullable String contentType) {
+ this.contentType = contentType;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setIsLive(boolean isLive) {
+ this.isLive = isLive;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setVideoWidth(int videoWidth) {
+ this.videoWidth = videoWidth;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setVideoHeight(int videoHeight) {
+ this.videoHeight = videoHeight;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setDuration(long duration) {
+ this.duration = duration;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult.Builder setProgressPercentage(int progressPercentage) {
+ this.progressPercentage = progressPercentage;
+ return this;
+ }
+ @Override
+ LocalSearchProvider.SearchResult build() {
+ String missing = "";
+ if (this.channelId == null) {
+ missing += " channelId";
+ }
+ if (this.isLive == null) {
+ missing += " isLive";
+ }
+ if (this.videoWidth == null) {
+ missing += " videoWidth";
+ }
+ if (this.videoHeight == null) {
+ missing += " videoHeight";
+ }
+ if (this.duration == null) {
+ missing += " duration";
+ }
+ if (this.progressPercentage == null) {
+ missing += " progressPercentage";
+ }
+ if (!missing.isEmpty()) {
+ throw new IllegalStateException("Missing required properties:" + missing);
+ }
+ return new AutoValue_LocalSearchProvider_SearchResult(
+ this.channelId,
+ this.channelNumber,
+ this.title,
+ this.description,
+ this.imageUri,
+ this.intentAction,
+ this.intentData,
+ this.intentExtraData,
+ this.contentType,
+ this.isLive,
+ this.videoWidth,
+ this.videoHeight,
+ this.duration,
+ this.progressPercentage);
+ }
+ }
+
+}
diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java
index d90908f1..82fb5016 100644
--- a/src/com/android/tv/search/DataManagerSearch.java
+++ b/src/com/android/tv/search/DataManagerSearch.java
@@ -26,17 +26,14 @@ import android.os.SystemClock;
import android.support.annotation.MainThread;
import android.text.TextUtils;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
+import com.android.tv.TvSingletons;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.search.LocalSearchProvider.SearchResult;
import com.android.tv.util.MainThreadExecutor;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -47,11 +44,11 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
- * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager}
- * and {@link ProgramDataManager}.
+ * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager} and
+ * {@link ProgramDataManager}.
*/
public class DataManagerSearch implements SearchInterface {
- private static final String TAG = "TvProviderSearch";
+ private static final String TAG = "DataManagerSearch";
private static final boolean DEBUG = false;
private final Context mContext;
@@ -62,20 +59,22 @@ public class DataManagerSearch implements SearchInterface {
DataManagerSearch(Context context) {
mContext = context;
mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mChannelDataManager = appSingletons.getChannelDataManager();
- mProgramDataManager = appSingletons.getProgramDataManager();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mChannelDataManager = tvSingletons.getChannelDataManager();
+ mProgramDataManager = tvSingletons.getProgramDataManager();
}
@Override
public List<SearchResult> search(final String query, final int limit, final int action) {
- Future<List<SearchResult>> future = MainThreadExecutor.getInstance()
- .submit(new Callable<List<SearchResult>>() {
- @Override
- public List<SearchResult> call() throws Exception {
- return searchFromDataManagers(query, limit, action);
- }
- });
+ Future<List<SearchResult>> future =
+ MainThreadExecutor.getInstance()
+ .submit(
+ new Callable<List<SearchResult>>() {
+ @Override
+ public List<SearchResult> call() throws Exception {
+ return searchFromDataManagers(query, limit, action);
+ }
+ });
try {
return future.get();
@@ -90,12 +89,12 @@ public class DataManagerSearch implements SearchInterface {
@MainThread
private List<SearchResult> searchFromDataManagers(String query, int limit, int action) {
+ // TODO(b/72499165): add a test.
List<SearchResult> results = new ArrayList<>();
if (!mChannelDataManager.isDbLoadFinished()) {
return results;
}
- if (action == ACTION_TYPE_SWITCH_CHANNEL
- || action == ACTION_TYPE_SWITCH_INPUT) {
+ if (action == ACTION_TYPE_SWITCH_CHANNEL || action == ACTION_TYPE_SWITCH_INPUT) {
// Voice search query should be handled by the a system TV app.
return results;
}
@@ -114,9 +113,14 @@ public class DataManagerSearch implements SearchInterface {
}
if (results.size() >= limit) {
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for" +
- " searching channels: " + (SystemClock.elapsedRealtime() - time) +
- "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " channels. Elapsed time for"
+ + " searching channels: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
@@ -133,16 +137,27 @@ public class DataManagerSearch implements SearchInterface {
}
if (results.size() >= limit) {
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for" +
- " searching channels: " + (SystemClock.elapsedRealtime() - time) +
- "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " channels. Elapsed time for"
+ + " searching channels: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
}
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for" +
- " searching channels: " + (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " channels. Elapsed time for"
+ + " searching channels: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
int channelResult = results.size();
if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
@@ -161,9 +176,14 @@ public class DataManagerSearch implements SearchInterface {
}
if (results.size() >= limit) {
if (DEBUG) {
- Log.d(TAG, "Found " + (results.size() - channelResult) + " programs. Elapsed" +
- " time for searching programs: " +
- (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + (results.size() - channelResult)
+ + " programs. Elapsed"
+ + " time for searching programs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
@@ -182,16 +202,27 @@ public class DataManagerSearch implements SearchInterface {
}
if (results.size() >= limit) {
if (DEBUG) {
- Log.d(TAG, "Found " + (results.size() - channelResult) + " programs. Elapsed" +
- " time for searching programs: " +
- (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + (results.size() - channelResult)
+ + " programs. Elapsed"
+ + " time for searching programs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
}
if (DEBUG) {
- Log.d(TAG, "Found " + (results.size() - channelResult) + " programs. Elapsed time for" +
- " searching programs: " + (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + (results.size() - channelResult)
+ + " programs. Elapsed time for"
+ + " searching programs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
@@ -201,11 +232,9 @@ public class DataManagerSearch implements SearchInterface {
return string != null && string.toLowerCase().contains(query);
}
- /**
- * If query is matched to channel, {@code program} should be null.
- */
- private void addResult(List<SearchResult> results, Set<Long> channelsFound, Channel channel,
- Program program) {
+ /** If query is matched to channel, {@code program} should be null. */
+ private void addResult(
+ List<SearchResult> results, Set<Long> channelsFound, Channel channel, Program program) {
if (program == null) {
program = mProgramDataManager.getCurrentProgram(channel.getId());
if (program != null && isRatingBlocked(program.getContentRatings())) {
@@ -213,47 +242,58 @@ public class DataManagerSearch implements SearchInterface {
}
}
- SearchResult result = new SearchResult();
+ SearchResult.Builder result = SearchResult.builder();
long channelId = channel.getId();
- result.channelId = channelId;
- result.channelNumber = channel.getDisplayNumber();
+ result.setChannelId(channelId);
+ result.setChannelNumber(channel.getDisplayNumber());
if (program == null) {
- result.title = channel.getDisplayName();
- result.description = channel.getDescription();
- result.imageUri = TvContract.buildChannelLogoUri(channelId).toString();
- result.intentAction = Intent.ACTION_VIEW;
- result.intentData = buildIntentData(channelId);
- result.contentType = Programs.CONTENT_ITEM_TYPE;
- result.isLive = true;
- result.progressPercentage = LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
+ result.setTitle(channel.getDisplayName());
+ result.setDescription(channel.getDescription());
+ result.setImageUri(TvContract.buildChannelLogoUri(channelId).toString());
+ result.setIntentAction(Intent.ACTION_VIEW);
+ result.setIntentData(buildIntentData(channelId));
+ result.setContentType(Programs.CONTENT_ITEM_TYPE);
+ result.setIsLive(true);
+ result.setProgressPercentage(LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE);
} else {
- result.title = program.getTitle();
- result.description = buildProgramDescription(channel.getDisplayNumber(),
- channel.getDisplayName(), program.getStartTimeUtcMillis(),
- program.getEndTimeUtcMillis());
- result.imageUri = program.getPosterArtUri();
- result.intentAction = Intent.ACTION_VIEW;
- result.intentData = buildIntentData(channelId);
- result.contentType = Programs.CONTENT_ITEM_TYPE;
- result.isLive = true;
- result.videoWidth = program.getVideoWidth();
- result.videoHeight = program.getVideoHeight();
- result.duration = program.getDurationMillis();
- result.progressPercentage = getProgressPercentage(
- program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis());
+ result.setTitle(program.getTitle());
+ result.setDescription(
+ buildProgramDescription(
+ channel.getDisplayNumber(),
+ channel.getDisplayName(),
+ program.getStartTimeUtcMillis(),
+ program.getEndTimeUtcMillis()));
+ result.setImageUri(program.getPosterArtUri());
+ result.setIntentAction(Intent.ACTION_VIEW);
+ result.setIntentData(buildIntentData(channelId));
+ result.setIntentExtraData(TvContract.buildProgramUri(program.getId()).toString());
+ result.setContentType(Programs.CONTENT_ITEM_TYPE);
+ result.setIsLive(true);
+ result.setVideoWidth(program.getVideoWidth());
+ result.setVideoHeight(program.getVideoHeight());
+ result.setDuration(program.getDurationMillis());
+ result.setProgressPercentage(
+ getProgressPercentage(
+ program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()));
}
if (DEBUG) {
Log.d(TAG, "Add a result : channel=" + channel + " program=" + program);
}
- results.add(result);
+ results.add(result.build());
channelsFound.add(channel.getId());
}
- private String buildProgramDescription(String channelNumber, String channelName,
- long programStartUtcMillis, long programEndUtcMillis) {
+ private String buildProgramDescription(
+ String channelNumber,
+ String channelName,
+ long programStartUtcMillis,
+ long programEndUtcMillis) {
return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
- + System.lineSeparator() + channelNumber + " " + channelName;
+ + System.lineSeparator()
+ + channelNumber
+ + " "
+ + channelName;
}
private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
@@ -261,7 +301,7 @@ public class DataManagerSearch implements SearchInterface {
if (startUtcMillis > current || endUtcMillis <= current) {
return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
}
- return (int)(100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
+ return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
}
private String buildIntentData(long channelId) {
@@ -269,7 +309,8 @@ public class DataManagerSearch implements SearchInterface {
}
private boolean isRatingBlocked(TvContentRating[] ratings) {
- if (ratings == null || ratings.length == 0
+ if (ratings == null
+ || ratings.length == 0
|| !mTvInputManager.isParentalControlsEnabled()) {
return false;
}
diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java
index ef9336d7..97e7f229 100644
--- a/src/com/android/tv/search/LocalSearchProvider.java
+++ b/src/com/android/tv/search/LocalSearchProvider.java
@@ -24,19 +24,19 @@ import android.database.MatrixCursor;
import android.net.Uri;
import android.os.SystemClock;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
-
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.CommonConstants;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.perf.EventNames;
import com.android.tv.perf.PerformanceMonitor;
import com.android.tv.perf.TimerEvent;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvUriMatcher;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -46,32 +46,34 @@ public class LocalSearchProvider extends ContentProvider {
private static final boolean DEBUG = false;
/** The authority for LocalSearchProvider. */
- public static final String AUTHORITY = "com.android.tv.search";
+ public static final String AUTHORITY = CommonConstants.BASE_PACKAGE + ".search";
public static final int PROGRESS_PERCENTAGE_HIDE = -1;
// TODO: Remove this once added to the SearchManager.
private static final String SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE = "progress_bar_percentage";
- private static final String[] SEARCHABLE_COLUMNS = new String[] {
- SearchManager.SUGGEST_COLUMN_TEXT_1,
- SearchManager.SUGGEST_COLUMN_TEXT_2,
- SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE,
- SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
- SearchManager.SUGGEST_COLUMN_INTENT_DATA,
- SearchManager.SUGGEST_COLUMN_CONTENT_TYPE,
- SearchManager.SUGGEST_COLUMN_IS_LIVE,
- SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH,
- SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT,
- SearchManager.SUGGEST_COLUMN_DURATION,
- SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE
- };
+ private static final String[] SEARCHABLE_COLUMNS =
+ new String[] {
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
+ SearchManager.SUGGEST_COLUMN_TEXT_2,
+ SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE,
+ SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA,
+ SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
+ SearchManager.SUGGEST_COLUMN_CONTENT_TYPE,
+ SearchManager.SUGGEST_COLUMN_IS_LIVE,
+ SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH,
+ SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT,
+ SearchManager.SUGGEST_COLUMN_DURATION,
+ SUGGEST_COLUMN_PROGRESS_BAR_PERCENTAGE
+ };
private static final String EXPECTED_PATH_PREFIX = "/" + SearchManager.SUGGEST_URI_PATH_QUERY;
static final String SUGGEST_PARAMETER_ACTION = "action";
// The launcher passes 10 as a 'limit' parameter by default.
- @VisibleForTesting
- static final int DEFAULT_SEARCH_LIMIT = 10;
+ @VisibleForTesting static final int DEFAULT_SEARCH_LIMIT = 10;
+
@VisibleForTesting
static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS;
@@ -85,26 +87,41 @@ public class LocalSearchProvider extends ContentProvider {
@Override
public boolean onCreate() {
- mPerformanceMonitor = TvApplication.getSingletons(getContext()).getPerformanceMonitor();
+ mPerformanceMonitor = TvSingletons.getSingletons(getContext()).getPerformanceMonitor();
return true;
}
@VisibleForTesting
void setSearchInterface(SearchInterface searchInterface) {
- SoftPreconditions.checkState(TvCommonUtils.isRunningInTest());
+ SoftPreconditions.checkState(CommonUtils.isRunningInTest());
mSearchInterface = searchInterface;
}
@Override
- public Cursor query(@NonNull Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
+ public Cursor query(
+ @NonNull Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
if (TvUriMatcher.match(uri) != TvUriMatcher.MATCH_ON_DEVICE_SEARCH) {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
TimerEvent queryTimer = mPerformanceMonitor.startTimer();
if (DEBUG) {
- Log.d(TAG, "query(" + uri + ", " + Arrays.toString(projection) + ", " + selection + ", "
- + Arrays.toString(selectionArgs) + ", " + sortOrder + ")");
+ Log.d(
+ TAG,
+ "query("
+ + uri
+ + ", "
+ + Arrays.toString(projection)
+ + ", "
+ + selection
+ + ", "
+ + Arrays.toString(selectionArgs)
+ + ", "
+ + sortOrder
+ + ")");
}
long time = SystemClock.elapsedRealtime();
SearchInterface search = mSearchInterface;
@@ -118,8 +135,8 @@ public class LocalSearchProvider extends ContentProvider {
}
}
String query = uri.getLastPathSegment();
- int limit = getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT,
- DEFAULT_SEARCH_LIMIT);
+ int limit =
+ getQueryParamater(uri, SearchManager.SUGGEST_PARAMETER_LIMIT, DEFAULT_SEARCH_LIMIT);
if (limit <= 0) {
limit = DEFAULT_SEARCH_LIMIT;
}
@@ -134,8 +151,13 @@ public class LocalSearchProvider extends ContentProvider {
}
Cursor c = createSuggestionsCursor(results);
if (DEBUG) {
- Log.d(TAG, "Elapsed time(count=" + c.getCount() + "): "
- + (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Elapsed time(count="
+ + c.getCount()
+ + "): "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
mPerformanceMonitor.stopTimer(queryTimer, EventNames.ON_DEVICE_SEARCH);
return c;
@@ -157,17 +179,18 @@ public class LocalSearchProvider extends ContentProvider {
int index = 0;
for (SearchResult result : results) {
row.clear();
- row.add(result.title);
- row.add(result.description);
- row.add(result.imageUri);
- row.add(result.intentAction);
- row.add(result.intentData);
- row.add(result.contentType);
- row.add(result.isLive ? LIVE_CONTENTS : NO_LIVE_CONTENTS);
- row.add(result.videoWidth == 0 ? null : String.valueOf(result.videoWidth));
- row.add(result.videoHeight == 0 ? null : String.valueOf(result.videoHeight));
- row.add(result.duration == 0 ? null : String.valueOf(result.duration));
- row.add(String.valueOf(result.progressPercentage));
+ row.add(result.getTitle());
+ row.add(result.getDescription());
+ row.add(result.getImageUri());
+ row.add(result.getIntentAction());
+ row.add(result.getIntentData());
+ row.add(result.getIntentExtraData());
+ row.add(result.getContentType());
+ row.add(result.getIsLive() ? LIVE_CONTENTS : NO_LIVE_CONTENTS);
+ row.add(result.getVideoWidth() == 0 ? null : String.valueOf(result.getVideoWidth()));
+ row.add(result.getVideoHeight() == 0 ? null : String.valueOf(result.getVideoHeight()));
+ row.add(result.getDuration() == 0 ? null : String.valueOf(result.getDuration()));
+ row.add(String.valueOf(result.getProgressPercentage()));
cursor.addRow(row);
if (DEBUG) Log.d(TAG, "Result[" + (++index) + "]: " + result);
}
@@ -199,40 +222,87 @@ public class LocalSearchProvider extends ContentProvider {
throw new UnsupportedOperationException();
}
- /**
- * A placeholder to a search result.
- */
- public static class SearchResult {
- public long channelId;
- public String channelNumber;
- public String title;
- public String description;
- public String imageUri;
- public String intentAction;
- public String intentData;
- public String contentType;
- public boolean isLive;
- public int videoWidth;
- public int videoHeight;
- public long duration;
- public int progressPercentage;
-
- @Override
- public String toString() {
- return "SearchResult{channelId=" + channelId +
- ", channelNumber=" + channelNumber +
- ", title=" + title +
- ", description=" + description +
- ", imageUri=" + imageUri +
- ", intentAction=" + intentAction +
- ", intentData=" + intentData +
- ", contentType=" + contentType +
- ", isLive=" + isLive +
- ", videoWidth=" + videoWidth +
- ", videoHeight=" + videoHeight +
- ", duration=" + duration +
- ", progressPercentage=" + progressPercentage +
- "}";
+ /** A placeholder to a search result. */
+ // TODO(b/72052568): Get autovalue to work in aosp master
+ public abstract static class SearchResult {
+ public static Builder builder() {
+ // primitive fields cannot be nullable. Set to default;
+ return new AutoValue_LocalSearchProvider_SearchResult.Builder()
+ .setChannelId(0)
+ .setIsLive(false)
+ .setVideoWidth(0)
+ .setVideoHeight(0)
+ .setDuration(0)
+ .setProgressPercentage(0);
+ }
+
+ // TODO(b/72052568): Get autovalue to work in aosp master
+ abstract static class Builder {
+ abstract Builder setChannelId(long value);
+
+ abstract Builder setChannelNumber(String value);
+
+ abstract Builder setTitle(String value);
+
+ abstract Builder setDescription(String value);
+
+ abstract Builder setImageUri(String value);
+
+ abstract Builder setIntentAction(String value);
+
+ abstract Builder setIntentData(String value);
+
+ abstract Builder setIntentExtraData(String value);
+
+ abstract Builder setContentType(String value);
+
+ abstract Builder setIsLive(boolean value);
+
+ abstract Builder setVideoWidth(int value);
+
+ abstract Builder setVideoHeight(int value);
+
+ abstract Builder setDuration(long value);
+
+ abstract Builder setProgressPercentage(int value);
+
+ abstract SearchResult build();
}
+
+ abstract long getChannelId();
+
+ @Nullable
+ abstract String getChannelNumber();
+
+ @Nullable
+ abstract String getTitle();
+
+ @Nullable
+ abstract String getDescription();
+
+ @Nullable
+ abstract String getImageUri();
+
+ @Nullable
+ abstract String getIntentAction();
+
+ @Nullable
+ abstract String getIntentData();
+
+ @Nullable
+ abstract String getIntentExtraData();
+
+ @Nullable
+ abstract String getContentType();
+
+ abstract boolean getIsLive();
+
+ abstract int getVideoWidth();
+
+ abstract int getVideoHeight();
+
+ abstract long getDuration();
+
+ abstract int getProgressPercentage();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java
index 87eec68e..cb26252b 100644
--- a/src/com/android/tv/search/ProgramGuideSearchFragment.java
+++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java
@@ -38,12 +38,10 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.util.ImageLoader;
-import com.android.tv.util.PermissionUtils;
-
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.util.images.ImageLoader;
import java.util.List;
public class ProgramGuideSearchFragment extends SearchFragment {
@@ -51,44 +49,50 @@ public class ProgramGuideSearchFragment extends SearchFragment {
private static final boolean DEBUG = false;
private static final int SEARCH_RESULT_MAX = 10;
- private final Presenter mPresenter = new Presenter() {
- @Override
- public Presenter.ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
- if (DEBUG) Log.d(TAG, "onCreateViewHolder");
-
- ImageCardView cardView = new ImageCardView(mMainActivity);
- cardView.setFocusable(true);
- cardView.setFocusableInTouchMode(true);
- cardView.setMainImageAdjustViewBounds(false);
-
- Resources res = mMainActivity.getResources();
- cardView.setMainImageDimensions(
- res.getDimensionPixelSize(R.dimen.card_image_layout_width),
- res.getDimensionPixelSize(R.dimen.card_image_layout_height));
-
- return new Presenter.ViewHolder(cardView);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder viewHolder, Object o) {
- ImageCardView cardView = (ImageCardView) viewHolder.view;
- LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o;
- if (DEBUG) Log.d(TAG, "onBindViewHolder result:" + result);
-
- cardView.setTitleText(result.title);
- if (!TextUtils.isEmpty(result.imageUri)) {
- ImageLoader.loadBitmap(mMainActivity, result.imageUri, mMainCardWidth,
- mMainCardHeight, createImageLoaderCallback(cardView));
- } else {
- cardView.setMainImage(mMainActivity.getDrawable(R.drawable.ic_launcher));
- }
- }
-
- @Override
- public void onUnbindViewHolder(ViewHolder viewHolder) {
- // Do nothing here.
- }
- };
+ private final Presenter mPresenter =
+ new Presenter() {
+ @Override
+ public Presenter.ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
+ if (DEBUG) Log.d(TAG, "onCreateViewHolder");
+
+ ImageCardView cardView = new ImageCardView(mMainActivity);
+ cardView.setFocusable(true);
+ cardView.setFocusableInTouchMode(true);
+ cardView.setMainImageAdjustViewBounds(false);
+
+ Resources res = mMainActivity.getResources();
+ cardView.setMainImageDimensions(
+ res.getDimensionPixelSize(R.dimen.card_image_layout_width),
+ res.getDimensionPixelSize(R.dimen.card_image_layout_height));
+
+ return new Presenter.ViewHolder(cardView);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, Object o) {
+ ImageCardView cardView = (ImageCardView) viewHolder.view;
+ LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o;
+ if (DEBUG) Log.d(TAG, "onBindViewHolder result:" + result);
+
+ cardView.setTitleText(result.getTitle());
+ if (!TextUtils.isEmpty(result.getImageUri())) {
+ ImageLoader.loadBitmap(
+ mMainActivity,
+ result.getImageUri(),
+ mMainCardWidth,
+ mMainCardHeight,
+ createImageLoaderCallback(cardView));
+ } else {
+ cardView.setMainImage(
+ mMainActivity.getDrawable(R.drawable.ic_live_channels_96x96));
+ }
+ }
+
+ @Override
+ public void onUnbindViewHolder(ViewHolder viewHolder) {
+ // Do nothing here.
+ }
+ };
private static ImageLoader.ImageLoaderCallback<ImageCardView> createImageLoaderCallback(
ImageCardView cardView) {
@@ -101,35 +105,42 @@ public class ProgramGuideSearchFragment extends SearchFragment {
};
}
- private final SearchResultProvider mSearchResultProvider = new SearchResultProvider() {
- @Override
- public ObjectAdapter getResultsAdapter() {
- return mResultAdapter;
- }
-
- @Override
- public boolean onQueryTextChange(String query) {
- searchAndRefresh(query);
- return true;
- }
-
- @Override
- public boolean onQueryTextSubmit(String query) {
- searchAndRefresh(query);
- return true;
- }
- };
-
- private final OnItemViewClickedListener mItemClickedListener = new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder viewHolder, Object o, RowPresenter
- .ViewHolder viewHolder1, Row row) {
- LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o;
- mMainActivity.getFragmentManager().popBackStack();
- mMainActivity.tuneToChannel(
- mMainActivity.getChannelDataManager().getChannel(result.channelId));
- }
- };
+ private final SearchResultProvider mSearchResultProvider =
+ new SearchResultProvider() {
+ @Override
+ public ObjectAdapter getResultsAdapter() {
+ return mResultAdapter;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String query) {
+ searchAndRefresh(query);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ searchAndRefresh(query);
+ return true;
+ }
+ };
+
+ private final OnItemViewClickedListener mItemClickedListener =
+ new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(
+ Presenter.ViewHolder viewHolder,
+ Object o,
+ RowPresenter.ViewHolder viewHolder1,
+ Row row) {
+ LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o;
+ mMainActivity.getFragmentManager().popBackStack();
+ mMainActivity.tuneToChannel(
+ mMainActivity
+ .getChannelDataManager()
+ .getChannel(result.getChannelId()));
+ }
+ };
private final ArrayObjectAdapter mResultAdapter =
new ArrayObjectAdapter(new ListRowPresenter());
@@ -160,7 +171,7 @@ public class ProgramGuideSearchFragment extends SearchFragment {
View v = super.onCreateView(inflater, container, savedInstanceState);
v.setBackgroundResource(R.color.program_guide_scrim);
- setBadgeDrawable(mMainActivity.getDrawable(R.drawable.ic_launcher));
+ setBadgeDrawable(mMainActivity.getDrawable(R.drawable.ic_live_channels_96x96));
setSearchResultProvider(mSearchResultProvider);
setOnItemViewClickedListener(mItemClickedListener);
return v;
@@ -185,8 +196,7 @@ public class ProgramGuideSearchFragment extends SearchFragment {
mSearchTask.execute();
}
- private class SearchTask extends
- AsyncTask<Void, Void, List<LocalSearchProvider.SearchResult>> {
+ private class SearchTask extends AsyncTask<Void, Void, List<LocalSearchProvider.SearchResult>> {
private final String mQuery;
public SearchTask(String query) {
@@ -195,8 +205,8 @@ public class ProgramGuideSearchFragment extends SearchFragment {
@Override
protected List<LocalSearchProvider.SearchResult> doInBackground(Void... params) {
- return mSearch.search(mQuery, SEARCH_RESULT_MAX,
- TvProviderSearch.ACTION_TYPE_AMBIGUOUS);
+ return mSearch.search(
+ mQuery, SEARCH_RESULT_MAX, TvProviderSearch.ACTION_TYPE_AMBIGUOUS);
}
@Override
@@ -205,20 +215,23 @@ public class ProgramGuideSearchFragment extends SearchFragment {
mResultAdapter.clear();
if (DEBUG) {
- Log.d(TAG, "searchAndRefresh query=" + mQuery
- + " results=" + ((results == null) ? 0 : results.size()));
+ Log.d(
+ TAG,
+ "searchAndRefresh query="
+ + mQuery
+ + " results="
+ + ((results == null) ? 0 : results.size()));
}
if (results == null || results.size() == 0) {
HeaderItem header =
- new HeaderItem(0, mMainActivity.getString(R.string
- .search_result_no_result));
+ new HeaderItem(
+ 0, mMainActivity.getString(R.string.search_result_no_result));
ArrayObjectAdapter resultsAdapter = new ArrayObjectAdapter(mPresenter);
mResultAdapter.add(new ListRow(header, resultsAdapter));
} else {
HeaderItem header =
- new HeaderItem(0, mMainActivity.getString(R.string
- .search_result_title));
+ new HeaderItem(0, mMainActivity.getString(R.string.search_result_title));
ArrayObjectAdapter resultsAdapter = new ArrayObjectAdapter(mPresenter);
resultsAdapter.addAll(0, results);
mResultAdapter.add(new ListRow(header, resultsAdapter));
diff --git a/src/com/android/tv/search/SearchInterface.java b/src/com/android/tv/search/SearchInterface.java
index d631972a..4866ee84 100644
--- a/src/com/android/tv/search/SearchInterface.java
+++ b/src/com/android/tv/search/SearchInterface.java
@@ -17,12 +17,9 @@
package com.android.tv.search;
import com.android.tv.search.LocalSearchProvider.SearchResult;
-
import java.util.List;
-/**
- * Interface for channel and program search.
- */
+/** Interface for channel and program search. */
public interface SearchInterface {
int ACTION_TYPE_START = 1;
int ACTION_TYPE_AMBIGUOUS = 1;
@@ -31,11 +28,11 @@ public interface SearchInterface {
int ACTION_TYPE_END = 3;
/**
- * Search channels, inputs, or programs.
- * This assumes that parental control settings will not be change while searching.
+ * Search channels, inputs, or programs. This assumes that parental control settings will not be
+ * change while searching.
*
* @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT},
- * or {@link #ACTION_TYPE_AMBIGUOUS},
+ * or {@link #ACTION_TYPE_AMBIGUOUS},
*/
List<SearchResult> search(String query, int limit, int action);
}
diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java
index e7d8a02d..92197f2d 100644
--- a/src/com/android/tv/search/TvProviderSearch.java
+++ b/src/com/android/tv/search/TvProviderSearch.java
@@ -32,12 +32,10 @@ import android.os.SystemClock;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
-
import com.android.tv.common.TvContentRatingCache;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.search.LocalSearchProvider.SearchResult;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -48,14 +46,15 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
-/**
- * An implementation of {@link SearchInterface} to search query from TvProvider directly.
- */
+/** An implementation of {@link SearchInterface} to search query from TvProvider directly. */
public class TvProviderSearch implements SearchInterface {
private static final String TAG = "TvProviderSearch";
private static final boolean DEBUG = false;
+ private static final long SEARCH_TIME_FRAME_MS = TimeUnit.DAYS.toMillis(14);
+
private static final int NO_LIMIT = 0;
private final Context mContext;
@@ -70,15 +69,16 @@ public class TvProviderSearch implements SearchInterface {
}
/**
- * Search channels, inputs, or programs from TvProvider.
- * This assumes that parental control settings will not be change while searching.
+ * Search channels, inputs, or programs from TvProvider. This assumes that parental control
+ * settings will not be change while searching.
*
* @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT},
- * or {@link #ACTION_TYPE_AMBIGUOUS},
+ * or {@link #ACTION_TYPE_AMBIGUOUS},
*/
@Override
@WorkerThread
public List<SearchResult> search(String query, int limit, int action) {
+ // TODO(b/72499463): add a test.
List<SearchResult> results = new ArrayList<>();
if (!PermissionUtils.hasAccessAllEpg(mContext)) {
// TODO: support this feature for non-system LC app. b/23939816
@@ -107,15 +107,19 @@ public class TvProviderSearch implements SearchInterface {
// Lastly, search programs.
limit -= results.size();
- results.addAll(searchPrograms(query, null, new String[] {
- Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION },
- channelsFound, limit));
+ results.addAll(
+ searchPrograms(
+ query,
+ null,
+ new String[] {Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION},
+ channelsFound,
+ limit));
}
return results;
}
- private void appendSelectionString(StringBuilder sb, String[] columnForExactMatching,
- String[] columnForPartialMatching) {
+ private void appendSelectionString(
+ StringBuilder sb, String[] columnForExactMatching, String[] columnForPartialMatching) {
boolean firstColumn = true;
if (columnForExactMatching != null) {
for (String column : columnForExactMatching) {
@@ -139,8 +143,12 @@ public class TvProviderSearch implements SearchInterface {
}
}
- private void insertSelectionArgumentStrings(String[] selectionArgs, int pos,
- String query, String[] columnForExactMatching, String[] columnForPartialMatching) {
+ private void insertSelectionArgumentStrings(
+ String[] selectionArgs,
+ int pos,
+ String query,
+ String[] columnForExactMatching,
+ String[] columnForPartialMatching) {
if (columnForExactMatching != null) {
int until = pos + columnForExactMatching.length;
for (; pos < until; ++pos) {
@@ -162,43 +170,66 @@ public class TvProviderSearch implements SearchInterface {
long time = SystemClock.elapsedRealtime();
List<SearchResult> results = new ArrayList<>();
if (TextUtils.isDigitsOnly(query)) {
- results.addAll(searchChannels(query, new String[] { Channels.COLUMN_DISPLAY_NUMBER },
- null, channels, NO_LIMIT));
+ results.addAll(
+ searchChannels(
+ query,
+ new String[] {Channels.COLUMN_DISPLAY_NUMBER},
+ null,
+ channels,
+ NO_LIMIT));
if (results.size() > 1) {
Collections.sort(results, new ChannelComparatorWithSameDisplayNumber());
}
}
if (results.size() < limit) {
- results.addAll(searchChannels(query, null,
- new String[] { Channels.COLUMN_DISPLAY_NAME, Channels.COLUMN_DESCRIPTION },
- channels, limit - results.size()));
+ results.addAll(
+ searchChannels(
+ query,
+ null,
+ new String[] {
+ Channels.COLUMN_DISPLAY_NAME, Channels.COLUMN_DESCRIPTION
+ },
+ channels,
+ limit - results.size()));
}
if (results.size() > limit) {
results = results.subList(0, limit);
}
- for (SearchResult result : results) {
- fillProgramInfo(result);
+ for (int i = 0; i < results.size(); i++) {
+ results.set(i, fillProgramInfo(results.get(i)));
}
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " channels. Elapsed time for searching" +
- " channels: " + (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " channels. Elapsed time for searching"
+ + " channels: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
@WorkerThread
- private List<SearchResult> searchChannels(String query, String[] columnForExactMatching,
- String[] columnForPartialMatching, Set<Long> channelsFound, int limit) {
+ private List<SearchResult> searchChannels(
+ String query,
+ String[] columnForExactMatching,
+ String[] columnForPartialMatching,
+ Set<Long> channelsFound,
+ int limit) {
String[] projection = {
- Channels._ID,
- Channels.COLUMN_DISPLAY_NUMBER,
- Channels.COLUMN_DISPLAY_NAME,
- Channels.COLUMN_DESCRIPTION
+ Channels._ID,
+ Channels.COLUMN_DISPLAY_NUMBER,
+ Channels.COLUMN_DISPLAY_NAME,
+ Channels.COLUMN_DESCRIPTION
};
StringBuilder sb = new StringBuilder();
- sb.append(Channels.COLUMN_BROWSABLE).append("=1 AND ")
- .append(Channels.COLUMN_SEARCHABLE).append("=1");
+ sb.append(Channels.COLUMN_BROWSABLE)
+ .append("=1 AND ")
+ .append(Channels.COLUMN_SEARCHABLE)
+ .append("=1");
if (mTvInputManager.isParentalControlsEnabled()) {
sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0");
}
@@ -207,16 +238,18 @@ public class TvProviderSearch implements SearchInterface {
sb.append(")");
String selection = sb.toString();
- int len = (columnForExactMatching == null ? 0 : columnForExactMatching.length) +
- (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
+ int len =
+ (columnForExactMatching == null ? 0 : columnForExactMatching.length)
+ + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
String[] selectionArgs = new String[len];
- insertSelectionArgumentStrings(selectionArgs, 0, query, columnForExactMatching,
- columnForPartialMatching);
+ insertSelectionArgumentStrings(
+ selectionArgs, 0, query, columnForExactMatching, columnForPartialMatching);
List<SearchResult> searchResults = new ArrayList<>();
- try (Cursor c = mContentResolver.query(Channels.CONTENT_URI, projection, selection,
- selectionArgs, null)) {
+ try (Cursor c =
+ mContentResolver.query(
+ Channels.CONTENT_URI, projection, selection, selectionArgs, null)) {
if (c != null) {
int count = 0;
while (c.moveToNext()) {
@@ -227,19 +260,19 @@ public class TvProviderSearch implements SearchInterface {
}
channelsFound.add(id);
- SearchResult result = new SearchResult();
- result.channelId = id;
- result.channelNumber = c.getString(1);
- result.title = c.getString(2);
- result.description = c.getString(3);
- result.imageUri = TvContract.buildChannelLogoUri(result.channelId).toString();
- result.intentAction = Intent.ACTION_VIEW;
- result.intentData = buildIntentData(result.channelId);
- result.contentType = Programs.CONTENT_ITEM_TYPE;
- result.isLive = true;
- result.progressPercentage = LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
+ SearchResult.Builder result = SearchResult.builder();
+ result.setChannelId(id);
+ result.setChannelNumber(c.getString(1));
+ result.setTitle(c.getString(2));
+ result.setDescription(c.getString(3));
+ result.setImageUri(TvContract.buildChannelLogoUri(id).toString());
+ result.setIntentAction(Intent.ACTION_VIEW);
+ result.setIntentData(buildIntentData(id));
+ result.setContentType(Programs.CONTENT_ITEM_TYPE);
+ result.setIsLive(true);
+ result.setProgressPercentage(LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE);
- searchResults.add(result);
+ searchResults.add(result.build());
if (limit != NO_LIMIT && ++count >= limit) {
break;
@@ -256,43 +289,55 @@ public class TvProviderSearch implements SearchInterface {
* blocked.
*/
@WorkerThread
- private void fillProgramInfo(SearchResult result) {
+ private SearchResult fillProgramInfo(SearchResult result) {
long now = System.currentTimeMillis();
- Uri uri = TvContract.buildProgramsUriForChannel(result.channelId, now, now);
- String[] projection = new String[] {
- Programs.COLUMN_TITLE,
- Programs.COLUMN_POSTER_ART_URI,
- Programs.COLUMN_CONTENT_RATING,
- Programs.COLUMN_VIDEO_WIDTH,
- Programs.COLUMN_VIDEO_HEIGHT,
- Programs.COLUMN_START_TIME_UTC_MILLIS,
- Programs.COLUMN_END_TIME_UTC_MILLIS
- };
+ Uri uri = TvContract.buildProgramsUriForChannel(result.getChannelId(), now, now);
+ String[] projection =
+ new String[] {
+ Programs.COLUMN_TITLE,
+ Programs.COLUMN_POSTER_ART_URI,
+ Programs.COLUMN_CONTENT_RATING,
+ Programs.COLUMN_VIDEO_WIDTH,
+ Programs.COLUMN_VIDEO_HEIGHT,
+ Programs.COLUMN_START_TIME_UTC_MILLIS,
+ Programs.COLUMN_END_TIME_UTC_MILLIS
+ };
try (Cursor c = mContentResolver.query(uri, projection, null, null, null)) {
if (c != null && c.moveToNext() && !isRatingBlocked(c.getString(2))) {
- String channelName = result.title;
+ String channelName = result.getTitle();
+ String channelNumber = result.getChannelNumber();
+ SearchResult.Builder builder = SearchResult.builder();
long startUtcMillis = c.getLong(5);
long endUtcMillis = c.getLong(6);
- result.title = c.getString(0);
- result.description = buildProgramDescription(result.channelNumber, channelName,
- startUtcMillis, endUtcMillis);
+ builder.setTitle(c.getString(0));
+ builder.setDescription(
+ buildProgramDescription(
+ channelNumber, channelName, startUtcMillis, endUtcMillis));
String imageUri = c.getString(1);
if (imageUri != null) {
- result.imageUri = imageUri;
+ builder.setImageUri(imageUri);
}
- result.videoWidth = c.getInt(3);
- result.videoHeight = c.getInt(4);
- result.duration = endUtcMillis - startUtcMillis;
- result.progressPercentage = getProgressPercentage(startUtcMillis, endUtcMillis);
+ builder.setVideoWidth(c.getInt(3));
+ builder.setVideoHeight(c.getInt(4));
+ builder.setDuration(endUtcMillis - startUtcMillis);
+ builder.setProgressPercentage(getProgressPercentage(startUtcMillis, endUtcMillis));
+ return builder.build();
}
}
+ return result;
}
- private String buildProgramDescription(String channelNumber, String channelName,
- long programStartUtcMillis, long programEndUtcMillis) {
+ private String buildProgramDescription(
+ String channelNumber,
+ String channelName,
+ long programStartUtcMillis,
+ long programEndUtcMillis) {
return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
- + System.lineSeparator() + channelNumber + " " + channelName;
+ + System.lineSeparator()
+ + channelNumber
+ + " "
+ + channelName;
}
private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
@@ -300,23 +345,28 @@ public class TvProviderSearch implements SearchInterface {
if (startUtcMillis > current || endUtcMillis <= current) {
return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
}
- return (int)(100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
+ return (int) (100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
}
@WorkerThread
- private List<SearchResult> searchPrograms(String query, String[] columnForExactMatching,
- String[] columnForPartialMatching, Set<Long> channelsFound, int limit) {
+ private List<SearchResult> searchPrograms(
+ String query,
+ String[] columnForExactMatching,
+ String[] columnForPartialMatching,
+ Set<Long> channelsFound,
+ int limit) {
if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
long time = SystemClock.elapsedRealtime();
String[] projection = {
- Programs.COLUMN_CHANNEL_ID,
- Programs.COLUMN_TITLE,
- Programs.COLUMN_POSTER_ART_URI,
- Programs.COLUMN_CONTENT_RATING,
- Programs.COLUMN_VIDEO_WIDTH,
- Programs.COLUMN_VIDEO_HEIGHT,
- Programs.COLUMN_START_TIME_UTC_MILLIS,
- Programs.COLUMN_END_TIME_UTC_MILLIS
+ Programs.COLUMN_CHANNEL_ID,
+ Programs.COLUMN_TITLE,
+ Programs.COLUMN_POSTER_ART_URI,
+ Programs.COLUMN_CONTENT_RATING,
+ Programs.COLUMN_VIDEO_WIDTH,
+ Programs.COLUMN_VIDEO_HEIGHT,
+ Programs.COLUMN_START_TIME_UTC_MILLIS,
+ Programs.COLUMN_END_TIME_UTC_MILLIS,
+ Programs._ID
};
StringBuilder sb = new StringBuilder();
@@ -327,17 +377,21 @@ public class TvProviderSearch implements SearchInterface {
sb.append(")");
String selection = sb.toString();
- int len = (columnForExactMatching == null ? 0 : columnForExactMatching.length) +
- (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
+ int len =
+ (columnForExactMatching == null ? 0 : columnForExactMatching.length)
+ + (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
String[] selectionArgs = new String[len + 2];
- selectionArgs[0] = selectionArgs[1] = String.valueOf(System.currentTimeMillis());
- insertSelectionArgumentStrings(selectionArgs, 2, query, columnForExactMatching,
- columnForPartialMatching);
+ long now = System.currentTimeMillis();
+ selectionArgs[0] = String.valueOf(now + SEARCH_TIME_FRAME_MS);
+ selectionArgs[1] = String.valueOf(now);
+ insertSelectionArgumentStrings(
+ selectionArgs, 2, query, columnForExactMatching, columnForPartialMatching);
List<SearchResult> searchResults = new ArrayList<>();
- try (Cursor c = mContentResolver.query(Programs.CONTENT_URI, projection, selection,
- selectionArgs, null)) {
+ try (Cursor c =
+ mContentResolver.query(
+ Programs.CONTENT_URI, projection, selection, selectionArgs, null)) {
if (c != null) {
int count = 0;
while (c.moveToNext()) {
@@ -350,41 +404,53 @@ public class TvProviderSearch implements SearchInterface {
// Don't know whether the channel is searchable or not.
String[] channelProjection = {
- Channels._ID,
- Channels.COLUMN_DISPLAY_NUMBER,
- Channels.COLUMN_DISPLAY_NAME
+ Channels._ID, Channels.COLUMN_DISPLAY_NUMBER, Channels.COLUMN_DISPLAY_NAME
};
sb = new StringBuilder();
- sb.append(Channels._ID).append("=? AND ")
- .append(Channels.COLUMN_BROWSABLE).append("=1 AND ")
- .append(Channels.COLUMN_SEARCHABLE).append("=1");
+ sb.append(Channels._ID)
+ .append("=? AND ")
+ .append(Channels.COLUMN_BROWSABLE)
+ .append("=1 AND ")
+ .append(Channels.COLUMN_SEARCHABLE)
+ .append("=1");
if (mTvInputManager.isParentalControlsEnabled()) {
sb.append(" AND ").append(Channels.COLUMN_LOCKED).append("=0");
}
String selectionChannel = sb.toString();
- try (Cursor cChannel = mContentResolver.query(Channels.CONTENT_URI,
- channelProjection, selectionChannel,
- new String[] { String.valueOf(id) }, null)) {
- if (cChannel != null && cChannel.moveToNext()
+ try (Cursor cChannel =
+ mContentResolver.query(
+ Channels.CONTENT_URI,
+ channelProjection,
+ selectionChannel,
+ new String[] {String.valueOf(id)},
+ null)) {
+ if (cChannel != null
+ && cChannel.moveToNext()
&& !isRatingBlocked(c.getString(3))) {
long startUtcMillis = c.getLong(6);
long endUtcMillis = c.getLong(7);
- SearchResult result = new SearchResult();
- result.channelId = c.getLong(0);
- result.title = c.getString(1);
- result.description = buildProgramDescription(cChannel.getString(1),
- cChannel.getString(2), startUtcMillis, endUtcMillis);
- result.imageUri = c.getString(2);
- result.intentAction = Intent.ACTION_VIEW;
- result.intentData = buildIntentData(id);
- result.contentType = Programs.CONTENT_ITEM_TYPE;
- result.isLive = true;
- result.videoWidth = c.getInt(4);
- result.videoHeight = c.getInt(5);
- result.duration = endUtcMillis - startUtcMillis;
- result.progressPercentage = getProgressPercentage(startUtcMillis,
- endUtcMillis);
- searchResults.add(result);
+ SearchResult.Builder result = SearchResult.builder();
+ result.setChannelId(c.getLong(0));
+ result.setTitle(c.getString(1));
+ result.setDescription(
+ buildProgramDescription(
+ cChannel.getString(1),
+ cChannel.getString(2),
+ startUtcMillis,
+ endUtcMillis));
+ result.setImageUri(c.getString(2));
+ result.setIntentAction(Intent.ACTION_VIEW);
+ result.setIntentData(buildIntentData(id));
+ result.setIntentExtraData(
+ TvContract.buildProgramUri(c.getLong(8)).toString());
+ result.setContentType(Programs.CONTENT_ITEM_TYPE);
+ result.setIsLive(true);
+ result.setVideoWidth(c.getInt(4));
+ result.setVideoHeight(c.getInt(5));
+ result.setDuration(endUtcMillis - startUtcMillis);
+ result.setProgressPercentage(
+ getProgressPercentage(startUtcMillis, endUtcMillis));
+ searchResults.add(result.build());
if (limit != NO_LIMIT && ++count >= limit) {
break;
@@ -395,8 +461,14 @@ public class TvProviderSearch implements SearchInterface {
}
}
if (DEBUG) {
- Log.d(TAG, "Found " + searchResults.size() + " programs. Elapsed time for searching" +
- " programs: " + (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + searchResults.size()
+ + " programs. Elapsed time for searching"
+ + " programs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return searchResults;
}
@@ -439,9 +511,14 @@ public class TvProviderSearch implements SearchInterface {
results.add(buildSearchResultForInput(input.getId()));
if (results.size() >= limit) {
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " inputs. Elapsed time for" +
- " searching inputs: " + (SystemClock.elapsedRealtime() - time) +
- "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " inputs. Elapsed time for"
+ + " searching inputs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
@@ -455,22 +532,33 @@ public class TvProviderSearch implements SearchInterface {
}
String label = canonicalizeLabel(input.loadLabel(mContext));
String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
- if ((label != null && label.contains(query)) ||
- (customLabel != null && customLabel.contains(query))) {
+ if ((label != null && label.contains(query))
+ || (customLabel != null && customLabel.contains(query))) {
results.add(buildSearchResultForInput(input.getId()));
if (results.size() >= limit) {
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " inputs. Elapsed time for" +
- " searching inputs: " + (SystemClock.elapsedRealtime() - time) +
- "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " inputs. Elapsed time for"
+ + " searching inputs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
}
}
if (DEBUG) {
- Log.d(TAG, "Found " + results.size() + " inputs. Elapsed time for searching" +
- " inputs: " + (SystemClock.elapsedRealtime() - time) + "(msec)");
+ Log.d(
+ TAG,
+ "Found "
+ + results.size()
+ + " inputs. Elapsed time for searching"
+ + " inputs: "
+ + (SystemClock.elapsedRealtime() - time)
+ + "(msec)");
}
return results;
}
@@ -481,10 +569,10 @@ public class TvProviderSearch implements SearchInterface {
}
private SearchResult buildSearchResultForInput(String inputId) {
- SearchResult result = new SearchResult();
- result.intentAction = Intent.ACTION_VIEW;
- result.intentData = TvContract.buildChannelUriForPassthroughInput(inputId).toString();
- return result;
+ SearchResult.Builder result = SearchResult.builder();
+ result.setIntentAction(Intent.ACTION_VIEW);
+ result.setIntentData(TvContract.buildChannelUriForPassthroughInput(inputId).toString());
+ return result.build();
}
@WorkerThread
@@ -494,33 +582,35 @@ public class TvProviderSearch implements SearchInterface {
@Override
public int compare(SearchResult lhs, SearchResult rhs) {
// Show recently watched channel first
- Long lhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(lhs.channelId);
+ Long lhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(lhs.getChannelId());
if (lhsMaxWatchStartTime == null) {
- lhsMaxWatchStartTime = getMaxWatchStartTime(lhs.channelId);
- mMaxWatchStartTimeMap.put(lhs.channelId, lhsMaxWatchStartTime);
+ lhsMaxWatchStartTime = getMaxWatchStartTime(lhs.getChannelId());
+ mMaxWatchStartTimeMap.put(lhs.getChannelId(), lhsMaxWatchStartTime);
}
- Long rhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(rhs.channelId);
+ Long rhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(rhs.getChannelId());
if (rhsMaxWatchStartTime == null) {
- rhsMaxWatchStartTime = getMaxWatchStartTime(rhs.channelId);
- mMaxWatchStartTimeMap.put(rhs.channelId, rhsMaxWatchStartTime);
+ rhsMaxWatchStartTime = getMaxWatchStartTime(rhs.getChannelId());
+ mMaxWatchStartTimeMap.put(rhs.getChannelId(), rhsMaxWatchStartTime);
}
if (!Objects.equals(lhsMaxWatchStartTime, rhsMaxWatchStartTime)) {
return Long.compare(rhsMaxWatchStartTime, lhsMaxWatchStartTime);
}
// Show recently added channel first if there's no watch history.
- return Long.compare(rhs.channelId, lhs.channelId);
+ return Long.compare(rhs.getChannelId(), lhs.getChannelId());
}
private long getMaxWatchStartTime(long channelId) {
Uri uri = WatchedPrograms.CONTENT_URI;
- String[] projections = new String[] {
- "MAX(" + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS
- + ") AS max_watch_start_time"
- };
+ String[] projections =
+ new String[] {
+ "MAX("
+ + WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS
+ + ") AS max_watch_start_time"
+ };
String selection = WatchedPrograms.COLUMN_CHANNEL_ID + "=?";
- String[] selectionArgs = new String[] { Long.toString(channelId) };
- try (Cursor c = mContentResolver.query(uri, projections, selection, selectionArgs,
- null)) {
+ String[] selectionArgs = new String[] {Long.toString(channelId)};
+ try (Cursor c =
+ mContentResolver.query(uri, projections, selection, selectionArgs, null)) {
if (c != null && c.moveToNext()) {
return c.getLong(0);
}
diff --git a/src/com/android/tv/setup/SystemSetupActivity.java b/src/com/android/tv/setup/SystemSetupActivity.java
index 7e627410..c6b10e52 100644
--- a/src/com/android/tv/setup/SystemSetupActivity.java
+++ b/src/com/android/tv/setup/SystemSetupActivity.java
@@ -24,27 +24,22 @@ import android.content.Intent;
import android.media.tv.TvInputInfo;
import android.os.Bundle;
import android.widget.Toast;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
import com.android.tv.SetupPassthroughActivity;
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.CommonConstants;
import com.android.tv.common.ui.setup.SetupActivity;
-import com.android.tv.common.ui.setup.SetupFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.TvApplication;
-import com.android.tv.data.ChannelDataManager;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.onboarding.SetupSourcesFragment;
import com.android.tv.util.OnboardingUtils;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
-/**
- * A activity to start input sources setup fragment for initial setup flow.
- */
+/** A activity to start input sources setup fragment for initial setup flow. */
public class SystemSetupActivity extends SetupActivity {
private static final String SYSTEM_SETUP =
- "com.android.tv.action.LAUNCH_SYSTEM_SETUP";
+ CommonConstants.BASE_PACKAGE + ".action.LAUNCH_SYSTEM_SETUP";
private static final int SHOW_RIPPLE_DURATION_MS = 266;
private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
@@ -58,7 +53,7 @@ public class SystemSetupActivity extends SetupActivity {
finish();
return;
}
- ApplicationSingletons singletons = TvApplication.getSingletons(this);
+ TvSingletons singletons = TvSingletons.getSingletons(this);
mInputManager = singletons.getTvInputManagerHelper();
}
@@ -68,12 +63,14 @@ public class SystemSetupActivity extends SetupActivity {
}
private void showMerchantCollection() {
- executeActionWithDelay(new Runnable() {
- @Override
- public void run() {
- startActivity(OnboardingUtils.ONLINE_STORE_INTENT);
- }
- }, SHOW_RIPPLE_DURATION_MS);
+ executeActionWithDelay(
+ new Runnable() {
+ @Override
+ public void run() {
+ startActivity(OnboardingUtils.ONLINE_STORE_INTENT);
+ }
+ },
+ SHOW_RIPPLE_DURATION_MS);
}
@Override
@@ -84,38 +81,50 @@ public class SystemSetupActivity extends SetupActivity {
case SetupSourcesFragment.ACTION_ONLINE_STORE:
showMerchantCollection();
return true;
- case SetupSourcesFragment.ACTION_SETUP_INPUT: {
- String inputId = params.getString(
- SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- Intent intent = TvCommonUtils.createSetupIntent(input);
- if (intent == null) {
- Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT)
- .show();
+ case SetupSourcesFragment.ACTION_SETUP_INPUT:
+ {
+ String inputId =
+ params.getString(
+ SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
+ TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+ Intent intent = CommonUtils.createSetupIntent(input);
+ if (intent == null) {
+ Toast.makeText(
+ this,
+ R.string.msg_no_setup_activity,
+ Toast.LENGTH_SHORT)
+ .show();
+ return true;
+ }
+ // Even though other app can handle the intent, the setup launched by
+ // Live
+ // channels should go through Live channels SetupPassthroughActivity.
+ intent.setComponent(
+ new ComponentName(this, SetupPassthroughActivity.class));
+ try {
+ // Now we know that the user intends to set up this input. Grant
+ // permission for writing EPG data.
+ SetupUtils.grantEpgPermission(
+ this, input.getServiceInfo().packageName);
+ startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(
+ this,
+ getString(
+ R.string.msg_unable_to_start_setup_activity,
+ input.loadLabel(this)),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
return true;
}
- // Even though other app can handle the intent, the setup launched by Live
- // channels should go through Live channels SetupPassthroughActivity.
- intent.setComponent(new ComponentName(this,
- SetupPassthroughActivity.class));
- try {
- // Now we know that the user intends to set up this input. Grant
- // permission for writing EPG data.
- SetupUtils.grantEpgPermission(this, input.getServiceInfo().packageName);
- startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this,
- getString(R.string.msg_unable_to_start_setup_activity,
- input.loadLabel(this)), Toast.LENGTH_SHORT).show();
+ case SetupMultiPaneFragment.ACTION_DONE:
+ {
+ // To make sure user can finish setup flow, set result as RESULT_OK.
+ setResult(Activity.RESULT_OK);
+ finish();
+ return true;
}
- return true;
- }
- case SetupMultiPaneFragment.ACTION_DONE: {
- // To make sure user can finish setup flow, set result as RESULT_OK.
- setResult(Activity.RESULT_OK);
- finish();
- return true;
- }
}
break;
}
diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java
deleted file mode 100644
index a255de3e..00000000
--- a/src/com/android/tv/tuner/ChannelScanFileParser.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner;
-
-import android.util.Log;
-
-import com.android.tv.tuner.data.nano.Channel;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Parses plain text formatted scan files, which contain the list of channels.
- */
-public class ChannelScanFileParser {
- private static final String TAG = "ChannelScanFileParser";
-
- public static final class ScanChannel {
- public final int type;
- public final int frequency;
- public final String modulation;
- public final String filename;
- /**
- * Radio frequency (channel) number specified at
- * https://en.wikipedia.org/wiki/North_American_television_frequencies
- * This can be {@code null} for cases like cable signal.
- */
- public final Integer radioFrequencyNumber;
-
- public static ScanChannel forTuner(int frequency, String modulation,
- Integer radioFrequencyNumber) {
- return new ScanChannel(Channel.TYPE_TUNER, frequency, modulation, null,
- radioFrequencyNumber);
- }
-
- public static ScanChannel forFile(int frequency, String filename) {
- return new ScanChannel(Channel.TYPE_FILE, frequency, "file:", filename, null);
- }
-
- private ScanChannel(int type, int frequency, String modulation, String filename,
- Integer radioFrequencyNumber) {
- this.type = type;
- this.frequency = frequency;
- this.modulation = modulation;
- this.filename = filename;
- this.radioFrequencyNumber = radioFrequencyNumber;
- }
- }
-
- /**
- * Parses a given scan file and returns the list of {@link ScanChannel} objects.
- *
- * @param is {@link InputStream} of a scan file. Each line matches one channel.
- * The line format of the scan file is as follows:<br>
- * "A &lt;frequency&gt; &lt;modulation&gt;".
- * @return a list of {@link ScanChannel} objects parsed
- */
- public static List<ScanChannel> parseScanFile(InputStream is) {
- BufferedReader in = new BufferedReader(new InputStreamReader(is));
- String line;
- List<ScanChannel> scanChannelList = new ArrayList<>();
- try {
- while ((line = in.readLine()) != null) {
- if (line.isEmpty()) {
- continue;
- }
- if (line.charAt(0) == '#') {
- // Skip comment line
- continue;
- }
- String[] tokens = line.split("\\s+");
- if (tokens.length != 3 && tokens.length != 4) {
- continue;
- }
- scanChannelList.add(ScanChannel.forTuner(Integer.parseInt(tokens[1]), tokens[2],
- tokens.length == 4 ? Integer.parseInt(tokens[3]) : null));
- }
- } catch (IOException e) {
- Log.e(TAG, "error on parseScanFile()", e);
- }
- return scanChannelList;
- }
-}
diff --git a/src/com/android/tv/tuner/DvbDeviceAccessor.java b/src/com/android/tv/tuner/DvbDeviceAccessor.java
deleted file mode 100644
index 4f5d8ee4..00000000
--- a/src/com/android/tv/tuner/DvbDeviceAccessor.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner;
-
-import android.content.Context;
-import android.media.tv.TvInputManager;
-import android.os.ParcelFileDescriptor;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.android.tv.common.recording.RecordingCapability;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Provides with the file descriptors to access DVB device.
- */
-public class DvbDeviceAccessor {
- private static final String TAG = "DvbDeviceAccessor";
-
- @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DvbDevice {}
- public static final int DVB_DEVICE_DEMUX = 0; // TvInputManager.DVB_DEVICE_DEMUX;
- public static final int DVB_DEVICE_DVR = 1; // TvInputManager.DVB_DEVICE_DVR;
- public static final int DVB_DEVICE_FRONTEND = 2; // TvInputManager.DVB_DEVICE_FRONTEND;
-
- private static Method sGetDvbDeviceListMethod;
- private static Method sOpenDvbDeviceMethod;
-
- private final TvInputManager mTvInputManager;
-
- static {
- try {
- Class tvInputManagerClass = Class.forName("android.media.tv.TvInputManager");
- Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo");
- sGetDvbDeviceListMethod = tvInputManagerClass.getDeclaredMethod("getDvbDeviceList");
- sGetDvbDeviceListMethod.setAccessible(true);
- sOpenDvbDeviceMethod = tvInputManagerClass.getDeclaredMethod(
- "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE);
- sOpenDvbDeviceMethod.setAccessible(true);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Couldn't find class", e);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "Couldn't find method", e);
- }
- }
-
- public DvbDeviceAccessor(Context context) {
- mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- }
-
- public List<DvbDeviceInfoWrapper> getDvbDeviceList() {
- try {
- List<DvbDeviceInfoWrapper> wrapperList = new ArrayList<>();
- List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager);
- for (Object dvbDeviceInfo : dvbDeviceInfoList) {
- wrapperList.add(new DvbDeviceInfoWrapper(dvbDeviceInfo));
- }
- Collections.sort(wrapperList);
- return wrapperList;
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- }
- return null;
- }
-
- /**
- * Returns the number of currently connected DVB devices.
- */
- public int getNumOfDvbDevices() {
- List<DvbDeviceInfoWrapper> dvbDeviceList = getDvbDeviceList();
- return dvbDeviceList == null ? 0 : dvbDeviceList.size();
- }
-
- public boolean isDvbDeviceAvailable() {
- try {
- List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager);
- return (!dvbDeviceInfoList.isEmpty());
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- }
- return false;
- }
-
- public ParcelFileDescriptor openDvbDevice(DvbDeviceInfoWrapper deviceInfo,
- @DvbDevice int device) {
- try {
- return (ParcelFileDescriptor) sOpenDvbDeviceMethod.invoke(
- mTvInputManager, deviceInfo.getDvbDeviceInfo(), device);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- }
- return null;
- }
-
- /**
- * Returns the current recording capability for USB tuner.
- * @param inputId the input id to use.
- */
- public RecordingCapability getRecordingCapability(String inputId) {
- List<DvbDeviceInfoWrapper> deviceList = getDvbDeviceList();
- // TODO(DVR) implement accurate capabilities and updating values when needed.
- return RecordingCapability.builder()
- .setInputId(inputId)
- .setMaxConcurrentPlayingSessions(1)
- .setMaxConcurrentTunedSessions(deviceList.size())
- .setMaxConcurrentSessionsOfAllTypes(deviceList.size() + 1)
- .build();
- }
-
- public static class DvbDeviceInfoWrapper implements Comparable<DvbDeviceInfoWrapper> {
- private static Method sGetAdapterIdMethod;
- private static Method sGetDeviceIdMethod;
- private final Object mDvbDeviceInfo;
- private final int mAdapterId;
- private final int mDeviceId;
- private final long mId;
-
- static {
- try {
- Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo");
- sGetAdapterIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getAdapterId");
- sGetAdapterIdMethod.setAccessible(true);
- sGetDeviceIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getDeviceId");
- sGetDeviceIdMethod.setAccessible(true);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Couldn't find class", e);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "Couldn't find method", e);
- }
- }
-
- public DvbDeviceInfoWrapper(Object dvbDeviceInfo) {
- mDvbDeviceInfo = dvbDeviceInfo;
- mAdapterId = initAdapterId();
- mDeviceId = initDeviceId();
- mId = (((long) getAdapterId()) << 32) | (getDeviceId() & 0xffffffffL);
- }
-
- public long getId() {
- return mId;
- }
-
- public int getAdapterId() {
- return mAdapterId;
- }
-
- private int initAdapterId() {
- try {
- return (int) sGetAdapterIdMethod.invoke(mDvbDeviceInfo);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- }
- return -1;
- }
-
- public int getDeviceId() {
- return mDeviceId;
- }
-
- private int initDeviceId() {
- try {
- return (int) sGetDeviceIdMethod.invoke(mDvbDeviceInfo);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- }
- return -1;
- }
-
- public Object getDvbDeviceInfo() {
- return mDvbDeviceInfo;
- }
-
- @Override
- public int compareTo(@NonNull DvbDeviceInfoWrapper another) {
- if (getAdapterId() != another.getAdapterId()) {
- return getAdapterId() - another.getAdapterId();
- }
- return getDeviceId() - another.getDeviceId();
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US, "DvbDeviceInfo {adapterId: %d, deviceId: %d}",
- getAdapterId(),
- getDeviceId());
- }
- }
-}
diff --git a/src/com/android/tv/tuner/DvbTunerHal.java b/src/com/android/tv/tuner/DvbTunerHal.java
deleted file mode 100644
index ea977230..00000000
--- a/src/com/android/tv/tuner/DvbTunerHal.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner;
-
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper;
-
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * A class to handle a hardware Linux DVB API supported tuner device.
- */
-public class DvbTunerHal extends TunerHal {
-
- private static final Object sLock = new Object();
- // @GuardedBy("sLock")
- private static final SortedSet<DvbDeviceInfoWrapper> sUsedDvbDevices = new TreeSet<>();
-
- private final DvbDeviceAccessor mDvbDeviceAccessor;
- private DvbDeviceInfoWrapper mDvbDeviceInfo;
-
- protected DvbTunerHal(Context context) {
- super(context);
- mDvbDeviceAccessor = new DvbDeviceAccessor(context);
- }
-
- @Override
- protected boolean openFirstAvailable() {
- List<DvbDeviceInfoWrapper> deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList();
- if (deviceInfoList == null || deviceInfoList.isEmpty()) {
- Log.e(TAG, "There's no dvb device attached");
- return false;
- }
- synchronized (sLock) {
- for (DvbDeviceInfoWrapper deviceInfo : deviceInfoList) {
- if (!sUsedDvbDevices.contains(deviceInfo)) {
- if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo);
- mDvbDeviceInfo = deviceInfo;
- sUsedDvbDevices.add(deviceInfo);
- getDeliverySystemTypeFromDevice();
- return true;
- }
- }
- }
- Log.e(TAG, "There's no available dvb devices");
- return false;
- }
-
- /**
- * Acquires the tuner device. The requested device will be locked to the current instance if
- * it's not acquired by others.
- *
- * @param deviceInfo a tuner device to open
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- protected boolean open(DvbDeviceInfoWrapper deviceInfo) {
- if (deviceInfo == null) {
- Log.e(TAG, "Device info should not be null");
- return false;
- }
- if (mDvbDeviceInfo != null) {
- Log.e(TAG, "Already acquired");
- return false;
- }
- List<DvbDeviceInfoWrapper> deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList();
- if (deviceInfoList == null || deviceInfoList.isEmpty()) {
- Log.e(TAG, "There's no dvb device attached");
- return false;
- }
- for (DvbDeviceInfoWrapper deviceInfoWrapper : deviceInfoList) {
- if (deviceInfoWrapper.compareTo(deviceInfo) == 0) {
- synchronized (sLock) {
- if (sUsedDvbDevices.contains(deviceInfo)) {
- Log.e(TAG, deviceInfo + " is already taken");
- return false;
- }
- sUsedDvbDevices.add(deviceInfo);
- }
- if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo);
- mDvbDeviceInfo = deviceInfo;
- return true;
- }
- }
- Log.e(TAG, "There's no such dvb device attached");
- return false;
- }
-
- @Override
- public void close() {
- if (mDvbDeviceInfo != null) {
- if (isStreaming()) {
- stopTune();
- }
- nativeFinalize(mDvbDeviceInfo.getId());
- synchronized (sLock) {
- sUsedDvbDevices.remove(mDvbDeviceInfo);
- }
- mDvbDeviceInfo = null;
- }
- }
-
- @Override
- protected boolean isDeviceOpen() {
- return (mDvbDeviceInfo != null);
- }
-
- @Override
- protected long getDeviceId() {
- if (mDvbDeviceInfo != null) {
- return mDvbDeviceInfo.getId();
- }
- return -1;
- }
-
- @Override
- protected int openDvbFrontEndFd() {
- if (mDvbDeviceInfo != null) {
- ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice(
- mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND);
- if (descriptor != null) {
- return descriptor.detachFd();
- }
- }
- return -1;
- }
-
- @Override
- protected int openDvbDemuxFd() {
- if (mDvbDeviceInfo != null) {
- ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice(
- mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX);
- if (descriptor != null) {
- return descriptor.detachFd();
- }
- }
- return -1;
- }
-
- @Override
- protected int openDvbDvrFd() {
- if (mDvbDeviceInfo != null) {
- ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice(
- mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR);
- if (descriptor != null) {
- return descriptor.detachFd();
- }
- }
- return -1;
- }
-
- /**
- * Gets the number of USB tuner devices currently present.
- */
- public static int getNumberOfDevices(Context context) {
- try {
- return (new DvbDeviceAccessor(context)).getNumOfDvbDevices();
- } catch (Exception e) {
- return 0;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java
deleted file mode 100644
index 1176cdf0..00000000
--- a/src/com/android/tv/tuner/TunerHal.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner;
-
-import android.content.Context;
-import android.support.annotation.IntDef;
-import android.support.annotation.StringDef;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.Features;
-import com.android.tv.customization.TvCustomizationManager;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * A base class to handle a hardware tuner device.
- */
-public abstract class TunerHal implements AutoCloseable {
- protected static final String TAG = "TunerHal";
- protected static final boolean DEBUG = false;
-
- @IntDef({ FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR })
- @Retention(RetentionPolicy.SOURCE)
- public @interface FilterType {}
- public static final int FILTER_TYPE_OTHER = 0;
- public static final int FILTER_TYPE_AUDIO = 1;
- public static final int FILTER_TYPE_VIDEO = 2;
- public static final int FILTER_TYPE_PCR = 3;
-
- @StringDef({ MODULATION_8VSB, MODULATION_QAM256 })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ModulationType {}
- public static final String MODULATION_8VSB = "8VSB";
- public static final String MODULATION_QAM256 = "QAM256";
-
- @IntDef({ DELIVERY_SYSTEM_UNDEFINED, DELIVERY_SYSTEM_ATSC, DELIVERY_SYSTEM_DVBC,
- DELIVERY_SYSTEM_DVBS, DELIVERY_SYSTEM_DVBS2, DELIVERY_SYSTEM_DVBT,
- DELIVERY_SYSTEM_DVBT2 })
- @Retention(RetentionPolicy.SOURCE)
- public @interface DeliverySystemType {}
- public static final int DELIVERY_SYSTEM_UNDEFINED = 0;
- public static final int DELIVERY_SYSTEM_ATSC = 1;
- public static final int DELIVERY_SYSTEM_DVBC = 2;
- public static final int DELIVERY_SYSTEM_DVBS = 3;
- public static final int DELIVERY_SYSTEM_DVBS2 = 4;
- public static final int DELIVERY_SYSTEM_DVBT = 5;
- public static final int DELIVERY_SYSTEM_DVBT2 = 6;
-
- @IntDef({ TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TunerType {}
- public static final int TUNER_TYPE_BUILT_IN = 1;
- public static final int TUNER_TYPE_USB = 2;
- public static final int TUNER_TYPE_NETWORK = 3;
-
- protected static final int PID_PAT = 0;
- protected static final int PID_ATSC_SI_BASE = 0x1ffb;
- protected static final int PID_DVB_SDT = 0x0011;
- protected static final int PID_DVB_EIT = 0x0012;
- protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000;
- protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for
- // QAM256 tuning.
- @IntDef({
- BUILT_IN_TUNER_TYPE_LINUX_DVB
- })
- @Retention(RetentionPolicy.SOURCE)
- private @interface BuiltInTunerType {}
- private static final int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1;
-
- private static Integer sBuiltInTunerType;
-
- protected @DeliverySystemType int mDeliverySystemType;
- private boolean mIsStreaming;
- private int mFrequency;
- private String mModulation;
-
- static {
- System.loadLibrary("tunertvinput_jni");
- }
-
- /**
- * Creates a TunerHal instance.
- * @param context context for creating the TunerHal instance
- * @return the TunerHal instance
- */
- @WorkerThread
- public synchronized static TunerHal createInstance(Context context) {
- TunerHal tunerHal = null;
- if (DvbTunerHal.getNumberOfDevices(context) > 0) {
- if (DEBUG) Log.d(TAG, "Use DvbTunerHal");
- tunerHal = new DvbTunerHal(context);
- }
- return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null;
- }
-
- /**
- * Gets the number of tuner devices currently present.
- */
- @WorkerThread
- public static Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
- if (useBuiltInTuner(context)) {
- if (getBuiltInTunerType(context) == BUILT_IN_TUNER_TYPE_LINUX_DVB) {
- return new Pair<>(TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context));
- }
- } else {
- int usbTunerCount = DvbTunerHal.getNumberOfDevices(context);
- if (usbTunerCount > 0) {
- return new Pair<>(TUNER_TYPE_USB, usbTunerCount);
- }
- }
- return new Pair<>(null, 0);
- }
-
- /**
- * Check a delivery system is for DVB or not.
- */
- public static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) {
- return deliverySystemType == DELIVERY_SYSTEM_DVBC
- || deliverySystemType == DELIVERY_SYSTEM_DVBS
- || deliverySystemType == DELIVERY_SYSTEM_DVBS2
- || deliverySystemType == DELIVERY_SYSTEM_DVBT
- || deliverySystemType == DELIVERY_SYSTEM_DVBT2;
- }
-
- /**
- * Returns if tuner input service would use built-in tuners instead of USB tuners or network
- * tuners.
- */
- static boolean useBuiltInTuner(Context context) {
- return getBuiltInTunerType(context) != 0;
- }
-
- private static @BuiltInTunerType int getBuiltInTunerType(Context context) {
- if (sBuiltInTunerType == null) {
- sBuiltInTunerType = 0;
- if (TvCustomizationManager.hasLinuxDvbBuiltInTuner(context)
- && DvbTunerHal.getNumberOfDevices(context) > 0) {
- sBuiltInTunerType = BUILT_IN_TUNER_TYPE_LINUX_DVB;
- }
- }
- return sBuiltInTunerType;
- }
-
- protected TunerHal(Context context) {
- mIsStreaming = false;
- mFrequency = -1;
- mModulation = null;
- }
-
- protected boolean isStreaming() {
- return mIsStreaming;
- }
-
- protected void getDeliverySystemTypeFromDevice() {
- if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) {
- mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId());
- }
- }
-
- /**
- * Returns {@code true} if this tuner HAL can be reused to save tuning time between channels
- * of the same frequency.
- */
- public boolean isReusable() {
- return true;
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- close();
- }
-
- protected native void nativeFinalize(long deviceId);
-
- /**
- * Acquires the first available tuner device. If there is a tuner device that is available, the
- * tuner device will be locked to the current instance.
- *
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- protected abstract boolean openFirstAvailable();
-
- protected abstract boolean isDeviceOpen();
-
- protected abstract long getDeviceId();
-
- /**
- * Sets the tuner channel. This should be called after acquiring a tuner device.
- *
- * @param frequency a frequency of the channel to tune to
- * @param modulation a modulation method of the channel to tune to
- * @param channelNumber channel number when channel number is already known. Some tuner HAL
- * may use channelNumber instead of frequency for tune.
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- public synchronized boolean tune(int frequency, @ModulationType String modulation,
- String channelNumber) {
- if (!isDeviceOpen()) {
- Log.e(TAG, "There's no available device");
- return false;
- }
- if (mIsStreaming) {
- nativeCloseAllPidFilters(getDeviceId());
- mIsStreaming = false;
- }
-
- // When tuning to a new channel in the same frequency, there's no need to stop current tuner
- // device completely and the only thing necessary for tuning is reopening pid filters.
- if (mFrequency == frequency && Objects.equals(mModulation, modulation)) {
- addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
- addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
- if (isDvbDeliverySystem(mDeliverySystemType)) {
- addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
- addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
- }
- mIsStreaming = true;
- return true;
- }
- int timeout_ms = modulation.equals(MODULATION_8VSB) ? DEFAULT_VSB_TUNE_TIMEOUT_MS
- : DEFAULT_QAM_TUNE_TIMEOUT_MS;
- if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) {
- addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
- addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
- if (isDvbDeliverySystem(mDeliverySystemType)) {
- addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
- addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
- }
- mFrequency = frequency;
- mModulation = modulation;
- mIsStreaming = true;
- return true;
- }
- return false;
- }
-
- protected native boolean nativeTune(long deviceId, int frequency,
- @ModulationType String modulation, int timeout_ms);
-
- /**
- * Sets a pid filter. This should be set after setting a channel.
- *
- * @param pid a pid number to be added to filter list
- * @param filterType a type of pid. Must be one of (FILTER_TYPE_XXX)
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- public synchronized boolean addPidFilter(int pid, @FilterType int filterType) {
- if (!isDeviceOpen()) {
- Log.e(TAG, "There's no available device");
- return false;
- }
- if (pid >= 0 && pid <= 0x1fff) {
- nativeAddPidFilter(getDeviceId(), pid, filterType);
- return true;
- }
- return false;
- }
-
- protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType);
- protected native void nativeCloseAllPidFilters(long deviceId);
- protected native void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune);
- protected native int nativeGetDeliverySystemType(long deviceId);
-
- /**
- * Stops current tuning. The tuner device and pid filters will be reset by this call and make
- * the tuner ready to accept another tune request.
- */
- public synchronized void stopTune() {
- if (isDeviceOpen()) {
- if (mIsStreaming) {
- nativeCloseAllPidFilters(getDeviceId());
- }
- nativeStopTune(getDeviceId());
- }
- mIsStreaming = false;
- mFrequency = -1;
- mModulation = null;
- }
-
- public void setHasPendingTune(boolean hasPendingTune) {
- nativeSetHasPendingTune(getDeviceId(), hasPendingTune);
- }
-
- public int getDeliverySystemType() {
- return mDeliverySystemType;
- }
-
- protected native void nativeStopTune(long deviceId);
-
- /**
- * This method must be called after {@link TunerHal#tune} and before
- * {@link TunerHal#stopTune}. Writes at most maxSize TS frames in a buffer
- * provided by the user. The frames employ MPEG encoding.
- *
- * @param javaBuffer a buffer to write the video data in
- * @param javaBufferSize the max amount of bytes to write in this buffer. Usually this number
- * should be equal to the length of the buffer.
- * @return the amount of bytes written in the buffer. Note that this value could be 0 if no new
- * frames have been obtained since the last call.
- */
- public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) {
- if (isDeviceOpen()) {
- return nativeWriteInBuffer(getDeviceId(), javaBuffer, javaBufferSize);
- } else {
- return 0;
- }
- }
-
- protected native int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize);
-
- /**
- * Opens Linux DVB frontend device. This method is called from native JNI and used only for
- * DvbTunerHal.
- */
- protected int openDvbFrontEndFd() {
- return -1;
- }
-
- /**
- * Opens Linux DVB demux device. This method is called from native JNI and used only for
- * DvbTunerHal.
- */
- protected int openDvbDemuxFd() {
- return -1;
- }
-
- /**
- * Opens Linux DVB dvr device. This method is called from native JNI and used only for
- * DvbTunerHal.
- */
- protected int openDvbDvrFd() {
- return -1;
- }
-}
diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java
index e06b9b4a..02611bbf 100644
--- a/src/com/android/tv/tuner/TunerInputController.java
+++ b/src/com/android/tv/tuner/TunerInputController.java
@@ -17,6 +17,9 @@
package com.android.tv.tuner;
import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -24,10 +27,14 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
@@ -38,118 +45,132 @@ import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
-
-import com.android.tv.Features;
import com.android.tv.R;
+import com.android.tv.Starter;
import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.setup.TunerSetupActivity;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-import com.android.tv.tuner.util.SystemPropertiesProxy;
-import com.android.tv.tuner.util.TunerInputInfoUtils;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.BuildConfig;
+import com.android.tv.common.util.SystemPropertiesProxy;
+
+import com.android.tv.tuner.setup.BaseTunerSetupActivity;
+import com.android.tv.tuner.util.TunerInputInfoUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
- * Controls the package visibility of {@link TunerTvInputService}.
- * <p>
- * Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED},
- * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}
- * to update the connection status of the supported USB TV tuners.
+ * Controls the package visibility of {@link BaseTunerTvInputService}.
+ *
+ * <p>Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, {@code
+ * UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} to
+ * update the connection status of the supported USB TV tuners.
*/
public class TunerInputController {
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final String TAG = "TunerInputController";
private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner";
private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch";
private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd";
+ private static final String PLAY_STORE_LINK_TEMPLATE = "market://details?id=%s";
- /**
- * Action of {@link Intent} to check network connection repeatedly when it is necessary.
- */
- private static final String CHECKING_NETWORK_CONNECTION =
- "com.android.tv.action.CHECKING_NETWORK_CONNECTION";
+ /** Action of {@link Intent} to check network connection repeatedly when it is necessary. */
+ private static final String CHECKING_NETWORK_TUNER_STATUS =
+ "com.android.tv.action.CHECKING_NETWORK_TUNER_STATUS";
private static final String EXTRA_CHECKING_DURATION =
"com.android.tv.action.extra.CHECKING_DURATION";
+ private static final String EXTRA_DEVICE_IP = "com.android.tv.action.extra.DEVICE_IP";
private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+ private static final String NOTIFICATION_CHANNEL_ID = "tuner_discovery_notification";
+ // TODO: Load settings from XML file
private static final TunerDevice[] TUNER_DEVICES = {
new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q
new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q
// WinTV-dualHD (bulk) will be supported after 2017 April security patch.
new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk)
- // STOPSHIP: Add WinTV-soloHD (Isoc) temporary for test. Remove this after test complete.
new TunerDevice(0x2040, 0x0264, null),
};
private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
private static final long DVB_DRIVER_CHECK_DELAY_MS = 300;
- /**
- * Checks status of USB devices to see if there are available USB tuners connected.
- */
- public static void onCheckingUsbTunerStatus(Context context, String action) {
- onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler());
- }
+ private final ComponentName usbTunerComponent;
+ private final ComponentName networkTunerComponent;
+ private final ComponentName builtInTunerComponent;
+ private final Map<TunerDevice, ComponentName> mTunerServiceMapping = new HashMap<>();
- private static void onCheckingUsbTunerStatus(Context context, String action,
- @NonNull CheckDvbDeviceHandler handler) {
- SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(context);
- if (TunerHal.useBuiltInTuner(context)) {
- enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN);
- return;
+ private final Map<ComponentName, String> mTunerApplicationNames = new HashMap<>();
+ private final Map<ComponentName, String> mNotificationMessages = new HashMap<>();
+ private final Map<ComponentName, Bitmap> mNotificationLargeIcons = new HashMap<>();
+
+ private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler(this);
+
+ public TunerInputController(ComponentName embeddedTuner) {
+ usbTunerComponent = embeddedTuner;
+ networkTunerComponent = usbTunerComponent;
+ builtInTunerComponent = usbTunerComponent;
+ for (TunerDevice device : TUNER_DEVICES) {
+ mTunerServiceMapping.put(device, usbTunerComponent);
}
- // Falls back to the below to check USB tuner devices.
- boolean enabled = isUsbTunerConnected(context);
+ }
+
+ /** Checks status of USB devices to see if there are available USB tuners connected. */
+ public void onCheckingUsbTunerStatus(Context context, String action) {
+ onCheckingUsbTunerStatus(context, action, mHandler);
+ }
+
+ private void onCheckingUsbTunerStatus(
+ Context context, String action, @NonNull CheckDvbDeviceHandler handler) {
+ Set<TunerDevice> connectedUsbTuners = getConnectedUsbTuners(context);
handler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
- if (enabled) {
+ if (!connectedUsbTuners.isEmpty()) {
// Need to check if DVB driver is accessible. Since the driver creation
// could be happen after the USB event, delay the checking by
// DVB_DRIVER_CHECK_DELAY_MS.
- handler.sendMessageDelayed(handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
+ handler.sendMessageDelayed(
+ handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
DVB_DRIVER_CHECK_DELAY_MS);
} else {
- if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
- // Since network tuner is attached, do not disable TunerTvInput,
- // just updates the TvInputInfo.
- TunerInputInfoUtils.updateTunerInputInfo(context);
- return;
- }
- enableTunerTvInputService(context, false, false, TextUtils
- .equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ?
- TunerHal.TUNER_TYPE_USB : null);
+ handleTunerStatusChanged(
+ context,
+ false,
+ connectedUsbTuners,
+ TextUtils.equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED)
+ ? TunerHal.TUNER_TYPE_USB
+ : null);
}
}
- private static void onNetworkTunerChanged(Context context, boolean enabled) {
+ private void onNetworkTunerChanged(Context context, boolean enabled) {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context);
+ if (sharedPreferences.contains(PREFERENCE_IS_NETWORK_TUNER_ATTACHED)
+ && sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)
+ == enabled) {
+ // the status is not changed
+ return;
+ }
if (enabled) {
- // Network tuner detection is initiated by UI. So the app should not
- // be killed.
sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply();
- enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK);
} else {
- sharedPreferences.edit()
- .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false).apply();
- if(!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) {
- // Network tuner detection is initiated by UI. So the app should not
- // be killed.
- enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK);
- } else {
- // Since USB tuner is attached, do not disable TunerTvInput,
- // just updates the TvInputInfo.
- TunerInputInfoUtils.updateTunerInputInfo(context);
- }
+ sharedPreferences
+ .edit()
+ .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)
+ .apply();
}
+ // Network tuner detection is initiated by UI. So the app should not
+ // be killed.
+ handleTunerStatusChanged(
+ context, true, getConnectedUsbTuners(context), TunerHal.TUNER_TYPE_NETWORK);
}
/**
@@ -158,66 +179,131 @@ public class TunerInputController {
* @param context {@link Context} instance
* @return {@code true} if any tuner device we support is plugged in
*/
- private static boolean isUsbTunerConnected(Context context) {
+ private Set<TunerDevice> getConnectedUsbTuners(Context context) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
Map<String, UsbDevice> deviceList = manager.getDeviceList();
String currentSecurityLevel =
SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null);
+ Set<TunerDevice> devices = new HashSet<>();
for (UsbDevice device : deviceList.values()) {
if (DEBUG) {
Log.d(TAG, "Device: " + device);
}
for (TunerDevice tuner : TUNER_DEVICES) {
- if (tuner.equals(device) && tuner.isSupported(currentSecurityLevel)) {
+ if (tuner.equalsTo(device) && tuner.isSupported(currentSecurityLevel)) {
Log.i(TAG, "Tuner found");
- return true;
+ devices.add(tuner);
}
}
}
- return false;
+ return devices;
+ }
+
+ private void handleTunerStatusChanged(
+ Context context,
+ boolean forceDontKillApp,
+ Set<TunerDevice> connectedUsbTuners,
+ Integer triggerType) {
+ Map<ComponentName, Integer> serviceToEnable = new HashMap<>();
+ Set<ComponentName> serviceToDisable = new HashSet<>();
+ serviceToDisable.add(builtInTunerComponent);
+ serviceToDisable.add(networkTunerComponent);
+ if (TunerFeatures.TUNER.isEnabled(context)) {
+ // TODO: support both built-in tuner and other tuners at the same time?
+ if (TunerHal.useBuiltInTuner(context)) {
+ enableTunerTvInputService(
+ context, true, false, TunerHal.TUNER_TYPE_BUILT_IN, builtInTunerComponent);
+ return;
+ }
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
+ serviceToEnable.put(networkTunerComponent, TunerHal.TUNER_TYPE_NETWORK);
+ }
+ }
+ for (TunerDevice device : TUNER_DEVICES) {
+ if (TunerFeatures.TUNER.isEnabled(context) && connectedUsbTuners.contains(device)) {
+ serviceToEnable.put(mTunerServiceMapping.get(device), TunerHal.TUNER_TYPE_USB);
+ } else {
+ serviceToDisable.add(mTunerServiceMapping.get(device));
+ }
+ }
+ serviceToDisable.removeAll(serviceToEnable.keySet());
+ for (ComponentName serviceComponent : serviceToEnable.keySet()) {
+ if (isTunerPackageInstalled(context, serviceComponent)) {
+ enableTunerTvInputService(
+ context,
+ true,
+ forceDontKillApp,
+ serviceToEnable.get(serviceComponent),
+ serviceComponent);
+ } else {
+ sendNotificationToInstallPackage(context, serviceComponent);
+ }
+ }
+ for (ComponentName serviceComponent : serviceToDisable) {
+ if (isTunerPackageInstalled(context, serviceComponent)) {
+ enableTunerTvInputService(
+ context, false, forceDontKillApp, triggerType, serviceComponent);
+ } else {
+ cancelNotificationToInstallPackage(context, serviceComponent);
+ }
+ }
}
/**
- * Enable/disable the component {@link TunerTvInputService}.
+ * Enable/disable the component {@link BaseTunerTvInputService}.
*
* @param context {@link Context} instance
* @param enabled {@code true} to enable the service; otherwise {@code false}
*/
- private static void enableTunerTvInputService(Context context, boolean enabled,
- boolean forceDontKillApp, Integer tunerType) {
+ private static void enableTunerTvInputService(
+ Context context,
+ boolean enabled,
+ boolean forceDontKillApp,
+ Integer tunerType,
+ ComponentName serviceComponent) {
if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled);
- PackageManager pm = context.getPackageManager();
- ComponentName componentName = new ComponentName(context, TunerTvInputService.class);
-
- // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling
- // TvActivity, the following pm.setComponentEnabledSetting doesn't work.
- ((TvApplication) context.getApplicationContext()).handleInputCountChanged(
- true, enabled, true);
- // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds
- // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only
- // when the LiveChannels app is active since we don't want to kill the running app.
- int flags = forceDontKillApp
- || TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
- ? PackageManager.DONT_KILL_APP : 0;
- int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- if (newState != pm.getComponentEnabledSetting(componentName)) {
- // Send/cancel the USB tuner TV input setup notification.
- TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
- // Enable/disable the USB tuner TV input.
- pm.setComponentEnabledSetting(componentName, newState, flags);
- if (!enabled && tunerType != null) {
- if (tunerType == TunerHal.TUNER_TYPE_USB) {
- Toast.makeText(context, R.string.msg_usb_tuner_disconnected,
- Toast.LENGTH_SHORT).show();
- } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
- Toast.makeText(context, R.string.msg_network_tuner_disconnected,
- Toast.LENGTH_SHORT).show();
+ PackageManager pm = context.getPackageManager();
+ int newState =
+ enabled
+ ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ if (newState != pm.getComponentEnabledSetting(serviceComponent)) {
+ int flags = forceDontKillApp ? PackageManager.DONT_KILL_APP : 0;
+ if (serviceComponent.getPackageName().equals(context.getPackageName())) {
+ // Don't kill APP when handling input count changing. Or the following
+ // setComponentEnabledSetting() call won't work.
+ ((TvApplication) context.getApplicationContext())
+ .handleInputCountChanged(true, enabled, true);
+ // Bundled input. Don't kill app if LiveChannels app is active since we don't want
+ // to kill the running app.
+ if (TvSingletons.getSingletons(context).getMainActivityWrapper().isCreated()) {
+ flags |= PackageManager.DONT_KILL_APP;
+ }
+ // Send/cancel the USB tuner TV input setup notification.
+ BaseTunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
+ if (!enabled && tunerType != null) {
+ if (tunerType == TunerHal.TUNER_TYPE_USB) {
+ Toast.makeText(
+ context,
+ R.string.msg_usb_tuner_disconnected,
+ Toast.LENGTH_SHORT)
+ .show();
+ } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
+ Toast.makeText(
+ context,
+ R.string.msg_network_tuner_disconnected,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
}
}
+ // Enable/disable the USB tuner TV input.
+ pm.setComponentEnabledSetting(serviceComponent, newState, flags);
if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
- } else if (enabled) {
+ } else if (enabled && serviceComponent.getPackageName().equals(context.getPackageName())) {
// When # of tuners is changed or the tuner input service is switching from/to using
// network tuners or the device just boots.
TunerInputInfoUtils.updateTunerInputInfo(context);
@@ -227,96 +313,179 @@ public class TunerInputController {
/**
* Discovers a network tuner. If the network connection is down, it won't repeatedly checking.
*/
- public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
- boolean runningInMainProcess =
- TvApplication.getSingletons(context).isRunningInMainProcess();
- SoftPreconditions.checkState(runningInMainProcess);
- if (!runningInMainProcess) {
- return;
- }
- executeNetworkTunerDiscoveryAsyncTask(context, 0);
+ public void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
+ executeNetworkTunerDiscoveryAsyncTask(context, 0, 0);
}
/**
* Discovers a network tuner.
+ *
* @param context {@link Context}
- * @param repeatedDurationMs the time length to wait to repeatedly check network status to start
- * finding network tuner when the network connection is not available.
- * {@code 0} to disable repeatedly checking.
+ * @param repeatedDurationMs The time length to wait to repeatedly check network status to start
+ * finding network tuner when the network connection is not available. {@code 0} to disable
+ * repeatedly checking.
+ * @param deviceIp The previous discovered device IP, 0 if none.
*/
- private static void executeNetworkTunerDiscoveryAsyncTask(final Context context,
- final long repeatedDurationMs) {
- if (!Features.NETWORK_TUNER.isEnabled(context)) {
+ private void executeNetworkTunerDiscoveryAsyncTask(
+ final Context context, final long repeatedDurationMs, final int deviceIp) {
+ if (!TunerFeatures.NETWORK_TUNER.isEnabled(context)) {
return;
}
- new AsyncTask<Void, Void, Boolean>() {
- @Override
- protected Boolean doInBackground(Void... params) {
- if (isNetworkConnected(context)) {
+ final Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
+ networkCheckingIntent.setAction(CHECKING_NETWORK_TUNER_STATUS);
+ if (!isNetworkConnected(context) && repeatedDurationMs > 0) {
+ sendCheckingAlarm(context, networkCheckingIntent, repeatedDurationMs);
+ } else {
+ new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ Boolean result = null;
// Implement and execute network tuner discovery AsyncTask here.
- } else if (repeatedDurationMs > 0) {
- AlarmManager alarmManager =
- (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
- networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION);
- networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs);
- PendingIntent alarmIntent = PendingIntent.getBroadcast(
- context, 0, networkCheckingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()
- + repeatedDurationMs, alarmIntent);
+ return result;
}
- return null;
- }
- @Override
- protected void onPostExecute(Boolean result) {
- if (result == null) {
- return;
+ @Override
+ protected void onPostExecute(Boolean foundNetworkTuner) {
+ if (foundNetworkTuner == null) {
+ return;
+ }
+ sendCheckingAlarm(
+ context,
+ networkCheckingIntent,
+ foundNetworkTuner ? INITIAL_CHECKING_DURATION_MS : repeatedDurationMs);
+ onNetworkTunerChanged(context, foundNetworkTuner);
}
- onNetworkTunerChanged(context, result);
- }
- }.execute();
+ }.execute();
+ }
}
private static boolean isNetworkConnected(Context context) {
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
+ private static void sendCheckingAlarm(Context context, Intent intent, long delayMs) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ intent.putExtra(EXTRA_CHECKING_DURATION, delayMs);
+ PendingIntent alarmIntent =
+ PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + delayMs,
+ alarmIntent);
+ }
+
+ private static boolean isTunerPackageInstalled(
+ Context context, ComponentName serviceComponent) {
+ try {
+ context.getPackageManager().getPackageInfo(serviceComponent.getPackageName(), 0);
+ return true;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void sendNotificationToInstallPackage(Context context, ComponentName serviceComponent) {
+ if (!BuildConfig.ENG) {
+ return;
+ }
+ String applicationName = mTunerApplicationNames.get(serviceComponent);
+ if (applicationName == null) {
+ applicationName = context.getString(R.string.tuner_install_default_application_name);
+ }
+ String contentTitle =
+ context.getString(
+ R.string.tuner_install_notification_content_title, applicationName);
+ String contentText = mNotificationMessages.get(serviceComponent);
+ if (contentText == null) {
+ contentText = context.getString(R.string.tuner_install_notification_content_text);
+ }
+ Bitmap largeIcon = mNotificationLargeIcons.get(serviceComponent);
+ if (largeIcon == null) {
+ // TODO: Make a better default image.
+ largeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_store);
+ }
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) {
+ createNotificationChannel(context, notificationManager);
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(
+ Uri.parse(
+ String.format(
+ PLAY_STORE_LINK_TEMPLATE, serviceComponent.getPackageName())));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
+ builder.setAutoCancel(true)
+ .setSmallIcon(R.drawable.ic_launcher_s)
+ .setLargeIcon(largeIcon)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setCategory(Notification.CATEGORY_RECOMMENDATION)
+ .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
+ notificationManager.notify(serviceComponent.getPackageName(), 0, builder.build());
+ }
+
+ private static void cancelNotificationToInstallPackage(
+ Context context, ComponentName serviceComponent) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(serviceComponent.getPackageName(), 0);
+ }
+
+ private static void createNotificationChannel(
+ Context context, NotificationManager notificationManager) {
+ notificationManager.createNotificationChannel(
+ new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ context.getResources()
+ .getString(R.string.ut_setup_notification_channel_name),
+ NotificationManager.IMPORTANCE_HIGH));
+ }
+
public static class IntentReceiver extends BroadcastReceiver {
- private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler();
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
- TvApplication.setCurrentRunningProcess(context, true);
- if (!Features.TUNER.isEnabled(context)) {
- enableTunerTvInputService(context, false, false, null);
+ Starter.start(context);
+ TunerInputController tunerInputController =
+ TvSingletons.getSingletons(context).getTunerInputController();
+ if (!TunerFeatures.TUNER.isEnabled(context)) {
+ tunerInputController.handleTunerStatusChanged(
+ context, false, Collections.emptySet(), null);
return;
}
switch (intent.getAction()) {
case Intent.ACTION_BOOT_COMPLETED:
- executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS);
+ tunerInputController.executeNetworkTunerDiscoveryAsyncTask(
+ context, INITIAL_CHECKING_DURATION_MS, 0);
+ // fall through
case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
case UsbManager.ACTION_USB_DEVICE_DETACHED:
- onCheckingUsbTunerStatus(context, intent.getAction(), mHandler);
+ tunerInputController.onCheckingUsbTunerStatus(context, intent.getAction());
break;
- case CHECKING_NETWORK_CONNECTION:
- long repeatedDurationMs = intent.getLongExtra(EXTRA_CHECKING_DURATION,
- INITIAL_CHECKING_DURATION_MS);
- executeNetworkTunerDiscoveryAsyncTask(context,
- Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS));
+ case CHECKING_NETWORK_TUNER_STATUS:
+ long repeatedDurationMs =
+ intent.getLongExtra(
+ EXTRA_CHECKING_DURATION, INITIAL_CHECKING_DURATION_MS);
+ tunerInputController.executeNetworkTunerDiscoveryAsyncTask(
+ context,
+ Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS),
+ intent.getIntExtra(EXTRA_DEVICE_IP, 0));
break;
+ default: // fall out
}
}
}
/**
- * Simple data holder for a USB device. Used to represent a tuner model, and compare
- * against {@link UsbDevice}.
+ * Simple data holder for a USB device. Used to represent a tuner model, and compare against
+ * {@link UsbDevice}.
*/
private static class TunerDevice {
private final int vendorId;
@@ -331,7 +500,7 @@ public class TunerInputController {
this.minSecurityLevel = minSecurityLevel;
}
- private boolean equals(UsbDevice device) {
+ private boolean equalsTo(UsbDevice device) {
return device.getVendorId() == vendorId && device.getProductId() == productId;
}
@@ -354,10 +523,13 @@ public class TunerInputController {
}
private static class CheckDvbDeviceHandler extends Handler {
+
+ private final TunerInputController mTunerInputController;
private DvbDeviceAccessor mDvbDeviceAccessor;
- CheckDvbDeviceHandler() {
+ CheckDvbDeviceHandler(TunerInputController tunerInputController) {
super(Looper.getMainLooper());
+ this.mTunerInputController = tunerInputController;
}
@Override
@@ -369,9 +541,15 @@ public class TunerInputController {
mDvbDeviceAccessor = new DvbDeviceAccessor(context);
}
boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable();
- enableTunerTvInputService(
- context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null);
+ mTunerInputController.handleTunerStatusChanged(
+ context,
+ false,
+ enabled
+ ? mTunerInputController.getConnectedUsbTuners(context)
+ : Collections.emptySet(),
+ TunerHal.TUNER_TYPE_USB);
break;
+ default: // fall out
}
}
}
diff --git a/src/com/android/tv/tuner/TunerPreferenceProvider.java b/src/com/android/tv/tuner/TunerPreferenceProvider.java
deleted file mode 100644
index 3a3561b6..00000000
--- a/src/com/android/tv/tuner/TunerPreferenceProvider.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-
-/**
- * A content provider for storing the preferences. It's used across TV app and USB tuner TV input.
- */
-public class TunerPreferenceProvider extends ContentProvider {
- /** The authority of the provider */
- public static final String AUTHORITY = "com.android.tv.tuner.preferences";
-
- private static final String PATH_PREFERENCES = "preferences";
-
- private static final int DATABASE_VERSION = 1;
- private static final String DATABASE_NAME = "usbtuner_preferences.db";
- private static final String PREFERENCES_TABLE = "preferences";
-
- private static final int MATCH_PREFERENCE = 1;
- private static final int MATCH_PREFERENCE_KEY = 2;
-
- private static final UriMatcher sUriMatcher;
-
- private DatabaseOpenHelper mDatabaseOpenHelper;
-
- static {
- sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- sUriMatcher.addURI(AUTHORITY, "preferences", MATCH_PREFERENCE);
- sUriMatcher.addURI(AUTHORITY, "preferences/*", MATCH_PREFERENCE_KEY);
- }
-
- /**
- * Builds a Uri that points to a specific preference.
-
- * @param key a key of the preference to point to
- */
- public static Uri buildPreferenceUri(String key) {
- return Preferences.CONTENT_URI.buildUpon().appendPath(key).build();
- }
-
- /**
- * Columns definitions for the preferences table.
- */
- public interface Preferences {
-
- /**
- * The content:// style for the preferences table.
- */
- Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH_PREFERENCES);
-
- /**
- * The MIME type of a directory of preferences.
- */
- String CONTENT_TYPE = "vnd.android.cursor.dir/preferences";
-
- /**
- * The MIME type of a single preference.
- */
- String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/preferences";
-
- /**
- * The ID of this preference.
- *
- * <p>This is auto-incremented.
- *
- * <p>Type: INTEGER
- */
- String _ID = "_id";
-
- /**
- * The key of this preference.
- *
- * <p>Should be unique.
- *
- * <p>Type: TEXT
- */
- String COLUMN_KEY = "key";
-
- /**
- * The value of this preference.
- *
- * <p>Type: TEXT
- */
- String COLUMN_VALUE = "value";
- }
-
- private static class DatabaseOpenHelper extends SQLiteOpenHelper {
- public DatabaseOpenHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + PREFERENCES_TABLE + " ("
- + Preferences._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + Preferences.COLUMN_KEY + " TEXT NOT NULL,"
- + Preferences.COLUMN_VALUE + " TEXT);");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // No-op
- }
- }
-
- @Override
- public boolean onCreate() {
- mDatabaseOpenHelper = new DatabaseOpenHelper(getContext());
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- int match = sUriMatcher.match(uri);
- if (match != MATCH_PREFERENCE && match != MATCH_PREFERENCE_KEY) {
- throw new UnsupportedOperationException();
- }
- SQLiteDatabase db = mDatabaseOpenHelper.getReadableDatabase();
- Cursor cursor = db.query(PREFERENCES_TABLE, projection, selection, selectionArgs,
- null, null, sortOrder);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
- return cursor;
- }
-
- @Override
- public String getType(Uri uri) {
- switch (sUriMatcher.match(uri)) {
- case MATCH_PREFERENCE:
- return Preferences.CONTENT_TYPE;
- case MATCH_PREFERENCE_KEY:
- return Preferences.CONTENT_ITEM_TYPE;
- }
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- /**
- * Inserts a preference row into the preference table.
- *
- * If a key is already exists in the table, it removes the old row and inserts a new row.
- *
- * @param uri the URL of the table to insert into
- * @param values the initial values for the newly inserted row
- * @return the URL of the newly created row
- */
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- if (sUriMatcher.match(uri) != MATCH_PREFERENCE) {
- throw new UnsupportedOperationException();
- }
- return insertRow(uri, values);
- }
-
- private Uri insertRow(Uri uri, ContentValues values) {
- SQLiteDatabase db = mDatabaseOpenHelper.getWritableDatabase();
-
- // Remove the old row.
- db.delete(PREFERENCES_TABLE, Preferences.COLUMN_KEY + " like ?",
- new String[]{values.getAsString(Preferences.COLUMN_KEY)});
-
- long rowId = db.insert(PREFERENCES_TABLE, null, values);
- if (rowId > 0) {
- Uri rowUri = buildPreferenceUri(values.getAsString(Preferences.COLUMN_KEY));
- getContext().getContentResolver().notifyChange(rowUri, null);
- return rowUri;
- }
-
- throw new SQLiteException("Failed to insert row into " + uri);
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java
deleted file mode 100644
index 11a6a969..00000000
--- a/src/com/android/tv/tuner/TunerPreferences.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.IntDef;
-import android.support.annotation.MainThread;
-
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.TunerPreferenceProvider.Preferences;
-import com.android.tv.tuner.util.TisConfiguration;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * A helper class for the USB tuner preferences.
- */
-public class TunerPreferences {
- private static final String TAG = "TunerPreferences";
-
- private static final String PREFS_KEY_CHANNEL_DATA_VERSION = "channel_data_version";
- private static final String PREFS_KEY_SCANNED_CHANNEL_COUNT = "scanned_channel_count";
- private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code";
- private static final String PREFS_KEY_SCAN_DONE = "scan_done";
- private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup";
- private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream";
- private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting";
- private static final String PREFS_KEY_TRICKPLAY_EXPIRED_MS = "trickplay_expired_ms";
-
- private static final String SHARED_PREFS_NAME = "com.android.tv.tuner.preferences";
-
- public static final int CHANNEL_DATA_VERSION_NOT_SET = -1;
-
- @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED})
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrickplaySetting {
- }
-
- /**
- * Trickplay setting is not changed by a user. Trickplay will be enabled in this case.
- */
- public static final int TRICKPLAY_SETTING_NOT_SET = -1;
-
- /**
- * Trickplay setting is disabled.
- */
- public static final int TRICKPLAY_SETTING_DISABLED = 0;
-
- /**
- * Trickplay setting is enabled.
- */
- public static final int TRICKPLAY_SETTING_ENABLED = 1;
-
- @GuardedBy("TunerPreferences.class")
- private static final Bundle sPreferenceValues = new Bundle();
- private static LoadPreferencesTask sLoadPreferencesTask;
- private static ContentObserver sContentObserver;
- private static TunerPreferencesChangedListener sPreferencesChangedListener = null;
-
- private static boolean sInitialized;
-
- /**
- * Listeners for TunerPreferences change.
- */
- public interface TunerPreferencesChangedListener {
- void onTunerPreferencesChanged();
- }
-
- /**
- * Initializes the USB tuner preferences.
- */
- @MainThread
- public static void initialize(final Context context) {
- if (sInitialized) {
- return;
- }
- sInitialized = true;
- if (useContentProvider(context)) {
- loadPreferences(context);
- sContentObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- loadPreferences(context);
- }
- };
- context.getContentResolver().registerContentObserver(
- TunerPreferenceProvider.Preferences.CONTENT_URI, true, sContentObserver);
- } else {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- getSharedPreferences(context);
- return null;
- }
- }.execute();
- }
- }
-
- /**
- * Releases the resources.
- */
- public static synchronized void release(Context context) {
- if (useContentProvider(context) && sContentObserver != null) {
- context.getContentResolver().unregisterContentObserver(sContentObserver);
- }
- setTunerPreferencesChangedListener(null);
- }
-
- /**
- * Sets the listener for TunerPreferences change.
- */
- public static void setTunerPreferencesChangedListener(
- TunerPreferencesChangedListener listener) {
- sPreferencesChangedListener = listener;
- }
-
- /**
- * Loads the preferences from database.
- * <p>
- * This preferences is used across processes, so the preferences should be loaded again when the
- * databases changes.
- */
- @MainThread
- public static void loadPreferences(Context context) {
- if (sLoadPreferencesTask != null
- && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) {
- sLoadPreferencesTask.cancel(true);
- }
- sLoadPreferencesTask = new LoadPreferencesTask(context);
- sLoadPreferencesTask.execute();
- }
-
- private static boolean useContentProvider(Context context) {
- // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access.
- return TisConfiguration.isPackagedWithLiveChannels(context);
- }
-
- public static synchronized int getChannelDataVersion(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION,
- CHANNEL_DATA_VERSION_NOT_SET);
- } else {
- return getSharedPreferences(context)
- .getInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION,
- CHANNEL_DATA_VERSION_NOT_SET);
- }
- }
-
- public static synchronized void setChannelDataVersion(Context context, int version) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version);
- } else {
- getSharedPreferences(context).edit()
- .putInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, version)
- .apply();
- }
- }
-
- public static synchronized int getScannedChannelCount(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT);
- } else {
- return getSharedPreferences(context)
- .getInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, 0);
- }
- }
-
- public static synchronized void setScannedChannelCount(Context context, int channelCount) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount);
- } else {
- getSharedPreferences(context).edit()
- .putInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount)
- .apply();
- }
- }
-
- public static synchronized String getLastPostalCode(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE);
- } else {
- return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null);
- }
- }
-
- public static synchronized void setLastPostalCode(Context context, String postalCode) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode);
- } else {
- getSharedPreferences(context).edit()
- .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode).apply();
- }
- }
-
- public static synchronized boolean isScanDone(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE);
- } else {
- return getSharedPreferences(context)
- .getBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, false);
- }
- }
-
- public static synchronized void setScanDone(Context context) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_SCAN_DONE, true);
- } else {
- getSharedPreferences(context).edit()
- .putBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, true)
- .apply();
- }
- }
-
- public static synchronized boolean shouldShowSetupActivity(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP);
- } else {
- return getSharedPreferences(context)
- .getBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, false);
- }
- }
-
- public static synchronized void setShouldShowSetupActivity(Context context, boolean need) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_LAUNCH_SETUP, need);
- } else {
- getSharedPreferences(context).edit()
- .putBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, need)
- .apply();
- }
- }
-
- public static synchronized long getTrickplayExpiredMs(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getLong(PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0);
- } else {
- return getSharedPreferences(context)
- .getLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0);
- }
- }
-
- public static synchronized void setTrickplayExpiredMs(Context context, long timeMs) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs);
- } else {
- getSharedPreferences(context).edit()
- .putLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs)
- .apply();
- }
- }
-
- public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
- } else {
- return getSharedPreferences(context)
- .getInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
- }
- }
-
- public static synchronized void setTrickplaySetting(Context context,
- @TrickplaySetting int trickplaySetting) {
- SoftPreconditions.checkState(sInitialized);
- SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET);
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting);
- } else {
- getSharedPreferences(context).edit()
- .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting)
- .apply();
- }
- }
-
- public static synchronized boolean getStoreTsStream(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
- } else {
- return getSharedPreferences(context)
- .getBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, false);
- }
- }
-
- public static synchronized void setStoreTsStream(Context context, boolean shouldStore) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore);
- } else {
- getSharedPreferences(context).edit()
- .putBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, shouldStore)
- .apply();
- }
- }
-
- private static SharedPreferences getSharedPreferences(Context context) {
- return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
- }
-
- private static synchronized void setPreference(Context context, String key, String value) {
- sPreferenceValues.putString(key, value);
- savePreference(context, key, value);
- }
-
- private static synchronized void setPreference(Context context, String key, int value) {
- sPreferenceValues.putInt(key, value);
- savePreference(context, key, Integer.toString(value));
- }
-
- private static synchronized void setPreference(Context context, String key, long value) {
- sPreferenceValues.putLong(key, value);
- savePreference(context, key, Long.toString(value));
- }
-
- private static synchronized void setPreference(Context context, String key, boolean value) {
- sPreferenceValues.putBoolean(key, value);
- savePreference(context, key, Boolean.toString(value));
- }
-
- private static void savePreference(final Context context, final String key,
- final String value) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- ContentResolver resolver = context.getContentResolver();
- ContentValues values = new ContentValues();
- values.put(Preferences.COLUMN_KEY, key);
- values.put(Preferences.COLUMN_VALUE, value);
- try {
- resolver.insert(Preferences.CONTENT_URI, values);
- } catch (Exception e) {
- SoftPreconditions.warn(TAG, "setPreference", "Error writing preference values",
- e);
- }
- return null;
- }
- }.execute();
- }
-
- private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> {
- private final Context mContext;
- private LoadPreferencesTask(Context context) {
- mContext = context;
- }
-
- @Override
- protected Bundle doInBackground(Void... params) {
- Bundle bundle = new Bundle();
- ContentResolver resolver = mContext.getContentResolver();
- String[] projection = new String[] { Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE };
- try (Cursor cursor = resolver.query(Preferences.CONTENT_URI, projection, null, null,
- null)) {
- if (cursor != null) {
- while (!isCancelled() && cursor.moveToNext()) {
- String key = cursor.getString(0);
- String value = cursor.getString(1);
- switch (key) {
- case PREFS_KEY_TRICKPLAY_EXPIRED_MS:
- bundle.putLong(key, Long.parseLong(value));
- break;
- case PREFS_KEY_CHANNEL_DATA_VERSION:
- case PREFS_KEY_SCANNED_CHANNEL_COUNT:
- case PREFS_KEY_TRICKPLAY_SETTING:
- try {
- bundle.putInt(key, Integer.parseInt(value));
- } catch (NumberFormatException e) {
- // Does nothing.
- }
- break;
- case PREFS_KEY_SCAN_DONE:
- case PREFS_KEY_LAUNCH_SETUP:
- case PREFS_KEY_STORE_TS_STREAM:
- bundle.putBoolean(key, Boolean.parseBoolean(value));
- break;
- case PREFS_KEY_LAST_POSTAL_CODE:
- bundle.putString(key, value);
- break;
- }
- }
- }
- } catch (Exception e) {
- SoftPreconditions.warn(TAG, "getPreference", "Error querying preference values", e);
- return null;
- }
- return bundle;
- }
-
- @Override
- protected void onPostExecute(Bundle bundle) {
- synchronized (TunerPreferences.class) {
- if (bundle != null) {
- sPreferenceValues.putAll(bundle);
- }
- }
- if (sPreferencesChangedListener != null) {
- sPreferencesChangedListener.onTunerPreferencesChanged();
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/cc/CaptionLayout.java b/src/com/android/tv/tuner/cc/CaptionLayout.java
deleted file mode 100644
index a88538df..00000000
--- a/src/com/android/tv/tuner/cc/CaptionLayout.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.cc;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.layout.ScaledLayout;
-
-/**
- * Layout containing the safe title area that helps the closed captions look more prominent.
- * This is required by CEA-708B.
- */
-public class CaptionLayout extends ScaledLayout {
- // The safe title area has 10% margins of the screen.
- private static final float SAFE_TITLE_AREA_SCALE_START_X = 0.1f;
- private static final float SAFE_TITLE_AREA_SCALE_END_X = 0.9f;
- private static final float SAFE_TITLE_AREA_SCALE_START_Y = 0.1f;
- private static final float SAFE_TITLE_AREA_SCALE_END_Y = 0.9f;
-
- private final ScaledLayout mSafeTitleAreaLayout;
- private AtscCaptionTrack mCaptionTrack;
-
- public CaptionLayout(Context context) {
- this(context, null);
- }
-
- public CaptionLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CaptionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mSafeTitleAreaLayout = new ScaledLayout(context);
- addView(mSafeTitleAreaLayout, new ScaledLayoutParams(
- SAFE_TITLE_AREA_SCALE_START_X, SAFE_TITLE_AREA_SCALE_END_X,
- SAFE_TITLE_AREA_SCALE_START_Y, SAFE_TITLE_AREA_SCALE_END_Y));
- }
-
- public void addOrUpdateViewToSafeTitleArea(CaptionWindowLayout captionWindowLayout,
- ScaledLayoutParams scaledLayoutParams) {
- int index = mSafeTitleAreaLayout.indexOfChild(captionWindowLayout);
- if (index < 0) {
- mSafeTitleAreaLayout.addView(captionWindowLayout, scaledLayoutParams);
- return;
- }
- mSafeTitleAreaLayout.updateViewLayout(captionWindowLayout, scaledLayoutParams);
- }
-
- public void removeViewFromSafeTitleArea(CaptionWindowLayout captionWindowLayout) {
- mSafeTitleAreaLayout.removeView(captionWindowLayout);
- }
-
- public void setCaptionTrack(AtscCaptionTrack captionTrack) {
- mCaptionTrack = captionTrack;
- }
-
- public AtscCaptionTrack getCaptionTrack() {
- return mCaptionTrack;
- }
-}
diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
deleted file mode 100644
index 24a0f354..00000000
--- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.cc;
-
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Decodes and renders CEA-708.
- */
-public class CaptionTrackRenderer implements Handler.Callback {
- // TODO: Remaining works
- // CaptionTrackRenderer does not support the full spec of CEA-708. The remaining works are
- // described in the follows.
- // C0 Table: Backspace, FF, and HCR are not supported. The rule for P16 is not standardized but
- // it is handled as EUC-KR charset for korea broadcasting.
- // C1 Table: All styles of windows and pens except underline, italic, pen size, and pen offset
- // specified in CEA-708 are ignored and this follows system wide cc preferences for
- // look and feel. SetPenLocation is not implemented.
- // G2 Table: TSP, NBTSP and BLK are not supported.
- // Text/commands: Word wrapping, fonts, row and column locking are not supported.
-
- private static final String TAG = "CaptionTrackRenderer";
- private static final boolean DEBUG = false;
-
- private static final long DELAY_IN_MILLIS = TimeUnit.MILLISECONDS.toMillis(100);
-
- // According to CEA-708B, there can exist up to 8 caption windows.
- private static final int CAPTION_WINDOWS_MAX = 8;
- private static final int CAPTION_ALL_WINDOWS_BITMAP = 255;
-
- private static final int MSG_DELAY_CANCEL = 1;
- private static final int MSG_CAPTION_CLEAR = 2;
-
- private static final long CAPTION_CLEAR_INTERVAL_MS = 60000;
-
- private final CaptionLayout mCaptionLayout;
- private boolean mIsDelayed = false;
- private CaptionWindowLayout mCurrentWindowLayout;
- private final CaptionWindowLayout[] mCaptionWindowLayouts =
- new CaptionWindowLayout[CAPTION_WINDOWS_MAX];
- private final ArrayList<CaptionEvent> mPendingCaptionEvents = new ArrayList<>();
- private final Handler mHandler;
-
- public CaptionTrackRenderer(CaptionLayout captionLayout) {
- mCaptionLayout = captionLayout;
- mHandler = new Handler(this);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DELAY_CANCEL:
- delayCancel();
- return true;
- case MSG_CAPTION_CLEAR:
- clearWindows(CAPTION_ALL_WINDOWS_BITMAP);
- return true;
- }
- return false;
- }
-
- public void start(AtscCaptionTrack captionTrack) {
- if (captionTrack == null) {
- stop();
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Start captionTrack " + captionTrack.language);
- }
- reset();
- mCaptionLayout.setCaptionTrack(captionTrack);
- mCaptionLayout.setVisibility(View.VISIBLE);
- }
-
- public void stop() {
- if (DEBUG) {
- Log.d(TAG, "Stop captionTrack");
- }
- mCaptionLayout.setVisibility(View.INVISIBLE);
- mHandler.removeMessages(MSG_CAPTION_CLEAR);
- }
-
- public void processCaptionEvent(CaptionEvent event) {
- if (mIsDelayed) {
- mPendingCaptionEvents.add(event);
- return;
- }
- switch (event.type) {
- case Cea708Parser.CAPTION_EMIT_TYPE_BUFFER:
- sendBufferToCurrentWindow((String) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_CONTROL:
- sendControlToCurrentWindow((char) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CWX:
- setCurrentWindowLayout((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CLW:
- clearWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DSW:
- displayWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_HDW:
- hideWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_TGW:
- toggleWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLW:
- deleteWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLY:
- delay((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLC:
- delayCancel();
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_RST:
- reset();
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPA:
- setPenAttr((CaptionPenAttr) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPC:
- setPenColor((CaptionPenColor) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPL:
- setPenLocation((CaptionPenLocation) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SWA:
- setWindowAttr((CaptionWindowAttr) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DFX:
- defineWindow((CaptionWindow) event.obj);
- break;
- }
- }
-
- // The window related caption commands
- private void setCurrentWindowLayout(int windowId) {
- if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
- return;
- }
- CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
- if (windowLayout == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "setCurrentWindowLayout to " + windowId);
- }
- mCurrentWindowLayout = windowLayout;
- }
-
- // Each bit of windowBitmap indicates a window.
- // If a bit is set, the window id is the same as the number of the trailing zeros of the bit.
- private ArrayList<CaptionWindowLayout> getWindowsFromBitmap(int windowBitmap) {
- ArrayList<CaptionWindowLayout> windows = new ArrayList<>();
- for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
- if ((windowBitmap & (1 << i)) != 0) {
- CaptionWindowLayout windowLayout = mCaptionWindowLayouts[i];
- if (windowLayout != null) {
- windows.add(windowLayout);
- }
- }
- }
- return windows;
- }
-
- private void clearWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.clear();
- }
- }
-
- private void displayWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.show();
- }
- }
-
- private void hideWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.hide();
- }
- }
-
- private void toggleWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- if (windowLayout.isShown()) {
- windowLayout.hide();
- } else {
- windowLayout.show();
- }
- }
- }
-
- private void deleteWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.removeFromCaptionView();
- mCaptionWindowLayouts[windowLayout.getCaptionWindowId()] = null;
- }
- }
-
- public void clear() {
- mHandler.sendEmptyMessage(MSG_CAPTION_CLEAR);
- }
-
- public void reset() {
- mCurrentWindowLayout = null;
- mIsDelayed = false;
- mPendingCaptionEvents.clear();
- for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
- if (mCaptionWindowLayouts[i] != null) {
- mCaptionWindowLayouts[i].removeFromCaptionView();
- }
- mCaptionWindowLayouts[i] = null;
- }
- mCaptionLayout.setVisibility(View.INVISIBLE);
- mHandler.removeMessages(MSG_CAPTION_CLEAR);
- }
-
- private void setWindowAttr(CaptionWindowAttr windowAttr) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setWindowAttr(windowAttr);
- }
- }
-
- private void defineWindow(CaptionWindow window) {
- if (window == null) {
- return;
- }
- int windowId = window.id;
- if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
- return;
- }
- CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
- if (windowLayout == null) {
- windowLayout = new CaptionWindowLayout(mCaptionLayout.getContext());
- }
- windowLayout.initWindow(mCaptionLayout, window);
- mCurrentWindowLayout = mCaptionWindowLayouts[windowId] = windowLayout;
- }
-
- // The job related caption commands
- private void delay(int tenthsOfSeconds) {
- if (tenthsOfSeconds < 0 || tenthsOfSeconds > 255) {
- return;
- }
- mIsDelayed = true;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DELAY_CANCEL),
- tenthsOfSeconds * DELAY_IN_MILLIS);
- }
-
- private void delayCancel() {
- mIsDelayed = false;
- processPendingBuffer();
- }
-
- private void processPendingBuffer() {
- for (CaptionEvent event : mPendingCaptionEvents) {
- processCaptionEvent(event);
- }
- mPendingCaptionEvents.clear();
- }
-
- // The implicit write caption commands
- private void sendControlToCurrentWindow(char control) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.sendControl(control);
- }
- }
-
- private void sendBufferToCurrentWindow(String buffer) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.sendBuffer(buffer);
- mHandler.removeMessages(MSG_CAPTION_CLEAR);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CAPTION_CLEAR),
- CAPTION_CLEAR_INTERVAL_MS);
- }
- }
-
- // The pen related caption commands
- private void setPenAttr(CaptionPenAttr attr) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setPenAttr(attr);
- }
- }
-
- private void setPenColor(CaptionPenColor color) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setPenColor(color);
- }
- }
-
- private void setPenLocation(CaptionPenLocation location) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setPenLocation(location.row, location.column);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java b/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
deleted file mode 100644
index 6f42b506..00000000
--- a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.cc;
-
-import android.content.Context;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.StyleSpan;
-import android.text.style.SubscriptSpan;
-import android.text.style.SuperscriptSpan;
-import android.text.style.UnderlineSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.CaptioningManager;
-import android.view.accessibility.CaptioningManager.CaptionStyle;
-import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
-import android.widget.RelativeLayout;
-
-import com.google.android.exoplayer.text.CaptionStyleCompat;
-import com.google.android.exoplayer.text.SubtitleView;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
-import com.android.tv.tuner.layout.ScaledLayout;
-
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Layout which renders a caption window of CEA-708B. It contains a {@link SubtitleView} that
- * takes care of displaying the actual cc text.
- */
-public class CaptionWindowLayout extends RelativeLayout implements View.OnLayoutChangeListener {
- private static final String TAG = "CaptionWindowLayout";
- private static final boolean DEBUG = false;
-
- private static final float PROPORTION_PEN_SIZE_SMALL = .75f;
- private static final float PROPORTION_PEN_SIZE_LARGE = 1.25f;
-
- // The following values indicates the maximum cell number of a window.
- private static final int ANCHOR_RELATIVE_POSITIONING_MAX = 99;
- private static final int ANCHOR_VERTICAL_MAX = 74;
- private static final int ANCHOR_HORIZONTAL_4_3_MAX = 159;
- private static final int ANCHOR_HORIZONTAL_16_9_MAX = 209;
-
- // The following values indicates a gravity of a window.
- private static final int ANCHOR_MODE_DIVIDER = 3;
- private static final int ANCHOR_HORIZONTAL_MODE_LEFT = 0;
- private static final int ANCHOR_HORIZONTAL_MODE_CENTER = 1;
- private static final int ANCHOR_HORIZONTAL_MODE_RIGHT = 2;
- private static final int ANCHOR_VERTICAL_MODE_TOP = 0;
- private static final int ANCHOR_VERTICAL_MODE_CENTER = 1;
- private static final int ANCHOR_VERTICAL_MODE_BOTTOM = 2;
-
- private static final int US_MAX_COLUMN_COUNT_16_9 = 42;
- private static final int US_MAX_COLUMN_COUNT_4_3 = 32;
- private static final int KR_MAX_COLUMN_COUNT_16_9 = 52;
- private static final int KR_MAX_COLUMN_COUNT_4_3 = 40;
- private static final int MAX_ROW_COUNT = 15;
-
- private static final String KOR_ALPHABET =
- new String("\uAC00".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
- private static final float WIDE_SCREEN_ASPECT_RATIO_THRESHOLD = 1.6f;
-
- private CaptionLayout mCaptionLayout;
- private CaptionStyleCompat mCaptionStyleCompat;
-
- // TODO: Replace SubtitleView to {@link com.google.android.exoplayer.text.SubtitleLayout}.
- private final SubtitleView mSubtitleView;
- private int mRowLimit = 0;
- private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
- private final List<CharacterStyle> mCharacterStyles = new ArrayList<>();
- private int mCaptionWindowId;
- private int mCurrentTextRow = -1;
- private float mFontScale;
- private float mTextSize;
- private String mWidestChar;
- private int mLastCaptionLayoutWidth;
- private int mLastCaptionLayoutHeight;
- private int mWindowJustify;
- private int mPrintDirection;
-
- private class SystemWideCaptioningChangeListener extends CaptioningChangeListener {
- @Override
- public void onUserStyleChanged(CaptionStyle userStyle) {
- mCaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(userStyle);
- mSubtitleView.setStyle(mCaptionStyleCompat);
- updateWidestChar();
- }
-
- @Override
- public void onFontScaleChanged(float fontScale) {
- mFontScale = fontScale;
- updateTextSize();
- }
- }
-
- public CaptionWindowLayout(Context context) {
- this(context, null);
- }
-
- public CaptionWindowLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CaptionWindowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- // Add a subtitle view to the layout.
- mSubtitleView = new SubtitleView(context);
- LayoutParams params = new RelativeLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- addView(mSubtitleView, params);
-
- // Set the system wide cc preferences to the subtitle view.
- CaptioningManager captioningManager =
- (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
- mFontScale = captioningManager.getFontScale();
- mCaptionStyleCompat =
- CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
- mSubtitleView.setStyle(mCaptionStyleCompat);
- mSubtitleView.setText("");
- captioningManager.addCaptioningChangeListener(new SystemWideCaptioningChangeListener());
- updateWidestChar();
- }
-
- public int getCaptionWindowId() {
- return mCaptionWindowId;
- }
-
- public void setCaptionWindowId(int captionWindowId) {
- mCaptionWindowId = captionWindowId;
- }
-
- public void clear() {
- clearText();
- hide();
- }
-
- public void show() {
- setVisibility(View.VISIBLE);
- requestLayout();
- }
-
- public void hide() {
- setVisibility(View.INVISIBLE);
- requestLayout();
- }
-
- public void setPenAttr(CaptionPenAttr penAttr) {
- mCharacterStyles.clear();
- if (penAttr.italic) {
- mCharacterStyles.add(new StyleSpan(Typeface.ITALIC));
- }
- if (penAttr.underline) {
- mCharacterStyles.add(new UnderlineSpan());
- }
- switch (penAttr.penSize) {
- case CaptionPenAttr.PEN_SIZE_SMALL:
- mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_SMALL));
- break;
- case CaptionPenAttr.PEN_SIZE_LARGE:
- mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_LARGE));
- break;
- }
- switch (penAttr.penOffset) {
- case CaptionPenAttr.OFFSET_SUBSCRIPT:
- mCharacterStyles.add(new SubscriptSpan());
- break;
- case CaptionPenAttr.OFFSET_SUPERSCRIPT:
- mCharacterStyles.add(new SuperscriptSpan());
- break;
- }
- }
-
- public void setPenColor(CaptionPenColor penColor) {
- // TODO: apply pen colors or skip this and use the style of system wide cc style as is.
- }
-
- public void setPenLocation(int row, int column) {
- // TODO: change the location of pen when window's justify isn't left.
- // According to the CEA708B spec 8.7, setPenLocation means set the pen cursor within
- // window's text buffer. When row > mCurrentTextRow, we add "\n" to make the cursor locate
- // at row. Adding white space to make cursor locate at column.
- if (mWindowJustify == CaptionWindowAttr.JUSTIFY_LEFT) {
- if (mCurrentTextRow >= 0) {
- for (int r = mCurrentTextRow; r < row; ++r) {
- appendText("\n");
- }
- if (mCurrentTextRow <= row) {
- for (int i = 0; i < column; ++i) {
- appendText(" ");
- }
- }
- }
- }
- mCurrentTextRow = row;
- }
-
- public void setWindowAttr(CaptionWindowAttr windowAttr) {
- // TODO: apply window attrs or skip this and use the style of system wide cc style as is.
- mWindowJustify = windowAttr.justify;
- mPrintDirection = windowAttr.printDirection;
- }
-
- public void sendBuffer(String buffer) {
- appendText(buffer);
- }
-
- public void sendControl(char control) {
- // TODO: there are a bunch of ASCII-style control codes.
- }
-
- /**
- * This method places the window on a given CaptionLayout along with the anchor of the window.
- * <p>
- * According to CEA-708B, the anchor id indicates the gravity of the window as the follows.
- * For example, A value 7 of a anchor id says that a window is align with its parent bottom and
- * is located at the center horizontally of its parent.
- * </p>
- * <h4>Anchor id and the gravity of a window</h4>
- * <table>
- * <tr>
- * <th>GRAVITY</th>
- * <th>LEFT</th>
- * <th>CENTER_HORIZONTAL</th>
- * <th>RIGHT</th>
- * </tr>
- * <tr>
- * <th>TOP</th>
- * <td>0</td>
- * <td>1</td>
- * <td>2</td>
- * </tr>
- * <tr>
- * <th>CENTER_VERTICAL</th>
- * <td>3</td>
- * <td>4</td>
- * <td>5</td>
- * </tr>
- * <tr>
- * <th>BOTTOM</th>
- * <td>6</td>
- * <td>7</td>
- * <td>8</td>
- * </tr>
- * </table>
- * <p>
- * In order to handle the gravity of a window, there are two steps. First, set the size of the
- * window. Since the window will be positioned at {@link ScaledLayout}, the size factors are
- * determined in a ratio. Second, set the gravity of the window. {@link CaptionWindowLayout} is
- * inherited from {@link RelativeLayout}. Hence, we could set the gravity of its child view,
- * {@link SubtitleView}.
- * </p>
- * <p>
- * The gravity of the window is also related to its size. When it should be pushed to a one of
- * the end of the window, like LEFT, RIGHT, TOP or BOTTOM, the anchor point should be a boundary
- * of the window. When it should be pushed in the horizontal/vertical center of its container,
- * the horizontal/vertical center point of the window should be the same as the anchor point.
- * </p>
- *
- * @param captionLayout a given {@link CaptionLayout}, which contains a safe title area
- * @param captionWindow a given {@link CaptionWindow}, which stores the construction info of the
- * window
- */
- public void initWindow(CaptionLayout captionLayout, CaptionWindow captionWindow) {
- if (DEBUG) {
- Log.d(TAG, "initWindow with "
- + (captionLayout != null ? captionLayout.getCaptionTrack() : null));
- }
- if (mCaptionLayout != captionLayout) {
- if (mCaptionLayout != null) {
- mCaptionLayout.removeOnLayoutChangeListener(this);
- }
- mCaptionLayout = captionLayout;
- mCaptionLayout.addOnLayoutChangeListener(this);
- updateWidestChar();
- }
-
- // Both anchor vertical and horizontal indicates the position cell number of the window.
- float scaleRow = (float) captionWindow.anchorVertical / (captionWindow.relativePositioning
- ? ANCHOR_RELATIVE_POSITIONING_MAX : ANCHOR_VERTICAL_MAX);
- float scaleCol = (float) captionWindow.anchorHorizontal /
- (captionWindow.relativePositioning ? ANCHOR_RELATIVE_POSITIONING_MAX
- : (isWideAspectRatio()
- ? ANCHOR_HORIZONTAL_16_9_MAX : ANCHOR_HORIZONTAL_4_3_MAX));
-
- // The range of scaleRow/Col need to be verified to be in [0, 1].
- // Otherwise a {@link RuntimeException} will be raised in {@link ScaledLayout}.
- if (scaleRow < 0 || scaleRow > 1) {
- Log.i(TAG, "The vertical position of the anchor point should be at the range of 0 and 1"
- + " but " + scaleRow);
- scaleRow = Math.max(0, Math.min(scaleRow, 1));
- }
- if (scaleCol < 0 || scaleCol > 1) {
- Log.i(TAG, "The horizontal position of the anchor point should be at the range of 0 and"
- + " 1 but " + scaleCol);
- scaleCol = Math.max(0, Math.min(scaleCol, 1));
- }
- int gravity = Gravity.CENTER;
- int horizontalMode = captionWindow.anchorId % ANCHOR_MODE_DIVIDER;
- int verticalMode = captionWindow.anchorId / ANCHOR_MODE_DIVIDER;
- float scaleStartRow = 0;
- float scaleEndRow = 1;
- float scaleStartCol = 0;
- float scaleEndCol = 1;
- switch (horizontalMode) {
- case ANCHOR_HORIZONTAL_MODE_LEFT:
- gravity = Gravity.LEFT;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_NORMAL);
- scaleStartCol = scaleCol;
- break;
- case ANCHOR_HORIZONTAL_MODE_CENTER:
- float gap = Math.min(1 - scaleCol, scaleCol);
-
- // Since all TV sets use left text alignment instead of center text alignment
- // for this case, we follow the industry convention if possible.
- int columnCount = captionWindow.columnCount + 1;
- if (isKoreanLanguageTrack()) {
- columnCount /= 2;
- }
- columnCount = Math.min(getScreenColumnCount(), columnCount);
- StringBuilder widestTextBuilder = new StringBuilder();
- for (int i = 0; i < columnCount; ++i) {
- widestTextBuilder.append(mWidestChar);
- }
- Paint paint = new Paint();
- paint.setTypeface(mCaptionStyleCompat.typeface);
- paint.setTextSize(mTextSize);
- float maxWindowWidth = paint.measureText(widestTextBuilder.toString());
- float halfMaxWidthScale = mCaptionLayout.getWidth() > 0
- ? maxWindowWidth / 2.0f / (mCaptionLayout.getWidth() * 0.8f) : 0.0f;
- if (halfMaxWidthScale > 0f && halfMaxWidthScale < scaleCol) {
- // Calculate the expected max window size based on the column count of the
- // caption window multiplied by average alphabets char width, then align the
- // left side of the window with the left side of the expected max window.
- gravity = Gravity.LEFT;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_NORMAL);
- scaleStartCol = scaleCol - halfMaxWidthScale;
- scaleEndCol = 1.0f;
- } else {
- // The gap will be the minimum distance value of the distances from both
- // horizontal end points to the anchor point.
- // If scaleCol <= 0.5, the range of scaleCol is [0, the anchor point * 2].
- // If scaleCol > 0.5, the range of scaleCol is [(1 - the anchor point) * 2, 1].
- // The anchor point is located at the horizontal center of the window in both
- // cases.
- gravity = Gravity.CENTER_HORIZONTAL;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_CENTER);
- scaleStartCol = scaleCol - gap;
- scaleEndCol = scaleCol + gap;
- }
- break;
- case ANCHOR_HORIZONTAL_MODE_RIGHT:
- gravity = Gravity.RIGHT;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_OPPOSITE);
- scaleEndCol = scaleCol;
- break;
- }
- switch (verticalMode) {
- case ANCHOR_VERTICAL_MODE_TOP:
- gravity |= Gravity.TOP;
- scaleStartRow = scaleRow;
- break;
- case ANCHOR_VERTICAL_MODE_CENTER:
- gravity |= Gravity.CENTER_VERTICAL;
-
- // See the above comment.
- float gap = Math.min(1 - scaleRow, scaleRow);
- scaleStartRow = scaleRow - gap;
- scaleEndRow = scaleRow + gap;
- break;
- case ANCHOR_VERTICAL_MODE_BOTTOM:
- gravity |= Gravity.BOTTOM;
- scaleEndRow = scaleRow;
- break;
- }
- mCaptionLayout.addOrUpdateViewToSafeTitleArea(this, new ScaledLayout
- .ScaledLayoutParams(scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
- setCaptionWindowId(captionWindow.id);
- setRowLimit(captionWindow.rowCount);
- setGravity(gravity);
- setWindowStyle(captionWindow.windowStyle);
- if (mWindowJustify == CaptionWindowAttr.JUSTIFY_CENTER) {
- mSubtitleView.setTextAlignment(Alignment.ALIGN_CENTER);
- }
- if (captionWindow.visible) {
- show();
- } else {
- hide();
- }
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- int width = right - left;
- int height = bottom - top;
- if (width != mLastCaptionLayoutWidth || height != mLastCaptionLayoutHeight) {
- mLastCaptionLayoutWidth = width;
- mLastCaptionLayoutHeight = height;
- updateTextSize();
- }
- }
-
- private boolean isKoreanLanguageTrack() {
- return mCaptionLayout != null && mCaptionLayout.getCaptionTrack() != null
- && mCaptionLayout.getCaptionTrack().language != null
- && "KOR".compareToIgnoreCase(mCaptionLayout.getCaptionTrack().language) == 0;
- }
-
- private boolean isWideAspectRatio() {
- return mCaptionLayout != null && mCaptionLayout.getCaptionTrack() != null
- && mCaptionLayout.getCaptionTrack().wideAspectRatio;
- }
-
- private void updateWidestChar() {
- if (isKoreanLanguageTrack()) {
- mWidestChar = KOR_ALPHABET;
- } else {
- Paint paint = new Paint();
- paint.setTypeface(mCaptionStyleCompat.typeface);
- Charset latin1 = Charset.forName("ISO-8859-1");
- float widestCharWidth = 0f;
- for (int i = 0; i < 256; ++i) {
- String ch = new String(new byte[]{(byte) i}, latin1);
- float charWidth = paint.measureText(ch);
- if (widestCharWidth < charWidth) {
- widestCharWidth = charWidth;
- mWidestChar = ch;
- }
- }
- }
- updateTextSize();
- }
-
- private void updateTextSize() {
- if (mCaptionLayout == null) return;
-
- // Calculate text size based on the max window size.
- StringBuilder widestTextBuilder = new StringBuilder();
- int screenColumnCount = getScreenColumnCount();
- for (int i = 0; i < screenColumnCount; ++i) {
- widestTextBuilder.append(mWidestChar);
- }
- String widestText = widestTextBuilder.toString();
- Paint paint = new Paint();
- paint.setTypeface(mCaptionStyleCompat.typeface);
- float startFontSize = 0f;
- float endFontSize = 255f;
- Rect boundRect = new Rect();
- while (startFontSize < endFontSize) {
- float testTextSize = (startFontSize + endFontSize) / 2f;
- paint.setTextSize(testTextSize);
- float width = paint.measureText(widestText);
- paint.getTextBounds(widestText, 0, widestText.length(), boundRect);
- float height = boundRect.height() + width - boundRect.width();
- // According to CEA-708B Section 9.13, the height of standard font size shouldn't taller
- // than 1/15 of the height of the safe-title area, and the width shouldn't wider than
- // 1/{@code getScreenColumnCount()} of the width of the safe-title area.
- if (mCaptionLayout.getWidth() * 0.8f > width
- && mCaptionLayout.getHeight() * 0.8f / MAX_ROW_COUNT > height) {
- startFontSize = testTextSize + 0.01f;
- } else {
- endFontSize = testTextSize - 0.01f;
- }
- }
- mTextSize = endFontSize * mFontScale;
- paint.setTextSize(mTextSize);
- float whiteSpaceWidth = paint.measureText(" ");
- mSubtitleView.setWhiteSpaceWidth(whiteSpaceWidth);
- mSubtitleView.setTextSize(mTextSize);
- }
-
- private int getScreenColumnCount() {
- float screenAspectRatio = (float) mCaptionLayout.getWidth() / mCaptionLayout.getHeight();
- boolean isWideAspectRationScreen = screenAspectRatio > WIDE_SCREEN_ASPECT_RATIO_THRESHOLD;
- if (isKoreanLanguageTrack()) {
- // Each korean character consumes two slots.
- if (isWideAspectRationScreen || isWideAspectRatio()) {
- return KR_MAX_COLUMN_COUNT_16_9 / 2;
- } else {
- return KR_MAX_COLUMN_COUNT_4_3 / 2;
- }
- } else {
- if (isWideAspectRationScreen || isWideAspectRatio()) {
- return US_MAX_COLUMN_COUNT_16_9;
- } else {
- return US_MAX_COLUMN_COUNT_4_3;
- }
- }
- }
-
- public void removeFromCaptionView() {
- if (mCaptionLayout != null) {
- mCaptionLayout.removeViewFromSafeTitleArea(this);
- mCaptionLayout.removeOnLayoutChangeListener(this);
- mCaptionLayout = null;
- }
- }
-
- public void setText(String text) {
- updateText(text, false);
- }
-
- public void appendText(String text) {
- updateText(text, true);
- }
-
- public void clearText() {
- mBuilder.clear();
- mSubtitleView.setText("");
- }
-
- private void updateText(String text, boolean appended) {
- if (!appended) {
- mBuilder.clear();
- }
- if (text != null && text.length() > 0) {
- int length = mBuilder.length();
- mBuilder.append(text);
- for (CharacterStyle characterStyle : mCharacterStyles) {
- mBuilder.setSpan(characterStyle, length, mBuilder.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- String[] lines = TextUtils.split(mBuilder.toString(), "\n");
-
- // Truncate text not to exceed the row limit.
- // Plus one here since the range of the rows is [0, mRowLimit].
- int startRow = Math.max(0, lines.length - (mRowLimit + 1));
- String truncatedText = TextUtils.join("\n", Arrays.copyOfRange(
- lines, startRow, lines.length));
- mBuilder.delete(0, mBuilder.length() - truncatedText.length());
- mCurrentTextRow = lines.length - startRow - 1;
-
- // Trim the buffer first then set text to {@link SubtitleView}.
- int start = 0, last = mBuilder.length() - 1;
- int end = last;
- while ((start <= end) && (mBuilder.charAt(start) <= ' ')) {
- ++start;
- }
- while (start - 1 >= 0 && start <= end && mBuilder.charAt(start - 1) != '\n') {
- --start;
- }
- while ((end >= start) && (mBuilder.charAt(end) <= ' ')) {
- --end;
- }
- if (start == 0 && end == last) {
- mSubtitleView.setPrefixSpaces(getPrefixSpaces(mBuilder));
- mSubtitleView.setText(mBuilder);
- } else {
- SpannableStringBuilder trim = new SpannableStringBuilder();
- trim.append(mBuilder);
- if (end < last) {
- trim.delete(end + 1, last + 1);
- }
- if (start > 0) {
- trim.delete(0, start);
- }
- mSubtitleView.setPrefixSpaces(getPrefixSpaces(trim));
- mSubtitleView.setText(trim);
- }
- }
-
- private static ArrayList<Integer> getPrefixSpaces(SpannableStringBuilder builder) {
- ArrayList<Integer> prefixSpaces = new ArrayList<>();
- String[] lines = TextUtils.split(builder.toString(), "\n");
- for (String line : lines) {
- int start = 0;
- while (start < line.length() && line.charAt(start) <= ' ') {
- start++;
- }
- prefixSpaces.add(start);
- }
- return prefixSpaces;
- }
-
- public void setRowLimit(int rowLimit) {
- if (rowLimit < 0) {
- throw new IllegalArgumentException("A rowLimit should have a positive number");
- }
- mRowLimit = rowLimit;
- }
-
- private void setWindowStyle(int windowStyle) {
- // TODO: Set other attributes of window style. Like fill opacity and fill color.
- switch (windowStyle) {
- case 2:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 3:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_CENTER;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 4:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 5:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 6:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_CENTER;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 7:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_TOP_TO_BOTTOM;
- break;
- default:
- if (windowStyle != 0 && windowStyle != 1) {
- Log.e(TAG, "Error predefined window style:" + windowStyle);
- }
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java
deleted file mode 100644
index d0f6cf11..00000000
--- a/src/com/android/tv/tuner/cc/Cea708Parser.java
+++ /dev/null
@@ -1,820 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.cc;
-
-import android.os.SystemClock;
-import android.support.annotation.IntDef;
-import android.util.Log;
-import android.util.SparseIntArray;
-
-import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.Cea708Data.CaptionColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
-import com.android.tv.tuner.data.Cea708Data.CcPacket;
-import com.android.tv.tuner.util.ByteArrayBuffer;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.TreeSet;
-
-/**
- * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV.
- *
- * <p>ATSC DTV closed caption data are carried on picture user data of video streams.
- * This class starts to parse from picture user data payload, so extraction process of user_data
- * from video streams is up to outside of this code.
- *
- * <p>There are 4 steps to decode user_data to provide closed caption services.
- *
- * <h3>Step 1. user_data -&gt; CcPacket ({@link #parseClosedCaption} method)</h3>
- *
- * <p>First, user_data consists of cc_data packets, which are 3-byte segments. Here, CcPacket is a
- * collection of cc_data packets in a frame along with same presentation timestamp. Because cc_data
- * packets must be reassembled in the frame display order, CcPackets are reordered.
- *
- * <h3>Step 2. CcPacket -&gt; DTVCC packet ({@link #parseCcPacket} method)</h3>
- *
- * <p>Each cc_data packet has a one byte for declaring a type of itself and data validity, and the
- * subsequent two bytes for input data of a DTVCC packet. There are 4 types for cc_data packet.
- * We're interested in DTVCC_PACKET_START(type 3) and DTVCC_PACKET_DATA(type 2). Each DTVCC packet
- * begins with DTVCC_PACKET_START(type 3) and the following cc_data packets which has
- * DTVCC_PACKET_DATA(type 2) are appended into the DTVCC packet being assembled.
- *
- * <h3>Step 3. DTVCC packet -&gt; Service Blocks ({@link #parseDtvCcPacket} method)</h3>
- *
- * <p>A DTVCC packet consists of multiple service blocks. Each service block represents a caption
- * track and has a service number, which ranges from 1 to 63, that denotes caption track identity.
- * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}.
- * Otherwise, just skip the other service blocks.
- *
- * <h3>Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX},
- * and {@link #parseExt1} methods)</h3>
- *
- * <p>Service block data is actual caption stream. it looks similar to telnet. It uses most parts of
- * ASCII table and consists of specially defined commands and some ASCII control codes which work
- * in a behavior slightly different from their original purpose. ASCII control codes and caption
- * commands are explicit instructions that control the state of a closed caption service and the
- * other ASCII and text codes are implicit instructions that send their characters to buffer.
- *
- * <p>There are 4 main code groups and 4 extended code groups. Both the range of code groups are the
- * same as the range of a byte.
- *
- * <p>4 main code groups: C0, C1, G0, G1
- * <br>4 extended code groups: C2, C3, G2, G3
- *
- * <p>Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group
- * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while
- * {@link #parseExt1} method maps on the extended code groups.
- *
- * <p>The main code groups:
- * <ul>
- * <li>C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA
- * standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc,
- * even for the alphanumeric characters instead of ASCII characters.</li>
- * <li>C1 - contains the caption commands. There are 3 categories of a caption command.</li>
- * <ul>
- * <li>Window commands: The window commands control a caption window which is addressable area being
- * with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX)</li>
- * <li>Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL)</li>
- * <li>Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, RST)</li>
- * </ul>
- * <li>G0 - same as printable ASCII character set except music note character.</li>
- * <li>G1 - same as ISO 8859-1 Latin 1 character set.</li>
- * </ul>
- * <p>Most of the extended code groups are being skipped.
- *
- */
-public class Cea708Parser {
- private static final String TAG = "Cea708Parser";
- private static final boolean DEBUG = false;
-
- // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
- private static final int MAX_ALLOCATED_SIZE = 9600 / 8;
- private static final String MUSIC_NOTE_CHAR = new String(
- "\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
-
- // The following values are denoting the type of closed caption data.
- // See CEA-708B section 4.4.1.
- private static final int CC_TYPE_DTVCC_PACKET_START = 3;
- private static final int CC_TYPE_DTVCC_PACKET_DATA = 2;
-
- // The following values are defined in CEA-708B Figure 4 and 6.
- private static final int DTVCC_MAX_PACKET_SIZE = 64;
- private static final int DTVCC_PACKET_SIZE_SCALE_FACTOR = 2;
- private static final int DTVCC_EXTENDED_SERVICE_NUMBER_POINT = 7;
-
- // The following values are for seeking closed caption tracks.
- private static final int DISCOVERY_PERIOD_MS = 10000; // 10 sec
- private static final int DISCOVERY_NUM_BYTES_THRESHOLD = 10; // 10 bytes
- private static final int DISCOVERY_CC_SERVICE_NUMBER_START = 1; // CC1
- private static final int DISCOVERY_CC_SERVICE_NUMBER_END = 4; // CC4
-
- private final ByteArrayBuffer mDtvCcPacket = new ByteArrayBuffer(MAX_ALLOCATED_SIZE);
- private final TreeSet<CcPacket> mCcPackets = new TreeSet<>();
- private final StringBuffer mBuffer = new StringBuffer();
- private final SparseIntArray mDiscoveredNumBytes = new SparseIntArray(); // per service number
- private long mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
- private int mCommand = 0;
- private int mListenServiceNumber = 0;
- private boolean mDtvCcPacking = false;
- private boolean mFirstServiceNumberDiscovered;
-
- // Assign a dummy listener in order to avoid null checks.
- private OnCea708ParserListener mListener = new OnCea708ParserListener() {
- @Override
- public void emitEvent(CaptionEvent event) {
- // do nothing
- }
-
- @Override
- public void discoverServiceNumber(int serviceNumber) {
- // do nothing
- }
- };
-
- /**
- * {@link Cea708Parser} emits caption event of three different types.
- * {@link OnCea708ParserListener#emitEvent} is invoked with the parameter
- * {@link CaptionEvent} to pass all the results to an observer of the decoding process.
- *
- * <p>{@link CaptionEvent#type} determines the type of the result and
- * {@link CaptionEvent#obj} contains the output value of a caption event.
- * The observer must do the casting to the corresponding type.
- *
- * <ul><li>{@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer.
- * {@code obj} must be of {@link String}.</li>
- *
- * <li>{@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a observer.
- * {@code obj} must be of {@link Character}.</li>
- *
- * <li>{@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer.
- * {@code obj} must be {@code NULL}.</li></ul>
- */
- @IntDef({CAPTION_EMIT_TYPE_BUFFER, CAPTION_EMIT_TYPE_CONTROL, CAPTION_EMIT_TYPE_COMMAND_CWX,
- CAPTION_EMIT_TYPE_COMMAND_CLW, CAPTION_EMIT_TYPE_COMMAND_DSW, CAPTION_EMIT_TYPE_COMMAND_HDW,
- CAPTION_EMIT_TYPE_COMMAND_TGW, CAPTION_EMIT_TYPE_COMMAND_DLW, CAPTION_EMIT_TYPE_COMMAND_DLY,
- CAPTION_EMIT_TYPE_COMMAND_DLC, CAPTION_EMIT_TYPE_COMMAND_RST, CAPTION_EMIT_TYPE_COMMAND_SPA,
- CAPTION_EMIT_TYPE_COMMAND_SPC, CAPTION_EMIT_TYPE_COMMAND_SPL, CAPTION_EMIT_TYPE_COMMAND_SWA,
- CAPTION_EMIT_TYPE_COMMAND_DFX})
- @Retention(RetentionPolicy.SOURCE)
- public @interface CaptionEmitType {}
- public static final int CAPTION_EMIT_TYPE_BUFFER = 1;
- public static final int CAPTION_EMIT_TYPE_CONTROL = 2;
- public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3;
- public static final int CAPTION_EMIT_TYPE_COMMAND_CLW = 4;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DSW = 5;
- public static final int CAPTION_EMIT_TYPE_COMMAND_HDW = 6;
- public static final int CAPTION_EMIT_TYPE_COMMAND_TGW = 7;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DLW = 8;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DLY = 9;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DLC = 10;
- public static final int CAPTION_EMIT_TYPE_COMMAND_RST = 11;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SPA = 12;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SPC = 13;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SPL = 14;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SWA = 15;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DFX = 16;
-
- public interface OnCea708ParserListener {
- void emitEvent(CaptionEvent event);
- void discoverServiceNumber(int serviceNumber);
- }
-
- public void setListener(OnCea708ParserListener listener) {
- if (listener != null) {
- mListener = listener;
- }
- }
-
- public void clear() {
- mDtvCcPacket.clear();
- mCcPackets.clear();
- mBuffer.setLength(0);
- mDiscoveredNumBytes.clear();
- mCommand = 0;
- mDtvCcPacking = false;
- }
-
- public void setListenServiceNumber(int serviceNumber) {
- mListenServiceNumber = serviceNumber;
- }
-
- private void emitCaptionEvent(CaptionEvent captionEvent) {
- // Emit the existing string buffer before a new event is arrived.
- emitCaptionBuffer();
- mListener.emitEvent(captionEvent);
- }
-
- private void emitCaptionBuffer() {
- if (mBuffer.length() > 0) {
- mListener.emitEvent(new CaptionEvent(CAPTION_EMIT_TYPE_BUFFER, mBuffer.toString()));
- mBuffer.setLength(0);
- }
- }
-
- // Step 1. user_data -> CcPacket ({@link #parseClosedCaption} method)
- public void parseClosedCaption(ByteBuffer data, long framePtsUs) {
- int ccCount = data.limit() / 3;
- byte[] ccBytes = new byte[3 * ccCount];
- for (int i = 0; i < 3 * ccCount; i++) {
- ccBytes[i] = data.get(i);
- }
- CcPacket ccPacket = new CcPacket(ccBytes, ccCount, framePtsUs);
- mCcPackets.add(ccPacket);
- }
-
- public boolean processClosedCaptions(long framePtsUs) {
- // To get the sorted cc packets that have lower frame pts than current frame pts,
- // the following offset divides off the lower side of the packets.
- CcPacket offsetPacket = new CcPacket(new byte[0], 0, framePtsUs);
- offsetPacket = mCcPackets.lower(offsetPacket);
- boolean processed = false;
- if (offsetPacket != null) {
- while (!mCcPackets.isEmpty() && offsetPacket.compareTo(mCcPackets.first()) >= 0) {
- CcPacket packet = mCcPackets.pollFirst();
- parseCcPacket(packet);
- processed = true;
- }
- }
- return processed;
- }
-
- // Step 2. CcPacket -> DTVCC packet ({@link #parseCcPacket} method)
- private void parseCcPacket(CcPacket ccPacket) {
- // For the details of cc packet, see ATSC TSG-676 - Table A8.
- byte[] bytes = ccPacket.bytes;
- int pos = 0;
- for (int i = 0; i < ccPacket.ccCount; ++i) {
- boolean ccValid = (bytes[pos] & 0x04) != 0;
- int ccType = bytes[pos] & 0x03;
-
- // The dtvcc should be considered complete:
- // - if either ccValid is set and ccType is 3
- // - or ccValid is clear and ccType is 2 or 3.
- if (ccValid) {
- if (ccType == CC_TYPE_DTVCC_PACKET_START) {
- if (mDtvCcPacking) {
- parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
- mDtvCcPacket.clear();
- }
- mDtvCcPacking = true;
- mDtvCcPacket.append(bytes[pos + 1]);
- mDtvCcPacket.append(bytes[pos + 2]);
- } else if (mDtvCcPacking && ccType == CC_TYPE_DTVCC_PACKET_DATA) {
- mDtvCcPacket.append(bytes[pos + 1]);
- mDtvCcPacket.append(bytes[pos + 2]);
- }
- } else {
- if ((ccType == CC_TYPE_DTVCC_PACKET_START || ccType == CC_TYPE_DTVCC_PACKET_DATA)
- && mDtvCcPacking) {
- mDtvCcPacking = false;
- parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
- mDtvCcPacket.clear();
- }
- }
- pos += 3;
- }
- }
-
- // Step 3. DTVCC packet -> Service Blocks ({@link #parseDtvCcPacket} method)
- private void parseDtvCcPacket(byte[] data, int limit) {
- // For the details of DTVCC packet, see CEA-708B Figure 4.
- int pos = 0;
- int packetSize = data[pos] & 0x3f;
- if (packetSize == 0) {
- packetSize = DTVCC_MAX_PACKET_SIZE;
- }
- int calculatedPacketSize = packetSize * DTVCC_PACKET_SIZE_SCALE_FACTOR;
- if (limit != calculatedPacketSize) {
- return;
- }
- ++pos;
- int len = pos + calculatedPacketSize;
- while (pos < len) {
- // For the details of Service Block, see CEA-708B Figure 5 and 6.
- int serviceNumber = (data[pos] & 0xe0) >> 5;
- int blockSize = data[pos] & 0x1f;
- ++pos;
- if (serviceNumber == DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
- serviceNumber = (data[pos] & 0x3f);
- ++pos;
-
- // Return if invalid service number
- if (serviceNumber < DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
- return;
- }
- }
- if (pos + blockSize > limit) {
- return;
- }
-
- // Send parsed service number in order to find unveiled closed caption tracks which
- // are not specified in any ATSC PSIP sections. Since some broadcasts send empty closed
- // caption tracks, it detects the proper closed caption tracks by counting the number of
- // bytes sent with the same service number during a discovery period.
- // The viewer in most TV sets chooses between CC1, CC2, CC3, CC4 to view different
- // language captions. Therefore, only CC1, CC2, CC3, CC4 are allowed to be reported.
- if (blockSize > 0 && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START
- && serviceNumber <= DISCOVERY_CC_SERVICE_NUMBER_END) {
- mDiscoveredNumBytes.put(
- serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0));
- }
- if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()
- || !mFirstServiceNumberDiscovered) {
- for (int i = 0; i < mDiscoveredNumBytes.size(); ++i) {
- int discoveredNumBytes = mDiscoveredNumBytes.valueAt(i);
- if (discoveredNumBytes >= DISCOVERY_NUM_BYTES_THRESHOLD) {
- int discoveredServiceNumber = mDiscoveredNumBytes.keyAt(i);
- mListener.discoverServiceNumber(discoveredServiceNumber);
- mFirstServiceNumberDiscovered = true;
- }
- }
- mDiscoveredNumBytes.clear();
- mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
- }
-
- // Skip current service block if either there is no block data or the service number
- // is not same as listening service number.
- if (blockSize == 0 || serviceNumber != mListenServiceNumber) {
- pos += blockSize;
- continue;
- }
-
- // From this point, starts to read DTVCC coding layer.
- // First, identify code groups, which is defined in CEA-708B Section 7.1.
- int blockLimit = pos + blockSize;
- while (pos < blockLimit) {
- pos = parseServiceBlockData(data, pos);
- }
-
- // Emit the buffer after reading codes.
- emitCaptionBuffer();
- pos = blockLimit;
- }
- }
-
- // Step 4. Main code groups
- private int parseServiceBlockData(byte[] data, int pos) {
- // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
- mCommand = data[pos] & 0xff;
- ++pos;
- if (mCommand == Cea708Data.CODE_C0_EXT1) {
- pos = parseExt1(data, pos);
- } else if (mCommand >= Cea708Data.CODE_C0_RANGE_START
- && mCommand <= Cea708Data.CODE_C0_RANGE_END) {
- pos = parseC0(data, pos);
- } else if (mCommand >= Cea708Data.CODE_C1_RANGE_START
- && mCommand <= Cea708Data.CODE_C1_RANGE_END) {
- pos = parseC1(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G0_RANGE_START
- && mCommand <= Cea708Data.CODE_G0_RANGE_END) {
- pos = parseG0(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G1_RANGE_START
- && mCommand <= Cea708Data.CODE_G1_RANGE_END) {
- pos = parseG1(data, pos);
- }
- return pos;
- }
-
- private int parseC0(byte[] data, int pos) {
- // For the details of C0 code group, see CEA-708B Section 7.4.1.
- // CL Group: C0 Subset of ASCII Control codes
- if (mCommand >= Cea708Data.CODE_C0_SKIP2_RANGE_START
- && mCommand <= Cea708Data.CODE_C0_SKIP2_RANGE_END) {
- if (mCommand == Cea708Data.CODE_C0_P16) {
- // TODO : P16 escapes next two bytes for the large character maps.(no standard rule)
- // TODO : For korea broadcasting, express whole letters by using this.
- try {
- if (data[pos] == 0) {
- mBuffer.append((char) data[pos + 1]);
- } else {
- String value = new String(
- Arrays.copyOfRange(data, pos, pos + 2),
- "EUC-KR");
- mBuffer.append(value);
- }
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "P16 Code - Could not find supported encoding", e);
- }
- }
- pos += 2;
- } else if (mCommand >= Cea708Data.CODE_C0_SKIP1_RANGE_START
- && mCommand <= Cea708Data.CODE_C0_SKIP1_RANGE_END) {
- ++pos;
- } else {
- // NUL, BS, FF, CR interpreted as they are in ASCII control codes.
- // HCR moves the pen location to th beginning of the current line and deletes contents.
- // FF clears the screen and moves the pen location to (0,0).
- // ETX is the NULL command which is used to flush text to the current window when no
- // other command is pending.
- switch (mCommand) {
- case Cea708Data.CODE_C0_NUL:
- break;
- case Cea708Data.CODE_C0_ETX:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- case Cea708Data.CODE_C0_BS:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- case Cea708Data.CODE_C0_FF:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- case Cea708Data.CODE_C0_CR:
- mBuffer.append('\n');
- break;
- case Cea708Data.CODE_C0_HCR:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- default:
- break;
- }
- }
- return pos;
- }
-
- private int parseC1(byte[] data, int pos) {
- // For the details of C1 code group, see CEA-708B Section 8.10.
- // CR Group: C1 Caption Control Codes
- switch (mCommand) {
- case Cea708Data.CODE_C1_CW0:
- case Cea708Data.CODE_C1_CW1:
- case Cea708Data.CODE_C1_CW2:
- case Cea708Data.CODE_C1_CW3:
- case Cea708Data.CODE_C1_CW4:
- case Cea708Data.CODE_C1_CW5:
- case Cea708Data.CODE_C1_CW6:
- case Cea708Data.CODE_C1_CW7: {
- // SetCurrentWindow0-7
- int windowId = mCommand - Cea708Data.CODE_C1_CW0;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_CLW: {
- // ClearWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DSW: {
- // DisplayWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_HDW: {
- // HideWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_TGW: {
- // ToggleWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DLW: {
- // DeleteWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DLY: {
- // Delay
- int tenthsOfSeconds = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand DLY %d tenths of seconds",
- tenthsOfSeconds));
- }
- break;
- }
- case Cea708Data.CODE_C1_DLC: {
- // DelayCancel
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null));
- if (DEBUG) {
- Log.d(TAG, "CaptionCommand DLC");
- }
- break;
- }
-
- case Cea708Data.CODE_C1_RST: {
- // Reset
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null));
- if (DEBUG) {
- Log.d(TAG, "CaptionCommand RST");
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SPA: {
- // SetPenAttributes
- int textTag = (data[pos] & 0xf0) >> 4;
- int penSize = data[pos] & 0x03;
- int penOffset = (data[pos] & 0x0c) >> 2;
- boolean italic = (data[pos + 1] & 0x80) != 0;
- boolean underline = (data[pos + 1] & 0x40) != 0;
- int edgeType = (data[pos + 1] & 0x38) >> 3;
- int fontTag = data[pos + 1] & 0x7;
- pos += 2;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPA,
- new CaptionPenAttr(penSize, penOffset, textTag, fontTag, edgeType,
- underline, italic)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, "
- + "fontTag: %d, edgeType: %d, underline: %s, italic: %s",
- penSize, penOffset, textTag, fontTag, edgeType, underline, italic));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SPC: {
- // SetPenColor
- int opacity = (data[pos] & 0xc0) >> 6;
- int red = (data[pos] & 0x30) >> 4;
- int green = (data[pos] & 0x0c) >> 2;
- int blue = data[pos] & 0x03;
- CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue);
- ++pos;
- opacity = (data[pos] & 0xc0) >> 6;
- red = (data[pos] & 0x30) >> 4;
- green = (data[pos] & 0x0c) >> 2;
- blue = data[pos] & 0x03;
- CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue);
- ++pos;
- red = (data[pos] & 0x30) >> 4;
- green = (data[pos] & 0x0c) >> 2;
- blue = data[pos] & 0x03;
- CaptionColor edgeColor = new CaptionColor(
- CaptionColor.OPACITY_SOLID, red, green, blue);
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPC,
- new CaptionPenColor(foregroundColor, backgroundColor, edgeColor)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s",
- foregroundColor, backgroundColor, edgeColor));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SPL: {
- // SetPenLocation
- // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats
- int row = data[pos] & 0x0f;
- int column = data[pos + 1] & 0x3f;
- pos += 2;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPL,
- new CaptionPenLocation(row, column)));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand SPL row: %d, column: %d",
- row, column));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SWA: {
- // SetWindowAttributes
- int opacity = (data[pos] & 0xc0) >> 6;
- int red = (data[pos] & 0x30) >> 4;
- int green = (data[pos] & 0x0c) >> 2;
- int blue = data[pos] & 0x03;
- CaptionColor fillColor = new CaptionColor(opacity, red, green, blue);
- int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5;
- red = (data[pos + 1] & 0x30) >> 4;
- green = (data[pos + 1] & 0x0c) >> 2;
- blue = data[pos + 1] & 0x03;
- CaptionColor borderColor = new CaptionColor(
- CaptionColor.OPACITY_SOLID, red, green, blue);
- boolean wordWrap = (data[pos + 2] & 0x40) != 0;
- int printDirection = (data[pos + 2] & 0x30) >> 4;
- int scrollDirection = (data[pos + 2] & 0x0c) >> 2;
- int justify = (data[pos + 2] & 0x03);
- int effectSpeed = (data[pos + 3] & 0xf0) >> 4;
- int effectDirection = (data[pos + 3] & 0x0c) >> 2;
- int displayEffect = data[pos + 3] & 0x3;
- pos += 4;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SWA,
- new CaptionWindowAttr(fillColor, borderColor, borderType, wordWrap,
- printDirection, scrollDirection, justify,
- effectDirection, effectSpeed, displayEffect)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d"
- + "wordWrap: %s, printDirection: %d, scrollDirection: %d, "
- + "justify: %s, effectDirection: %d, effectSpeed: %d, "
- + "displayEffect: %d",
- fillColor, borderColor, borderType, wordWrap, printDirection,
- scrollDirection, justify, effectDirection, effectSpeed, displayEffect));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DF0:
- case Cea708Data.CODE_C1_DF1:
- case Cea708Data.CODE_C1_DF2:
- case Cea708Data.CODE_C1_DF3:
- case Cea708Data.CODE_C1_DF4:
- case Cea708Data.CODE_C1_DF5:
- case Cea708Data.CODE_C1_DF6:
- case Cea708Data.CODE_C1_DF7: {
- // DefineWindow0-7
- int windowId = mCommand - Cea708Data.CODE_C1_DF0;
- boolean visible = (data[pos] & 0x20) != 0;
- boolean rowLock = (data[pos] & 0x10) != 0;
- boolean columnLock = (data[pos] & 0x08) != 0;
- int priority = data[pos] & 0x07;
- boolean relativePositioning = (data[pos + 1] & 0x80) != 0;
- int anchorVertical = data[pos + 1] & 0x7f;
- int anchorHorizontal = data[pos + 2] & 0xff;
- int anchorId = (data[pos + 3] & 0xf0) >> 4;
- int rowCount = data[pos + 3] & 0x0f;
- int columnCount = data[pos + 4] & 0x3f;
- int windowStyle = (data[pos + 5] & 0x38) >> 3;
- int penStyle = data[pos + 5] & 0x07;
- pos += 6;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DFX,
- new CaptionWindow(windowId, visible, rowLock, columnLock, priority,
- relativePositioning, anchorVertical, anchorHorizontal, anchorId,
- rowCount, columnCount, penStyle, windowStyle)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, "
- + "rowLock: %s, visible: %s, anchorVertical: %d, "
- + "relativePositioning: %s, anchorHorizontal: %d, "
- + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, "
- + "windowStyle: %d",
- windowId, priority, columnLock, rowLock, visible, anchorVertical,
- relativePositioning, anchorHorizontal, rowCount, anchorId, columnCount,
- penStyle, windowStyle));
- }
- break;
- }
-
- default:
- break;
- }
- return pos;
- }
-
- private int parseG0(byte[] data, int pos) {
- // For the details of G0 code group, see CEA-708B Section 7.4.3.
- // GL Group: G0 Modified version of ANSI X3.4 Printable Character Set (ASCII)
- if (mCommand == Cea708Data.CODE_G0_MUSICNOTE) {
- // Music note.
- mBuffer.append(MUSIC_NOTE_CHAR);
- } else {
- // Put ASCII code into buffer.
- mBuffer.append((char) mCommand);
- }
- return pos;
- }
-
- private int parseG1(byte[] data, int pos) {
- // For the details of G0 code group, see CEA-708B Section 7.4.4.
- // GR Group: G1 ISO 8859-1 Latin 1 Characters
- // Put ASCII Extended character set into buffer.
- mBuffer.append((char) mCommand);
- return pos;
- }
-
- // Step 4. Extended code groups
- private int parseExt1(byte[] data, int pos) {
- // For the details of EXT1 code group, see CEA-708B Section 7.2.
- mCommand = data[pos] & 0xff;
- ++pos;
- if (mCommand >= Cea708Data.CODE_C2_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_RANGE_END) {
- pos = parseC2(data, pos);
- } else if (mCommand >= Cea708Data.CODE_C3_RANGE_START
- && mCommand <= Cea708Data.CODE_C3_RANGE_END) {
- pos = parseC3(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G2_RANGE_START
- && mCommand <= Cea708Data.CODE_G2_RANGE_END) {
- pos = parseG2(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G3_RANGE_START
- && mCommand <= Cea708Data.CODE_G3_RANGE_END) {
- pos = parseG3(data ,pos);
- }
- return pos;
- }
-
- private int parseC2(byte[] data, int pos) {
- // For the details of C2 code group, see CEA-708B Section 7.4.7.
- // Extended Miscellaneous Control Codes
- // C2 Table : No commands as of CEA-708B. A decoder must skip.
- if (mCommand >= Cea708Data.CODE_C2_SKIP0_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP0_RANGE_END) {
- // Do nothing.
- } else if (mCommand >= Cea708Data.CODE_C2_SKIP1_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP1_RANGE_END) {
- ++pos;
- } else if (mCommand >= Cea708Data.CODE_C2_SKIP2_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP2_RANGE_END) {
- pos += 2;
- } else if (mCommand >= Cea708Data.CODE_C2_SKIP3_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP3_RANGE_END) {
- pos += 3;
- }
- return pos;
- }
-
- private int parseC3(byte[] data, int pos) {
- // For the details of C3 code group, see CEA-708B Section 7.4.8.
- // Extended Control Code Set 2
- // C3 Table : No commands as of CEA-708B. A decoder must skip.
- if (mCommand >= Cea708Data.CODE_C3_SKIP4_RANGE_START
- && mCommand <= Cea708Data.CODE_C3_SKIP4_RANGE_END) {
- pos += 4;
- } else if (mCommand >= Cea708Data.CODE_C3_SKIP5_RANGE_START
- && mCommand <= Cea708Data.CODE_C3_SKIP5_RANGE_END) {
- pos += 5;
- }
- return pos;
- }
-
- private int parseG2(byte[] data, int pos) {
- // For the details of C3 code group, see CEA-708B Section 7.4.5.
- // Extended Control Code Set 1(G2 Table)
- switch (mCommand) {
- case Cea708Data.CODE_G2_TSP:
- // TODO : TSP is the Transparent space
- break;
- case Cea708Data.CODE_G2_NBTSP:
- // TODO : NBTSP is Non-Breaking Transparent Space.
- break;
- case Cea708Data.CODE_G2_BLK:
- // TODO : BLK indicates a solid block which fills the entire character block
- // TODO : with a solid foreground color.
- break;
- default:
- break;
- }
- return pos;
- }
-
- private int parseG3(byte[] data, int pos) {
- // For the details of C3 code group, see CEA-708B Section 7.4.6.
- // Future characters and icons(G3 Table)
- if (mCommand == Cea708Data.CODE_G3_CC) {
- // TODO : [CC] icon with square corners
- }
-
- // Do nothing
- return pos;
- }
-}
diff --git a/src/com/android/tv/tuner/data/Cea708Data.java b/src/com/android/tv/tuner/data/Cea708Data.java
deleted file mode 100644
index 6350d63c..00000000
--- a/src/com/android/tv/tuner/data/Cea708Data.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.data;
-
-import com.android.tv.tuner.cc.Cea708Parser;
-
-import android.graphics.Color;
-import android.support.annotation.NonNull;
-
-/**
- * Collection of CEA-708 structures.
- */
-public class Cea708Data {
-
- private Cea708Data() {
- }
-
- // According to CEA-708B, the range of valid service number is between 1 and 63.
- public static final int EMPTY_SERVICE_NUMBER = 0;
-
- // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
- public static final int CODE_C0_RANGE_START = 0x00;
- public static final int CODE_C0_RANGE_END = 0x1f;
- public static final int CODE_C1_RANGE_START = 0x80;
- public static final int CODE_C1_RANGE_END = 0x9f;
- public static final int CODE_G0_RANGE_START = 0x20;
- public static final int CODE_G0_RANGE_END = 0x7f;
- public static final int CODE_G1_RANGE_START = 0xa0;
- public static final int CODE_G1_RANGE_END = 0xff;
- public static final int CODE_C2_RANGE_START = 0x00;
- public static final int CODE_C2_RANGE_END = 0x1f;
- public static final int CODE_C3_RANGE_START = 0x80;
- public static final int CODE_C3_RANGE_END = 0x9f;
- public static final int CODE_G2_RANGE_START = 0x20;
- public static final int CODE_G2_RANGE_END = 0x7f;
- public static final int CODE_G3_RANGE_START = 0xa0;
- public static final int CODE_G3_RANGE_END = 0xff;
-
- // The following ranges are defined in CEA-708B Section 7.4.1.
- public static final int CODE_C0_SKIP2_RANGE_START = 0x18;
- public static final int CODE_C0_SKIP2_RANGE_END = 0x1f;
- public static final int CODE_C0_SKIP1_RANGE_START = 0x10;
- public static final int CODE_C0_SKIP1_RANGE_END = 0x17;
-
- // The following ranges are defined in CEA-708B Section 7.4.7.
- public static final int CODE_C2_SKIP0_RANGE_START = 0x00;
- public static final int CODE_C2_SKIP0_RANGE_END = 0x07;
- public static final int CODE_C2_SKIP1_RANGE_START = 0x08;
- public static final int CODE_C2_SKIP1_RANGE_END = 0x0f;
- public static final int CODE_C2_SKIP2_RANGE_START = 0x10;
- public static final int CODE_C2_SKIP2_RANGE_END = 0x17;
- public static final int CODE_C2_SKIP3_RANGE_START = 0x18;
- public static final int CODE_C2_SKIP3_RANGE_END = 0x1f;
-
- // The following ranges are defined in CEA-708B Section 7.4.8.
- public static final int CODE_C3_SKIP4_RANGE_START = 0x80;
- public static final int CODE_C3_SKIP4_RANGE_END = 0x87;
- public static final int CODE_C3_SKIP5_RANGE_START = 0x88;
- public static final int CODE_C3_SKIP5_RANGE_END = 0x8f;
-
- // The following values are the special characters of CEA-708 spec.
- public static final int CODE_C0_NUL = 0x00;
- public static final int CODE_C0_ETX = 0x03;
- public static final int CODE_C0_BS = 0x08;
- public static final int CODE_C0_FF = 0x0c;
- public static final int CODE_C0_CR = 0x0d;
- public static final int CODE_C0_HCR = 0x0e;
- public static final int CODE_C0_EXT1 = 0x10;
- public static final int CODE_C0_P16 = 0x18;
- public static final int CODE_G0_MUSICNOTE = 0x7f;
- public static final int CODE_G2_TSP = 0x20;
- public static final int CODE_G2_NBTSP = 0x21;
- public static final int CODE_G2_BLK = 0x30;
- public static final int CODE_G3_CC = 0xa0;
-
- // The following values are the command bits of CEA-708 spec.
- public static final int CODE_C1_CW0 = 0x80;
- public static final int CODE_C1_CW1 = 0x81;
- public static final int CODE_C1_CW2 = 0x82;
- public static final int CODE_C1_CW3 = 0x83;
- public static final int CODE_C1_CW4 = 0x84;
- public static final int CODE_C1_CW5 = 0x85;
- public static final int CODE_C1_CW6 = 0x86;
- public static final int CODE_C1_CW7 = 0x87;
- public static final int CODE_C1_CLW = 0x88;
- public static final int CODE_C1_DSW = 0x89;
- public static final int CODE_C1_HDW = 0x8a;
- public static final int CODE_C1_TGW = 0x8b;
- public static final int CODE_C1_DLW = 0x8c;
- public static final int CODE_C1_DLY = 0x8d;
- public static final int CODE_C1_DLC = 0x8e;
- public static final int CODE_C1_RST = 0x8f;
- public static final int CODE_C1_SPA = 0x90;
- public static final int CODE_C1_SPC = 0x91;
- public static final int CODE_C1_SPL = 0x92;
- public static final int CODE_C1_SWA = 0x97;
- public static final int CODE_C1_DF0 = 0x98;
- public static final int CODE_C1_DF1 = 0x99;
- public static final int CODE_C1_DF2 = 0x9a;
- public static final int CODE_C1_DF3 = 0x9b;
- public static final int CODE_C1_DF4 = 0x9c;
- public static final int CODE_C1_DF5 = 0x9d;
- public static final int CODE_C1_DF6 = 0x9e;
- public static final int CODE_C1_DF7 = 0x9f;
-
- public static class CcPacket implements Comparable<CcPacket> {
- public final byte[] bytes;
- public final int ccCount;
- public final long pts;
-
- public CcPacket(byte[] bytes, int ccCount, long pts) {
- this.bytes = bytes;
- this.ccCount = ccCount;
- this.pts = pts;
- }
-
- @Override
- public int compareTo(@NonNull CcPacket another) {
- return Long.compare(pts, another.pts);
- }
- }
-
- /**
- * CEA-708B-specific color.
- */
- public static class CaptionColor {
- public static final int OPACITY_SOLID = 0;
- public static final int OPACITY_FLASH = 1;
- public static final int OPACITY_TRANSLUCENT = 2;
- public static final int OPACITY_TRANSPARENT = 3;
-
- private static final int[] COLOR_MAP = new int[] { 0x00, 0x0f, 0xf0, 0xff };
- private static final int[] OPACITY_MAP = new int[] { 0xff, 0xfe, 0x80, 0x00 };
-
- public final int opacity;
- public final int red;
- public final int green;
- public final int blue;
-
- public CaptionColor(int opacity, int red, int green, int blue) {
- this.opacity = opacity;
- this.red = red;
- this.green = green;
- this.blue = blue;
- }
-
- public int getArgbValue() {
- return Color.argb(
- OPACITY_MAP[opacity], COLOR_MAP[red], COLOR_MAP[green], COLOR_MAP[blue]);
- }
- }
-
- /**
- * Caption event generated by {@link Cea708Parser}.
- */
- public static class CaptionEvent {
- @Cea708Parser.CaptionEmitType public final int type;
- public final Object obj;
-
- public CaptionEvent(int type, Object obj) {
- this.type = type;
- this.obj = obj;
- }
- }
-
- /**
- * Pen style information.
- */
- public static class CaptionPenAttr {
- // Pen sizes
- public static final int PEN_SIZE_SMALL = 0;
- public static final int PEN_SIZE_STANDARD = 1;
- public static final int PEN_SIZE_LARGE = 2;
-
- // Offsets
- public static final int OFFSET_SUBSCRIPT = 0;
- public static final int OFFSET_NORMAL = 1;
- public static final int OFFSET_SUPERSCRIPT = 2;
-
- public final int penSize;
- public final int penOffset;
- public final int textTag;
- public final int fontTag;
- public final int edgeType;
- public final boolean underline;
- public final boolean italic;
-
- public CaptionPenAttr(int penSize, int penOffset, int textTag, int fontTag, int edgeType,
- boolean underline, boolean italic) {
- this.penSize = penSize;
- this.penOffset = penOffset;
- this.textTag = textTag;
- this.fontTag = fontTag;
- this.edgeType = edgeType;
- this.underline = underline;
- this.italic = italic;
- }
- }
-
- /**
- * {@link CaptionColor} objects that indicate the foreground, background, and edge color of a
- * pen.
- */
- public static class CaptionPenColor {
- public final CaptionColor foregroundColor;
- public final CaptionColor backgroundColor;
- public final CaptionColor edgeColor;
-
- public CaptionPenColor(CaptionColor foregroundColor, CaptionColor backgroundColor,
- CaptionColor edgeColor) {
- this.foregroundColor = foregroundColor;
- this.backgroundColor = backgroundColor;
- this.edgeColor = edgeColor;
- }
- }
-
- /**
- * Location information of a pen.
- */
- public static class CaptionPenLocation {
- public final int row;
- public final int column;
-
- public CaptionPenLocation(int row, int column) {
- this.row = row;
- this.column = column;
- }
- }
-
- /**
- * Attributes of a caption window, which is defined in CEA-708B.
- */
- public static class CaptionWindowAttr {
- public static final int JUSTIFY_LEFT = 0;
- public static final int JUSTIFY_CENTER = 2;
- public static final int PRINT_LEFT_TO_RIGHT = 0;
- public static final int PRINT_RIGHT_TO_LEFT = 1;
- public static final int PRINT_TOP_TO_BOTTOM = 2;
- public static final int PRINT_BOTTOM_TO_TOP = 3;
-
- public final CaptionColor fillColor;
- public final CaptionColor borderColor;
- public final int borderType;
- public final boolean wordWrap;
- public final int printDirection;
- public final int scrollDirection;
- public final int justify;
- public final int effectDirection;
- public final int effectSpeed;
- public final int displayEffect;
-
- public CaptionWindowAttr(CaptionColor fillColor, CaptionColor borderColor, int borderType,
- boolean wordWrap, int printDirection, int scrollDirection, int justify,
- int effectDirection,
- int effectSpeed, int displayEffect) {
- this.fillColor = fillColor;
- this.borderColor = borderColor;
- this.borderType = borderType;
- this.wordWrap = wordWrap;
- this.printDirection = printDirection;
- this.scrollDirection = scrollDirection;
- this.justify = justify;
- this.effectDirection = effectDirection;
- this.effectSpeed = effectSpeed;
- this.displayEffect = displayEffect;
- }
- }
-
- /**
- * Construction information of the caption window of CEA-708B.
- */
- public static class CaptionWindow {
- public final int id;
- public final boolean visible;
- public final boolean rowLock;
- public final boolean columnLock;
- public final int priority;
- public final boolean relativePositioning;
- public final int anchorVertical;
- public final int anchorHorizontal;
- public final int anchorId;
- public final int rowCount;
- public final int columnCount;
- public final int penStyle;
- public final int windowStyle;
-
- public CaptionWindow(int id, boolean visible,
- boolean rowLock, boolean columnLock, int priority, boolean relativePositioning,
- int anchorVertical, int anchorHorizontal, int anchorId,
- int rowCount, int columnCount, int penStyle, int windowStyle) {
- this.id = id;
- this.visible = visible;
- this.rowLock = rowLock;
- this.columnLock = columnLock;
- this.priority = priority;
- this.relativePositioning = relativePositioning;
- this.anchorVertical = anchorVertical;
- this.anchorHorizontal = anchorHorizontal;
- this.anchorId = anchorId;
- this.rowCount = rowCount;
- this.columnCount = columnCount;
- this.penStyle = penStyle;
- this.windowStyle = windowStyle;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java
deleted file mode 100644
index 67700c6a..00000000
--- a/src/com/android/tv/tuner/data/PsiData.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.tv.tuner.data;
-
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-
-import java.util.List;
-
-/**
- * Collection of MPEG PSI table items.
- */
-public class PsiData {
-
- private PsiData() {
- }
-
- public static class PatItem {
- private final int mProgramNo;
- private final int mPmtPid;
-
- public PatItem(int programNo, int pmtPid) {
- mProgramNo = programNo;
- mPmtPid = pmtPid;
- }
-
- public int getProgramNo() {
- return mProgramNo;
- }
-
- public int getPmtPid() {
- return mPmtPid;
- }
-
- @Override
- public String toString() {
- return String.format("Program No: %x PMT Pid: %x", mProgramNo, mPmtPid);
- }
- }
-
- public static class PmtItem {
- public static final int ES_PID_PCR = 0x100;
-
- private final int mStreamType;
- private final int mEsPid;
- private final List<AtscAudioTrack> mAudioTracks;
- private final List<AtscCaptionTrack> mCaptionTracks;
-
- public PmtItem(int streamType, int esPid,
- List<AtscAudioTrack> audioTracks, List<AtscCaptionTrack> captionTracks) {
- mStreamType = streamType;
- mEsPid = esPid;
- mAudioTracks = audioTracks;
- mCaptionTracks = captionTracks;
- }
-
- public int getStreamType() {
- return mStreamType;
- }
-
- public int getEsPid() {
- return mEsPid;
- }
-
- public List<AtscAudioTrack> getAudioTracks() {
- return mAudioTracks;
- }
-
- public List<AtscCaptionTrack> getCaptionTracks() {
- return mCaptionTracks;
- }
-
- @Override
- public String toString() {
- return String.format("Stream Type: %x ES Pid: %x AudioTracks: %s CaptionTracks: %s",
- mStreamType, mEsPid, mAudioTracks, mCaptionTracks);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java
deleted file mode 100644
index 8f98e67c..00000000
--- a/src/com/android/tv/tuner/data/PsipData.java
+++ /dev/null
@@ -1,820 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.data;
-
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.ts.SectionParser;
-import com.android.tv.tuner.util.ConvertUtils;
-import com.android.tv.util.StringUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Collection of ATSC PSIP table items.
- */
-public class PsipData {
-
- private PsipData() {
- }
-
- public static class PsipSection {
- private final int mTableId;
- private final int mTableIdExtension;
- private final int mSectionNumber;
- private final boolean mCurrentNextIndicator;
-
- public static PsipSection create(byte[] data) {
- if (data.length < 9) {
- return null;
- }
- int tableId = data[0] & 0xff;
- int tableIdExtension = (data[3] & 0xff) << 8 | (data[4] & 0xff);
- int sectionNumber = data[6] & 0xff;
- boolean currentNextIndicator = (data[5] & 0x01) != 0;
- return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator);
- }
-
- private PsipSection(int tableId, int tableIdExtension, int sectionNumber,
- boolean currentNextIndicator) {
- mTableId = tableId;
- mTableIdExtension = tableIdExtension;
- mSectionNumber = sectionNumber;
- mCurrentNextIndicator = currentNextIndicator;
- }
-
- public int getTableId() {
- return mTableId;
- }
-
- public int getTableIdExtension() {
- return mTableIdExtension;
- }
-
- public int getSectionNumber() {
- return mSectionNumber;
- }
-
- // This is for indicating that the section sent is applicable.
- // We only consider a situation where currentNextIndicator is expected to have a true value.
- // So, we are not going to compare this variable in hashCode() and equals() methods.
- public boolean getCurrentNextIndicator() {
- return mCurrentNextIndicator;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + mTableId;
- result = 31 * result + mTableIdExtension;
- result = 31 * result + mSectionNumber;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof PsipSection) {
- PsipSection another = (PsipSection) obj;
- return mTableId == another.getTableId()
- && mTableIdExtension == another.getTableIdExtension()
- && mSectionNumber == another.getSectionNumber();
- }
- return false;
- }
- }
-
- /**
- * {@link TvTracksInterface} for serving the audio and caption tracks.
- */
- public interface TvTracksInterface {
- /**
- * Set the flag that tells the caption tracks have been found in this section container.
- */
- void setHasCaptionTrack();
-
- /**
- * Returns whether or not the caption tracks have been found in this section container.
- * If true, zero caption track will be interpreted as a clearance of the caption tracks.
- */
- boolean hasCaptionTrack();
-
- /**
- * Returns the audio tracks received.
- */
- List<AtscAudioTrack> getAudioTracks();
-
- /**
- * Returns the caption tracks received.
- */
- List<AtscCaptionTrack> getCaptionTracks();
- }
-
- public static class MgtItem {
- public static final int TABLE_TYPE_EIT_RANGE_START = 0x0100;
- public static final int TABLE_TYPE_EIT_RANGE_END = 0x017f;
- public static final int TABLE_TYPE_CHANNEL_ETT = 0x0004;
- public static final int TABLE_TYPE_ETT_RANGE_START = 0x0200;
- public static final int TABLE_TYPE_ETT_RANGE_END = 0x027f;
-
- private final int mTableType;
- private final int mTableTypePid;
-
- public MgtItem(int tableType, int tableTypePid) {
- mTableType = tableType;
- mTableTypePid = tableTypePid;
- }
-
- public int getTableType() {
- return mTableType;
- }
-
- public int getTableTypePid() {
- return mTableTypePid;
- }
- }
-
- public static class VctItem {
- private final String mShortName;
- private final String mLongName;
- private final int mServiceType;
- private final int mChannelTsid;
- private final int mProgramNumber;
- private final int mMajorChannelNumber;
- private final int mMinorChannelNumber;
- private final int mSourceId;
- private String mDescription;
-
- public VctItem(String shortName, String longName, int serviceType, int channelTsid,
- int programNumber, int majorChannelNumber, int minorChannelNumber, int sourceId) {
- mShortName = shortName;
- mLongName = longName;
- mServiceType = serviceType;
- mChannelTsid = channelTsid;
- mProgramNumber = programNumber;
- mMajorChannelNumber = majorChannelNumber;
- mMinorChannelNumber = minorChannelNumber;
- mSourceId = sourceId;
- }
-
- public String getShortName() {
- return mShortName;
- }
-
- public String getLongName() {
- return mLongName;
- }
-
- public int getServiceType() {
- return mServiceType;
- }
-
- public int getChannelTsid() {
- return mChannelTsid;
- }
-
- public int getProgramNumber() {
- return mProgramNumber;
- }
-
- public int getMajorChannelNumber() {
- return mMajorChannelNumber;
- }
-
- public int getMinorChannelNumber() {
- return mMinorChannelNumber;
- }
-
- public int getSourceId() {
- return mSourceId;
- }
-
- @Override
- public String toString() {
- return String
- .format(Locale.US, "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x "
- + "ProgramNumber:%d %d-%d SourceId: %x",
- mShortName, mLongName, mServiceType, mChannelTsid,
- mProgramNumber, mMajorChannelNumber, mMinorChannelNumber, mSourceId);
- }
-
- public void setDescription(String description) {
- mDescription = description;
- }
-
- public String getDescription() {
- return mDescription;
- }
- }
-
- public static class SdtItem {
- private final String mServiceName;
- private final String mServiceProviderName;
- private final int mServiceType;
- private final int mServiceId;
- private final int mOriginalNetWorkId;
-
- public SdtItem(String serviceName, String serviceProviderName, int serviceType,
- int serviceId, int originalNetWorkId) {
- mServiceName = serviceName;
- mServiceProviderName = serviceProviderName;
- mServiceType = serviceType;
- mServiceId = serviceId;
- mOriginalNetWorkId = originalNetWorkId;
- }
-
- public String getServiceName() {
- return mServiceName;
- }
-
- public String getServiceProviderName() {
- return mServiceProviderName;
- }
-
- public int getServiceType() {
- return mServiceType;
- }
-
- public int getServiceId() {
- return mServiceId;
- }
-
- public int getOriginalNetworkId() {
- return mOriginalNetWorkId;
- }
-
- @Override
- public String toString() {
- return String.format("ServiceName: %s ServiceProviderName:%s ServiceType:%d "
- + "OriginalNetworkId:%d",
- mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId);
- }
- }
-
- /**
- * A base class for descriptors of Ts packets.
- */
- public abstract static class TsDescriptor {
- public abstract int getTag();
- }
-
- public static class ContentAdvisoryDescriptor extends TsDescriptor {
- private final List<RatingRegion> mRatingRegions;
-
- public ContentAdvisoryDescriptor(List<RatingRegion> ratingRegions) {
- mRatingRegions = ratingRegions;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_CONTENT_ADVISORY;
- }
-
- public List<RatingRegion> getRatingRegions() {
- return mRatingRegions;
- }
- }
-
- public static class CaptionServiceDescriptor extends TsDescriptor {
- private final List<AtscCaptionTrack> mCaptionTracks;
-
- public CaptionServiceDescriptor(List<AtscCaptionTrack> captionTracks) {
- mCaptionTracks = captionTracks;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_CAPTION_SERVICE;
- }
-
- public List<AtscCaptionTrack> getCaptionTracks() {
- return mCaptionTracks;
- }
- }
-
- public static class ExtendedChannelNameDescriptor extends TsDescriptor {
- private final String mLongChannelName;
-
- public ExtendedChannelNameDescriptor(String longChannelName) {
- mLongChannelName = longChannelName;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME;
- }
-
- public String getLongChannelName() {
- return mLongChannelName;
- }
- }
-
- public static class GenreDescriptor extends TsDescriptor {
- private final String[] mBroadcastGenres;
- private final String[] mCanonicalGenres;
-
- public GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres) {
- mBroadcastGenres = broadcastGenres;
- mCanonicalGenres = canonicalGenres;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_GENRE;
- }
-
- public String[] getBroadcastGenres() {
- return mBroadcastGenres;
- }
-
- public String[] getCanonicalGenres() {
- return mCanonicalGenres;
- }
- }
-
- public static class Ac3AudioDescriptor extends TsDescriptor {
- // See A/52 Annex A. Table A4.2
- private static final byte SAMPLE_RATE_CODE_48000HZ = 0;
- private static final byte SAMPLE_RATE_CODE_44100HZ = 1;
- private static final byte SAMPLE_RATE_CODE_32000HZ = 2;
-
- private final byte mSampleRateCode;
- private final byte mBsid;
- private final byte mBitRateCode;
- private final byte mSurroundMode;
- private final byte mBsmod;
- private final int mNumChannels;
- private final boolean mFullSvc;
- private final byte mLangCod;
- private final byte mLangCod2;
- private final byte mMainId;
- private final byte mPriority;
- private final byte mAsvcflags;
- private final String mText;
- private final String mLanguage;
- private final String mLanguage2;
-
- public Ac3AudioDescriptor(byte sampleRateCode, byte bsid, byte bitRateCode,
- byte surroundMode, byte bsmod, int numChannels, boolean fullSvc, byte langCod,
- byte langCod2, byte mainId, byte priority, byte asvcflags, String text,
- String language, String language2) {
- mSampleRateCode = sampleRateCode;
- mBsid = bsid;
- mBitRateCode = bitRateCode;
- mSurroundMode = surroundMode;
- mBsmod = bsmod;
- mNumChannels = numChannels;
- mFullSvc = fullSvc;
- mLangCod = langCod;
- mLangCod2 = langCod2;
- mMainId = mainId;
- mPriority = priority;
- mAsvcflags = asvcflags;
- mText = text;
- mLanguage = language;
- mLanguage2 = language2;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_AC3_AUDIO_STREAM;
- }
-
- public byte getSampleRateCode() {
- return mSampleRateCode;
- }
-
- public int getSampleRate() {
- switch (mSampleRateCode) {
- case SAMPLE_RATE_CODE_48000HZ:
- return 48000;
- case SAMPLE_RATE_CODE_44100HZ:
- return 44100;
- case SAMPLE_RATE_CODE_32000HZ:
- return 32000;
- default:
- return 0;
- }
- }
-
- public byte getBsid() {
- return mBsid;
- }
-
- public byte getBitRateCode() {
- return mBitRateCode;
- }
-
- public byte getSurroundMode() {
- return mSurroundMode;
- }
-
- public byte getBsmod() {
- return mBsmod;
- }
-
- public int getNumChannels() {
- return mNumChannels;
- }
-
- public boolean isFullSvc() {
- return mFullSvc;
- }
-
- public byte getLangCod() {
- return mLangCod;
- }
-
- public byte getLangCod2() {
- return mLangCod2;
- }
-
- public byte getMainId() {
- return mMainId;
- }
-
- public byte getPriority() {
- return mPriority;
- }
-
- public byte getAsvcflags() {
- return mAsvcflags;
- }
-
- public String getText() {
- return mText;
- }
-
- public String getLanguage() {
- return mLanguage;
- }
-
- public String getLanguage2() {
- return mLanguage2;
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US,
- "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, "
- + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, "
- + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s"
- + ", language2: %s", mSampleRateCode, mBsid, mBitRateCode, mSurroundMode,
- mBsmod, mNumChannels, mFullSvc, mLangCod, mLangCod2, mMainId, mPriority,
- mAsvcflags, mText, mLanguage, mLanguage2);
- }
- }
-
- public static class Iso639LanguageDescriptor extends TsDescriptor {
- private final List<AtscAudioTrack> mAudioTracks;
-
- public Iso639LanguageDescriptor(List<AtscAudioTrack> audioTracks) {
- mAudioTracks = audioTracks;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_ISO639LANGUAGE;
- }
-
- public List<AtscAudioTrack> getAudioTracks() {
- return mAudioTracks;
- }
-
- @Override
- public String toString() {
- return String.format("%s %s", getClass().getName(), mAudioTracks);
- }
- }
-
- public static class ServiceDescriptor extends TsDescriptor {
- private final int mServiceType;
- private final String mServiceProviderName;
- private final String mServiceName;
-
- public ServiceDescriptor(int serviceType, String serviceProviderName, String serviceName) {
- mServiceType = serviceType;
- mServiceProviderName = serviceProviderName;
- mServiceName = serviceName;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DVB_DESCRIPTOR_TAG_SERVICE;
- }
-
- public int getServiceType() {
- return mServiceType;
- }
-
- public String getServiceProviderName() {
- return mServiceProviderName;
- }
-
- public String getServiceName() {
- return mServiceName;
- }
-
- @Override
- public String toString() {
- return String.format(
- "Service descriptor, service type: %d, "
- + "service provider name: %s, "
- + "service name: %s", mServiceType, mServiceProviderName, mServiceName);
- }
- }
-
- public static class ShortEventDescriptor extends TsDescriptor {
- private final String mLanguage;
- private final String mEventName;
- private final String mText;
-
- public ShortEventDescriptor(String language, String eventName, String text) {
- mLanguage = language;
- mEventName = eventName;
- mText = text;
- }
-
- public String getEventName() {
- return mEventName;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DVB_DESCRIPTOR_TAG_SHORT_EVENT;
- }
-
- @Override
- public String toString() {
- return String.format("ShortEvent Descriptor, language:%s, event name: %s, "
- + "text:%s", mLanguage, mEventName, mText);
- }
- }
-
- public static class ParentalRatingDescriptor extends TsDescriptor {
- private final HashMap<String, Integer> mRatings;
-
- public ParentalRatingDescriptor(HashMap<String, Integer> ratings) {
- mRatings = ratings;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DVB_DESCRIPTOR_TAG_PARENTAL_RATING;
- }
-
- public HashMap<String, Integer> getRatings() {
- return mRatings;
- }
-
- @Override
- public String toString() {
- return String.format("Parental rating descriptor, ratings:" + mRatings);
- }
- }
-
- public static class RatingRegion {
- private final int mName;
- private final String mDescription;
- private final List<RegionalRating> mRegionalRatings;
-
- public RatingRegion(int name, String description, List<RegionalRating> regionalRatings) {
- mName = name;
- mDescription = description;
- mRegionalRatings = regionalRatings;
- }
-
- public int getName() {
- return mName;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- public List<RegionalRating> getRegionalRatings() {
- return mRegionalRatings;
- }
- }
-
- public static class RegionalRating {
- private final int mDimension;
- private final int mRating;
-
- public RegionalRating(int dimension, int rating) {
- mDimension = dimension;
- mRating = rating;
- }
-
- public int getDimension() {
- return mDimension;
- }
-
- public int getRating() {
- return mRating;
- }
- }
-
- public static class EitItem implements Comparable<EitItem>, TvTracksInterface {
- public static final long INVALID_PROGRAM_ID = -1;
-
- // A program id is a primary key of TvContract.Programs table. So it must be positive.
- private final long mProgramId;
- private final int mEventId;
- private final String mTitleText;
- private String mDescription;
- private final long mStartTime;
- private final int mLengthInSecond;
- private final String mContentRating;
- private final List<AtscAudioTrack> mAudioTracks;
- private final List<AtscCaptionTrack> mCaptionTracks;
- private boolean mHasCaptionTrack;
- private final String mBroadcastGenre;
- private final String mCanonicalGenre;
-
- public EitItem(long programId, int eventId, String titleText, long startTime,
- int lengthInSecond, String contentRating, List<AtscAudioTrack> audioTracks,
- List<AtscCaptionTrack> captionTracks, String broadcastGenre, String canonicalGenre,
- String description) {
- mProgramId = programId;
- mEventId = eventId;
- mTitleText = titleText;
- mStartTime = startTime;
- mLengthInSecond = lengthInSecond;
- mContentRating = contentRating;
- mAudioTracks = audioTracks;
- mCaptionTracks = captionTracks;
- mBroadcastGenre = broadcastGenre;
- mCanonicalGenre = canonicalGenre;
- mDescription = description;
- }
-
- public long getProgramId() {
- return mProgramId;
- }
-
- public int getEventId() {
- return mEventId;
- }
-
- public String getTitleText() {
- return mTitleText;
- }
-
- public void setDescription(String description) {
- mDescription = description;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- public long getStartTime() {
- return mStartTime;
- }
-
- public int getLengthInSecond() {
- return mLengthInSecond;
- }
-
- public long getStartTimeUtcMillis() {
- return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime) * DateUtils.SECOND_IN_MILLIS;
- }
-
- public long getEndTimeUtcMillis() {
- return ConvertUtils.convertGPSTimeToUnixEpoch(
- mStartTime + mLengthInSecond) * DateUtils.SECOND_IN_MILLIS;
- }
-
- public String getContentRating() {
- return mContentRating;
- }
-
- @Override
- public List<AtscAudioTrack> getAudioTracks() {
- return mAudioTracks;
- }
-
- @Override
- public List<AtscCaptionTrack> getCaptionTracks() {
- return mCaptionTracks;
- }
-
- public String getBroadcastGenre() {
- return mBroadcastGenre;
- }
-
- public String getCanonicalGenre() {
- return mCanonicalGenre;
- }
-
- @Override
- public void setHasCaptionTrack() {
- mHasCaptionTrack = true;
- }
-
- @Override
- public boolean hasCaptionTrack() {
- return mHasCaptionTrack;
- }
-
- @Override
- public int compareTo(@NonNull EitItem item) {
- // The list of caption tracks and the program ids are not compared in here because the
- // channels in TIF have the concept of the caption and audio tracks while the programs
- // do not and the programs in TIF only have a program id since they are the rows of
- // Content Provider.
- int ret = mEventId - item.getEventId();
- if (ret != 0) {
- return ret;
- }
- ret = StringUtils.compare(mTitleText, item.getTitleText());
- if (ret != 0) {
- return ret;
- }
- if (mStartTime > item.getStartTime()) {
- return 1;
- } else if (mStartTime < item.getStartTime()) {
- return -1;
- }
- if (mLengthInSecond > item.getLengthInSecond()) {
- return 1;
- } else if (mLengthInSecond < item.getLengthInSecond()) {
- return -1;
- }
-
- // Compares content ratings
- ret = StringUtils.compare(mContentRating, item.getContentRating());
- if (ret != 0) {
- return ret;
- }
-
- // Compares broadcast genres
- ret = StringUtils.compare(mBroadcastGenre, item.getBroadcastGenre());
- if (ret != 0) {
- return ret;
- }
- // Compares canonical genres
- ret = StringUtils.compare(mCanonicalGenre, item.getCanonicalGenre());
- if (ret != 0) {
- return ret;
- }
-
- // Compares descriptions
- return StringUtils.compare(mDescription, item.getDescription());
- }
-
- public String getAudioLanguage() {
- if (mAudioTracks == null) {
- return "";
- }
- ArrayList<String> languages = new ArrayList<>();
- for (AtscAudioTrack audioTrack : mAudioTracks) {
- languages.add(audioTrack.language);
- }
- return TextUtils.join(",", languages);
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US,
- "EitItem programId: %d, eventId: %d, title: %s, startTime: %10d, "
- + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, "
- + "genres (broadcast: %s, canonical: %s), description: %s",
- mProgramId, mEventId, mTitleText, mStartTime, mLengthInSecond, mContentRating,
- mAudioTracks != null ? mAudioTracks.size() : 0,
- mCaptionTracks != null ? mCaptionTracks.size() : 0,
- mBroadcastGenre, mCanonicalGenre, mDescription);
- }
- }
-
- public static class EttItem {
- public final int eventId;
- public final String text;
-
- public EttItem(int eventId, String text) {
- this.eventId = eventId;
- this.text = text;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java
deleted file mode 100644
index 1cf514c1..00000000
--- a/src/com/android/tv/tuner/data/TunerChannel.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.data;
-
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Channel.TunerChannelProto;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.Ints;
-import com.android.tv.util.StringUtils;
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A class that represents a single channel accessible through a tuner.
- */
-public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracksInterface {
- private static final String TAG = "TunerChannel";
-
- /**
- * Channel number separator between major number and minor number.
- */
- public static final char CHANNEL_NUMBER_SEPARATOR = '-';
-
- // See ATSC Code Points Registry.
- private static final String[] ATSC_SERVICE_TYPE_NAMES = new String[] {
- "ATSC Reserved",
- "Analog television channels",
- "ATSC_digital_television",
- "ATSC_audio",
- "ATSC_data_only_service",
- "Software Download",
- "Unassociated/Small Screen Service",
- "Parameterized Service",
- "ATSC NRT Service",
- "Extended Parameterized Service" };
- private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
- ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_RESERVED];
-
- public static final int INVALID_FREQUENCY = -1;
-
- // According to RFC4259, The number of available PIDs ranges from 0 to 8191.
- public static final int INVALID_PID = -1;
-
- // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff.
- public static final int INVALID_STREAMTYPE = -1;
-
- // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766
- private final TunerChannelProto mProto;
-
- private TunerChannel(PsipData.VctItem channel, int programNumber,
- List<PsiData.PmtItem> pmtItems, int type) {
- mProto = new TunerChannelProto();
- if (channel == null) {
- mProto.shortName = "";
- mProto.tsid = 0;
- mProto.programNumber = programNumber;
- mProto.virtualMajor = 0;
- mProto.virtualMinor = 0;
- } else {
- mProto.shortName = channel.getShortName();
- if (channel.getLongName() != null) {
- mProto.longName = channel.getLongName();
- }
- mProto.tsid = channel.getChannelTsid();
- mProto.programNumber = channel.getProgramNumber();
- mProto.virtualMajor = channel.getMajorChannelNumber();
- mProto.virtualMinor = channel.getMinorChannelNumber();
- if (channel.getDescription() != null) {
- mProto.description = channel.getDescription();
- }
- mProto.serviceType = channel.getServiceType();
- }
- initProto(pmtItems, type);
- }
-
- private void initProto(List<PsiData.PmtItem> pmtItems, int type) {
- mProto.type = type;
- mProto.channelId = -1L;
- mProto.frequency = INVALID_FREQUENCY;
- mProto.videoPid = INVALID_PID;
- mProto.videoStreamType = INVALID_STREAMTYPE;
- List<Integer> audioPids = new ArrayList<>();
- List<Integer> audioStreamTypes = new ArrayList<>();
- for (PsiData.PmtItem pmt : pmtItems) {
- switch (pmt.getStreamType()) {
- // MPEG ES stream video types
- case Channel.MPEG1:
- case Channel.MPEG2:
- case Channel.H263:
- case Channel.H264:
- case Channel.H265:
- mProto.videoPid = pmt.getEsPid();
- mProto.videoStreamType = pmt.getStreamType();
- break;
-
- // MPEG ES stream audio types
- case Channel.MPEG1AUDIO:
- case Channel.MPEG2AUDIO:
- case Channel.MPEG2AACAUDIO:
- case Channel.MPEG4LATMAACAUDIO:
- case Channel.A52AC3AUDIO:
- case Channel.EAC3AUDIO:
- audioPids.add(pmt.getEsPid());
- audioStreamTypes.add(pmt.getStreamType());
- break;
-
- // Non MPEG ES stream types
- case 0x100: // PmtItem.ES_PID_PCR:
- mProto.pcrPid = pmt.getEsPid();
- break;
- }
- }
- mProto.audioPids = Ints.toArray(audioPids);
- mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
- mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1;
- }
-
- private TunerChannel(int programNumber, int type, PsipData.SdtItem channel,
- List<PsiData.PmtItem> pmtItems) {
- mProto = new TunerChannelProto();
- mProto.tsid = 0;
- mProto.virtualMajor = 0;
- mProto.virtualMinor = 0;
- if (channel == null) {
- mProto.shortName = "";
- mProto.programNumber = programNumber;
- } else {
- mProto.shortName = channel.getServiceName();
- mProto.programNumber = channel.getServiceId();
- mProto.serviceType = channel.getServiceType();
- }
- initProto(pmtItems, type);
- }
-
- /**
- * Initialize tuner channel with VCT items and PMT items.
- */
- public TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
- this(channel, 0, pmtItems, Channel.TYPE_TUNER);
- }
-
- /**
- * Initialize tuner channel with program number and PMT items.
- */
- public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) {
- this(null, programNumber, pmtItems, Channel.TYPE_TUNER);
- }
-
- /**
- * Initialize tuner channel with SDT items and PMT items.
- */
- public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- this(0, Channel.TYPE_TUNER, channel, pmtItems);
- }
-
- private TunerChannel(TunerChannelProto tunerChannelProto) {
- mProto = tunerChannelProto;
- }
-
- public static TunerChannel forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
- return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE);
- }
-
- public static TunerChannel forDvbFile(
- PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- return new TunerChannel(0, Channel.TYPE_FILE, channel, pmtItems);
- }
-
- /**
- * Create a TunerChannel object suitable for network tuners
- * @param major Channel number major
- * @param minor Channel number minor
- * @param programNumber Program number
- * @param shortName Short name
- * @param recordingProhibited Recording prohibition info
- * @param videoFormat Video format. Should be {@code null} or one of the followings:
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P}
- * @return a TunerChannel object
- */
- public static TunerChannel forNetwork(int major, int minor, int programNumber,
- String shortName, boolean recordingProhibited, String videoFormat) {
- TunerChannel tunerChannel = new TunerChannel(
- null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK);
- tunerChannel.setVirtualMajor(major);
- tunerChannel.setVirtualMinor(minor);
- tunerChannel.setShortName(shortName);
- // Set audio and video pids in order to work around the audio-only channel check.
- tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0)));
- tunerChannel.selectAudioTrack(0);
- tunerChannel.setVideoPid(0);
- tunerChannel.setRecordingProhibited(recordingProhibited);
- if (videoFormat != null) {
- tunerChannel.setVideoFormat(videoFormat);
- }
- return tunerChannel;
- }
-
- public String getName() {
- return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName;
- }
-
- public String getShortName() {
- return mProto.shortName;
- }
-
- public int getProgramNumber() {
- return mProto.programNumber;
- }
-
- public int getServiceType() {
- return mProto.serviceType;
- }
-
- public String getServiceTypeName() {
- int serviceType = mProto.serviceType;
- if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
- return ATSC_SERVICE_TYPE_NAMES[serviceType];
- }
- return ATSC_SERVICE_TYPE_NAME_RESERVED;
- }
-
- public int getVirtualMajor() {
- return mProto.virtualMajor;
- }
-
- public int getVirtualMinor() {
- return mProto.virtualMinor;
- }
-
- public int getFrequency() {
- return mProto.frequency;
- }
-
- public String getModulation() {
- return mProto.modulation;
- }
-
- public int getTsid() {
- return mProto.tsid;
- }
-
- public int getVideoPid() {
- return mProto.videoPid;
- }
-
- synchronized public void setVideoPid(int videoPid) {
- mProto.videoPid = videoPid;
- }
-
- public int getVideoStreamType() {
- return mProto.videoStreamType;
- }
-
- public int getAudioPid() {
- if (mProto.audioTrackIndex == -1) {
- return INVALID_PID;
- }
- return mProto.audioPids[mProto.audioTrackIndex];
- }
-
- public int getAudioStreamType() {
- if (mProto.audioTrackIndex == -1) {
- return INVALID_STREAMTYPE;
- }
- return mProto.audioStreamTypes[mProto.audioTrackIndex];
- }
-
- public List<Integer> getAudioPids() {
- return Ints.asList(mProto.audioPids);
- }
-
- synchronized public void setAudioPids(List<Integer> audioPids) {
- mProto.audioPids = Ints.toArray(audioPids);
- }
-
- public List<Integer> getAudioStreamTypes() {
- return Ints.asList(mProto.audioStreamTypes);
- }
-
- synchronized public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
- mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
- }
-
- public int getPcrPid() {
- return mProto.pcrPid;
- }
-
- public int getType() {
- return mProto.type;
- }
-
- synchronized public void setFilepath(String filepath) {
- mProto.filepath = filepath == null ? "" : filepath;
- }
-
- public String getFilepath() {
- return mProto.filepath;
- }
-
- synchronized public void setVirtualMajor(int virtualMajor) {
- mProto.virtualMajor = virtualMajor;
- }
-
- synchronized public void setVirtualMinor(int virtualMinor) {
- mProto.virtualMinor = virtualMinor;
- }
-
- synchronized public void setShortName(String shortName) {
- mProto.shortName = shortName == null ? "" : shortName;
- }
-
- synchronized public void setFrequency(int frequency) {
- mProto.frequency = frequency;
- }
-
- synchronized public void setModulation(String modulation) {
- mProto.modulation = modulation == null ? "" : modulation;
- }
-
- public boolean hasVideo() {
- return mProto.videoPid != INVALID_PID;
- }
-
- public boolean hasAudio() {
- return getAudioPid() != INVALID_PID;
- }
-
- public long getChannelId() {
- return mProto.channelId;
- }
-
- synchronized public void setChannelId(long channelId) {
- mProto.channelId = channelId;
- }
-
- public String getDisplayNumber() {
- return getDisplayNumber(true);
- }
-
- public String getDisplayNumber(boolean ignoreZeroMinorNumber) {
- if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) {
- return String.format("%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR,
- mProto.virtualMinor);
- } else if (mProto.virtualMajor != 0) {
- return Integer.toString(mProto.virtualMajor);
- } else {
- return Integer.toString(mProto.programNumber);
- }
- }
-
- public String getDescription() {
- return mProto.description;
- }
-
- @Override
- synchronized public void setHasCaptionTrack() {
- mProto.hasCaptionTrack = true;
- }
-
- @Override
- public boolean hasCaptionTrack() {
- return mProto.hasCaptionTrack;
- }
-
- @Override
- public List<AtscAudioTrack> getAudioTracks() {
- return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
- }
-
- synchronized public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
- mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
- }
-
- @Override
- public List<AtscCaptionTrack> getCaptionTracks() {
- return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
- }
-
- synchronized public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
- mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
- }
-
- synchronized public void selectAudioTrack(int index) {
- if (0 <= index && index < mProto.audioPids.length) {
- mProto.audioTrackIndex = index;
- } else {
- mProto.audioTrackIndex = -1;
- }
- }
-
- synchronized public void setRecordingProhibited(boolean recordingProhibited) {
- mProto.recordingProhibited = recordingProhibited;
- }
-
- public boolean isRecordingProhibited() {
- return mProto.recordingProhibited;
- }
-
- synchronized public void setVideoFormat(String videoFormat) {
- mProto.videoFormat = videoFormat == null ? "" : videoFormat;
- }
-
- public String getVideoFormat() {
- return mProto.videoFormat;
- }
-
- @Override
- public String toString() {
- switch (mProto.type) {
- case Channel.TYPE_FILE:
- return String.format("{%d-%d %s} Filepath: %s, ProgramNumber %d",
- mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
- mProto.filepath, mProto.programNumber);
- //case Channel.TYPE_TUNER:
- default:
- return String.format("{%d-%d %s} Frequency: %d, ProgramNumber %d",
- mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
- mProto.frequency, mProto.programNumber);
- }
- }
-
- @Override
- public int compareTo(@NonNull TunerChannel channel) {
- // In the same frequency, the program number acts as the sub-channel number.
- int ret = getFrequency() - channel.getFrequency();
- if (ret != 0) {
- return ret;
- }
- ret = getProgramNumber() - channel.getProgramNumber();
- if (ret != 0) {
- return ret;
- }
- ret = StringUtils.compare(getName(), channel.getName());
- if (ret != 0) {
- return ret;
- }
- // For FileTsStreamer, file paths should be compared.
- return StringUtils.compare(getFilepath(), channel.getFilepath());
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof TunerChannel)) {
- return false;
- }
- return compareTo((TunerChannel) o) == 0;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath());
- }
-
- // Serialization
- synchronized public byte[] toByteArray() {
- try {
- return MessageNano.toByteArray(mProto);
- } catch (Exception e) {
- // Retry toByteArray. b/34197766
- Log.w(TAG, "TunerChannel or its variables are modified in multiple thread without lock",
- e);
- return MessageNano.toByteArray(mProto);
- }
- }
-
- public static TunerChannel parseFrom(byte[] data) {
- if (data == null) {
- return null;
- }
- try {
- return new TunerChannel(TunerChannelProto.parseFrom(data));
- } catch (IOException e) {
- Log.e(TAG, "Could not parse from byte array", e);
- return null;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
deleted file mode 100644
index 5f536708..00000000
--- a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer;
-
-import android.util.Log;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaClock;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.util.Assertions;
-import com.android.tv.tuner.cc.Cea708Parser;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-
-import java.io.IOException;
-
-/**
- * A {@link TrackRenderer} for CEA-708 textual subtitles.
- */
-public class Cea708TextTrackRenderer extends TrackRenderer implements
- Cea708Parser.OnCea708ParserListener {
- private static final String TAG = "Cea708TextTrackRenderer";
- private static final boolean DEBUG = false;
-
- public static final int MSG_SERVICE_NUMBER = 1;
- public static final int MSG_ENABLE_CLOSED_CAPTION = 2;
-
- // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
- private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8;
-
- private final SampleSource.SampleSourceReader mSource;
- private final SampleHolder mSampleHolder;
- private final MediaFormatHolder mFormatHolder;
- private int mServiceNumber;
- private boolean mInputStreamEnded;
- private long mCurrentPositionUs;
- private long mPresentationTimeUs;
- private int mTrackIndex;
- private boolean mRenderingDisabled;
- private Cea708Parser mCea708Parser;
- private CcListener mCcListener;
-
- public interface CcListener {
- void emitEvent(CaptionEvent captionEvent);
- void clearCaption();
- void discoverServiceNumber(int serviceNumber);
- }
-
- public Cea708TextTrackRenderer(SampleSource source) {
- mSource = source.register();
- mTrackIndex = -1;
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mFormatHolder = new MediaFormatHolder();
- }
-
- @Override
- protected MediaClock getMediaClock() {
- return null;
- }
-
- private boolean handlesMimeType(String mimeType) {
- return mimeType.equals(MpegTsSampleExtractor.MIMETYPE_TEXT_CEA_708);
- }
-
- @Override
- protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
- boolean sourcePrepared = mSource.prepare(positionUs);
- if (!sourcePrepared) {
- return false;
- }
- int trackCount = mSource.getTrackCount();
- for (int i = 0; i < trackCount; ++i) {
- MediaFormat trackFormat = mSource.getFormat(i);
- if (handlesMimeType(trackFormat.mimeType)) {
- mTrackIndex = i;
- clearDecodeState();
- return true;
- }
- }
- // TODO: Check this case. (Source do not have the proper mime type.)
- return true;
- }
-
- @Override
- protected void onEnabled(int track, long positionUs, boolean joining) {
- Assertions.checkArgument(mTrackIndex != -1 && track == 0);
- mSource.enable(mTrackIndex, positionUs);
- mInputStreamEnded = false;
- mPresentationTimeUs = positionUs;
- mCurrentPositionUs = Long.MIN_VALUE;
- }
-
- @Override
- protected void onDisabled() {
- mSource.disable(mTrackIndex);
- }
-
- @Override
- protected void onReleased() {
- mSource.release();
- mCea708Parser = null;
- }
-
- @Override
- protected boolean isEnded() {
- return mInputStreamEnded;
- }
-
- @Override
- protected boolean isReady() {
- // Since this track will be fed by {@link VideoTrackRenderer},
- // it is not required to control transition between ready state and buffering state.
- return true;
- }
-
- @Override
- protected int getTrackCount() {
- return mTrackIndex < 0 ? 0 : 1;
- }
-
- @Override
- protected MediaFormat getFormat(int track) {
- Assertions.checkArgument(mTrackIndex != -1 && track == 0);
- return mSource.getFormat(mTrackIndex);
- }
-
- @Override
- protected void maybeThrowError() throws ExoPlaybackException {
- try {
- mSource.maybeThrowError();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- @Override
- protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- try {
- mPresentationTimeUs = positionUs;
- if (!mInputStreamEnded) {
- processOutput();
- feedInputBuffer();
- }
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- private boolean processOutput() {
- return !mInputStreamEnded && mCea708Parser != null &&
- mCea708Parser.processClosedCaptions(mPresentationTimeUs);
- }
-
- private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
- if (mInputStreamEnded) {
- return false;
- }
- long discontinuity = mSource.readDiscontinuity(mTrackIndex);
- if (discontinuity != SampleSource.NO_DISCONTINUITY) {
- if (DEBUG) {
- Log.d(TAG, "Read discontinuity happened");
- }
-
- // TODO: handle input discontinuity for trickplay.
- clearDecodeState();
- mPresentationTimeUs = discontinuity;
- return false;
- }
- mSampleHolder.data.clear();
- mSampleHolder.size = 0;
- int result = mSource.readData(mTrackIndex, mPresentationTimeUs,
- mFormatHolder, mSampleHolder);
- switch (result) {
- case SampleSource.NOTHING_READ: {
- return false;
- }
- case SampleSource.FORMAT_READ: {
- if (DEBUG) {
- Log.i(TAG, "Format was read again");
- }
- return true;
- }
- case SampleSource.END_OF_STREAM: {
- if (DEBUG) {
- Log.i(TAG, "End of stream from SampleSource");
- }
- mInputStreamEnded = true;
- return false;
- }
- case SampleSource.SAMPLE_READ: {
- mSampleHolder.data.flip();
- if (mCea708Parser != null && !mRenderingDisabled) {
- mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs);
- }
- return true;
- }
- }
- return false;
- }
-
- private void clearDecodeState() {
- mCea708Parser = new Cea708Parser();
- mCea708Parser.setListener(this);
- mCea708Parser.setListenServiceNumber(mServiceNumber);
- }
-
- @Override
- protected long getDurationUs() {
- return mSource.getFormat(mTrackIndex).durationUs;
- }
-
- @Override
- protected long getBufferedPositionUs() {
- return mSource.getBufferedPositionUs();
- }
-
- @Override
- protected void seekTo(long currentPositionUs) throws ExoPlaybackException {
- mSource.seekToUs(currentPositionUs);
- mInputStreamEnded = false;
- mPresentationTimeUs = currentPositionUs;
- mCurrentPositionUs = Long.MIN_VALUE;
- }
-
- @Override
- protected void onStarted() {
- // do nothing.
- }
-
- @Override
- protected void onStopped() {
- // do nothing.
- }
-
- private void setServiceNumber(int serviceNumber) {
- mServiceNumber = serviceNumber;
- if (mCea708Parser != null) {
- mCea708Parser.setListenServiceNumber(serviceNumber);
- }
- }
-
- @Override
- public void emitEvent(CaptionEvent event) {
- if (mCcListener != null) {
- mCcListener.emitEvent(event);
- }
- }
-
- @Override
- public void discoverServiceNumber(int serviceNumber) {
- if (mCcListener != null) {
- mCcListener.discoverServiceNumber(serviceNumber);
- }
- }
-
- public void setCcListener(CcListener ccListener) {
- mCcListener = ccListener;
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- switch (messageType) {
- case MSG_SERVICE_NUMBER:
- setServiceNumber((int) message);
- break;
- case MSG_ENABLE_CLOSED_CAPTION:
- boolean renderingDisabled = (Boolean) message == false;
- if (mRenderingDisabled != renderingDisabled) {
- mRenderingDisabled = renderingDisabled;
- if (mRenderingDisabled) {
- if (mCea708Parser != null) {
- mCea708Parser.clear();
- }
- if (mCcListener != null) {
- mCcListener.clearCaption();
- }
- }
- }
- break;
- default:
- super.handleMessage(messageType, message);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java
deleted file mode 100644
index 0ab6d8c4..00000000
--- a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tv.tuner.exoplayer;
-
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
-import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorsFactory;
-import com.google.android.exoplayer2.extractor.TimestampAdjuster;
-import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
-import com.google.android.exoplayer2.extractor.ts.TsExtractor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Extractor factory, mainly aim at create TsExtractor with FLAG_ALLOW_NON_IDR_KEYFRAMES flags for
- * H.264 stream
- */
-public final class ExoPlayerExtractorsFactory implements ExtractorsFactory {
- @Override
- public Extractor[] createExtractors() {
- // Only create TsExtractor since we only target MPEG2TS stream.
- Extractor[] extractors = {
- new TsExtractor(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(
- DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES), false) };
- return extractors;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
deleted file mode 100644
index 0b648400..00000000
--- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer;
-
-import android.net.Uri;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Pair;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
-import com.google.android.exoplayer2.source.ExtractorMediaSource.EventListener;
-import com.google.android.exoplayer2.source.MediaPeriod;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.SampleStream;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.upstream.DataSpec;
-import com.google.android.exoplayer2.upstream.DefaultAllocator;
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
-import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A class that extracts samples from a live broadcast stream while storing the sample on the disk.
- * For demux, this class relies on {@link com.google.android.exoplayer.extractor.ts.TsExtractor}.
- */
-public class ExoPlayerSampleExtractor implements SampleExtractor {
- private static final String TAG = "ExoPlayerSampleExtracto";
-
- private static final int INVALID_TRACK_INDEX = -1;
- private final HandlerThread mSourceReaderThread;
- private final long mId;
-
- private final Handler.Callback mSourceReaderWorker;
-
- private BufferManager.SampleBuffer mSampleBuffer;
- private Handler mSourceReaderHandler;
- private volatile boolean mPrepared;
- private AtomicBoolean mOnCompletionCalled = new AtomicBoolean();
- private IOException mExceptionOnPrepare;
- private List<MediaFormat> mTrackFormats;
- private int mVideoTrackIndex = INVALID_TRACK_INDEX;
- private boolean mVideoTrackMet;
- private long mBaseSamplePts = Long.MIN_VALUE;
- private HashMap<Integer, Long> mLastExtractedPositionUsMap = new HashMap<>();
- private final List<Pair<Integer, SampleHolder>> mPendingSamples = new LinkedList<>();
- private OnCompletionListener mOnCompletionListener;
- private Handler mOnCompletionListenerHandler;
- private IOException mError;
-
- public ExoPlayerSampleExtractor(Uri uri, final DataSource source, BufferManager bufferManager,
- PlaybackBufferListener bufferListener, boolean isRecording) {
- // It'll be used as a timeshift file chunk name's prefix.
- mId = System.currentTimeMillis();
-
- EventListener eventListener = new EventListener() {
- @Override
- public void onLoadError(IOException error) {
- mError = error;
- }
- };
-
- mSourceReaderThread = new HandlerThread("SourceReaderThread");
- mSourceReaderWorker = new SourceReaderWorker(new ExtractorMediaSource(uri,
- new com.google.android.exoplayer2.upstream.DataSource.Factory() {
- @Override
- public com.google.android.exoplayer2.upstream.DataSource createDataSource() {
- // Returns an adapter implementation for ExoPlayer V2 DataSource interface.
- return new com.google.android.exoplayer2.upstream.DataSource() {
- @Override
- public long open(DataSpec dataSpec) throws IOException {
- return source.open(
- new com.google.android.exoplayer.upstream.DataSpec(
- dataSpec.uri, dataSpec.postBody,
- dataSpec.absoluteStreamPosition, dataSpec.position,
- dataSpec.length, dataSpec.key, dataSpec.flags));
- }
-
- @Override
- public int read(byte[] buffer, int offset, int readLength)
- throws IOException {
- return source.read(buffer, offset, readLength);
- }
-
- @Override
- public Uri getUri() {
- return null;
- }
-
- @Override
- public void close() throws IOException {
- source.close();
- }
- };
- }
- },
- new ExoPlayerExtractorsFactory(),
- // Do not create a handler if we not on a looper. e.g. test.
- Looper.myLooper() != null ? new Handler() : null, eventListener));
- if (isRecording) {
- mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, false,
- RecordingSampleBuffer.BUFFER_REASON_RECORDING);
- } else {
- if (bufferManager == null) {
- mSampleBuffer = new SimpleSampleBuffer(bufferListener);
- } else {
- mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, true,
- RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK);
- }
- }
- }
-
- @Override
- public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {
- mOnCompletionListener = listener;
- mOnCompletionListenerHandler = handler;
- }
-
- private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback {
- public static final int MSG_PREPARE = 1;
- public static final int MSG_FETCH_SAMPLES = 2;
- public static final int MSG_RELEASE = 3;
- private static final int RETRY_INTERVAL_MS = 50;
-
- private final MediaSource mSampleSource;
- private MediaPeriod mMediaPeriod;
- private SampleStream[] mStreams;
- private boolean[] mTrackMetEos;
- private boolean mMetEos = false;
- private long mCurrentPosition;
- private DecoderInputBuffer mDecoderInputBuffer;
- private SampleHolder mSampleHolder;
- private boolean mPrepareRequested;
-
- public SourceReaderWorker(MediaSource sampleSource) {
- mSampleSource = sampleSource;
- mSampleSource.prepareSource(null, false, new MediaSource.Listener() {
- @Override
- public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
- // Dynamic stream change is not supported yet. b/28169263
- // For now, this will cause EOS and playback reset.
- }
- });
- mDecoderInputBuffer = new DecoderInputBuffer(
- DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
- }
-
- MediaFormat convertFormat(Format format) {
- if (format.sampleMimeType.startsWith("audio/")) {
- return MediaFormat.createAudioFormat(format.id, format.sampleMimeType,
- format.bitrate, format.maxInputSize,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.channelCount,
- format.sampleRate, format.initializationData, format.language,
- format.pcmEncoding);
- } else if (format.sampleMimeType.startsWith("video/")) {
- return MediaFormat.createVideoFormat(
- format.id, format.sampleMimeType, format.bitrate, format.maxInputSize,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.width, format.height,
- format.initializationData, format.rotationDegrees,
- format.pixelWidthHeightRatio, format.projectionData, format.stereoMode);
- } else if (format.sampleMimeType.endsWith("/cea-608")
- || format.sampleMimeType.startsWith("text/")) {
- return MediaFormat.createTextFormat(
- format.id, format.sampleMimeType, format.bitrate,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.language);
- } else {
- return MediaFormat.createFormatForMimeType(
- format.id, format.sampleMimeType, format.bitrate,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US);
- }
- }
-
- @Override
- public void onPrepared(MediaPeriod mediaPeriod) {
- if (mMediaPeriod == null) {
- // This instance is already released while the extractor is preparing.
- return;
- }
- TrackSelection.Factory selectionFactory = new FixedTrackSelection.Factory();
- TrackGroupArray trackGroupArray = mMediaPeriod.getTrackGroups();
- TrackSelection[] selections = new TrackSelection[trackGroupArray.length];
- for (int i = 0; i < selections.length; ++i) {
- selections[i] = selectionFactory.createTrackSelection(trackGroupArray.get(i), 0);
- }
- boolean retain[] = new boolean[trackGroupArray.length];
- boolean reset[] = new boolean[trackGroupArray.length];
- mStreams = new SampleStream[trackGroupArray.length];
- mMediaPeriod.selectTracks(selections, retain, mStreams, reset, 0);
- if (mTrackFormats == null) {
- int trackCount = trackGroupArray.length;
- mTrackMetEos = new boolean[trackCount];
- List<MediaFormat> trackFormats = new ArrayList<>();
- int videoTrackCount = 0;
- for (int i = 0; i < trackCount; i++) {
- Format format = trackGroupArray.get(i).getFormat(0);
- if (format.sampleMimeType.startsWith("video/")) {
- videoTrackCount++;
- mVideoTrackIndex = i;
- }
- trackFormats.add(convertFormat(format));
- }
- if (videoTrackCount > 1) {
- // Disable dropping samples when there are multiple video tracks.
- mVideoTrackIndex = INVALID_TRACK_INDEX;
- }
- mTrackFormats = trackFormats;
- List<String> ids = new ArrayList<>();
- for (int i = 0; i < mTrackFormats.size(); i++) {
- ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i));
- }
- try {
- mSampleBuffer.init(ids, mTrackFormats);
- } catch (IOException e) {
- // In this case, we will not schedule any further operation.
- // mExceptionOnPrepare will be notified to ExoPlayer, and ExoPlayer will
- // call release() eventually.
- mExceptionOnPrepare = e;
- return;
- }
- mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
- mPrepared = true;
- }
- }
-
- @Override
- public void onContinueLoadingRequested(MediaPeriod source) {
- source.continueLoading(mCurrentPosition);
- }
-
- @Override
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case MSG_PREPARE:
- if (!mPrepareRequested) {
- mPrepareRequested = true;
- mMediaPeriod = mSampleSource.createPeriod(0,
- new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), 0);
- mMediaPeriod.prepare(this);
- try {
- mMediaPeriod.maybeThrowPrepareError();
- } catch (IOException e) {
- mError = e;
- }
- }
- return true;
- case MSG_FETCH_SAMPLES:
- boolean didSomething = false;
- ConditionVariable conditionVariable = new ConditionVariable();
- int trackCount = mStreams.length;
- for (int i = 0; i < trackCount; ++i) {
- if (!mTrackMetEos[i] && C.RESULT_NOTHING_READ
- != fetchSample(i, mSampleHolder, conditionVariable)) {
- if (mMetEos) {
- // If mMetEos was on during fetchSample() due to an error,
- // fetching from other tracks is not necessary.
- break;
- }
- didSomething = true;
- }
- }
- mMediaPeriod.continueLoading(mCurrentPosition);
- if (!mMetEos) {
- if (didSomething) {
- mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
- } else {
- mSourceReaderHandler.sendEmptyMessageDelayed(MSG_FETCH_SAMPLES,
- RETRY_INTERVAL_MS);
- }
- } else {
- notifyCompletionIfNeeded(false);
- }
- return true;
- case MSG_RELEASE:
- if (mMediaPeriod != null) {
- mSampleSource.releasePeriod(mMediaPeriod);
- mSampleSource.releaseSource();
- mMediaPeriod = null;
- }
- cleanUp();
- mSourceReaderHandler.removeCallbacksAndMessages(null);
- return true;
- }
- return false;
- }
-
- private int fetchSample(int track, SampleHolder sample,
- ConditionVariable conditionVariable) {
- FormatHolder dummyFormatHolder = new FormatHolder();
- mDecoderInputBuffer.clear();
- int ret = mStreams[track].readData(dummyFormatHolder, mDecoderInputBuffer);
- if (ret == C.RESULT_BUFFER_READ
- // Double-check if the extractor provided the data to prevent NPE. b/33758354
- && mDecoderInputBuffer.data != null) {
- if (mCurrentPosition < mDecoderInputBuffer.timeUs) {
- mCurrentPosition = mDecoderInputBuffer.timeUs;
- }
- try {
- Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track);
- if (lastExtractedPositionUs == null) {
- mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs);
- } else {
- mLastExtractedPositionUsMap.put(track,
- Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs));
- }
- queueSample(track, conditionVariable);
- } catch (IOException e) {
- mLastExtractedPositionUsMap.clear();
- mMetEos = true;
- mSampleBuffer.setEos();
- }
- } else if (ret == C.RESULT_END_OF_INPUT) {
- mTrackMetEos[track] = true;
- for (int i = 0; i < mTrackMetEos.length; ++i) {
- if (!mTrackMetEos[i]) {
- break;
- }
- if (i == mTrackMetEos.length - 1) {
- mMetEos = true;
- mSampleBuffer.setEos();
- }
- }
- }
- // TODO: Handle C.RESULT_FORMAT_READ for dynamic resolution change. b/28169263
- return ret;
- }
-
- private void queueSample(int index, ConditionVariable conditionVariable)
- throws IOException {
- if (mVideoTrackIndex != INVALID_TRACK_INDEX) {
- if (!mVideoTrackMet) {
- if (index != mVideoTrackIndex) {
- SampleHolder sample =
- new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
- mSampleHolder.flags =
- (mDecoderInputBuffer.isKeyFrame()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0)
- | (mDecoderInputBuffer.isDecodeOnly()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY
- : 0);
- sample.timeUs = mDecoderInputBuffer.timeUs;
- sample.size = mDecoderInputBuffer.data.position();
- sample.ensureSpaceForWrite(sample.size);
- mDecoderInputBuffer.flip();
- sample.data.position(0);
- sample.data.put(mDecoderInputBuffer.data);
- sample.data.flip();
- mPendingSamples.add(new Pair<>(index, sample));
- return;
- }
- mVideoTrackMet = true;
- mBaseSamplePts =
- mDecoderInputBuffer.timeUs
- - MpegTsDefaultAudioTrackRenderer
- .INITIAL_AUDIO_BUFFERING_TIME_US;
- for (Pair<Integer, SampleHolder> pair : mPendingSamples) {
- if (pair.second.timeUs >= mBaseSamplePts) {
- mSampleBuffer.writeSample(pair.first, pair.second, conditionVariable);
- }
- }
- mPendingSamples.clear();
- } else {
- if (mDecoderInputBuffer.timeUs < mBaseSamplePts
- && mVideoTrackIndex != index) {
- return;
- }
- }
- }
- // Copy the decoder input to the sample holder.
- mSampleHolder.clearData();
- mSampleHolder.flags =
- (mDecoderInputBuffer.isKeyFrame()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0)
- | (mDecoderInputBuffer.isDecodeOnly()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY : 0);
- mSampleHolder.timeUs = mDecoderInputBuffer.timeUs;
- mSampleHolder.size = mDecoderInputBuffer.data.position();
- mSampleHolder.ensureSpaceForWrite(mSampleHolder.size);
- mDecoderInputBuffer.flip();
- mSampleHolder.data.position(0);
- mSampleHolder.data.put(mDecoderInputBuffer.data);
- mSampleHolder.data.flip();
- long writeStartTimeNs = SystemClock.elapsedRealtimeNanos();
- mSampleBuffer.writeSample(index, mSampleHolder, conditionVariable);
-
- // Checks whether the storage has enough bandwidth for recording samples.
- if (mSampleBuffer.isWriteSpeedSlow(mSampleHolder.size,
- SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) {
- mSampleBuffer.handleWriteSpeedSlow();
- }
- }
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- if (mError != null) {
- IOException e = mError;
- mError = null;
- throw e;
- }
- }
-
- @Override
- public boolean prepare() throws IOException {
- if (!mSourceReaderThread.isAlive()) {
- mSourceReaderThread.start();
- mSourceReaderHandler = new Handler(mSourceReaderThread.getLooper(),
- mSourceReaderWorker);
- mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_PREPARE);
- }
- if (mExceptionOnPrepare != null) {
- throw mExceptionOnPrepare;
- }
- return mPrepared;
- }
-
- @Override
- public List<MediaFormat> getTrackFormats() {
- return mTrackFormats;
- }
-
- @Override
- public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
- outMediaFormatHolder.format = mTrackFormats.get(track);
- outMediaFormatHolder.drmInitData = null;
- }
-
- @Override
- public void selectTrack(int index) {
- mSampleBuffer.selectTrack(index);
- }
-
- @Override
- public void deselectTrack(int index) {
- mSampleBuffer.deselectTrack(index);
- }
-
- @Override
- public long getBufferedPositionUs() {
- return mSampleBuffer.getBufferedPositionUs();
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- return mSampleBuffer.continueBuffering(positionUs);
- }
-
- @Override
- public void seekTo(long positionUs) {
- mSampleBuffer.seekTo(positionUs);
- }
-
- @Override
- public int readSample(int track, SampleHolder sampleHolder) {
- return mSampleBuffer.readSample(track, sampleHolder);
- }
-
- @Override
- public void release() {
- if (mSourceReaderThread.isAlive()) {
- mSourceReaderHandler.removeCallbacksAndMessages(null);
- mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_RELEASE);
- mSourceReaderThread.quitSafely();
- // Return early in this case so that session worker can start working on the next
- // request as early as it can. The clean up will be done in the reader thread while
- // handling MSG_RELEASE.
- } else {
- cleanUp();
- }
- }
-
- private void cleanUp() {
- boolean result = true;
- try {
- if (mSampleBuffer != null) {
- mSampleBuffer.release();
- mSampleBuffer = null;
- }
- } catch (IOException e) {
- result = false;
- }
- notifyCompletionIfNeeded(result);
- setOnCompletionListener(null, null);
- }
-
- private void notifyCompletionIfNeeded(final boolean result) {
- if (!mOnCompletionCalled.getAndSet(true)) {
- final OnCompletionListener listener = mOnCompletionListener;
- final long lastExtractedPositionUs = getLastExtractedPositionUs();
- if (mOnCompletionListenerHandler != null && mOnCompletionListener != null) {
- mOnCompletionListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- listener.onCompletion(result, lastExtractedPositionUs);
- }
- });
- }
- }
- }
-
- private long getLastExtractedPositionUs() {
- long lastExtractedPositionUs = Long.MIN_VALUE;
- for (Map.Entry<Integer, Long> entry : mLastExtractedPositionUsMap.entrySet()) {
- if (mVideoTrackIndex != entry.getKey()) {
- lastExtractedPositionUs = Math.max(lastExtractedPositionUs, entry.getValue());
- }
- }
- if (lastExtractedPositionUs == Long.MIN_VALUE) {
- lastExtractedPositionUs = com.google.android.exoplayer.C.UNKNOWN_TIME_US;
- }
- return lastExtractedPositionUs;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
deleted file mode 100644
index b7e42a7c..00000000
--- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.MediaFormatUtil;
-import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-import android.os.Handler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A class that plays a recorded stream without using {@link android.media.MediaExtractor},
- * since all samples are extracted and stored to the permanent storage already.
- */
-public class FileSampleExtractor implements SampleExtractor{
- private static final String TAG = "FileSampleExtractor";
- private static final boolean DEBUG = false;
-
- private int mTrackCount;
- private boolean mReleased;
-
- private final List<MediaFormat> mTrackFormats = new ArrayList<>();
- private final BufferManager mBufferManager;
- private final PlaybackBufferListener mBufferListener;
- private BufferManager.SampleBuffer mSampleBuffer;
-
- public FileSampleExtractor(
- BufferManager bufferManager, PlaybackBufferListener bufferListener) {
- mBufferManager = bufferManager;
- mBufferListener = bufferListener;
- mTrackCount = -1;
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- // Do nothing.
- }
-
- @Override
- public boolean prepare() throws IOException {
- List<BufferManager.TrackFormat> trackFormatList = mBufferManager.readTrackInfoFiles();
- if (trackFormatList == null || trackFormatList.isEmpty()) {
- throw new IOException("Cannot find meta files for the recording.");
- }
- mTrackCount = trackFormatList.size();
- List<String> ids = new ArrayList<>();
- mTrackFormats.clear();
- for (int i = 0; i < mTrackCount; ++i) {
- BufferManager.TrackFormat trackFormat = trackFormatList.get(i);
- ids.add(trackFormat.trackId);
- mTrackFormats.add(MediaFormatUtil.createMediaFormat(trackFormat.format));
- }
- mSampleBuffer = new RecordingSampleBuffer(mBufferManager, mBufferListener, true,
- RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK);
- mSampleBuffer.init(ids, mTrackFormats);
- return true;
- }
-
- @Override
- public List<MediaFormat> getTrackFormats() {
- return mTrackFormats;
- }
-
- @Override
- public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
- outMediaFormatHolder.format = mTrackFormats.get(track);
- outMediaFormatHolder.drmInitData = null;
- }
-
- @Override
- public void release() {
- if (!mReleased) {
- if (mSampleBuffer != null) {
- try {
- mSampleBuffer.release();
- } catch (IOException e) {
- // Do nothing. Playback ends now.
- }
- }
- }
- mReleased = true;
- }
-
- @Override
- public void selectTrack(int index) {
- mSampleBuffer.selectTrack(index);
- }
-
- @Override
- public void deselectTrack(int index) {
- mSampleBuffer.deselectTrack(index);
- }
-
- @Override
- public long getBufferedPositionUs() {
- return mSampleBuffer.getBufferedPositionUs();
- }
-
- @Override
- public void seekTo(long positionUs) {
- mSampleBuffer.seekTo(positionUs);
- }
-
- @Override
- public int readSample(int track, SampleHolder sampleHolder) {
- return mSampleBuffer.readSample(track, sampleHolder);
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- return mSampleBuffer.continueBuffering(positionUs);
- }
-
- @Override
- public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
deleted file mode 100644
index 2694298a..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
+++ /dev/null
@@ -1,696 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer;
-
-import android.content.Context;
-import android.media.AudioFormat;
-import android.media.MediaCodec.CryptoException;
-import android.media.PlaybackParams;
-import android.os.Handler;
-import android.support.annotation.IntDef;
-import android.view.Surface;
-
-import com.google.android.exoplayer.DummyTrackRenderer;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.ExoPlayer;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.audio.MpegTsMediaCodecAudioTrackRenderer;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.tvinput.EventDetector;
-import com.android.tv.tuner.tvinput.TunerDebug;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** MPEG-2 TS stream player implementation using ExoPlayer. */
-public class MpegTsPlayer
- implements ExoPlayer.Listener,
- MediaCodecVideoTrackRenderer.EventListener,
- MpegTsDefaultAudioTrackRenderer.EventListener,
- MpegTsMediaCodecAudioTrackRenderer.Ac3EventListener {
- private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER;
-
- /**
- * Interface definition for building specific track renderers.
- */
- public interface RendererBuilder {
- void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource,
- boolean hasSoftwareAudioDecoder, RendererBuilderCallback callback);
- }
-
- /**
- * Interface definition for {@link RendererBuilder#buildRenderers} to notify the result.
- */
- public interface RendererBuilderCallback {
- void onRenderers(String[][] trackNames, TrackRenderer[] renderers);
- void onRenderersError(Exception e);
- }
-
- /**
- * Interface definition for a callback to be notified of changes in player state.
- */
- public interface Listener {
- void onStateChanged(boolean playWhenReady, int playbackState);
- void onError(Exception e);
- void onVideoSizeChanged(int width, int height,
- float pixelWidthHeightRatio);
- void onDrawnToSurface(MpegTsPlayer player, Surface surface);
- void onAudioUnplayable();
- void onSmoothTrickplayForceStopped();
- }
-
- /**
- * Interface definition for a callback to be notified of changes on video display.
- */
- public interface VideoEventListener {
- /**
- * Notifies the caption event.
- */
- void onEmitCaptionEvent(CaptionEvent event);
-
- /**
- * Notifies clearing up whole closed caption event.
- */
- void onClearCaptionEvent();
-
- /**
- * Notifies the discovered caption service number.
- */
- void onDiscoverCaptionServiceNumber(int serviceNumber);
- }
-
- public static final int RENDERER_COUNT = 3;
- public static final int MIN_BUFFER_MS = 0;
- public static final int MIN_REBUFFER_MS = 500;
-
- @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrackType {}
- public static final int TRACK_TYPE_VIDEO = 0;
- public static final int TRACK_TYPE_AUDIO = 1;
- public static final int TRACK_TYPE_TEXT = 2;
-
- @IntDef({RENDERER_BUILDING_STATE_IDLE, RENDERER_BUILDING_STATE_BUILDING,
- RENDERER_BUILDING_STATE_BUILT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface RendererBuildingState {}
- private static final int RENDERER_BUILDING_STATE_IDLE = 1;
- private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
- private static final int RENDERER_BUILDING_STATE_BUILT = 3;
-
- private static final float MAX_SMOOTH_TRICKPLAY_SPEED = 9.0f;
- private static final float MIN_SMOOTH_TRICKPLAY_SPEED = 0.1f;
-
- private final RendererBuilder mRendererBuilder;
- private final ExoPlayer mPlayer;
- private final Handler mMainHandler;
- private final AudioCapabilities mAudioCapabilities;
- private final TsDataSourceManager mSourceManager;
-
- private Listener mListener;
- @RendererBuildingState private int mRendererBuildingState;
-
- private Surface mSurface;
- private TsDataSource mDataSource;
- private InternalRendererBuilderCallback mBuilderCallback;
- private TrackRenderer mVideoRenderer;
- private TrackRenderer mAudioRenderer;
- private Cea708TextTrackRenderer mTextRenderer;
- private final Cea708TextTrackRenderer.CcListener mCcListener;
- private VideoEventListener mVideoEventListener;
- private boolean mTrickplayRunning;
- private float mVolume;
-
- /**
- * Creates MPEG2-TS stream player.
- *
- * @param rendererBuilder the builder of track renderers
- * @param handler the handler for the playback events in track renderers
- * @param sourceManager the manager for {@link DataSource}
- * @param capabilities the {@link AudioCapabilities} of the current device
- * @param listener the listener for playback state changes
- */
- public MpegTsPlayer(RendererBuilder rendererBuilder, Handler handler,
- TsDataSourceManager sourceManager, AudioCapabilities capabilities,
- Listener listener) {
- mRendererBuilder = rendererBuilder;
- mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS);
- mPlayer.addListener(this);
- mMainHandler = handler;
- mAudioCapabilities = capabilities;
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- mCcListener = new MpegTsCcListener();
- mSourceManager = sourceManager;
- mListener = listener;
- }
-
- /**
- * Sets the video event listener.
- *
- * @param videoEventListener the listener for video events
- */
- public void setVideoEventListener(VideoEventListener videoEventListener) {
- mVideoEventListener = videoEventListener;
- }
-
- /**
- * Sets the closed caption service number.
- *
- * @param captionServiceNumber the service number of CEA-708 closed caption
- */
- public void setCaptionServiceNumber(int captionServiceNumber) {
- mCaptionServiceNumber = captionServiceNumber;
- if (mTextRenderer != null) {
- mPlayer.sendMessage(mTextRenderer,
- Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
- }
- }
-
- /**
- * Sets the surface for the player.
- *
- * @param surface the {@link Surface} to render video
- */
- public void setSurface(Surface surface) {
- mSurface = surface;
- pushSurface(false);
- }
-
- /**
- * Returns the current surface of the player.
- */
- public Surface getSurface() {
- return mSurface;
- }
-
- /**
- * Clears the surface and waits until the surface is being cleaned.
- */
- public void blockingClearSurface() {
- mSurface = null;
- pushSurface(true);
- }
-
- /**
- * Creates renderers and {@link DataSource} and initializes player.
- * @param context a {@link Context} instance
- * @param channel to play
- * @param hasSoftwareAudioDecoder {@code true} if there is connected software decoder
- * @param eventListener for program information which will be scanned from MPEG2-TS stream
- * @return true when everything is created and initialized well, false otherwise
- */
- public boolean prepare(Context context, TunerChannel channel, boolean hasSoftwareAudioDecoder,
- EventDetector.EventListener eventListener) {
- TsDataSource source = null;
- if (channel != null) {
- source = mSourceManager.createDataSource(context, channel, eventListener);
- if (source == null) {
- return false;
- }
- }
- mDataSource = source;
- if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
- mPlayer.stop();
- }
- if (mBuilderCallback != null) {
- mBuilderCallback.cancel();
- }
- mRendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
- mBuilderCallback = new InternalRendererBuilderCallback();
- mRendererBuilder.buildRenderers(this, source, hasSoftwareAudioDecoder, mBuilderCallback);
- return true;
- }
-
- /**
- * Returns {@link TsDataSource} which provides MPEG2-TS stream.
- */
- public TsDataSource getDataSource() {
- return mDataSource;
- }
-
- private void onRenderers(TrackRenderer[] renderers) {
- mBuilderCallback = null;
- for (int i = 0; i < RENDERER_COUNT; i++) {
- if (renderers[i] == null) {
- // Convert a null renderer to a dummy renderer.
- renderers[i] = new DummyTrackRenderer();
- }
- }
- mVideoRenderer = renderers[TRACK_TYPE_VIDEO];
- mAudioRenderer = renderers[TRACK_TYPE_AUDIO];
- mTextRenderer = (Cea708TextTrackRenderer) renderers[TRACK_TYPE_TEXT];
- mTextRenderer.setCcListener(mCcListener);
- mPlayer.sendMessage(
- mTextRenderer, Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
- mRendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
- pushSurface(false);
- mPlayer.prepare(renderers);
- pushTrackSelection(TRACK_TYPE_VIDEO, true);
- pushTrackSelection(TRACK_TYPE_AUDIO, true);
- pushTrackSelection(TRACK_TYPE_TEXT, true);
- }
-
- private void onRenderersError(Exception e) {
- mBuilderCallback = null;
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- if (mListener != null) {
- mListener.onError(e);
- }
- }
-
- /**
- * Sets the player state to pause or play.
- *
- * @param playWhenReady sets the player state to being ready to play when {@code true},
- * sets the player state to being paused when {@code false}
- *
- */
- public void setPlayWhenReady(boolean playWhenReady) {
- mPlayer.setPlayWhenReady(playWhenReady);
- stopSmoothTrickplay(false);
- }
-
- /**
- * Returns true, if trickplay is supported.
- */
- public boolean supportSmoothTrickPlay(float playbackSpeed) {
- return playbackSpeed > MIN_SMOOTH_TRICKPLAY_SPEED
- && playbackSpeed < MAX_SMOOTH_TRICKPLAY_SPEED;
- }
-
- /**
- * Starts trickplay. It'll be reset, if {@link #seekTo} or {@link #setPlayWhenReady} is called.
- */
- public void startSmoothTrickplay(PlaybackParams playbackParams) {
- SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed()));
- mPlayer.setPlayWhenReady(true);
- mTrickplayRunning = true;
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer,
- MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED,
- playbackParams.getSpeed());
- } else {
- mPlayer.sendMessage(mAudioRenderer,
- MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS,
- playbackParams);
- }
- }
-
- private void stopSmoothTrickplay(boolean calledBySeek) {
- if (mTrickplayRunning) {
- mTrickplayRunning = false;
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED,
- 1.0f);
- } else {
- mPlayer.sendMessage(mAudioRenderer,
- MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS,
- new PlaybackParams().setSpeed(1.0f));
- }
- if (!calledBySeek) {
- mPlayer.seekTo(mPlayer.getCurrentPosition());
- }
- }
- }
-
- /**
- * Seeks to the specified position of the current playback.
- *
- * @param positionMs the specified position in milli seconds.
- */
- public void seekTo(long positionMs) {
- mPlayer.seekTo(positionMs);
- stopSmoothTrickplay(true);
- }
-
- /**
- * Releases the player.
- */
- public void release() {
- if (mDataSource != null) {
- mSourceManager.releaseDataSource(mDataSource);
- mDataSource = null;
- }
- if (mBuilderCallback != null) {
- mBuilderCallback.cancel();
- mBuilderCallback = null;
- }
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- mSurface = null;
- mListener = null;
- mPlayer.release();
- }
-
- /**
- * Returns the current status of the player.
- */
- public int getPlaybackState() {
- if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
- return ExoPlayer.STATE_PREPARING;
- }
- return mPlayer.getPlaybackState();
- }
-
- /**
- * Returns {@code true} when the player is prepared to play, {@code false} otherwise.
- */
- public boolean isPrepared() {
- int state = getPlaybackState();
- return state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING;
- }
-
- /**
- * Returns {@code true} when the player is being ready to play, {@code false} otherwise.
- */
- public boolean isPlaying() {
- int state = getPlaybackState();
- return (state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING)
- && mPlayer.getPlayWhenReady();
- }
-
- /**
- * Returns {@code true} when the player is buffering, {@code false} otherwise.
- */
- public boolean isBuffering() {
- return getPlaybackState() == ExoPlayer.STATE_BUFFERING;
- }
-
- /**
- * Returns the current position of the playback in milli seconds.
- */
- public long getCurrentPosition() {
- return mPlayer.getCurrentPosition();
- }
-
- /**
- * Returns the total duration of the playback.
- */
- public long getDuration() {
- return mPlayer.getDuration();
- }
-
- /**
- * Returns {@code true} when the player is being ready to play,
- * {@code false} when the player is paused.
- */
- public boolean getPlayWhenReady() {
- return mPlayer.getPlayWhenReady();
- }
-
- /**
- * Sets the volume of the audio.
- *
- * @param volume see also {@link AudioTrack#setVolume(float)}
- */
- public void setVolume(float volume) {
- mVolume = volume;
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_VOLUME,
- volume);
- } else {
- mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
- volume);
- }
- }
-
- /**
- * Enables or disables audio and closed caption.
- *
- * @param enable enables the audio and closed caption when {@code true}, disables otherwise.
- */
- public void setAudioTrackAndClosedCaption(boolean enable) {
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_AUDIO_TRACK,
- enable ? 1 : 0);
- } else {
- mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
- enable ? mVolume : 0.0f);
- }
- mPlayer.sendMessage(mTextRenderer, Cea708TextTrackRenderer.MSG_ENABLE_CLOSED_CAPTION,
- enable);
- }
-
- /**
- * Returns {@code true} when AC3 audio can be played, {@code false} otherwise.
- */
- public boolean isAc3Playable() {
- return mAudioCapabilities != null
- && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3);
- }
-
- /**
- * Notifies when the audio cannot be played by the current device.
- */
- public void onAudioUnplayable() {
- if (mListener != null) {
- mListener.onAudioUnplayable();
- }
- }
-
- /**
- * Returns {@code true} if the player has any video track, {@code false} otherwise.
- */
- public boolean hasVideo() {
- return mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0;
- }
-
- /**
- * Returns {@code true} if the player has any audio trock, {@code false} otherwise.
- */
- public boolean hasAudio() {
- return mPlayer.getTrackCount(TRACK_TYPE_AUDIO) > 0;
- }
-
- /**
- * Returns the number of tracks exposed by the specified renderer.
- */
- public int getTrackCount(int rendererIndex) {
- return mPlayer.getTrackCount(rendererIndex);
- }
-
- /**
- * Selects a track for the specified renderer.
- */
- public void setSelectedTrack(int rendererIndex, int trackIndex) {
- if (trackIndex >= getTrackCount(rendererIndex)) {
- return;
- }
- mPlayer.setSelectedTrack(rendererIndex, trackIndex);
- }
-
- /**
- * Returns the index of the currently selected track for the specified renderer.
- *
- * @param rendererIndex The index of the renderer.
- * @return The selected track. A negative value or a value greater than or equal to the renderer's
- * track count indicates that the renderer is disabled.
- */
- public int getSelectedTrack(int rendererIndex) {
- return mPlayer.getSelectedTrack(rendererIndex);
- }
-
- /**
- * Returns the format of a track.
- *
- * @param rendererIndex The index of the renderer.
- * @param trackIndex The index of the track.
- * @return The format of the track.
- */
- public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
- return mPlayer.getTrackFormat(rendererIndex, trackIndex);
- }
-
- /**
- * Gets the main handler of the player.
- */
- /* package */ Handler getMainHandler() {
- return mMainHandler;
- }
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, int state) {
- if (mListener == null) {
- return;
- }
- mListener.onStateChanged(playWhenReady, state);
- if (state == ExoPlayer.STATE_READY && mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0
- && playWhenReady) {
- MediaFormat format = mPlayer.getTrackFormat(TRACK_TYPE_VIDEO, 0);
- mListener.onVideoSizeChanged(format.width,
- format.height, format.pixelWidthHeightRatio);
- }
- }
-
- @Override
- public void onPlayerError(ExoPlaybackException exception) {
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- if (mListener != null) {
- mListener.onError(exception);
- }
- }
-
- @Override
- public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
- float pixelWidthHeightRatio) {
- if (mListener != null) {
- mListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio);
- }
- }
-
- @Override
- public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
- long initializationDurationMs) {
- // Do nothing.
- }
-
- @Override
- public void onDecoderInitializationError(DecoderInitializationException e) {
- // Do nothing.
- }
-
- @Override
- public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
- if (mListener != null) {
- mListener.onAudioUnplayable();
- }
- }
-
- @Override
- public void onAudioTrackWriteError(AudioTrack.WriteException e) {
- // Do nothing.
- }
-
- @Override
- public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
- long elapsedSinceLastFeedMs) {
- // Do nothing.
- }
-
- @Override
- public void onCryptoError(CryptoException e) {
- // Do nothing.
- }
-
- @Override
- public void onPlayWhenReadyCommitted() {
- // Do nothing.
- }
-
- @Override
- public void onDrawnToSurface(Surface surface) {
- if (mListener != null) {
- mListener.onDrawnToSurface(this, surface);
- }
- }
-
- @Override
- public void onDroppedFrames(int count, long elapsed) {
- TunerDebug.notifyVideoFrameDrop(count, elapsed);
- if (mTrickplayRunning && mListener != null) {
- mListener.onSmoothTrickplayForceStopped();
- }
- }
-
- @Override
- public void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e) {
- if (mTrickplayRunning && mListener != null) {
- mListener.onSmoothTrickplayForceStopped();
- }
- }
-
- private void pushSurface(boolean blockForSurfacePush) {
- if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
- return;
- }
-
- if (blockForSurfacePush) {
- mPlayer.blockingSendMessage(
- mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
- } else {
- mPlayer.sendMessage(
- mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
- }
- }
-
- private void pushTrackSelection(@TrackType int type, boolean allowRendererEnable) {
- if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
- return;
- }
- mPlayer.setSelectedTrack(type, allowRendererEnable ? 0 : -1);
- }
-
- private class MpegTsCcListener implements Cea708TextTrackRenderer.CcListener {
-
- @Override
- public void emitEvent(CaptionEvent captionEvent) {
- if (mVideoEventListener != null) {
- mVideoEventListener.onEmitCaptionEvent(captionEvent);
- }
- }
-
- @Override
- public void clearCaption() {
- if (mVideoEventListener != null) {
- mVideoEventListener.onClearCaptionEvent();
- }
- }
-
- @Override
- public void discoverServiceNumber(int serviceNumber) {
- if (mVideoEventListener != null) {
- mVideoEventListener.onDiscoverCaptionServiceNumber(serviceNumber);
- }
- }
- }
-
- private class InternalRendererBuilderCallback implements RendererBuilderCallback {
- private boolean canceled;
-
- public void cancel() {
- canceled = true;
- }
-
- @Override
- public void onRenderers(String[][] trackNames, TrackRenderer[] renderers) {
- if (!canceled) {
- MpegTsPlayer.this.onRenderers(renderers);
- }
- }
-
- @Override
- public void onRenderersError(Exception e) {
- if (!canceled) {
- MpegTsPlayer.this.onRenderersError(e);
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
deleted file mode 100644
index 006ccac2..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer;
-
-import android.content.Context;
-
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.android.tv.Features;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilder;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilderCallback;
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-/**
- * Builder for renderer objects for {@link MpegTsPlayer}.
- */
-public class MpegTsRendererBuilder implements RendererBuilder {
- private final Context mContext;
- private final BufferManager mBufferManager;
- private final PlaybackBufferListener mBufferListener;
-
- public MpegTsRendererBuilder(Context context, BufferManager bufferManager,
- PlaybackBufferListener bufferListener) {
- mContext = context;
- mBufferManager = bufferManager;
- mBufferListener = bufferListener;
- }
-
- @Override
- public void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource,
- boolean mHasSoftwareAudioDecoder, RendererBuilderCallback callback) {
- // Build the video and audio renderers.
- SampleExtractor extractor = dataSource == null ?
- new MpegTsSampleExtractor(mBufferManager, mBufferListener) :
- new MpegTsSampleExtractor(dataSource, mBufferManager, mBufferListener);
- SampleSource sampleSource = new MpegTsSampleSource(extractor);
- MpegTsVideoTrackRenderer videoRenderer = new MpegTsVideoTrackRenderer(mContext,
- sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer);
- // TODO: Only using MpegTsDefaultAudioTrackRenderer for A/V sync issue. We will use
- // {@link MpegTsMediaCodecAudioTrackRenderer} when we use ExoPlayer's extractor.
- TrackRenderer audioRenderer =
- new MpegTsDefaultAudioTrackRenderer(
- sampleSource,
- MediaCodecSelector.DEFAULT,
- mpegTsPlayer.getMainHandler(),
- mpegTsPlayer,
- mHasSoftwareAudioDecoder,
- !Features.AC3_SOFTWARE_DECODE.isEnabled(mContext));
- Cea708TextTrackRenderer textRenderer = new Cea708TextTrackRenderer(sampleSource);
-
- TrackRenderer[] renderers = new TrackRenderer[MpegTsPlayer.RENDERER_COUNT];
- renderers[MpegTsPlayer.TRACK_TYPE_VIDEO] = videoRenderer;
- renderers[MpegTsPlayer.TRACK_TYPE_AUDIO] = audioRenderer;
- renderers[MpegTsPlayer.TRACK_TYPE_TEXT] = textRenderer;
- callback.onRenderers(null, renderers);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
deleted file mode 100644
index 7bf116c8..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer;
-
-import android.net.Uri;
-import android.os.Handler;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.SamplePool;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Extracts samples from {@link DataSource} for MPEG-TS streams.
- */
-public final class MpegTsSampleExtractor implements SampleExtractor {
- public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
-
- private static final int CC_BUFFER_SIZE_IN_BYTES = 9600 / 8;
-
- private final SampleExtractor mSampleExtractor;
- private final List<MediaFormat> mTrackFormats = new ArrayList<>();
- private final List<Boolean> mReachedEos = new ArrayList<>();
- private int mVideoTrackIndex;
- private final SamplePool mCcSamplePool = new SamplePool();
- private final List<SampleHolder> mPendingCcSamples = new LinkedList<>();
-
- private int mCea708TextTrackIndex;
- private boolean mCea708TextTrackSelected;
-
- private CcParser mCcParser;
-
- private void init() {
- mVideoTrackIndex = -1;
- mCea708TextTrackIndex = -1;
- mCea708TextTrackSelected = false;
- }
-
- /**
- * Creates MpegTsSampleExtractor for {@link DataSource}.
- *
- * @param source the {@link DataSource} to extract from
- * @param bufferManager the manager for reading & writing samples backed by physical storage
- * @param bufferListener the {@link PlaybackBufferListener}
- * to notify buffer storage status change
- */
- public MpegTsSampleExtractor(DataSource source, BufferManager bufferManager,
- PlaybackBufferListener bufferListener) {
- mSampleExtractor = new ExoPlayerSampleExtractor(Uri.EMPTY, source, bufferManager,
- bufferListener, false);
- init();
- }
-
- /**
- * Creates MpegTsSampleExtractor for a recorded program.
- *
- * @param bufferManager the samples provider which is stored in physical storage
- * @param bufferListener the {@link PlaybackBufferListener}
- * to notify buffer storage status change
- */
- public MpegTsSampleExtractor(BufferManager bufferManager,
- PlaybackBufferListener bufferListener) {
- mSampleExtractor = new FileSampleExtractor(bufferManager, bufferListener);
- init();
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- if (mSampleExtractor != null) {
- mSampleExtractor.maybeThrowError();
- }
- }
-
- @Override
- public boolean prepare() throws IOException {
- if(!mSampleExtractor.prepare()) {
- return false;
- }
- List<MediaFormat> formats = mSampleExtractor.getTrackFormats();
- int trackCount = formats.size();
- mTrackFormats.clear();
- mReachedEos.clear();
-
- for (int i = 0; i < trackCount; ++i) {
- mTrackFormats.add(formats.get(i));
- mReachedEos.add(false);
- String mime = formats.get(i).mimeType;
- if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) {
- mVideoTrackIndex = i;
- if (android.media.MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) {
- mCcParser = new Mpeg2CcParser();
- } else if (android.media.MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
- mCcParser = new H264CcParser();
- }
- }
- }
-
- if (mVideoTrackIndex != -1) {
- mCea708TextTrackIndex = trackCount;
- }
- if (mCea708TextTrackIndex >= 0) {
- mTrackFormats.add(MediaFormat.createTextFormat(null, MIMETYPE_TEXT_CEA_708, 0,
- mTrackFormats.get(0).durationUs, ""));
- }
- return true;
- }
-
- @Override
- public List<MediaFormat> getTrackFormats() {
- return mTrackFormats;
- }
-
- @Override
- public void selectTrack(int index) {
- if (index == mCea708TextTrackIndex) {
- mCea708TextTrackSelected = true;
- return;
- }
- mSampleExtractor.selectTrack(index);
- }
-
- @Override
- public void deselectTrack(int index) {
- if (index == mCea708TextTrackIndex) {
- mCea708TextTrackSelected = false;
- return;
- }
- mSampleExtractor.deselectTrack(index);
- }
-
- @Override
- public long getBufferedPositionUs() {
- return mSampleExtractor.getBufferedPositionUs();
- }
-
- @Override
- public void seekTo(long positionUs) {
- mSampleExtractor.seekTo(positionUs);
- for (SampleHolder holder : mPendingCcSamples) {
- mCcSamplePool.releaseSample(holder);
- }
- mPendingCcSamples.clear();
- }
-
- @Override
- public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
- if (track != mCea708TextTrackIndex) {
- mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder);
- }
- }
-
- @Override
- public int readSample(int track, SampleHolder sampleHolder) {
- if (track == mCea708TextTrackIndex) {
- if (mCea708TextTrackSelected && !mPendingCcSamples.isEmpty()) {
- SampleHolder holder = mPendingCcSamples.remove(0);
- holder.data.flip();
- sampleHolder.timeUs = holder.timeUs;
- sampleHolder.data.put(holder.data);
- mCcSamplePool.releaseSample(holder);
- return SampleSource.SAMPLE_READ;
- } else {
- return mVideoTrackIndex < 0 || mReachedEos.get(mVideoTrackIndex)
- ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ;
- }
- }
-
- int result = mSampleExtractor.readSample(track, sampleHolder);
- switch (result) {
- case SampleSource.END_OF_STREAM: {
- mReachedEos.set(track, true);
- break;
- }
- case SampleSource.SAMPLE_READ: {
- if (mCea708TextTrackSelected && track == mVideoTrackIndex
- && sampleHolder.data != null) {
- mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs);
- }
- break;
- }
- }
- return result;
- }
-
- @Override
- public void release() {
- mSampleExtractor.release();
- mVideoTrackIndex = -1;
- mCea708TextTrackIndex = -1;
- mCea708TextTrackSelected = false;
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- return mSampleExtractor.continueBuffering(positionUs);
- }
-
- @Override
- public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { }
-
- private abstract class CcParser {
- // Interim buffer for reduce direct access to ByteBuffer which is expensive. Using
- // relatively small buffer size in order to minimize memory footprint increase.
- protected final byte[] mBuffer = new byte[1024];
-
- abstract void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs);
-
- protected int parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) {
- // For the details of user_data_type_structure, see ATSC A/53 Part 4 - Table 6.9.
- int pos = offset;
- if (pos + 2 >= buffer.position()) {
- return offset;
- }
- boolean processCcDataFlag = (buffer.get(pos) & 64) != 0;
- int ccCount = buffer.get(pos) & 0x1f;
- pos += 2;
- if (!processCcDataFlag || pos + 3 * ccCount >= buffer.position() || ccCount == 0) {
- return offset;
- }
- SampleHolder holder = mCcSamplePool.acquireSample(CC_BUFFER_SIZE_IN_BYTES);
- for (int i = 0; i < 3 * ccCount; i++) {
- holder.data.put(buffer.get(pos++));
- }
- holder.timeUs = presentationTimeUs;
- mPendingCcSamples.add(holder);
- return pos;
- }
- }
-
- private class Mpeg2CcParser extends CcParser {
- private static final int PATTERN_LENGTH = 9;
-
- @Override
- public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) {
- int totalSize = buffer.position();
- // Reading the frame in bulk to reduce the overhead from ByteBuffer.get() with
- // overlapping to handle the case that the pattern exists in the boundary.
- for (int i = 0; i < totalSize; i += mBuffer.length - PATTERN_LENGTH) {
- buffer.position(i);
- int size = Math.min(totalSize - i, mBuffer.length);
- buffer.get(mBuffer, 0, size);
- int j = 0;
- while (j < size - PATTERN_LENGTH) {
- // Find the start prefix code of private user data.
- if (mBuffer[j] == 0
- && mBuffer[j + 1] == 0
- && mBuffer[j + 2] == 1
- && (mBuffer[j + 3] & 0xff) == 0xb2) {
- // ATSC closed caption data embedded in MPEG2VIDEO stream has 'GA94' user
- // identifier and user data type code 3.
- if (mBuffer[j + 4] == 'G'
- && mBuffer[j + 5] == 'A'
- && mBuffer[j + 6] == '9'
- && mBuffer[j + 7] == '4'
- && mBuffer[j + 8] == 3) {
- j = parseClosedCaption(buffer, i + j + PATTERN_LENGTH,
- presentationTimeUs) - i;
- } else {
- j += PATTERN_LENGTH;
- }
- } else {
- ++j;
- }
- }
- }
- buffer.position(totalSize);
- }
- }
-
- private class H264CcParser extends CcParser {
- private static final int PATTERN_LENGTH = 14;
-
- @Override
- public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) {
- int totalSize = buffer.position();
- // Reading the frame in bulk to reduce the overhead from ByteBuffer.get() with
- // overlapping to handle the case that the pattern exists in the boundary.
- for (int i = 0; i < totalSize; i += mBuffer.length - PATTERN_LENGTH) {
- buffer.position(i);
- int size = Math.min(totalSize - i, mBuffer.length);
- buffer.get(mBuffer, 0, size);
- int j = 0;
- while (j < size - PATTERN_LENGTH) {
- // Find the start prefix code of a NAL Unit.
- if (mBuffer[j] == 0
- && mBuffer[j + 1] == 0
- && mBuffer[j + 2] == 1) {
- int nalType = mBuffer[j + 3] & 0x1f;
- int payloadType = mBuffer[j + 4] & 0xff;
-
- // ATSC closed caption data embedded in H264 private user data has NAL type
- // 6, payload type 4, and 'GA94' user identifier for ATSC.
- if (nalType == 6 && payloadType == 4 && mBuffer[j + 9] == 'G'
- && mBuffer[j + 10] == 'A'
- && mBuffer[j + 11] == '9'
- && mBuffer[j + 12] == '4') {
- j = parseClosedCaption(buffer, i + j + PATTERN_LENGTH,
- presentationTimeUs) - i;
- } else {
- j += 7;
- }
- } else {
- ++j;
- }
- }
- }
- buffer.position(totalSize);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java
deleted file mode 100644
index 6007b0be..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tv.tuner.exoplayer;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.SampleSource.SampleSourceReader;
-import com.google.android.exoplayer.util.Assertions;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/** {@link SampleSource} that extracts sample data using a {@link SampleExtractor}. */
-public final class MpegTsSampleSource implements SampleSource, SampleSourceReader {
-
- private static final int TRACK_STATE_DISABLED = 0;
- private static final int TRACK_STATE_ENABLED = 1;
- private static final int TRACK_STATE_FORMAT_SENT = 2;
-
- private final SampleExtractor mSampleExtractor;
- private final List<Integer> mTrackStates = new ArrayList<>();
- private final List<Boolean> mPendingDiscontinuities = new ArrayList<>();
-
- private boolean mPrepared;
- private IOException mPreparationError;
- private int mRemainingReleaseCount;
-
- private long mLastSeekPositionUs;
- private long mPendingSeekPositionUs;
-
- /**
- * Creates a new sample source that extracts samples using {@code mSampleExtractor}.
- *
- * @param sampleExtractor a sample extractor for accessing media samples
- */
- public MpegTsSampleSource(SampleExtractor sampleExtractor) {
- mSampleExtractor = Assertions.checkNotNull(sampleExtractor);
- }
-
- @Override
- public SampleSourceReader register() {
- mRemainingReleaseCount++;
- return this;
- }
-
- @Override
- public boolean prepare(long positionUs) {
- if (!mPrepared) {
- if (mPreparationError != null) {
- return false;
- }
- try {
- if (mSampleExtractor.prepare()) {
- int trackCount = mSampleExtractor.getTrackFormats().size();
- mTrackStates.clear();
- mPendingDiscontinuities.clear();
- for (int i = 0; i < trackCount; ++i) {
- mTrackStates.add(i, TRACK_STATE_DISABLED);
- mPendingDiscontinuities.add(i, false);
- }
- mPrepared = true;
- } else {
- return false;
- }
- } catch (IOException e) {
- mPreparationError = e;
- return false;
- }
- }
- return true;
- }
-
- @Override
- public int getTrackCount() {
- Assertions.checkState(mPrepared);
- return mSampleExtractor.getTrackFormats().size();
- }
-
- @Override
- public MediaFormat getFormat(int track) {
- Assertions.checkState(mPrepared);
- return mSampleExtractor.getTrackFormats().get(track);
- }
-
- @Override
- public void enable(int track, long positionUs) {
- Assertions.checkState(mPrepared);
- Assertions.checkState(mTrackStates.get(track) == TRACK_STATE_DISABLED);
- mTrackStates.set(track, TRACK_STATE_ENABLED);
- mSampleExtractor.selectTrack(track);
- seekToUsInternal(positionUs, positionUs != 0);
- }
-
- @Override
- public void disable(int track) {
- Assertions.checkState(mPrepared);
- Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
- mSampleExtractor.deselectTrack(track);
- mPendingDiscontinuities.set(track, false);
- mTrackStates.set(track, TRACK_STATE_DISABLED);
- }
-
- @Override
- public boolean continueBuffering(int track, long positionUs) {
- return mSampleExtractor.continueBuffering(positionUs);
- }
-
- @Override
- public long readDiscontinuity(int track) {
- if (mPendingDiscontinuities.get(track)) {
- mPendingDiscontinuities.set(track, false);
- return mLastSeekPositionUs;
- }
- return NO_DISCONTINUITY;
- }
-
- @Override
- public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
- SampleHolder sampleHolder) {
- Assertions.checkState(mPrepared);
- Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
- if (mPendingDiscontinuities.get(track)) {
- return NOTHING_READ;
- }
- if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) {
- mSampleExtractor.getTrackMediaFormat(track, formatHolder);
- mTrackStates.set(track, TRACK_STATE_FORMAT_SENT);
- return FORMAT_READ;
- }
-
- mPendingSeekPositionUs = C.UNKNOWN_TIME_US;
- return mSampleExtractor.readSample(track, sampleHolder);
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- if (mPreparationError != null) {
- throw mPreparationError;
- }
- if (mSampleExtractor != null) {
- mSampleExtractor.maybeThrowError();
- }
- }
-
- @Override
- public void seekToUs(long positionUs) {
- Assertions.checkState(mPrepared);
- seekToUsInternal(positionUs, false);
- }
-
- @Override
- public long getBufferedPositionUs() {
- Assertions.checkState(mPrepared);
- return mSampleExtractor.getBufferedPositionUs();
- }
-
- @Override
- public void release() {
- Assertions.checkState(mRemainingReleaseCount > 0);
- if (--mRemainingReleaseCount == 0) {
- mSampleExtractor.release();
- }
- }
-
- private void seekToUsInternal(long positionUs, boolean force) {
- // Unless forced, avoid duplicate calls to the underlying extractor's seek method
- // in the case that there have been no interleaving calls to readSample.
- if (force || mPendingSeekPositionUs != positionUs) {
- mLastSeekPositionUs = positionUs;
- mPendingSeekPositionUs = positionUs;
- mSampleExtractor.seekTo(positionUs);
- for (int i = 0; i < mTrackStates.size(); ++i) {
- if (mTrackStates.get(i) != TRACK_STATE_DISABLED) {
- mPendingDiscontinuities.set(i, true);
- }
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java
deleted file mode 100644
index 19360c69..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.android.tv.tuner.exoplayer;
-
-import android.content.Context;
-import android.media.MediaCodec;
-import android.os.Handler;
-import android.util.Log;
-
-import com.google.android.exoplayer.DecoderInfo;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.MediaCodecUtil;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.MediaSoftwareCodecUtil;
-import com.google.android.exoplayer.SampleSource;
-import com.android.tv.common.feature.CommonFeatures;
-
-import java.lang.reflect.Field;
-
-/**
- * MPEG-2 TS video track renderer
- */
-public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer {
- private static final String TAG = "MpegTsVideoTrackRender";
-
- private static final int VIDEO_PLAYBACK_DEADLINE_IN_MS = 5000;
- // If DROPPED_FRAMES_NOTIFICATION_THRESHOLD frames are consecutively dropped, it'll be notified.
- private static final int DROPPED_FRAMES_NOTIFICATION_THRESHOLD = 10;
- private static final int MIN_HD_HEIGHT = 720;
- private static final String MIMETYPE_MPEG2 = "video/mpeg2";
- private static Field sRenderedFirstFrameField;
-
- private final boolean mIsSwCodecEnabled;
- private boolean mCodecIsSwPreferred;
- private boolean mSetRenderedFirstFrame;
-
- static {
- // Remove the reflection below once b/31223646 is resolved.
- try {
- sRenderedFirstFrameField = MediaCodecVideoTrackRenderer.class.getDeclaredField(
- "renderedFirstFrame");
- sRenderedFirstFrameField.setAccessible(true);
- } catch (NoSuchFieldException e) {
- // Null-checking for {@code sRenderedFirstFrameField} will do the error handling.
- }
- }
-
- public MpegTsVideoTrackRenderer(Context context, SampleSource source, Handler handler,
- MediaCodecVideoTrackRenderer.EventListener listener) {
- super(context, source, MediaCodecSelector.DEFAULT,
- MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_PLAYBACK_DEADLINE_IN_MS, handler,
- listener, DROPPED_FRAMES_NOTIFICATION_THRESHOLD);
- mIsSwCodecEnabled = CommonFeatures.USE_SW_CODEC_FOR_SD.isEnabled(context);
- }
-
- @Override
- protected DecoderInfo getDecoderInfo(MediaCodecSelector codecSelector, String mimeType,
- boolean requiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException {
- try {
- if (mIsSwCodecEnabled && mCodecIsSwPreferred) {
- DecoderInfo swCodec = MediaSoftwareCodecUtil.getSoftwareDecoderInfo(
- mimeType, requiresSecureDecoder);
- if (swCodec != null) {
- return swCodec;
- }
- }
- } catch (MediaSoftwareCodecUtil.DecoderQueryException e) {
- }
- return super.getDecoderInfo(codecSelector, mimeType,requiresSecureDecoder);
- }
-
- @Override
- protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException {
- mCodecIsSwPreferred = MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType)
- && holder.format.height < MIN_HD_HEIGHT;
- super.onInputFormatChanged(holder);
- }
-
- @Override
- protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
- super.onDiscontinuity(positionUs);
- // Disabling pre-rendering of the first frame in order to avoid a frozen picture when
- // starting the playback. We do this only once, when the renderer is enabled at first, since
- // we need to pre-render the frame in advance when we do trickplay backed by seeking.
- if (!mSetRenderedFirstFrame) {
- setRenderedFirstFrame(true);
- mSetRenderedFirstFrame = true;
- }
- }
-
- private void setRenderedFirstFrame(boolean renderedFirstFrame) {
- if (sRenderedFirstFrameField != null) {
- try {
- sRenderedFirstFrameField.setBoolean(this, renderedFirstFrame);
- } catch (IllegalAccessException e) {
- Log.w(TAG, "renderedFirstFrame is not accessible. Playback may start with a frozen"
- +" picture.");
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java b/src/com/android/tv/tuner/exoplayer/SampleExtractor.java
deleted file mode 100644
index 543588c7..00000000
--- a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tv.tuner.exoplayer;
-
-import android.os.Handler;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Extractor for reading track metadata and samples stored in tracks.
- *
- * <p>Call {@link #prepare} until it returns {@code true}, then access track metadata via
- * {@link #getTrackFormats} and {@link #getTrackMediaFormat}.
- *
- * <p>Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected
- * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample
- * data or seeking. Initially, all tracks are deselected.
- *
- * <p>Call {@link #release()} when the extractor is no longer needed to free resources.
- */
-public interface SampleExtractor {
-
- /**
- * If the extractor is currently having difficulty preparing or loading samples, then this
- * method throws the underlying error. Otherwise does nothing.
- *
- * @throws IOException The underlying error.
- */
- void maybeThrowError() throws IOException;
-
- /**
- * Prepares the extractor for reading track metadata and samples.
- *
- * @return whether the source is ready; if {@code false}, this method must be called again.
- * @throws IOException thrown if the source can't be read
- */
- boolean prepare() throws IOException;
-
- /** Returns track information about all tracks that can be selected. */
- List<MediaFormat> getTrackFormats();
-
- /** Selects the track at {@code index} for reading sample data. */
- void selectTrack(int index);
-
- /** Deselects the track at {@code index}, so no more samples will be read from that track. */
- void deselectTrack(int index);
-
- /**
- * Returns an estimate of the position up to which data is buffered.
- *
- * <p>This method should not be called until after the extractor has been successfully prepared.
- *
- * @return an estimate of the absolute position in microseconds up to which data is buffered,
- * or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or
- * {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available.
- */
- long getBufferedPositionUs();
-
- /**
- * Seeks to the specified time in microseconds.
- *
- * <p>This method should not be called until after the extractor has been successfully prepared.
- *
- * @param positionUs the seek position in microseconds
- */
- void seekTo(long positionUs);
-
- /** Stores the {@link MediaFormat} of {@code track}. */
- void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder);
-
- /**
- * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, returning
- * {@link SampleSource#SAMPLE_READ} if it is available.
- *
- * <p>Advances to the next sample if a sample was read.
- *
- * @param track the index of the track from which to read a sample
- * @param sampleHolder the holder for read sample data, if {@link SampleSource#SAMPLE_READ} is
- * returned
- * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or
- * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or
- * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not
- * loaded.
- */
- int readSample(int track, SampleHolder sampleHolder);
-
- /** Releases resources associated with this extractor. */
- void release();
-
- /** Indicates to the source that it should still be buffering data. */
- boolean continueBuffering(long positionUs);
-
- /**
- * Sets OnCompletionListener for notifying the completion of SampleExtractor.
- *
- * @param listener the OnCompletionListener
- * @param handler the {@link Handler} for {@link Handler#post(Runnable)} of OnCompletionListener
- */
- void setOnCompletionListener(OnCompletionListener listener, Handler handler);
-
- /**
- * The listener for SampleExtractor being completed.
- */
- interface OnCompletionListener {
-
- /**
- * Called when sample extraction is completed.
- *
- * @param result {@code true} when the extractor is finished without an error,
- * {@code false} otherwise (storage error, weak signal, being reached at EoS
- * prematurely, etc.)
- * @param lastExtractedPositionUs the last extracted position when extractor is completed
- */
- void onCompletion(boolean result, long lastExtractedPositionUs);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java
deleted file mode 100644
index 5666c5b9..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import com.android.tv.common.SoftPreconditions;
-
-import android.os.SystemClock;
-
-/**
- * Copy of {@link com.google.android.exoplayer.MediaClock}.
- * <p>
- * A simple clock for tracking the progression of media time. The clock can be started, stopped and
- * its time can be set and retrieved. When started, this clock is based on
- * {@link SystemClock#elapsedRealtime()}.
- */
-/* package */ class AudioClock {
- private boolean mStarted;
-
- /**
- * The media time when the clock was last set or stopped.
- */
- private long mPositionUs;
-
- /**
- * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs}
- * when the clock was last set or mStarted.
- */
- private long mDeltaUs;
-
- private float mPlaybackSpeed = 1.0f;
- private long mDeltaUpdatedTimeUs;
-
- /**
- * Starts the clock. Does nothing if the clock is already started.
- */
- public void start() {
- if (!mStarted) {
- mStarted = true;
- mDeltaUs = elapsedRealtimeMinus(mPositionUs);
- mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000;
- }
- }
-
- /**
- * Stops the clock. Does nothing if the clock is already stopped.
- */
- public void stop() {
- if (mStarted) {
- mPositionUs = elapsedRealtimeMinus(mDeltaUs);
- mStarted = false;
- }
- }
-
- /**
- * @param timeUs The position to set in microseconds.
- */
- public void setPositionUs(long timeUs) {
- this.mPositionUs = timeUs;
- mDeltaUs = elapsedRealtimeMinus(timeUs);
- mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000;
- }
-
- /**
- * @return The current position in microseconds.
- */
- public long getPositionUs() {
- if (!mStarted) {
- return mPositionUs;
- }
- if (mPlaybackSpeed != 1.0f) {
- long elapsedTimeFromPlaybackSpeedChanged = SystemClock.elapsedRealtime() * 1000
- - mDeltaUpdatedTimeUs;
- return elapsedRealtimeMinus(mDeltaUs)
- + (long) ((mPlaybackSpeed - 1.0f) * elapsedTimeFromPlaybackSpeedChanged);
- } else {
- return elapsedRealtimeMinus(mDeltaUs);
- }
- }
-
- /**
- * Sets playback speed. {@code speed} should be positive.
- */
- public void setPlaybackSpeed(float speed) {
- SoftPreconditions.checkState(speed > 0);
- mDeltaUs = elapsedRealtimeMinus(getPositionUs());
- mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000;
- mPlaybackSpeed = speed;
- }
-
- private long elapsedRealtimeMinus(long toSubtractUs) {
- return SystemClock.elapsedRealtime() * 1000 - toSubtractUs;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java
deleted file mode 100644
index e581092a..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-
-import java.nio.ByteBuffer;
-
-/** A base class for audio decoders. */
-public abstract class AudioDecoder {
-
- /**
- * Decodes an audio sample.
- *
- * @param sampleHolder a holder that contains the sample data and corresponding metadata
- */
- public abstract void decode(SampleHolder sampleHolder);
-
- /** Returns a decoded sample from decoder. */
- public abstract ByteBuffer getDecodedSample();
-
- /** Returns the presentation time for the decoded sample. */
- public abstract long getDecodedTimeUs();
-
- /**
- * Clear previous decode state if any. Prepares to decode samples of the specified encoding.
- * This method should be called before using decode.
- *
- * @param mime audio encoding
- */
- public abstract void resetDecoderState(String mimeType);
-
- /** Releases all the resource. */
- public abstract void release();
-
- /**
- * Init decoder if needed.
- *
- * @param format the format used to initialize decoder
- */
- public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException {
- // Do nothing.
- }
-
- /** Returns input buffer that will be used in decoder. */
- public ByteBuffer getInputBuffer() {
- return null;
- }
-
- /** Returns the output format. */
- public android.media.MediaFormat getOutputFormat() {
- return null;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java
deleted file mode 100644
index ec616b13..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.util.MimeTypes;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Monitors the rendering position of {@link AudioTrack}.
- */
-public class AudioTrackMonitor {
- private static final String TAG = "AudioTrackMonitor";
- private static final boolean DEBUG = false;
-
- // For fetched audio samples
- private final ArrayList<Pair<Long, Integer>> mPtsList = new ArrayList<>();
- private final Set<Integer> mSampleSize = new HashSet<>();
- private final Set<Integer> mCurSampleSize = new HashSet<>();
- private final Set<Integer> mHeader = new HashSet<>();
-
- private long mExpireMs;
- private long mDuration;
- private long mSampleCount;
- private long mTotalCount;
- private long mStartMs;
-
- private boolean mIsMp2;
-
- private void flush() {
- mExpireMs += mDuration;
- mSampleCount = 0;
- mCurSampleSize.clear();
- mPtsList.clear();
- }
-
- /**
- * Resets and initializes {@link AudioTrackMonitor}.
- *
- * @param duration the frequency of monitoring in milliseconds
- */
- public void reset(long duration) {
- mExpireMs = SystemClock.elapsedRealtime();
- mDuration = duration;
- mTotalCount = 0;
- mStartMs = 0;
- mSampleSize.clear();
- mHeader.clear();
- flush();
- }
-
- public void setEncoding(String mime) {
- mIsMp2 = MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mime);
- }
-
- /**
- * Adds an audio sample information for monitoring.
- *
- * @param pts the presentation timestamp of the sample
- * @param sampleSize the size in bytes of the sample
- * @param header the bitrate &amp; sampling information header of the sample
- */
- public void addPts(long pts, int sampleSize, int header) {
- mTotalCount++;
- mSampleCount++;
- mSampleSize.add(sampleSize);
- mHeader.add(header);
- mCurSampleSize.add(sampleSize);
- if (mTotalCount == 1) {
- mStartMs = SystemClock.elapsedRealtime();
- }
- if (mPtsList.isEmpty() || mPtsList.get(mPtsList.size() - 1).first != pts) {
- mPtsList.add(Pair.create(pts, 1));
- return;
- }
- Pair<Long, Integer> pair = mPtsList.get(mPtsList.size() - 1);
- mPtsList.set(mPtsList.size() - 1, Pair.create(pair.first, pair.second + 1));
- }
-
- /**
- * Logs if interested events are present.
- * <p>
- * Periodic logging is not enabled in release mode in order to avoid verbose logging.
- */
- public void maybeLog() {
- long now = SystemClock.elapsedRealtime();
- if (mExpireMs != 0 && now >= mExpireMs) {
- if (DEBUG) {
- long unitDuration = mIsMp2 ? MpegTsDefaultAudioTrackRenderer.MP2_SAMPLE_DURATION_US
- : MpegTsDefaultAudioTrackRenderer.AC3_SAMPLE_DURATION_US;
- long sampleDuration = (mTotalCount - 1) * unitDuration / 1000;
- long totalDuration = now - mStartMs;
- StringBuilder ptsBuilder = new StringBuilder();
- ptsBuilder.append("PTS received ").append(mSampleCount).append(", ")
- .append(totalDuration - sampleDuration).append(' ');
-
- for (Pair<Long, Integer> pair : mPtsList) {
- ptsBuilder.append('[').append(pair.first).append(':').append(pair.second)
- .append("], ");
- }
- Log.d(TAG, ptsBuilder.toString());
- }
- if (DEBUG || mCurSampleSize.size() > 1) {
- Log.d(TAG, "PTS received sample size: "
- + String.valueOf(mSampleSize) + mCurSampleSize + mHeader);
- }
- flush();
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java
deleted file mode 100644
index 953c9fc4..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import android.media.MediaFormat;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.audio.AudioTrack;
-
-import java.nio.ByteBuffer;
-
-/**
- * {@link AudioTrack} wrapper class for trickplay operations including FF/RW.
- * FF/RW trickplay operations do not need framework {@link AudioTrack}.
- * This wrapper class will do nothing in disabled status for those operations.
- */
-public class AudioTrackWrapper {
- private static final int PCM16_FRAME_BYTES = 2;
- private static final int AC3_FRAMES_IN_ONE_SAMPLE = 1536;
- private static final int BUFFERED_SAMPLES_IN_AUDIOTRACK =
- MpegTsDefaultAudioTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK;
- private final AudioTrack mAudioTrack = new AudioTrack();
- private int mAudioSessionID;
- private boolean mIsEnabled;
-
- AudioTrackWrapper() {
- mIsEnabled = true;
- }
-
- public void resetSessionId() {
- mAudioSessionID = AudioTrack.SESSION_ID_NOT_SET;
- }
-
- public boolean isInitialized() {
- return mIsEnabled && mAudioTrack.isInitialized();
- }
-
- public void restart() {
- if (mAudioTrack.isInitialized()) {
- mAudioTrack.release();
- }
- mIsEnabled = true;
- resetSessionId();
- }
-
- public void release() {
- if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) {
- mAudioTrack.release();
- }
- }
-
- public void initialize() throws AudioTrack.InitializationException {
- if (!mIsEnabled) {
- return;
- }
- if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) {
- mAudioTrack.initialize(mAudioSessionID);
- } else {
- mAudioSessionID = mAudioTrack.initialize();
- }
- }
-
- public void reset() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.reset();
- }
-
- public boolean isEnded() {
- return !mIsEnabled || !mAudioTrack.hasPendingData();
- }
-
- public boolean isReady() {
- // In the case of not playing actual audio data, Audio track is always ready.
- return !mIsEnabled || mAudioTrack.hasPendingData();
- }
-
- public void play() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.play();
- }
-
- public void pause() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.pause();
- }
-
- public void setVolume(float volume) {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.setVolume(volume);
- }
-
- public void reconfigure(MediaFormat format, int audioBufferSize) {
- if (!mIsEnabled || format == null) {
- return;
- }
- String mimeType = format.getString(MediaFormat.KEY_MIME);
- int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
- int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
- int pcmEncoding;
- try {
- pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING);
- } catch (Exception e) {
- pcmEncoding = C.ENCODING_PCM_16BIT;
- }
- // TODO: Handle non-AC3.
- if (MediaFormat.MIMETYPE_AUDIO_AC3.equalsIgnoreCase(mimeType) && channelCount != 2) {
- // Workarounds b/25955476.
- // Since all devices and platforms does not support passthrough for non-stereo AC3,
- // It is safe to fake non-stereo AC3 as AC3 stereo which is default passthrough mode.
- // In other words, the channel count should be always 2.
- channelCount = 2;
- }
- if (MediaFormat.MIMETYPE_AUDIO_RAW.equalsIgnoreCase(mimeType)) {
- audioBufferSize =
- channelCount
- * PCM16_FRAME_BYTES
- * AC3_FRAMES_IN_ONE_SAMPLE
- * BUFFERED_SAMPLES_IN_AUDIOTRACK;
- }
- mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, audioBufferSize);
- }
-
- public void handleDiscontinuity() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.handleDiscontinuity();
- }
-
- public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs)
- throws AudioTrack.WriteException {
- if (!mIsEnabled) {
- return AudioTrack.RESULT_BUFFER_CONSUMED;
- }
- return mAudioTrack.handleBuffer(buffer, offset, size, presentationTimeUs);
- }
-
- public void setStatus(boolean enable) {
- if (enable == mIsEnabled) {
- return;
- }
- mAudioTrack.reset();
- mIsEnabled = enable;
- }
-
- public boolean isEnabled() {
- return mIsEnabled;
- }
-
- // This should be used only in case of being enabled.
- public long getCurrentPositionUs(boolean isEnded) {
- return mAudioTrack.getCurrentPositionUs(isEnded);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java
deleted file mode 100644
index 72bc68b6..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import android.media.MediaCodec;
-import android.util.Log;
-
-import com.google.android.exoplayer.CodecCounters;
-import com.google.android.exoplayer.DecoderInfo;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.MediaCodecUtil;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/** A decoder to use MediaCodec for decoding audio stream. */
-public class MediaCodecAudioDecoder extends AudioDecoder {
- private static final String TAG = "MediaCodecAudioDecoder";
-
- public static final int INDEX_INVALID = -1;
-
- private final CodecCounters mCodecCounters;
- private final MediaCodecSelector mSelector;
-
- private MediaCodec mCodec;
- private MediaCodec.BufferInfo mOutputBufferInfo;
- private ByteBuffer mMediaCodecOutputBuffer;
- private ArrayList<Long> mDecodeOnlyPresentationTimestamps;
- private boolean mWaitingForFirstSyncFrame;
- private boolean mIsNewIndex;
- private int mInputIndex;
- private int mOutputIndex;
-
- /** Creates a MediaCodec based audio decoder. */
- public MediaCodecAudioDecoder(MediaCodecSelector selector) {
- mSelector = selector;
- mOutputBufferInfo = new MediaCodec.BufferInfo();
- mCodecCounters = new CodecCounters();
- mDecodeOnlyPresentationTimestamps = new ArrayList<>();
- }
-
- /** Returns {@code true} if there is decoder for {@code mimeType}. */
- public static boolean supportMimeType(MediaCodecSelector selector, String mimeType) {
- if (selector == null) {
- return false;
- }
- return getDecoderInfo(selector, mimeType) != null;
- }
-
- private static DecoderInfo getDecoderInfo(MediaCodecSelector selector, String mimeType) {
- try {
- return selector.getDecoderInfo(mimeType, false);
- } catch (MediaCodecUtil.DecoderQueryException e) {
- Log.e(TAG, "Select decoder error:" + e);
- return null;
- }
- }
-
- private boolean shouldInitCodec(MediaFormat format) {
- return format != null && mCodec == null;
- }
-
- @Override
- public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException {
- if (!shouldInitCodec(format)) {
- return;
- }
-
- String mimeType = format.mimeType;
- DecoderInfo decoderInfo = getDecoderInfo(mSelector, mimeType);
- if (decoderInfo == null) {
- Log.i(TAG, "There is not decoder found for " + mimeType);
- return;
- }
-
- String codecName = decoderInfo.name;
- try {
- mCodec = MediaCodec.createByCodecName(codecName);
- mCodec.configure(format.getFrameworkMediaFormatV16(), null, null, 0);
- mCodec.start();
- } catch (Exception e) {
- Log.e(TAG, "Failed when configure or start codec:" + e);
- throw new ExoPlaybackException(e);
- }
- mInputIndex = INDEX_INVALID;
- mOutputIndex = INDEX_INVALID;
- mWaitingForFirstSyncFrame = true;
- mCodecCounters.codecInitCount++;
- }
-
- @Override
- public void resetDecoderState(String mimeType) {
- if (mCodec == null) {
- return;
- }
- mInputIndex = INDEX_INVALID;
- mOutputIndex = INDEX_INVALID;
- mDecodeOnlyPresentationTimestamps.clear();
- mCodec.flush();
- mWaitingForFirstSyncFrame = true;
- }
-
- @Override
- public void release() {
- if (mCodec != null) {
- mDecodeOnlyPresentationTimestamps.clear();
- mInputIndex = INDEX_INVALID;
- mOutputIndex = INDEX_INVALID;
- mCodecCounters.codecReleaseCount++;
- try {
- mCodec.stop();
- } finally {
- try {
- mCodec.release();
- } finally {
- mCodec = null;
- }
- }
- }
- }
-
- /** Returns the index of input buffer which is ready for using. */
- public int getInputIndex() {
- return mInputIndex;
- }
-
- @Override
- public ByteBuffer getInputBuffer() {
- if (mInputIndex < 0) {
- mInputIndex = mCodec.dequeueInputBuffer(0);
- if (mInputIndex < 0) {
- return null;
- }
- return mCodec.getInputBuffer(mInputIndex);
- }
- return mCodec.getInputBuffer(mInputIndex);
- }
-
- @Override
- public void decode(SampleHolder sampleHolder) {
- if (mWaitingForFirstSyncFrame) {
- if (!sampleHolder.isSyncFrame()) {
- sampleHolder.clearData();
- return;
- }
- mWaitingForFirstSyncFrame = false;
- }
- long presentationTimeUs = sampleHolder.timeUs;
- if (sampleHolder.isDecodeOnly()) {
- mDecodeOnlyPresentationTimestamps.add(presentationTimeUs);
- }
- mCodec.queueInputBuffer(mInputIndex, 0, sampleHolder.data.limit(), presentationTimeUs, 0);
- mInputIndex = INDEX_INVALID;
- mCodecCounters.inputBufferCount++;
- }
-
- private int getDecodeOnlyIndex(long presentationTimeUs) {
- final int size = mDecodeOnlyPresentationTimestamps.size();
- for (int i = 0; i < size; i++) {
- if (mDecodeOnlyPresentationTimestamps.get(i).longValue() == presentationTimeUs) {
- return i;
- }
- }
- return INDEX_INVALID;
- }
-
- /** Returns the index of output buffer which is ready for using. */
- public int getOutputIndex() {
- if (mOutputIndex < 0) {
- mOutputIndex = mCodec.dequeueOutputBuffer(mOutputBufferInfo, 0);
- mIsNewIndex = true;
- } else {
- mIsNewIndex = false;
- }
- return mOutputIndex;
- }
-
- @Override
- public android.media.MediaFormat getOutputFormat() {
- return mCodec.getOutputFormat();
- }
-
- /** Returns {@code true} if the output is only for decoding but not for rendering. */
- public boolean maybeDecodeOnlyIndex() {
- int decodeOnlyIndex = getDecodeOnlyIndex(mOutputBufferInfo.presentationTimeUs);
- if (decodeOnlyIndex != INDEX_INVALID) {
- mCodec.releaseOutputBuffer(mOutputIndex, false);
- mCodecCounters.skippedOutputBufferCount++;
- mDecodeOnlyPresentationTimestamps.remove(decodeOnlyIndex);
- mOutputIndex = INDEX_INVALID;
- return true;
- }
- return false;
- }
-
- @Override
- public ByteBuffer getDecodedSample() {
- if (maybeDecodeOnlyIndex() || mOutputIndex < 0) {
- return null;
- }
- if (mIsNewIndex) {
- mMediaCodecOutputBuffer = mCodec.getOutputBuffer(mOutputIndex);
- }
- return mMediaCodecOutputBuffer;
- }
-
- @Override
- public long getDecodedTimeUs() {
- return mOutputBufferInfo.presentationTimeUs;
- }
-
- /** Releases the output buffer after rendering. */
- public void releaseOutputBuffer() {
- mCodecCounters.renderedOutputBufferCount++;
- mCodec.releaseOutputBuffer(mOutputIndex, false);
- mOutputIndex = INDEX_INVALID;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
deleted file mode 100644
index 77170419..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
+++ /dev/null
@@ -1,735 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import android.media.MediaCodec;
-import android.os.Build;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.google.android.exoplayer.CodecCounters;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaClock;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.util.Assertions;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient;
-import com.android.tv.tuner.tvinput.TunerDebug;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/**
- * Decodes and renders DTV audio. Supports MediaCodec based decoding, passthrough playback and
- * ffmpeg based software decoding (AC3, MP2).
- */
-public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements MediaClock {
- public static final int MSG_SET_VOLUME = 10000;
- public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1;
- public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2;
-
- // ATSC/53 allows sample rate to be only 48Khz.
- // One AC3 sample has 1536 frames, and its duration is 32ms.
- public static final long AC3_SAMPLE_DURATION_US = 32000;
-
- // TODO: Check whether DVB broadcasting uses sample rate other than 48Khz.
- // MPEG-1 audio Layer II and III has 1152 frames per sample.
- // 1152 frames duration is 24ms when sample rate is 48Khz.
- static final long MP2_SAMPLE_DURATION_US = 24000;
-
- // This is around 150ms, 150ms is big enough not to under-run AudioTrack,
- // and 150ms is also small enough to fill the buffer rapidly.
- static int BUFFERED_SAMPLES_IN_AUDIOTRACK = 5;
- public static final long INITIAL_AUDIO_BUFFERING_TIME_US =
- BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US;
-
-
- private static final String TAG = "MpegTsDefaultAudioTrac";
- private static final boolean DEBUG = false;
-
- /**
- * Interface definition for a callback to be notified of
- * {@link com.google.android.exoplayer.audio.AudioTrack} error.
- */
- public interface EventListener {
- void onAudioTrackInitializationError(AudioTrack.InitializationException e);
- void onAudioTrackWriteError(AudioTrack.WriteException e);
- }
-
- private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2;
- private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024;
- private static final int MONITOR_DURATION_MS = 1000;
- private static final int AC3_HEADER_BITRATE_OFFSET = 4;
- private static final int MP2_HEADER_BITRATE_OFFSET = 2;
- private static final int MP2_HEADER_BITRATE_MASK = 0xfc;
-
- // Keep this as static in order to prevent new framework AudioTrack creation
- // while old AudioTrack is being released.
- private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper();
- private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000;
-
- // Ignore AudioTrack backward movement if duration of movement is below the threshold.
- private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000;
-
- // AudioTrack position cannot go ahead beyond this limit.
- private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000;
-
- // Since MediaCodec processing and AudioTrack playing add delay,
- // PTS interpolated time should be delayed reasonably when AudioTrack is not used.
- private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000;
-
- private final MediaCodecSelector mSelector;
-
- private final CodecCounters mCodecCounters;
- private final SampleSource.SampleSourceReader mSource;
- private final MediaFormatHolder mFormatHolder;
- private final EventListener mEventListener;
- private final Handler mEventHandler;
- private final AudioTrackMonitor mMonitor;
- private final AudioClock mAudioClock;
- private final boolean mAc3Passthrough;
- private final boolean mSoftwareDecoderAvailable;
-
- private MediaFormat mFormat;
- private SampleHolder mSampleHolder;
- private String mDecodingMime;
- private boolean mFormatConfigured;
- private int mSampleSize;
- private final ByteBuffer mOutputBuffer;
- private AudioDecoder mAudioDecoder;
- private boolean mOutputReady;
- private int mTrackIndex;
- private boolean mSourceStateReady;
- private boolean mInputStreamEnded;
- private boolean mOutputStreamEnded;
- private long mEndOfStreamMs;
- private long mCurrentPositionUs;
- private int mPresentationCount;
- private long mPresentationTimeUs;
- private long mInterpolatedTimeUs;
- private long mPreviousPositionUs;
- private boolean mIsStopped;
- private boolean mEnabled = true;
- private boolean mIsMuted;
- private ArrayList<Integer> mTracksIndex;
- private boolean mUseFrameworkDecoder;
-
- public MpegTsDefaultAudioTrackRenderer(
- SampleSource source,
- MediaCodecSelector selector,
- Handler eventHandler,
- EventListener listener,
- boolean hasSoftwareAudioDecoder,
- boolean usePassthrough) {
- mSource = source.register();
- mSelector = selector;
- mEventHandler = eventHandler;
- mEventListener = listener;
- mTrackIndex = -1;
- mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE);
- mFormatHolder = new MediaFormatHolder();
- AUDIO_TRACK.restart();
- mCodecCounters = new CodecCounters();
- mMonitor = new AudioTrackMonitor();
- mAudioClock = new AudioClock();
- mTracksIndex = new ArrayList<>();
- mAc3Passthrough = usePassthrough;
- mSoftwareDecoderAvailable = hasSoftwareAudioDecoder && FfmpegDecoderClient.isAvailable();
- }
-
- @Override
- protected MediaClock getMediaClock() {
- return this;
- }
-
- private boolean handlesMimeType(String mimeType) {
- return mimeType.equals(MimeTypes.AUDIO_AC3)
- || mimeType.equals(MimeTypes.AUDIO_E_AC3)
- || mimeType.equals(MimeTypes.AUDIO_MPEG_L2)
- || MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType);
- }
-
- @Override
- protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
- boolean sourcePrepared = mSource.prepare(positionUs);
- if (!sourcePrepared) {
- return false;
- }
- for (int i = 0; i < mSource.getTrackCount(); i++) {
- String mimeType = mSource.getFormat(i).mimeType;
- if (MimeTypes.isAudio(mimeType) && handlesMimeType(mimeType)) {
- if (mTrackIndex < 0) {
- mTrackIndex = i;
- }
- mTracksIndex.add(i);
- }
- }
-
- // TODO: Check this case. Source does not have the proper mime type.
- return true;
- }
-
- @Override
- protected int getTrackCount() {
- return mTracksIndex.size();
- }
-
- @Override
- protected MediaFormat getFormat(int track) {
- Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
- return mSource.getFormat(mTracksIndex.get(track));
- }
-
- @Override
- protected void onEnabled(int track, long positionUs, boolean joining) {
- Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
- mTrackIndex = mTracksIndex.get(track);
- mSource.enable(mTrackIndex, positionUs);
- seekToInternal(positionUs);
- }
-
- @Override
- protected void onDisabled() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- AUDIO_TRACK.resetSessionId();
- }
- clearDecodeState();
- mFormat = null;
- mSource.disable(mTrackIndex);
- }
-
- @Override
- protected void onReleased() {
- releaseDecoder();
- AUDIO_TRACK.release();
- mSource.release();
- }
-
- @Override
- protected boolean isEnded() {
- return mOutputStreamEnded && AUDIO_TRACK.isEnded();
- }
-
- @Override
- protected boolean isReady() {
- return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady));
- }
-
- private void seekToInternal(long positionUs) {
- mMonitor.reset(MONITOR_DURATION_MS);
- mSourceStateReady = false;
- mInputStreamEnded = false;
- mOutputStreamEnded = false;
- mPresentationTimeUs = positionUs;
- mPresentationCount = 0;
- mPreviousPositionUs = 0;
- mCurrentPositionUs = Long.MIN_VALUE;
- mInterpolatedTimeUs = Long.MIN_VALUE;
- mAudioClock.setPositionUs(positionUs);
- }
-
- @Override
- protected void seekTo(long positionUs) {
- mSource.seekToUs(positionUs);
- AUDIO_TRACK.reset();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- // resetSessionId() will create a new framework AudioTrack instead of reusing old one.
- AUDIO_TRACK.resetSessionId();
- }
- seekToInternal(positionUs);
- clearDecodeState();
- }
-
- @Override
- protected void onStarted() {
- AUDIO_TRACK.play();
- mAudioClock.start();
- mIsStopped = false;
- }
-
- @Override
- protected void onStopped() {
- AUDIO_TRACK.pause();
- mAudioClock.stop();
- mIsStopped = true;
- }
-
- @Override
- protected void maybeThrowError() throws ExoPlaybackException {
- try {
- mSource.maybeThrowError();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- @Override
- protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- mMonitor.maybeLog();
- try {
- if (mEndOfStreamMs != 0) {
- // Ensure playback stops, after EoS was notified.
- // Sometimes MediaCodecTrackRenderer does not fetch EoS timely
- // after EoS was notified here long before.
- long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs;
- if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) {
- throw new ExoPlaybackException("Much time has elapsed after EoS");
- }
- }
- boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs);
- if (mSourceStateReady != continueBuffering) {
- mSourceStateReady = continueBuffering;
- if (DEBUG) {
- Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady));
- }
- }
- long discontinuity = mSource.readDiscontinuity(mTrackIndex);
- if (discontinuity != SampleSource.NO_DISCONTINUITY) {
- AUDIO_TRACK.handleDiscontinuity();
- mPresentationTimeUs = discontinuity;
- mPresentationCount = 0;
- clearDecodeState();
- return;
- }
- if (mFormat == null) {
- readFormat();
- return;
- }
-
- if (mAudioDecoder != null) {
- mAudioDecoder.maybeInitDecoder(mFormat);
- }
- // Process only one sample at a time for doSomeWork() when using FFmpeg decoder.
- if (processOutput()) {
- if (!mOutputReady) {
- while (feedInputBuffer()) {
- if (mOutputReady) break;
- }
- }
- }
- mCodecCounters.ensureUpdated();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- private void ensureAudioTrackInitialized() {
- if (!AUDIO_TRACK.isInitialized()) {
- try {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack initialized");
- }
- AUDIO_TRACK.initialize();
- } catch (AudioTrack.InitializationException e) {
- Log.e(TAG, "Error on AudioTrack initialization", e);
- notifyAudioTrackInitializationError(e);
-
- // Do not throw exception here but just disabling audioTrack to keep playing
- // video without audio.
- AUDIO_TRACK.setStatus(false);
- }
- if (getState() == TrackRenderer.STATE_STARTED) {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack played");
- }
- AUDIO_TRACK.play();
- }
- }
- }
-
- private void clearDecodeState() {
- mOutputReady = false;
- if (mAudioDecoder != null) {
- mAudioDecoder.resetDecoderState(mDecodingMime);
- }
- AUDIO_TRACK.reset();
- }
-
- private void releaseDecoder() {
- if (mAudioDecoder != null) {
- mAudioDecoder.release();
- }
- }
-
- private void readFormat() throws IOException, ExoPlaybackException {
- int result = mSource.readData(mTrackIndex, mCurrentPositionUs,
- mFormatHolder, mSampleHolder);
- if (result == SampleSource.FORMAT_READ) {
- onInputFormatChanged(mFormatHolder);
- }
- }
-
- private MediaFormat convertMediaFormatToRaw(MediaFormat format) {
- return MediaFormat.createAudioFormat(
- format.trackId,
- MimeTypes.AUDIO_RAW,
- format.bitrate,
- format.maxInputSize,
- format.durationUs,
- format.channelCount,
- format.sampleRate,
- format.initializationData,
- format.language);
- }
-
- private void onInputFormatChanged(MediaFormatHolder formatHolder)
- throws ExoPlaybackException {
- String mimeType = formatHolder.format.mimeType;
- mUseFrameworkDecoder = MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType);
- if (mUseFrameworkDecoder) {
- mAudioDecoder = new MediaCodecAudioDecoder(mSelector);
- mFormat = formatHolder.format;
- mAudioDecoder.maybeInitDecoder(mFormat);
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
- } else if (mSoftwareDecoderAvailable
- && (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mimeType)
- || MimeTypes.AUDIO_AC3.equalsIgnoreCase(mimeType) && !mAc3Passthrough)) {
- releaseDecoder();
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mAudioDecoder = FfmpegDecoderClient.getInstance();
- mDecodingMime = mimeType;
- mFormat = convertMediaFormatToRaw(formatHolder.format);
- } else {
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mFormat = formatHolder.format;
- releaseDecoder();
- }
- mFormatConfigured = true;
- mMonitor.setEncoding(mimeType);
- if (DEBUG && !mUseFrameworkDecoder) {
- Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString());
- }
- clearDecodeState();
- if (!mUseFrameworkDecoder) {
- AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), 0);
- }
- }
-
- private void onSampleSizeChanged(int sampleSize) {
- if (DEBUG) {
- Log.d(TAG, "Sample size was changed to : " + sampleSize);
- }
- clearDecodeState();
- int audioBufferSize = sampleSize * BUFFERED_SAMPLES_IN_AUDIOTRACK;
- mSampleSize = sampleSize;
- AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), audioBufferSize);
- }
-
- private void onOutputFormatChanged(android.media.MediaFormat format) {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack was configured to FORMAT: " + format.toString());
- }
- AUDIO_TRACK.reconfigure(format, 0);
- }
-
- private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
- if (mInputStreamEnded) {
- return false;
- }
-
- if (mUseFrameworkDecoder) {
- boolean indexChanged =
- ((MediaCodecAudioDecoder) mAudioDecoder).getInputIndex()
- == MediaCodecAudioDecoder.INDEX_INVALID;
- if (indexChanged) {
- mSampleHolder.data = mAudioDecoder.getInputBuffer();
- if (mSampleHolder.data != null) {
- mSampleHolder.clearData();
- } else {
- return false;
- }
- }
- } else {
- mSampleHolder.data.clear();
- mSampleHolder.size = 0;
- }
- int result =
- mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder);
- switch (result) {
- case SampleSource.NOTHING_READ: {
- return false;
- }
- case SampleSource.FORMAT_READ: {
- Log.i(TAG, "Format was read again");
- onInputFormatChanged(mFormatHolder);
- return true;
- }
- case SampleSource.END_OF_STREAM: {
- Log.i(TAG, "End of stream from SampleSource");
- mInputStreamEnded = true;
- return false;
- }
- default: {
- if (mSampleHolder.size != mSampleSize
- && mFormatConfigured
- && !mUseFrameworkDecoder) {
- onSampleSizeChanged(mSampleHolder.size);
- }
- mSampleHolder.data.flip();
- if (!mUseFrameworkDecoder) {
- if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) {
- mMonitor.addPts(
- mSampleHolder.timeUs,
- mOutputBuffer.position(),
- mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET)
- & MP2_HEADER_BITRATE_MASK);
- } else {
- mMonitor.addPts(
- mSampleHolder.timeUs,
- mOutputBuffer.position(),
- mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff);
- }
- }
- if (mAudioDecoder != null) {
- mAudioDecoder.decode(mSampleHolder);
- if (mUseFrameworkDecoder) {
- int outputIndex =
- ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex();
- if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- onOutputFormatChanged(mAudioDecoder.getOutputFormat());
- return true;
- } else if (outputIndex < 0) {
- return true;
- }
- if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) {
- AUDIO_TRACK.handleDiscontinuity();
- return true;
- }
- }
- ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample();
- long presentationTimeUs = mAudioDecoder.getDecodedTimeUs();
- decodeDone(outputBuffer, presentationTimeUs);
- } else {
- decodeDone(mSampleHolder.data, mSampleHolder.timeUs);
- }
- return true;
- }
- }
- }
-
- private boolean processOutput() throws ExoPlaybackException {
- if (mOutputStreamEnded) {
- return false;
- }
- if (!mOutputReady) {
- if (mInputStreamEnded) {
- mOutputStreamEnded = true;
- mEndOfStreamMs = SystemClock.elapsedRealtime();
- return false;
- }
- return true;
- }
-
- ensureAudioTrackInitialized();
- int handleBufferResult;
- try {
- // To reduce discontinuity, interpolate presentation time.
- if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) {
- mInterpolatedTimeUs = mPresentationTimeUs
- + mPresentationCount * MP2_SAMPLE_DURATION_US;
- } else if (!mUseFrameworkDecoder) {
- mInterpolatedTimeUs = mPresentationTimeUs
- + mPresentationCount * AC3_SAMPLE_DURATION_US;
- } else {
- mInterpolatedTimeUs = mPresentationTimeUs;
- }
- handleBufferResult =
- AUDIO_TRACK.handleBuffer(
- mOutputBuffer, 0, mOutputBuffer.limit(), mInterpolatedTimeUs);
- } catch (AudioTrack.WriteException e) {
- notifyAudioTrackWriteError(e);
- throw new ExoPlaybackException(e);
- }
- if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
- Log.i(TAG, "Play discontinuity happened");
- mCurrentPositionUs = Long.MIN_VALUE;
- }
- if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
- mCodecCounters.renderedOutputBufferCount++;
- mOutputReady = false;
- if (mUseFrameworkDecoder) {
- ((MediaCodecAudioDecoder) mAudioDecoder).releaseOutputBuffer();
- }
- return true;
- }
- return false;
- }
-
- @Override
- protected long getDurationUs() {
- return mSource.getFormat(mTrackIndex).durationUs;
- }
-
- @Override
- protected long getBufferedPositionUs() {
- long pos = mSource.getBufferedPositionUs();
- return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US
- ? pos : Math.max(pos, getPositionUs());
- }
-
- @Override
- public long getPositionUs() {
- if (!AUDIO_TRACK.isInitialized()) {
- return mAudioClock.getPositionUs();
- } else if (!AUDIO_TRACK.isEnabled()) {
- if (mInterpolatedTimeUs > 0 && !mUseFrameworkDecoder) {
- return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US;
- }
- return mPresentationTimeUs;
- }
- long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded());
- if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) {
- mPreviousPositionUs = 0L;
- if (DEBUG) {
- long oldPositionUs = Math.max(mCurrentPositionUs, 0);
- long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
- Log.d(TAG, "Audio position is not set, diff in us: "
- + String.valueOf(currentPositionUs - oldPositionUs));
- }
- mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
- } else {
- if (mPreviousPositionUs
- > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) {
- Log.e(TAG, "audio_position BACK JUMP: "
- + (mPreviousPositionUs - audioTrackCurrentPositionUs));
- mCurrentPositionUs = audioTrackCurrentPositionUs;
- } else {
- mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs);
- }
- mPreviousPositionUs = audioTrackCurrentPositionUs;
- }
- long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US;
- if (mCurrentPositionUs > upperBound) {
- mCurrentPositionUs = upperBound;
- }
- return mCurrentPositionUs;
- }
-
- private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) {
- if (outputBuffer == null || mOutputBuffer == null) {
- return;
- }
- if (presentationTimeUs < 0) {
- Log.e(TAG, "decodeDone - invalid presentationTimeUs");
- return;
- }
-
- if (TunerDebug.ENABLED) {
- TunerDebug.setAudioPtsUs(presentationTimeUs);
- }
-
- mOutputBuffer.clear();
- Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit());
-
- mOutputBuffer.put(outputBuffer);
- if (presentationTimeUs == mPresentationTimeUs) {
- mPresentationCount++;
- } else {
- mPresentationCount = 0;
- mPresentationTimeUs = presentationTimeUs;
- }
- mOutputBuffer.flip();
- mOutputReady = true;
- }
-
- private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
- if (mEventHandler == null || mEventListener == null) {
- return;
- }
- mEventHandler.post(new Runnable() {
- @Override
- public void run() {
- mEventListener.onAudioTrackInitializationError(e);
- }
- });
- }
-
- private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
- if (mEventHandler == null || mEventListener == null) {
- return;
- }
- mEventHandler.post(new Runnable() {
- @Override
- public void run() {
- mEventListener.onAudioTrackWriteError(e);
- }
- });
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- switch (messageType) {
- case MSG_SET_VOLUME:
- float volume = (Float) message;
- // Workaround: we cannot mute the audio track by setting the volume to 0, we need to
- // disable the AUDIO_TRACK for this intent. However, enabling/disabling audio track
- // whenever volume is being set might cause side effects, therefore we only handle
- // "explicit mute operations", i.e., only after certain non-zero volume has been
- // set, the subsequent volume setting operations will be consider as mute/un-mute
- // operations and thus enable/disable the audio track.
- if (mIsMuted && volume > 0) {
- mIsMuted = false;
- if (mEnabled) {
- setStatus(true);
- }
- } else if (!mIsMuted && volume == 0) {
- mIsMuted = true;
- if (mEnabled) {
- setStatus(false);
- }
- }
- AUDIO_TRACK.setVolume(volume);
- break;
- case MSG_SET_AUDIO_TRACK:
- mEnabled = (Integer) message == 1;
- setStatus(mEnabled);
- break;
- case MSG_SET_PLAYBACK_SPEED:
- mAudioClock.setPlaybackSpeed((Float) message);
- break;
- default:
- super.handleMessage(messageType, message);
- }
- }
-
- private void setStatus(boolean enabled) {
- if (enabled == AUDIO_TRACK.isEnabled()) {
- return;
- }
- if (!enabled) {
- // mAudioClock can be different from getPositionUs. In order to sync them,
- // we set mAudioClock.
- mAudioClock.setPositionUs(getPositionUs());
- }
- AUDIO_TRACK.setStatus(enabled);
- if (enabled) {
- // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to
- // the current position. If not, AUDIO_TRACK has the obsolete data.
- seekTo(mAudioClock.getPositionUs());
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java
deleted file mode 100644
index 142aa9b2..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.audio;
-
-import android.os.Handler;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.SampleSource;
-
-/**
- * MPEG-2 TS audio track renderer.
- *
- * <p>Since the audio output from {@link android.media.MediaExtractor} contains extra samples at the
- * beginning, using original {@link MediaCodecAudioTrackRenderer} as audio renderer causes
- * asynchronous Audio/Video outputs. This class calculates the offset of audio data and adjust the
- * presentation times to avoid the asynchronous Audio/Video problem.
- */
-public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRenderer {
- private final Ac3EventListener mListener;
-
- public interface Ac3EventListener extends EventListener {
- /**
- * Invoked when a {@link android.media.PlaybackParams} set to an
- * {@link android.media.AudioTrack} is not valid.
- *
- * @param e The corresponding exception.
- */
- void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e);
- }
-
- public MpegTsMediaCodecAudioTrackRenderer(
- SampleSource source,
- MediaCodecSelector mediaCodecSelector,
- Handler eventHandler,
- EventListener eventListener) {
- super(source, mediaCodecSelector, eventHandler, eventListener);
- mListener = (Ac3EventListener) eventListener;
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- if (messageType == MSG_SET_PLAYBACK_PARAMS) {
- try {
- super.handleMessage(messageType, message);
- } catch (IllegalArgumentException e) {
- if (isAudioTrackSetPlaybackParamsError(e)) {
- notifyAudioTrackSetPlaybackParamsError(e);
- }
- }
- return;
- }
- super.handleMessage(messageType, message);
- }
-
- private void notifyAudioTrackSetPlaybackParamsError(final IllegalArgumentException e) {
- if (eventHandler != null && mListener != null) {
- eventHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onAudioTrackSetPlaybackParamsError(e);
- }
- });
- }
- }
-
- static private boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) {
- if (e.getStackTrace() == null || e.getStackTrace().length < 1) {
- return false;
- }
- for (StackTraceElement element : e.getStackTrace()) {
- String elementString = element.toString();
- if (elementString.startsWith("android.media.AudioTrack.setPlaybackParams")) {
- return true;
- }
- }
- return false;
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
deleted file mode 100644
index 112e9dc4..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.media.MediaFormat;
-import android.os.ConditionVariable;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-import com.android.tv.util.Utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.ConcurrentModificationException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * Manages {@link SampleChunk} objects.
- * <p>
- * The buffer manager can be disabled, while running, if the write throughput to the associated
- * external storage is detected to be lower than a threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}".
- * This leads to restarting playback flow.
- */
-public class BufferManager {
- private static final String TAG = "BufferManager";
- private static final boolean DEBUG = false;
-
- // Constants for the disk write speed checking
- private static final long MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK =
- 10L * 1024 * 1024; // Checks for every 10M disk write
- private static final int MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK = 15 * 1024;
- private static final int MAXIMUM_SPEED_CHECK_COUNT = 5; // Checks only 5 times
- private static final int MINIMUM_DISK_WRITE_SPEED_MBPS = 3; // 3 Megabytes per second
-
- private final SampleChunk.SampleChunkCreator mSampleChunkCreator;
- // Maps from track name to a map which maps from starting position to {@link SampleChunk}.
- private final Map<String, SortedMap<Long, Pair<SampleChunk, Integer>>> mChunkMap =
- new ArrayMap<>();
- private final Map<String, Long> mStartPositionMap = new ArrayMap<>();
- private final Map<String, ChunkEvictedListener> mEvictListeners = new ArrayMap<>();
- private final StorageManager mStorageManager;
- private long mBufferSize = 0;
- private final EvictChunkQueueMap mPendingDelete = new EvictChunkQueueMap();
- private final SampleChunk.ChunkCallback mChunkCallback = new SampleChunk.ChunkCallback() {
- @Override
- public void onChunkWrite(SampleChunk chunk) {
- mBufferSize += chunk.getSize();
- }
-
- @Override
- public void onChunkDelete(SampleChunk chunk) {
- mBufferSize -= chunk.getSize();
- }
- };
-
- private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK;
- private long mTotalWriteSize;
- private long mTotalWriteTimeNs;
- private float mWriteBandwidth = 0.0f;
- private volatile int mSpeedCheckCount;
-
- public interface ChunkEvictedListener {
- void onChunkEvicted(String id, long createdTimeMs);
- }
- /**
- * Handles I/O
- * between BufferManager and {@link SampleExtractor}.
- */
- public interface SampleBuffer {
-
- /**
- * Initializes SampleBuffer.
- * @param Ids track identifiers for storage read/write.
- * @param mediaFormats meta-data for each track.
- * @throws IOException
- */
- void init(@NonNull List<String> Ids,
- @NonNull List<com.google.android.exoplayer.MediaFormat> mediaFormats)
- throws IOException;
-
- /**
- * Selects the track {@code index} for reading sample data.
- */
- void selectTrack(int index);
-
- /**
- * Deselects the track at {@code index},
- * so that no more samples will be read from the track.
- */
- void deselectTrack(int index);
-
- /**
- * Writes sample to storage.
- *
- * @param index track index
- * @param sample sample to write at storage
- * @param conditionVariable notifies the completion of writing sample.
- * @throws IOException
- */
- void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable)
- throws IOException;
-
- /**
- * Checks whether storage write speed is slow.
- */
- boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs);
-
- /**
- * Handles when write speed is slow.
- * @throws IOException
- */
- void handleWriteSpeedSlow() throws IOException;
-
- /**
- * Sets the flag when EoS was reached.
- */
- void setEos();
-
- /**
- * Reads the next sample in the track at index {@code track} into {@code sampleHolder},
- * returning {@link com.google.android.exoplayer.SampleSource#SAMPLE_READ}
- * if it is available.
- * If the next sample is not available,
- * returns {@link com.google.android.exoplayer.SampleSource#NOTHING_READ}.
- */
- int readSample(int index, SampleHolder outSample);
-
- /**
- * Seeks to the specified time in microseconds.
- */
- void seekTo(long positionUs);
-
- /**
- * Returns an estimate of the position up to which data is buffered.
- */
- long getBufferedPositionUs();
-
- /**
- * Returns whether there is buffered data.
- */
- boolean continueBuffering(long positionUs);
-
- /**
- * Cleans up and releases everything.
- * @throws IOException
- */
- void release() throws IOException;
- }
-
- /**
- * A Track format which will be loaded and saved from the permanent storage for recordings.
- */
- public static class TrackFormat {
-
- /**
- * The track id for the specified track. The track id will be used as a track identifier
- * for recordings.
- */
- public final String trackId;
-
- /**
- * The {@link MediaFormat} for the specified track.
- */
- public final MediaFormat format;
-
- /**
- * Creates TrackFormat.
- * @param trackId
- * @param format
- */
- public TrackFormat(String trackId, MediaFormat format) {
- this.trackId = trackId;
- this.format = format;
- }
- }
-
- /**
- * A Holder for a sample position which will be loaded from the index file for recordings.
- */
- public static class PositionHolder {
-
- /**
- * The current sample position in microseconds.
- * The position is identical to the PTS(presentation time stamp) of the sample.
- */
- public final long positionUs;
-
- /**
- * Base sample position for the current {@link SampleChunk}.
- */
- public final long basePositionUs;
-
- /**
- * The file offset for the current sample in the current {@link SampleChunk}.
- */
- public final int offset;
-
- /**
- * Creates a holder for a specific position in the recording.
- * @param positionUs
- * @param offset
- */
- public PositionHolder(long positionUs, long basePositionUs, int offset) {
- this.positionUs = positionUs;
- this.basePositionUs = basePositionUs;
- this.offset = offset;
- }
- }
-
- /**
- * Storage configuration and policy manager for {@link BufferManager}
- */
- public interface StorageManager {
-
- /**
- * Provides eligible storage directory for {@link BufferManager}.
- *
- * @return a directory to save buffer(chunks) and meta files
- */
- File getBufferDir();
-
- /**
- * Informs whether the storage is used for persistent use. (eg. dvr recording/play)
- *
- * @return {@code true} if stored files are persistent
- */
- boolean isPersistent();
-
- /**
- * Informs whether the storage usage exceeds pre-determined size.
- *
- * @param bufferSize the current total usage of Storage in bytes.
- * @param pendingDelete the current storage usage which will be deleted in near future by
- * bytes
- * @return {@code true} if it reached pre-determined max size
- */
- boolean reachedStorageMax(long bufferSize, long pendingDelete);
-
- /**
- * Informs whether the storage has enough remained space.
- *
- * @param pendingDelete the current storage usage which will be deleted in near future by
- * bytes
- * @return {@code true} if it has enough space
- */
- boolean hasEnoughBuffer(long pendingDelete);
-
- /**
- * Reads track name & {@link MediaFormat} from storage.
- *
- * @param isAudio {@code true} if it is for audio track
- * @return {@link List} of TrackFormat
- */
- List<TrackFormat> readTrackInfoFiles(boolean isAudio);
-
- /**
- * Reads key sample positions for each written sample from storage.
- *
- * @param trackId track name
- * @return indexes of the specified track
- * @throws IOException
- */
- ArrayList<PositionHolder> readIndexFile(String trackId) throws IOException;
-
- /**
- * Writes track information to storage.
- *
- * @param formatList {@list List} of TrackFormat
- * @param isAudio {@code true} if it is for audio track
- * @throws IOException
- */
- void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio)
- throws IOException;
-
- /**
- * Writes index file to storage.
- *
- * @param trackName track name
- * @param index {@link SampleChunk} container
- * @throws IOException
- */
- void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
- throws IOException;
- }
-
- private static class EvictChunkQueueMap {
- private final Map<String, LinkedList<SampleChunk>> mEvictMap = new ArrayMap<>();
- private long mSize;
-
- private void init(String key) {
- mEvictMap.put(key, new LinkedList<>());
- }
-
- private void add(String key, SampleChunk chunk) {
- LinkedList<SampleChunk> queue = mEvictMap.get(key);
- if (queue != null) {
- mSize += chunk.getSize();
- queue.add(chunk);
- }
- }
-
- private SampleChunk poll(String key, long startPositionUs) {
- LinkedList<SampleChunk> queue = mEvictMap.get(key);
- if (queue != null) {
- SampleChunk chunk = queue.peek();
- if (chunk != null && chunk.getStartPositionUs() < startPositionUs) {
- mSize -= chunk.getSize();
- return queue.poll();
- }
- }
- return null;
- }
-
- private long getSize() {
- return mSize;
- }
-
- private void release() {
- for (Map.Entry<String, LinkedList<SampleChunk>> entry : mEvictMap.entrySet()) {
- for (SampleChunk chunk : entry.getValue()) {
- SampleChunk.IoState.release(chunk, true);
- }
- }
- mEvictMap.clear();
- mSize = 0;
- }
- }
-
- public BufferManager(StorageManager storageManager) {
- this(storageManager, new SampleChunk.SampleChunkCreator());
- }
-
- public BufferManager(StorageManager storageManager,
- SampleChunk.SampleChunkCreator sampleChunkCreator) {
- mStorageManager = storageManager;
- mSampleChunkCreator = sampleChunkCreator;
- }
-
- public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) {
- mEvictListeners.put(id, listener);
- }
-
- public void unregisterChunkEvictedListener(String id) {
- mEvictListeners.remove(id);
- }
-
- private static String getFileName(String id, long positionUs) {
- return String.format(Locale.ENGLISH, "%s_%016x.chunk", id, positionUs);
- }
-
- /**
- * Creates a new {@link SampleChunk} for caching samples if it is needed.
- *
- * @param id the name of the track
- * @param positionUs current position to write a sample in micro seconds.
- * @param samplePool {@link SamplePool} for the fast creation of samples.
- * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create
- * a new {@link SampleChunk}.
- * @param currentOffset the current offset to write.
- * @return returns the created {@link SampleChunk}.
- * @throws IOException
- */
- public SampleChunk createNewWriteFileIfNeeded(String id, long positionUs, SamplePool samplePool,
- SampleChunk currentChunk, int currentOffset) throws IOException {
- if (!maybeEvictChunk()) {
- throw new IOException("Not enough storage space");
- }
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
- if (map == null) {
- map = new TreeMap<>();
- mChunkMap.put(id, map);
- mStartPositionMap.put(id, positionUs);
- mPendingDelete.init(id);
- }
- if (currentChunk == null) {
- File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs));
- SampleChunk sampleChunk = mSampleChunkCreator
- .createSampleChunk(samplePool, file, positionUs, mChunkCallback);
- map.put(positionUs, new Pair(sampleChunk, 0));
- return sampleChunk;
- } else {
- map.put(positionUs, new Pair(currentChunk, currentOffset));
- return null;
- }
- }
-
- /**
- * Loads a track using {@link BufferManager.StorageManager}.
- *
- * @param trackId the name of the track.
- * @param samplePool {@link SamplePool} for the fast creation of samples.
- * @throws IOException
- */
- public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException {
- ArrayList<PositionHolder> keyPositions = mStorageManager.readIndexFile(trackId);
- long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0;
-
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(trackId);
- if (map == null) {
- map = new TreeMap<>();
- mChunkMap.put(trackId, map);
- mStartPositionMap.put(trackId, startPositionUs);
- mPendingDelete.init(trackId);
- }
- SampleChunk chunk = null;
- long basePositionUs = -1;
- for (PositionHolder position: keyPositions) {
- if (position.basePositionUs != basePositionUs) {
- chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool,
- mStorageManager.getBufferDir(), getFileName(trackId, position.positionUs),
- position.positionUs, mChunkCallback, chunk);
- basePositionUs = position.basePositionUs;
- }
- map.put(position.positionUs, new Pair(chunk, position.offset));
- }
- }
-
- /**
- * Finds a {@link SampleChunk} for the specified track name and the position.
- *
- * @param id the name of the track.
- * @param positionUs the position.
- * @return returns the found {@link SampleChunk}.
- */
- public Pair<SampleChunk, Integer> getReadFile(String id, long positionUs) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
- if (map == null) {
- return null;
- }
- Pair<SampleChunk, Integer> ret;
- SortedMap<Long, Pair<SampleChunk, Integer>> headMap = map.headMap(positionUs + 1);
- if (!headMap.isEmpty()) {
- ret = headMap.get(headMap.lastKey());
- } else {
- ret = map.get(map.firstKey());
- }
- return ret;
- }
-
- /**
- * Evicts chunks which are ready to be evicted for the specified track
- *
- * @param id the specified track
- * @param earlierThanPositionUs the start position of the {@link SampleChunk}
- * should be earlier than
- */
- public void evictChunks(String id, long earlierThanPositionUs) {
- SampleChunk chunk = null;
- while ((chunk = mPendingDelete.poll(id, earlierThanPositionUs)) != null) {
- SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()) ;
- }
- }
-
- /**
- * Returns the start position of the specified track in micro seconds.
- *
- * @param id the specified track
- */
- public long getStartPositionUs(String id) {
- Long ret = mStartPositionMap.get(id);
- return ret == null ? 0 : ret;
- }
-
- private boolean maybeEvictChunk() {
- long pendingDelete = mPendingDelete.getSize();
- while (mStorageManager.reachedStorageMax(mBufferSize, pendingDelete)
- || !mStorageManager.hasEnoughBuffer(pendingDelete)) {
- if (mStorageManager.isPersistent()) {
- // Since chunks are persistent, we cannot evict chunks.
- return false;
- }
- SortedMap<Long, Pair<SampleChunk, Integer>> earliestChunkMap = null;
- SampleChunk earliestChunk = null;
- String earliestChunkId = null;
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
- if (map.isEmpty()) {
- continue;
- }
- SampleChunk chunk = map.get(map.firstKey()).first;
- if (earliestChunk == null
- || chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) {
- earliestChunkMap = map;
- earliestChunk = chunk;
- earliestChunkId = entry.getKey();
- }
- }
- if (earliestChunk == null) {
- break;
- }
- mPendingDelete.add(earliestChunkId, earliestChunk);
- earliestChunkMap.remove(earliestChunk.getStartPositionUs());
- if (DEBUG) {
- Log.d(TAG, String.format("bufferSize = %d; pendingDelete = %b; "
- + "earliestChunk size = %d; %s@%d (%s)",
- mBufferSize, pendingDelete, earliestChunk.getSize(), earliestChunkId,
- earliestChunk.getStartPositionUs(),
- Utils.toIsoDateTimeString(earliestChunk.getCreatedTimeMs())));
- }
- ChunkEvictedListener listener = mEvictListeners.get(earliestChunkId);
- if (listener != null) {
- listener.onChunkEvicted(earliestChunkId, earliestChunk.getCreatedTimeMs());
- }
- pendingDelete = mPendingDelete.getSize();
- }
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
- if (map.isEmpty()) {
- continue;
- }
- mStartPositionMap.put(entry.getKey(), map.firstKey());
- }
- return true;
- }
-
- /**
- * Reads track information which includes {@link MediaFormat}.
- *
- * @return returns all track information which is found by {@link BufferManager.StorageManager}.
- * @throws IOException
- */
- public List<TrackFormat> readTrackInfoFiles() throws IOException {
- List<TrackFormat> trackFormatList = new ArrayList<>();
- trackFormatList.addAll(mStorageManager.readTrackInfoFiles(false));
- trackFormatList.addAll(mStorageManager.readTrackInfoFiles(true));
- if (trackFormatList.isEmpty()) {
- throw new IOException("No track information to load");
- }
- return trackFormatList;
- }
-
- /**
- * Writes track information and index information for all tracks.
- *
- * @param audios list of audio track information
- * @param videos list of audio track information
- * @throws IOException
- */
- public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos)
- throws IOException {
- if (audios.isEmpty() && videos.isEmpty()) {
- throw new IOException("No track information to save");
- }
- if (!audios.isEmpty()) {
- mStorageManager.writeTrackInfoFiles(audios, true);
- for (TrackFormat trackFormat : audios) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map =
- mChunkMap.get(trackFormat.trackId);
- if (map == null) {
- throw new IOException("Audio track index missing");
- }
- mStorageManager.writeIndexFile(trackFormat.trackId, map);
- }
- }
- if (!videos.isEmpty()) {
- mStorageManager.writeTrackInfoFiles(videos, false);
- for (TrackFormat trackFormat : videos) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map =
- mChunkMap.get(trackFormat.trackId);
- if (map == null) {
- throw new IOException("Video track index missing");
- }
- mStorageManager.writeIndexFile(trackFormat.trackId, map);
- }
- }
- }
-
- /**
- * Releases all the resources.
- */
- public void release() {
- try {
- mPendingDelete.release();
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SampleChunk toRelease = null;
- for (Pair<SampleChunk, Integer> positions : entry.getValue().values()) {
- if (toRelease != positions.first) {
- toRelease = positions.first;
- SampleChunk.IoState.release(toRelease, !mStorageManager.isPersistent());
- }
- }
- }
- mChunkMap.clear();
- } catch (ConcurrentModificationException | NullPointerException e) {
- // TODO: remove this after it it confirmed that race condition issues are resolved.
- // b/32492258, b/32373376
- SoftPreconditions.checkState(false, "Exception on BufferManager#release: ",
- e.toString());
- }
- }
-
- private void resetWriteStat(float writeBandwidth) {
- mWriteBandwidth = writeBandwidth;
- mTotalWriteSize = 0;
- mTotalWriteTimeNs = 0;
- }
-
- /**
- * Adds a disk write sample size to calculate the average disk write bandwidth.
- */
- public void addWriteStat(long size, long timeNs) {
- if (size >= mMinSampleSizeForSpeedCheck) {
- mTotalWriteSize += size;
- mTotalWriteTimeNs += timeNs;
- }
- }
-
- /**
- * Returns if the average disk write bandwidth is slower than
- * threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}.
- */
- public boolean isWriteSlow() {
- if (mTotalWriteSize < MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK) {
- return false;
- }
-
- // Checks write speed for only MAXIMUM_SPEED_CHECK_COUNT times to ignore outliers
- // by temporary system overloading during the playback.
- if (mSpeedCheckCount > MAXIMUM_SPEED_CHECK_COUNT) {
- return false;
- }
- mSpeedCheckCount++;
- float megabytePerSecond = calculateWriteBandwidth();
- resetWriteStat(megabytePerSecond);
- if (DEBUG) {
- Log.d(TAG, "Measured disk write performance: " + megabytePerSecond + "MBps");
- }
- return megabytePerSecond < MINIMUM_DISK_WRITE_SPEED_MBPS;
- }
-
- /**
- * Returns recent write bandwidth in MBps. If recent bandwidth is not available,
- * returns {float -1.0f}.
- */
- public float getWriteBandwidth() {
- return mWriteBandwidth == 0.0f ? -1.0f : mWriteBandwidth;
- }
-
- private float calculateWriteBandwidth() {
- if (mTotalWriteTimeNs == 0) {
- return -1;
- }
- return ((float) mTotalWriteSize * 1000 / mTotalWriteTimeNs);
- }
-
- /**
- * Returns if {@link BufferManager} has checked the write speed,
- * which is suitable for Trickplay.
- */
- @VisibleForTesting
- public boolean hasSpeedCheckDone() {
- return mSpeedCheckCount > 0;
- }
-
- /**
- * Sets minimum sample size for write speed check.
- * @param sampleSize minimum sample size for write speed check.
- */
- @VisibleForTesting
- public void setMinimumSampleSizeForSpeedCheck(int sampleSize) {
- mMinSampleSizeForSpeedCheck = sampleSize;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
deleted file mode 100644
index 6a09016c..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.media.MediaFormat;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-
-/**
- * Manages DVR storage.
- */
-public class DvrStorageManager implements BufferManager.StorageManager {
- private static final String TAG = "DvrStorageManager";
-
- // TODO: make serializable classes and use protobuf after internal data structure is finalized.
- private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO =
- "com.google.android.videos.pixelWidthHeightRatio";
- private static final String META_FILE_TYPE_AUDIO = "audio";
- private static final String META_FILE_TYPE_VIDEO = "video";
- private static final String META_FILE_TYPE_CAPTION = "caption";
- private static final String META_FILE_SUFFIX = ".meta";
- private static final String IDX_FILE_SUFFIX = ".idx";
- private static final String IDX_FILE_SUFFIX_V2 = IDX_FILE_SUFFIX + "2";
-
- // Size of minimum reserved storage buffer which will be used to save meta files
- // and index files after actual recording finished.
- private static final long MIN_BUFFER_BYTES = 256L * 1024 * 1024;
- private static final int NO_VALUE = -1;
- private static final long NO_VALUE_LONG = -1L;
-
- private final File mBufferDir;
-
- // {@code true} when this is for recording, {@code false} when this is for replaying.
- private final boolean mIsRecording;
-
- public DvrStorageManager(File file, boolean isRecording) {
- mBufferDir = file;
- mBufferDir.mkdirs();
- mIsRecording = isRecording;
- }
-
- @Override
- public File getBufferDir() {
- return mBufferDir;
- }
-
- @Override
- public boolean isPersistent() {
- return true;
- }
-
- @Override
- public boolean reachedStorageMax(long bufferSize, long pendingDelete) {
- return false;
- }
-
- @Override
- public boolean hasEnoughBuffer(long pendingDelete) {
- return !mIsRecording || mBufferDir.getUsableSpace() >= MIN_BUFFER_BYTES;
- }
-
- private void readFormatInt(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- int val = in.readInt();
- if (val != NO_VALUE) {
- format.setInteger(key, val);
- }
- }
-
- private void readFormatLong(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- long val = in.readLong();
- if (val != NO_VALUE_LONG) {
- format.setLong(key, val);
- }
- }
-
- private void readFormatFloat(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- float val = in.readFloat();
- if (val != NO_VALUE) {
- format.setFloat(key, val);
- }
- }
-
- private String readString(DataInputStream in) throws IOException {
- int len = in.readInt();
- if (len <= 0) {
- return null;
- }
- byte [] strBytes = new byte[len];
- in.readFully(strBytes);
- return new String(strBytes, StandardCharsets.UTF_8);
- }
-
- private void readFormatString(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- String str = readString(in);
- if (str != null) {
- format.setString(key, str);
- }
- }
-
- private void readFormatStringOptional(DataInputStream in, MediaFormat format, String key) {
- try {
- String str = readString(in);
- if (str != null) {
- format.setString(key, str);
- }
- } catch (IOException e) {
- // Since we are reading optional field, ignore the exception.
- }
- }
-
- private ByteBuffer readByteBuffer(DataInputStream in) throws IOException {
- int len = in.readInt();
- if (len <= 0) {
- return null;
- }
- byte [] bytes = new byte[len];
- in.readFully(bytes);
- ByteBuffer buffer = ByteBuffer.allocate(len);
- buffer.put(bytes);
- buffer.flip();
-
- return buffer;
- }
-
- private void readFormatByteBuffer(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- ByteBuffer buffer = readByteBuffer(in);
- if (buffer != null) {
- format.setByteBuffer(key, buffer);
- }
- }
-
- @Override
- public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) {
- List<BufferManager.TrackFormat> trackFormatList = new ArrayList<>();
- int index = 0;
- boolean trackNotFound = false;
- do {
- String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO)
- + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
- String name = readString(in);
- MediaFormat format = new MediaFormat();
- readFormatString(in, format, MediaFormat.KEY_MIME);
- readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE);
- readFormatInt(in, format, MediaFormat.KEY_WIDTH);
- readFormatInt(in, format, MediaFormat.KEY_HEIGHT);
- readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT);
- readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE);
- readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
- for (int i = 0; i < 3; ++i) {
- readFormatByteBuffer(in, format, "csd-" + i);
- }
- readFormatLong(in, format, MediaFormat.KEY_DURATION);
-
- // This is optional since language field is added later.
- readFormatStringOptional(in, format, MediaFormat.KEY_LANGUAGE);
- trackFormatList.add(new BufferManager.TrackFormat(name, format));
- } catch (IOException e) {
- trackNotFound = true;
- }
- index++;
- } while(!trackNotFound);
- return trackFormatList;
- }
-
- /**
- * Reads caption information from files.
- *
- * @return a list of {@link AtscCaptionTrack} objects which store caption information.
- */
- public List<AtscCaptionTrack> readCaptionInfoFiles() {
- List<AtscCaptionTrack> tracks = new ArrayList<>();
- int index = 0;
- boolean trackNotFound = false;
- do {
- String fileName = META_FILE_TYPE_CAPTION +
- ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
- byte[] data = new byte[(int) file.length()];
- in.read(data);
- tracks.add(AtscCaptionTrack.parseFrom(data));
- } catch (IOException e) {
- trackNotFound = true;
- }
- index++;
- } while(!trackNotFound);
- return tracks;
- }
-
- private ArrayList<BufferManager.PositionHolder> readOldIndexFile(File indexFile)
- throws IOException {
- ArrayList<BufferManager.PositionHolder> indices = new ArrayList<>();
- try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) {
- long count = in.readLong();
- for (long i = 0; i < count; ++i) {
- long positionUs = in.readLong();
- indices.add(new BufferManager.PositionHolder(positionUs, positionUs, 0));
- }
- return indices;
- }
- }
-
- private ArrayList<BufferManager.PositionHolder> readNewIndexFile(File indexFile)
- throws IOException {
- ArrayList<BufferManager.PositionHolder> indices = new ArrayList<>();
- try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) {
- long count = in.readLong();
- for (long i = 0; i < count; ++i) {
- long positionUs = in.readLong();
- long basePositionUs = in.readLong();
- int offset = in.readInt();
- indices.add(new BufferManager.PositionHolder(positionUs, basePositionUs, offset));
- }
- return indices;
- }
- }
-
- @Override
- public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId)
- throws IOException {
- File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX_V2);
- if (file.exists()) {
- return readNewIndexFile(file);
- } else {
- return readOldIndexFile(new File(getBufferDir(),trackId + IDX_FILE_SUFFIX));
- }
- }
-
- private void writeFormatInt(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- out.writeInt(format.getInteger(key));
- } else {
- out.writeInt(NO_VALUE);
- }
- }
-
- private void writeFormatLong(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- out.writeLong(format.getLong(key));
- } else {
- out.writeLong(NO_VALUE_LONG);
- }
- }
-
- private void writeFormatFloat(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- out.writeFloat(format.getFloat(key));
- } else {
- out.writeFloat(NO_VALUE);
- }
- }
-
- private void writeString(DataOutputStream out, String str) throws IOException {
- byte [] data = str.getBytes(StandardCharsets.UTF_8);
- out.writeInt(data.length);
- if (data.length > 0) {
- out.write(data);
- }
- }
-
- private void writeFormatString(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- writeString(out, format.getString(key));
- } else {
- out.writeInt(0);
- }
- }
-
- private void writeByteBuffer(DataOutputStream out, ByteBuffer buffer) throws IOException {
- byte [] data = new byte[buffer.limit()];
- buffer.get(data);
- buffer.flip();
- out.writeInt(data.length);
- if (data.length > 0) {
- out.write(data);
- } else {
- out.writeInt(0);
- }
- }
-
- private void writeFormatByteBuffer(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- writeByteBuffer(out, format.getByteBuffer(key));
- } else {
- out.writeInt(0);
- }
- }
-
- @Override
- public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio)
- throws IOException {
- for (int i = 0; i < formatList.size() ; ++i) {
- BufferManager.TrackFormat trackFormat = formatList.get(i);
- String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO)
- + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
- writeString(out, trackFormat.trackId);
- writeFormatString(out, trackFormat.format, MediaFormat.KEY_MIME);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_MAX_INPUT_SIZE);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_WIDTH);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_HEIGHT);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_CHANNEL_COUNT);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_SAMPLE_RATE);
- writeFormatFloat(out, trackFormat.format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
- for (int j = 0; j < 3; ++j) {
- writeFormatByteBuffer(out, trackFormat.format, "csd-" + j);
- }
- writeFormatLong(out, trackFormat.format, MediaFormat.KEY_DURATION);
- writeFormatString(out, trackFormat.format, MediaFormat.KEY_LANGUAGE);
- }
- }
- }
-
- /**
- * Writes caption information to files.
- *
- * @param tracks a list of {@link AtscCaptionTrack} objects which store caption information.
- */
- public void writeCaptionInfoFiles(List<AtscCaptionTrack> tracks) {
- if (tracks == null || tracks.isEmpty()) {
- return;
- }
- for (int i = 0; i < tracks.size(); i++) {
- AtscCaptionTrack track = tracks.get(i);
- String fileName = META_FILE_TYPE_CAPTION +
- ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
- out.write(MessageNano.toByteArray(track));
- } catch (Exception e) {
- Log.e(TAG, "Fail to write caption info to files", e);
- }
- }
- }
-
- @Override
- public void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
- throws IOException {
- File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2);
- try (DataOutputStream out = new DataOutputStream(new FileOutputStream(indexFile))) {
- out.writeLong(index.size());
- for (Map.Entry<Long, Pair<SampleChunk, Integer>> entry : index.entrySet()) {
- out.writeLong(entry.getKey());
- out.writeLong(entry.getValue().first.getStartPositionUs());
- out.writeInt(entry.getValue().second);
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
deleted file mode 100644
index af0c3f0d..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.os.ConditionVariable;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.util.Assertions;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Handles I/O between {@link SampleExtractor} and
- * {@link BufferManager}.Reads & writes samples from/to {@link SampleChunk} which is backed
- * by physical storage.
- */
-public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
- BufferManager.ChunkEvictedListener {
- private static final String TAG = "RecordingSampleBuffer";
-
- @IntDef({BUFFER_REASON_LIVE_PLAYBACK, BUFFER_REASON_RECORDED_PLAYBACK, BUFFER_REASON_RECORDING})
- @Retention(RetentionPolicy.SOURCE)
- public @interface BufferReason {}
-
- /**
- * A buffer reason for live-stream playback.
- */
- public static final int BUFFER_REASON_LIVE_PLAYBACK = 0;
-
- /**
- * A buffer reason for playback of a recorded program.
- */
- public static final int BUFFER_REASON_RECORDED_PLAYBACK = 1;
-
- /**
- * A buffer reason for recording a program.
- */
- public static final int BUFFER_REASON_RECORDING = 2;
-
- /**
- * The minimum duration to support seek in Trickplay.
- */
- static final long MIN_SEEK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500);
-
- /**
- * The duration of a {@link SampleChunk} for recordings.
- */
- static final long RECORDING_CHUNK_DURATION_US = MIN_SEEK_DURATION_US * 1200; // 10 minutes
- private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds
- private static final long BUFFER_NEEDED_US =
- 1000L * Math.max(MpegTsPlayer.MIN_BUFFER_MS, MpegTsPlayer.MIN_REBUFFER_MS);
-
- private final BufferManager mBufferManager;
- private final PlaybackBufferListener mBufferListener;
- private final @BufferReason int mBufferReason;
-
- private int mTrackCount;
- private boolean[] mTrackSelected;
- private List<SampleQueue> mReadSampleQueues;
- private final SamplePool mSamplePool = new SamplePool();
- private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US;
- private long mCurrentPlaybackPositionUs = 0;
-
- // An error in I/O thread of {@link SampleChunkIoHelper} will be notified.
- private volatile boolean mError;
-
- // Eos was reached in I/O thread of {@link SampleChunkIoHelper}.
- private volatile boolean mEos;
- private SampleChunkIoHelper mSampleChunkIoHelper;
- private final SampleChunkIoHelper.IoCallback mIoCallback =
- new SampleChunkIoHelper.IoCallback() {
- @Override
- public void onIoReachedEos() {
- mEos = true;
- }
-
- @Override
- public void onIoError() {
- mError = true;
- }
- };
-
- /**
- * Creates {@link BufferManager.SampleBuffer} with
- * cached I/O backed by physical storage (e.g. trickplay,recording,recorded-playback).
- *
- * @param bufferManager the manager of {@link SampleChunk}
- * @param bufferListener the listener for buffer I/O event
- * @param enableTrickplay {@code true} when trickplay should be enabled
- * @param bufferReason the reason for caching samples {@link RecordingSampleBuffer.BufferReason}
- */
- public RecordingSampleBuffer(BufferManager bufferManager, PlaybackBufferListener bufferListener,
- boolean enableTrickplay, @BufferReason int bufferReason) {
- mBufferManager = bufferManager;
- mBufferListener = bufferListener;
- if (bufferListener != null) {
- bufferListener.onBufferStateChanged(enableTrickplay);
- }
- mBufferReason = bufferReason;
- }
-
- @Override
- public void init(@NonNull List<String> ids, @NonNull List<MediaFormat> mediaFormats)
- throws IOException {
- mTrackCount = ids.size();
- if (mTrackCount <= 0) {
- throw new IOException("No tracks to initialize");
- }
- mTrackSelected = new boolean[mTrackCount];
- mReadSampleQueues = new ArrayList<>();
- mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason,
- mBufferManager, mSamplePool, mIoCallback);
- for (int i = 0; i < mTrackCount; ++i) {
- mReadSampleQueues.add(i, new SampleQueue(mSamplePool));
- }
- mSampleChunkIoHelper.init();
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.registerChunkEvictedListener(ids.get(i), RecordingSampleBuffer.this);
- }
- }
-
- @Override
- public void selectTrack(int index) {
- if (!mTrackSelected[index]) {
- mTrackSelected[index] = true;
- mReadSampleQueues.get(index).clear();
- mSampleChunkIoHelper.openRead(index, mCurrentPlaybackPositionUs);
- }
- }
-
- @Override
- public void deselectTrack(int index) {
- if (mTrackSelected[index]) {
- mTrackSelected[index] = false;
- mReadSampleQueues.get(index).clear();
- mSampleChunkIoHelper.closeRead(index);
- }
- }
-
- @Override
- public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable)
- throws IOException {
- mSampleChunkIoHelper.writeSample(index, sample, conditionVariable);
-
- if (!conditionVariable.block(BUFFER_WRITE_TIMEOUT_MS)) {
- Log.e(TAG, "Error: Serious delay on writing buffer");
- conditionVariable.block();
- }
- }
-
- @Override
- public boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs) {
- if (mBufferReason == BUFFER_REASON_RECORDED_PLAYBACK) {
- return false;
- }
- mBufferManager.addWriteStat(sampleSize, writeDurationNs);
- return mBufferManager.isWriteSlow();
- }
-
- @Override
- public void handleWriteSpeedSlow() throws IOException{
- if (mBufferReason == BUFFER_REASON_RECORDING) {
- // Recording does not need to stop because I/O speed is slow temporarily.
- // If fixed size buffer of TsStreamer overflows, TsDataSource will reach EoS.
- // Reaching EoS will stop recording eventually.
- Log.w(TAG, "Disk I/O speed is slow for recording temporarily: "
- + mBufferManager.getWriteBandwidth() + "MBps");
- return;
- }
- // Disables buffering samples afterwards, and notifies the disk speed is slow.
- Log.w(TAG, "Disk is too slow for trickplay");
- mBufferListener.onDiskTooSlow();
- }
-
- @Override
- public void setEos() {
- mSampleChunkIoHelper.closeWrite();
- }
-
- private boolean maybeReadSample(SampleQueue queue, int index) {
- if (queue.getLastQueuedPositionUs() != null
- && queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US
- && queue.isDurationGreaterThan(MIN_SEEK_DURATION_US)) {
- // The speed of queuing samples can be higher than the playback speed.
- // If the duration of the samples in the queue is not limited,
- // samples can be accumulated and there can be out-of-memory issues.
- // But, the throttling should provide enough samples for the player to
- // finish the buffering state.
- return false;
- }
- SampleHolder sample = mSampleChunkIoHelper.readSample(index);
- if (sample != null) {
- queue.queueSample(sample);
- return true;
- }
- return false;
- }
-
- @Override
- public int readSample(int track, SampleHolder outSample) {
- Assertions.checkState(mTrackSelected[track]);
- maybeReadSample(mReadSampleQueues.get(track), track);
- int result = mReadSampleQueues.get(track).dequeueSample(outSample);
- if ((result != SampleSource.SAMPLE_READ && mEos) || mError) {
- return SampleSource.END_OF_STREAM;
- }
- return result;
- }
-
- @Override
- public void seekTo(long positionUs) {
- for (int i = 0; i < mTrackCount; ++i) {
- if (mTrackSelected[i]) {
- mReadSampleQueues.get(i).clear();
- mSampleChunkIoHelper.openRead(i, positionUs);
- }
- }
- mLastBufferedPositionUs = positionUs;
- }
-
- @Override
- public long getBufferedPositionUs() {
- Long result = null;
- for (int i = 0; i < mTrackCount; ++i) {
- if (!mTrackSelected[i]) {
- continue;
- }
- Long lastQueuedSamplePositionUs =
- mReadSampleQueues.get(i).getLastQueuedPositionUs();
- if (lastQueuedSamplePositionUs == null) {
- // No sample has been queued.
- result = mLastBufferedPositionUs;
- continue;
- }
- if (result == null || result > lastQueuedSamplePositionUs) {
- result = lastQueuedSamplePositionUs;
- }
- }
- if (result == null) {
- return mLastBufferedPositionUs;
- }
- return (mLastBufferedPositionUs = result);
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- mCurrentPlaybackPositionUs = positionUs;
- for (int i = 0; i < mTrackCount; ++i) {
- if (!mTrackSelected[i]) {
- continue;
- }
- SampleQueue queue = mReadSampleQueues.get(i);
- maybeReadSample(queue, i);
- if (queue.getLastQueuedPositionUs() == null
- || positionUs > queue.getLastQueuedPositionUs()) {
- // No more buffered data.
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void release() throws IOException {
- if (mTrackCount <= 0) {
- return;
- }
- if (mSampleChunkIoHelper != null) {
- mSampleChunkIoHelper.release();
- }
- }
-
- // onChunkEvictedListener
- @Override
- public void onChunkEvicted(String id, long createdTimeMs) {
- if (mBufferListener != null) {
- mBufferListener.onBufferStartTimeChanged(
- createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US));
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
deleted file mode 100644
index 04b5a071..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import com.google.android.exoplayer.SampleHolder;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-
-/**
- * {@link SampleChunk} stores samples into file and makes them available for read.
- * Stored file = { Header, Sample } * N
- * Header = sample size : int, sample flag : int, sample PTS in micro second : long
- */
-public class SampleChunk {
- private static final String TAG = "SampleChunk";
- private static final boolean DEBUG = false;
-
- private final long mCreatedTimeMs;
- private final long mStartPositionUs;
- private SampleChunk mNextChunk;
-
- // Header = sample size : int, sample flag : int, sample PTS in micro second : long
- private static final int SAMPLE_HEADER_LENGTH = 16;
-
- private final File mFile;
- private final ChunkCallback mChunkCallback;
- private final SamplePool mSamplePool;
- private RandomAccessFile mAccessFile;
- private long mWriteOffset;
- private boolean mWriteFinished;
- private boolean mIsReading;
- private boolean mIsWriting;
-
- /**
- * A callback for chunks being committed to permanent storage.
- */
- public static abstract class ChunkCallback {
-
- /**
- * Notifies when writing a SampleChunk is completed.
- *
- * @param chunk SampleChunk which is written completely
- */
- public void onChunkWrite(SampleChunk chunk) {
-
- }
-
- /**
- * Notifies when a SampleChunk is deleted.
- *
- * @param chunk SampleChunk which is deleted from storage
- */
- public void onChunkDelete(SampleChunk chunk) {
- }
- }
-
- /**
- * A class for SampleChunk creation.
- */
- public static class SampleChunkCreator {
-
- /**
- * Returns a newly created SampleChunk to read & write samples.
- *
- * @param samplePool sample allocator
- * @param file filename which will be created newly
- * @param startPositionUs the start position of the earliest sample to be stored
- * @param chunkCallback for total storage usage change notification
- */
- SampleChunk createSampleChunk(SamplePool samplePool, File file,
- long startPositionUs, ChunkCallback chunkCallback) {
- return new SampleChunk(samplePool, file, startPositionUs, System.currentTimeMillis(),
- chunkCallback);
- }
-
- /**
- * Returns a newly created SampleChunk which is backed by an existing file.
- * Created SampleChunk is read-only.
- *
- * @param samplePool sample allocator
- * @param bufferDir the directory where the file to read is located
- * @param filename the filename which will be read afterwards
- * @param startPositionUs the start position of the earliest sample in the file
- * @param chunkCallback for total storage usage change notification
- * @param prev the previous SampleChunk just before the newly created SampleChunk
- * @throws IOException
- */
- SampleChunk loadSampleChunkFromFile(SamplePool samplePool, File bufferDir,
- String filename, long startPositionUs, ChunkCallback chunkCallback,
- SampleChunk prev) throws IOException {
- File file = new File(bufferDir, filename);
- SampleChunk chunk =
- new SampleChunk(samplePool, file, startPositionUs, chunkCallback);
- if (prev != null) {
- prev.mNextChunk = chunk;
- }
- return chunk;
- }
- }
-
- /**
- * Handles I/O for SampleChunk.
- * Maintains current SampleChunk and the current offset for next I/O operation.
- */
- static class IoState {
- private SampleChunk mChunk;
- private long mCurrentOffset;
-
- private boolean equals(SampleChunk chunk, long offset) {
- return chunk == mChunk && mCurrentOffset == offset;
- }
-
- /**
- * Returns whether read I/O operation is finished.
- */
- boolean isReadFinished() {
- return mChunk == null;
- }
-
- /**
- * Returns the start position of the current SampleChunk
- */
- long getStartPositionUs() {
- return mChunk == null ? 0 : mChunk.getStartPositionUs();
- }
-
- private void reset(@Nullable SampleChunk chunk) {
- mChunk = chunk;
- mCurrentOffset = 0;
- }
-
- private void reset(SampleChunk chunk, long offset) {
- mChunk = chunk;
- mCurrentOffset = offset;
- }
-
- /**
- * Prepares for read I/O operation from a new SampleChunk.
- *
- * @param chunk the new SampleChunk to read from
- * @throws IOException
- */
- void openRead(SampleChunk chunk, long offset) throws IOException {
- if (mChunk != null) {
- mChunk.closeRead();
- }
- chunk.openRead();
- reset(chunk, offset);
- }
-
- /**
- * Prepares for write I/O operation to a new SampleChunk.
- *
- * @param chunk the new SampleChunk to write samples afterwards
- * @throws IOException
- */
- void openWrite(SampleChunk chunk) throws IOException{
- if (mChunk != null) {
- mChunk.closeWrite(chunk);
- }
- chunk.openWrite();
- reset(chunk);
- }
-
- /**
- * Reads a sample if it is available.
- *
- * @return Returns a sample if it is available, null otherwise.
- * @throws IOException
- */
- SampleHolder read() throws IOException {
- if (mChunk != null && mChunk.isReadFinished(this)) {
- SampleChunk next = mChunk.mNextChunk;
- mChunk.closeRead();
- if (next != null) {
- next.openRead();
- }
- reset(next);
- }
- if (mChunk != null) {
- try {
- return mChunk.read(this);
- } catch (IllegalStateException e) {
- // Write is finished and there is no additional buffer to read.
- Log.w(TAG, "Tried to read sample over EOS.");
- return null;
- }
- } else {
- return null;
- }
- }
-
- /**
- * Writes a sample.
- *
- * @param sample to write
- * @param nextChunk if this is {@code null} writes at the current SampleChunk,
- * otherwise close current SampleChunk and writes at this
- * @throws IOException
- */
- void write(SampleHolder sample, SampleChunk nextChunk)
- throws IOException {
- if (nextChunk != null) {
- if (mChunk == null || mChunk.mNextChunk != null) {
- throw new IllegalStateException("Requested write for wrong SampleChunk");
- }
- mChunk.closeWrite(nextChunk);
- mChunk.mChunkCallback.onChunkWrite(mChunk);
- nextChunk.openWrite();
- reset(nextChunk);
- }
- mChunk.write(sample, this);
- }
-
- /**
- * Finishes write I/O operation.
- *
- * @throws IOException
- */
- void closeWrite() throws IOException {
- if (mChunk != null) {
- mChunk.closeWrite(null);
- }
- }
-
- /**
- * Returns the current SampleChunk for subsequent I/O operation.
- */
- SampleChunk getChunk() {
- return mChunk;
- }
-
- /**
- * Returns the current offset of the current SampleChunk for subsequent I/O operation.
- */
- long getOffset() {
- return mCurrentOffset;
- }
-
- /**
- * Releases SampleChunk. the SampleChunk will not be used anymore.
- *
- * @param chunk to release
- * @param delete {@code true} when the backed file needs to be deleted,
- * {@code false} otherwise.
- */
- static void release(SampleChunk chunk, boolean delete) {
- chunk.release(delete);
- }
- }
-
- @VisibleForTesting
- protected SampleChunk(SamplePool samplePool, File file, long startPositionUs,
- long createdTimeMs, ChunkCallback chunkCallback) {
- mStartPositionUs = startPositionUs;
- mCreatedTimeMs = createdTimeMs;
- mSamplePool = samplePool;
- mFile = file;
- mChunkCallback = chunkCallback;
- }
-
- // Constructor of SampleChunk which is backed by the given existing file.
- private SampleChunk(SamplePool samplePool, File file, long startPositionUs,
- ChunkCallback chunkCallback) throws IOException {
- mStartPositionUs = startPositionUs;
- mCreatedTimeMs = mStartPositionUs / 1000;
- mSamplePool = samplePool;
- mFile = file;
- mChunkCallback = chunkCallback;
- mWriteFinished = true;
- }
-
- private void openRead() throws IOException {
- if (!mIsReading) {
- if (mAccessFile == null) {
- mAccessFile = new RandomAccessFile(mFile, "r");
- }
- if (mWriteFinished && mWriteOffset == 0) {
- // Lazy loading of write offset, in order not to load
- // all SampleChunk's write offset at start time of recorded playback.
- mWriteOffset = mAccessFile.length();
- }
- mIsReading = true;
- }
- }
-
- private void openWrite() throws IOException {
- if (mWriteFinished) {
- throw new IllegalStateException("Opened for write though write is already finished");
- }
- if (!mIsWriting) {
- if (mIsReading) {
- throw new IllegalStateException("Write is requested for "
- + "an already opened SampleChunk");
- }
- mAccessFile = new RandomAccessFile(mFile, "rw");
- mIsWriting = true;
- }
- }
-
- private void CloseAccessFileIfNeeded() throws IOException {
- if (!mIsReading && !mIsWriting) {
- try {
- if (mAccessFile != null) {
- mAccessFile.close();
- }
- } finally {
- mAccessFile = null;
- }
- }
- }
-
- private void closeRead() throws IOException{
- if (mIsReading) {
- mIsReading = false;
- CloseAccessFileIfNeeded();
- }
- }
-
- private void closeWrite(SampleChunk nextChunk)
- throws IOException {
- if (mIsWriting) {
- mNextChunk = nextChunk;
- mIsWriting = false;
- mWriteFinished = true;
- CloseAccessFileIfNeeded();
- }
- }
-
- private boolean isReadFinished(IoState state) {
- return mWriteFinished && state.equals(this, mWriteOffset);
- }
-
- private SampleHolder read(IoState state) throws IOException {
- if (mAccessFile == null || state.mChunk != this) {
- throw new IllegalStateException("Requested read for wrong SampleChunk");
- }
- long offset = state.mCurrentOffset;
- if (offset >= mWriteOffset) {
- if (mWriteFinished) {
- throw new IllegalStateException("Requested read for wrong range");
- } else {
- if (offset != mWriteOffset) {
- Log.e(TAG, "This should not happen!");
- }
- return null;
- }
- }
- mAccessFile.seek(offset);
- int size = mAccessFile.readInt();
- SampleHolder sample = mSamplePool.acquireSample(size);
- sample.size = size;
- sample.flags = mAccessFile.readInt();
- sample.timeUs = mAccessFile.readLong();
- sample.clearData();
- sample.data.put(mAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY,
- offset + SAMPLE_HEADER_LENGTH, sample.size));
- offset += sample.size + SAMPLE_HEADER_LENGTH;
- state.mCurrentOffset = offset;
- return sample;
- }
-
- @VisibleForTesting
- protected void write(SampleHolder sample, IoState state)
- throws IOException {
- if (mAccessFile == null || mNextChunk != null || !state.equals(this, mWriteOffset)) {
- throw new IllegalStateException("Requested write for wrong SampleChunk");
- }
-
- mAccessFile.seek(mWriteOffset);
- mAccessFile.writeInt(sample.size);
- mAccessFile.writeInt(sample.flags);
- mAccessFile.writeLong(sample.timeUs);
- sample.data.position(0).limit(sample.size);
- mAccessFile.getChannel().position(mWriteOffset + SAMPLE_HEADER_LENGTH).write(sample.data);
- mWriteOffset += sample.size + SAMPLE_HEADER_LENGTH;
- state.mCurrentOffset = mWriteOffset;
- }
-
- private void release(boolean delete) {
- mWriteFinished = true;
- mIsReading = mIsWriting = false;
- try {
- if (mAccessFile != null) {
- mAccessFile.close();
- }
- } catch (IOException e) {
- // Since the SampleChunk will not be reused, ignore exception.
- }
- if (delete) {
- mFile.delete();
- mChunkCallback.onChunkDelete(this);
- }
- }
-
- /**
- * Returns the start position.
- */
- public long getStartPositionUs() {
- return mStartPositionUs;
- }
-
- /**
- * Returns the creation time.
- */
- public long getCreatedTimeMs() {
- return mCreatedTimeMs;
- }
-
- /**
- * Returns the current size.
- */
- public long getSize() {
- return mWriteOffset;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
deleted file mode 100644
index ca97a91a..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.media.MediaCodec;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * Handles all {@link SampleChunk} I/O operations.
- * An I/O dedicated thread handles all I/O operations for synchronization.
- */
-public class SampleChunkIoHelper implements Handler.Callback {
- private static final String TAG = "SampleChunkIoHelper";
-
- private static final int MAX_READ_BUFFER_SAMPLES = 3;
- private static final int READ_RESCHEDULING_DELAY_MS = 10;
-
- private static final int MSG_OPEN_READ = 1;
- private static final int MSG_OPEN_WRITE = 2;
- private static final int MSG_CLOSE_READ = 3;
- private static final int MSG_CLOSE_WRITE = 4;
- private static final int MSG_READ = 5;
- private static final int MSG_WRITE = 6;
- private static final int MSG_RELEASE = 7;
-
- private final long mSampleChunkDurationUs;
- private final int mTrackCount;
- private final List<String> mIds;
- private final List<MediaFormat> mMediaFormats;
- private final @BufferReason int mBufferReason;
- private final BufferManager mBufferManager;
- private final SamplePool mSamplePool;
- private final IoCallback mIoCallback;
-
- private Handler mIoHandler;
- private final ConcurrentLinkedQueue<SampleHolder> mReadSampleBuffers[];
- private final ConcurrentLinkedQueue<SampleHolder> mHandlerReadSampleBuffers[];
- private final long[] mWriteIndexEndPositionUs;
- private final long[] mWriteChunkEndPositionUs;
- private final SampleChunk.IoState[] mReadIoStates;
- private final SampleChunk.IoState[] mWriteIoStates;
- private final Set<Integer> mSelectedTracks = new ArraySet<>();
- private long mBufferDurationUs = 0;
- private boolean mWriteEnded;
- private boolean mErrorNotified;
- private boolean mFinished;
-
- /**
- * A Callback for I/O events.
- */
- public static abstract class IoCallback {
-
- /**
- * Called when there is no sample to read.
- */
- public void onIoReachedEos() {
- }
-
- /**
- * Called when there is an irrecoverable error during I/O.
- */
- public void onIoError() {
- }
- }
-
- private class IoParams {
- private final int index;
- private final long positionUs;
- private final SampleHolder sample;
- private final ConditionVariable conditionVariable;
- private final ConcurrentLinkedQueue<SampleHolder> readSampleBuffer;
-
- private IoParams(int index, long positionUs, SampleHolder sample,
- ConditionVariable conditionVariable,
- ConcurrentLinkedQueue<SampleHolder> readSampleBuffer) {
- this.index = index;
- this.positionUs = positionUs;
- this.sample = sample;
- this.conditionVariable = conditionVariable;
- this.readSampleBuffer = readSampleBuffer;
- }
- }
-
- /**
- * Creates {@link SampleChunk} I/O handler.
- *
- * @param ids track names
- * @param mediaFormats {@link android.media.MediaFormat} for each track
- * @param bufferReason reason to be buffered
- * @param bufferManager manager of {@link SampleChunk} collections
- * @param samplePool allocator for a sample
- * @param ioCallback listeners for I/O events
- */
- public SampleChunkIoHelper(List<String> ids, List<MediaFormat> mediaFormats,
- @BufferReason int bufferReason, BufferManager bufferManager, SamplePool samplePool,
- IoCallback ioCallback) {
- mTrackCount = ids.size();
- mIds = ids;
- mMediaFormats = mediaFormats;
- mBufferReason = bufferReason;
- mBufferManager = bufferManager;
- mSamplePool = samplePool;
- mIoCallback = ioCallback;
-
- mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
- mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
- mWriteIndexEndPositionUs = new long[mTrackCount];
- mWriteChunkEndPositionUs = new long[mTrackCount];
- mReadIoStates = new SampleChunk.IoState[mTrackCount];
- mWriteIoStates = new SampleChunk.IoState[mTrackCount];
-
- // Small chunk duration for live playback will give more fine grained storage usage
- // and eviction handling for trickplay.
- mSampleChunkDurationUs =
- bufferReason == RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK ?
- RecordingSampleBuffer.MIN_SEEK_DURATION_US :
- RecordingSampleBuffer.RECORDING_CHUNK_DURATION_US;
- for (int i = 0; i < mTrackCount; ++i) {
- mWriteIndexEndPositionUs[i] = RecordingSampleBuffer.MIN_SEEK_DURATION_US;
- mWriteChunkEndPositionUs[i] = mSampleChunkDurationUs;
- mReadIoStates[i] = new SampleChunk.IoState();
- mWriteIoStates[i] = new SampleChunk.IoState();
- }
- }
-
- /**
- * Prepares and initializes for I/O operations.
- *
- * @throws IOException
- */
- public void init() throws IOException {
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mIoHandler = new Handler(handlerThread.getLooper(), this);
- if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK) {
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.loadTrackFromStorage(mIds.get(i), mSamplePool);
- }
- mWriteEnded = true;
- } else {
- for (int i = 0; i < mTrackCount; ++i) {
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_WRITE, i));
- }
- }
- }
-
- /**
- * Reads a sample if it is available.
- *
- * @param index track index
- * @return {@code null} if a sample is not available, otherwise returns a sample
- */
- public SampleHolder readSample(int index) {
- SampleHolder sample = mReadSampleBuffers[index].poll();
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index));
- return sample;
- }
-
- /**
- * Writes a sample.
- *
- * @param index track index
- * @param sample to write
- * @param conditionVariable which will be wait until the write is finished
- * @throws IOException
- */
- public void writeSample(int index, SampleHolder sample,
- ConditionVariable conditionVariable) throws IOException {
- if (mErrorNotified) {
- throw new IOException("Storage I/O error happened");
- }
- conditionVariable.close();
- IoParams params = new IoParams(index, 0, sample, conditionVariable, null);
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_WRITE, params));
- }
-
- /**
- * Starts read from the specified position.
- *
- * @param index track index
- * @param positionUs the specified position
- */
- public void openRead(int index, long positionUs) {
- // Old mReadSampleBuffers may have a pending read.
- mReadSampleBuffers[index] = new ConcurrentLinkedQueue<>();
- IoParams params = new IoParams(index, positionUs, null, null, mReadSampleBuffers[index]);
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_READ, params));
- }
-
- /**
- * Closes read from the specified track.
- *
- * @param index track index
- */
- public void closeRead(int index) {
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_CLOSE_READ, index));
- }
-
- /**
- * Notifies writes are finished.
- */
- public void closeWrite() {
- mIoHandler.sendEmptyMessage(MSG_CLOSE_WRITE);
- }
-
- /**
- * Finishes I/O operations and releases all the resources.
- * @throws IOException
- */
- public void release() throws IOException {
- if (mIoHandler == null) {
- return;
- }
- // Finishes all I/O operations.
- ConditionVariable conditionVariable = new ConditionVariable();
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_RELEASE, conditionVariable));
- conditionVariable.block();
-
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.unregisterChunkEvictedListener(mIds.get(i));
- }
- try {
- if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING && mTrackCount > 0) {
- // Saves meta information for recording.
- List<BufferManager.TrackFormat> audios = new LinkedList<>();
- List<BufferManager.TrackFormat> videos = new LinkedList<>();
- for (int i = 0; i < mTrackCount; ++i) {
- android.media.MediaFormat format =
- mMediaFormats.get(i).getFrameworkMediaFormatV16();
- format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs);
- if (MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) {
- audios.add(new BufferManager.TrackFormat(mIds.get(i), format));
- } else if (MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) {
- videos.add(new BufferManager.TrackFormat(mIds.get(i), format));
- }
- }
- mBufferManager.writeMetaFiles(audios, videos);
- }
- } finally {
- mBufferManager.release();
- mIoHandler.getLooper().quitSafely();
- }
- }
-
- @Override
- public boolean handleMessage(Message message) {
- if (mFinished) {
- return true;
- }
- releaseEvictedChunks();
- try {
- switch (message.what) {
- case MSG_OPEN_READ:
- doOpenRead((IoParams) message.obj);
- return true;
- case MSG_OPEN_WRITE:
- doOpenWrite((int) message.obj);
- return true;
- case MSG_CLOSE_READ:
- doCloseRead((int) message.obj);
- return true;
- case MSG_CLOSE_WRITE:
- doCloseWrite();
- return true;
- case MSG_READ:
- doRead((int) message.obj);
- return true;
- case MSG_WRITE:
- doWrite((IoParams) message.obj);
- // Since only write will increase storage, eviction will be handled here.
- return true;
- case MSG_RELEASE:
- doRelease((ConditionVariable) message.obj);
- return true;
- }
- } catch (IOException e) {
- mIoCallback.onIoError();
- mErrorNotified = true;
- Log.e(TAG, "IoException happened", e);
- return true;
- }
- return false;
- }
-
- private void doOpenRead(IoParams params) throws IOException {
- int index = params.index;
- mIoHandler.removeMessages(MSG_READ, index);
- Pair<SampleChunk, Integer> readPosition =
- mBufferManager.getReadFile(mIds.get(index), params.positionUs);
- if (readPosition == null) {
- String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs
- + "is not found";
- SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage);
- throw new IOException(errorMessage);
- }
- mSelectedTracks.add(index);
- mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second);
- if (mHandlerReadSampleBuffers[index] != null) {
- SampleHolder sample;
- while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) {
- mSamplePool.releaseSample(sample);
- }
- }
- mHandlerReadSampleBuffers[index] = params.readSampleBuffer;
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index));
- }
-
- private void doOpenWrite(int index) throws IOException {
- SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0,
- mSamplePool, null, 0);
- mWriteIoStates[index].openWrite(chunk);
- }
-
- private void doCloseRead(int index) {
- mSelectedTracks.remove(index);
- if (mHandlerReadSampleBuffers[index] != null) {
- SampleHolder sample;
- while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) {
- mSamplePool.releaseSample(sample);
- }
- }
- mIoHandler.removeMessages(MSG_READ, index);
- }
-
- private void doRead(int index) throws IOException {
- mIoHandler.removeMessages(MSG_READ, index);
- if (mHandlerReadSampleBuffers[index].size() >= MAX_READ_BUFFER_SAMPLES) {
- // If enough samples are buffered, try again few moments later hoping that
- // buffered samples are consumed.
- mIoHandler.sendMessageDelayed(
- mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS);
- } else {
- if (mReadIoStates[index].isReadFinished()) {
- for (int i = 0; i < mTrackCount; ++i) {
- if (!mReadIoStates[i].isReadFinished()) {
- return;
- }
- }
- mIoCallback.onIoReachedEos();
- return;
- }
- SampleHolder sample = mReadIoStates[index].read();
- if (sample != null) {
- mHandlerReadSampleBuffers[index].offer(sample);
- } else {
- // Read reached write but write is not finished yet --- wait a few moments to
- // see if another sample is written.
- mIoHandler.sendMessageDelayed(
- mIoHandler.obtainMessage(MSG_READ, index),
- READ_RESCHEDULING_DELAY_MS);
- }
- }
- }
-
- private void doWrite(IoParams params) throws IOException {
- try {
- if (mWriteEnded) {
- SoftPreconditions.checkState(false);
- return;
- }
- int index = params.index;
- SampleHolder sample = params.sample;
- SampleChunk nextChunk = null;
- if ((sample.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
- if (sample.timeUs > mBufferDurationUs) {
- mBufferDurationUs = sample.timeUs;
- }
- if (sample.timeUs >= mWriteIndexEndPositionUs[index]) {
- SampleChunk currentChunk = sample.timeUs >= mWriteChunkEndPositionUs[index] ?
- null : mWriteIoStates[params.index].getChunk();
- int currentOffset = (int) mWriteIoStates[params.index].getOffset();
- nextChunk = mBufferManager.createNewWriteFileIfNeeded(
- mIds.get(index), mWriteIndexEndPositionUs[index], mSamplePool,
- currentChunk, currentOffset);
- mWriteIndexEndPositionUs[index] =
- ((sample.timeUs / RecordingSampleBuffer.MIN_SEEK_DURATION_US) + 1) *
- RecordingSampleBuffer.MIN_SEEK_DURATION_US;
- if (nextChunk != null) {
- mWriteChunkEndPositionUs[index] =
- ((sample.timeUs / mSampleChunkDurationUs) + 1)
- * mSampleChunkDurationUs;
- }
- }
- }
- mWriteIoStates[params.index].write(params.sample, nextChunk);
- } finally {
- params.conditionVariable.open();
- }
- }
-
- private void doCloseWrite() throws IOException {
- if (mWriteEnded) {
- return;
- }
- mWriteEnded = true;
- boolean readFinished = true;
- for (int i = 0; i < mTrackCount; ++i) {
- readFinished = readFinished && mReadIoStates[i].isReadFinished();
- mWriteIoStates[i].closeWrite();
- }
- if (readFinished) {
- mIoCallback.onIoReachedEos();
- }
- }
-
- private void doRelease(ConditionVariable conditionVariable) {
- mIoHandler.removeCallbacksAndMessages(null);
- mFinished = true;
- conditionVariable.open();
- mSelectedTracks.clear();
- }
-
- private void releaseEvictedChunks() {
- if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK
- || mSelectedTracks.isEmpty()) {
- return;
- }
- long currentStartPositionUs = Long.MAX_VALUE;
- for (int trackIndex : mSelectedTracks) {
- currentStartPositionUs = Math.min(currentStartPositionUs,
- mReadIoStates[trackIndex].getStartPositionUs());
- }
- for (int i = 0; i < mTrackCount; ++i) {
- long evictEndPositionUs = Math.min(mBufferManager.getStartPositionUs(mIds.get(i)),
- currentStartPositionUs);
- mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java b/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java
deleted file mode 100644
index bb048e85..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import com.google.android.exoplayer.SampleHolder;
-
-import java.util.LinkedList;
-
-/**
- * Pool of samples to recycle ByteBuffers as much as possible.
- */
-public class SamplePool {
- private final LinkedList<SampleHolder> mSamplePool = new LinkedList<>();
-
- /**
- * Acquires a sample with a buffer larger than size from the pool. Allocate new one or resize
- * an existing buffer if necessary.
- */
- public synchronized SampleHolder acquireSample(int size) {
- if (mSamplePool.isEmpty()) {
- SampleHolder sample = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
- sample.ensureSpaceForWrite(size);
- return sample;
- }
- SampleHolder smallestSufficientSample = null;
- SampleHolder maxSample = mSamplePool.getFirst();
- for (SampleHolder sample : mSamplePool) {
- // Grab the smallest sufficient sample.
- if (sample.data.capacity() >= size && (smallestSufficientSample == null
- || smallestSufficientSample.data.capacity() > sample.data.capacity())) {
- smallestSufficientSample = sample;
- }
-
- // Grab the max size sample.
- if (maxSample.data.capacity() < sample.data.capacity()) {
- maxSample = sample;
- }
- }
- SampleHolder sampleFromPool = smallestSufficientSample;
-
- // If there's no sufficient sample, grab the maximum sample and resize it to size.
- if (sampleFromPool == null) {
- sampleFromPool = maxSample;
- sampleFromPool.ensureSpaceForWrite(size);
- }
- mSamplePool.remove(sampleFromPool);
- return sampleFromPool;
- }
-
- /**
- * Releases the sample back to the pool.
- */
- public synchronized void releaseSample(SampleHolder sample) {
- sample.clearData();
- mSamplePool.offerLast(sample);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
deleted file mode 100644
index 75eac5a2..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-
-import java.util.LinkedList;
-
-/**
- * A sample queue which reads from the buffer and passes to player pipeline.
- */
-public class SampleQueue {
- private final LinkedList<SampleHolder> mQueue = new LinkedList<>();
- private final SamplePool mSamplePool;
- private Long mLastQueuedPositionUs = null;
-
- public SampleQueue(SamplePool samplePool) {
- mSamplePool = samplePool;
- }
-
- public void queueSample(SampleHolder sample) {
- mQueue.offer(sample);
- mLastQueuedPositionUs = sample.timeUs;
- }
-
- public int dequeueSample(SampleHolder sample) {
- SampleHolder sampleFromQueue = mQueue.poll();
- if (sampleFromQueue == null) {
- return SampleSource.NOTHING_READ;
- }
- sample.ensureSpaceForWrite(sampleFromQueue.size);
- sample.size = sampleFromQueue.size;
- sample.flags = sampleFromQueue.flags;
- sample.timeUs = sampleFromQueue.timeUs;
- sample.clearData();
- sampleFromQueue.data.position(0).limit(sample.size);
- sample.data.put(sampleFromQueue.data);
- mSamplePool.releaseSample(sampleFromQueue);
- return SampleSource.SAMPLE_READ;
- }
-
- public void clear() {
- while (!mQueue.isEmpty()) {
- mSamplePool.releaseSample(mQueue.poll());
- }
- mLastQueuedPositionUs = null;
- }
-
- public Long getLastQueuedPositionUs() {
- return mLastQueuedPositionUs;
- }
-
- public boolean isDurationGreaterThan(long durationUs) {
- return !mQueue.isEmpty() && mQueue.getLast().timeUs - mQueue.getFirst().timeUs > durationUs;
- }
-
- public boolean isEmpty() {
- return mQueue.isEmpty();
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
deleted file mode 100644
index 159fde18..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.os.ConditionVariable;
-
-import android.support.annotation.NonNull;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Handles I/O for {@link SampleExtractor} when
- * physical storage based buffer is not used. Trickplay is disabled.
- */
-public class SimpleSampleBuffer implements BufferManager.SampleBuffer {
- private final SamplePool mSamplePool = new SamplePool();
- private SampleQueue[] mPlayingSampleQueues;
- private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US;
-
- private volatile boolean mEos;
-
- public SimpleSampleBuffer(PlaybackBufferListener bufferListener) {
- if (bufferListener != null) {
- // Disables trickplay.
- bufferListener.onBufferStateChanged(false);
- }
- }
-
- @Override
- public synchronized void init(@NonNull List<String> ids,
- @NonNull List<MediaFormat> mediaFormats) {
- int trackCount = ids.size();
- mPlayingSampleQueues = new SampleQueue[trackCount];
- for (int i = 0; i < trackCount; i++) {
- mPlayingSampleQueues[i] = null;
- }
- }
-
- @Override
- public void setEos() {
- mEos = true;
- }
-
- private boolean reachedEos() {
- return mEos;
- }
-
- @Override
- public void selectTrack(int index) {
- synchronized (this) {
- if (mPlayingSampleQueues[index] == null) {
- mPlayingSampleQueues[index] = new SampleQueue(mSamplePool);
- } else {
- mPlayingSampleQueues[index].clear();
- }
- }
- }
-
- @Override
- public void deselectTrack(int index) {
- synchronized (this) {
- if (mPlayingSampleQueues[index] != null) {
- mPlayingSampleQueues[index].clear();
- mPlayingSampleQueues[index] = null;
- }
- }
- }
-
- @Override
- public synchronized long getBufferedPositionUs() {
- Long result = null;
- for (SampleQueue queue : mPlayingSampleQueues) {
- if (queue == null) {
- continue;
- }
- Long lastQueuedSamplePositionUs = queue.getLastQueuedPositionUs();
- if (lastQueuedSamplePositionUs == null) {
- // No sample has been queued.
- result = mLastBufferedPositionUs;
- continue;
- }
- if (result == null || result > lastQueuedSamplePositionUs) {
- result = lastQueuedSamplePositionUs;
- }
- }
- if (result == null) {
- return mLastBufferedPositionUs;
- }
- return (mLastBufferedPositionUs = result);
- }
-
- @Override
- public synchronized int readSample(int track, SampleHolder sampleHolder) {
- SampleQueue queue = mPlayingSampleQueues[track];
- SoftPreconditions.checkNotNull(queue);
- int result = queue == null ? SampleSource.NOTHING_READ : queue.dequeueSample(sampleHolder);
- if (result != SampleSource.SAMPLE_READ && reachedEos()) {
- return SampleSource.END_OF_STREAM;
- }
- return result;
- }
-
- @Override
- public void writeSample(int index, SampleHolder sample,
- ConditionVariable conditionVariable) throws IOException {
- sample.data.position(0).limit(sample.size);
- SampleHolder sampleToQueue = mSamplePool.acquireSample(sample.size);
- sampleToQueue.size = sample.size;
- sampleToQueue.clearData();
- sampleToQueue.data.put(sample.data);
- sampleToQueue.timeUs = sample.timeUs;
- sampleToQueue.flags = sample.flags;
-
- synchronized (this) {
- if (mPlayingSampleQueues[index] != null) {
- mPlayingSampleQueues[index].queueSample(sampleToQueue);
- }
- }
- }
-
- @Override
- public boolean isWriteSpeedSlow(int sampleSize, long durationNs) {
- // Since SimpleSampleBuffer write samples only to memory (not to physical storage),
- // write speed is always fine.
- return false;
- }
-
- @Override
- public void handleWriteSpeedSlow() {
- // no-op
- }
-
- @Override
- public synchronized boolean continueBuffering(long positionUs) {
- for (SampleQueue queue : mPlayingSampleQueues) {
- if (queue == null) {
- continue;
- }
- if (queue.getLastQueuedPositionUs() == null
- || positionUs > queue.getLastQueuedPositionUs()) {
- // No more buffered data.
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void seekTo(long positionUs) {
- // Not used.
- }
-
- @Override
- public void release() {
- // Not used.
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
deleted file mode 100644
index 9fe921b8..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.buffer;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.util.Pair;
-
-import com.android.tv.common.SoftPreconditions;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedMap;
-
-/**
- * Manages Trickplay storage.
- */
-public class TrickplayStorageManager implements BufferManager.StorageManager {
- // TODO: Support multi-sessions.
- private static final String BUFFER_DIR = "timeshift";
-
- // Copied from android.provider.Settings.Global (hidden fields)
- private static final String
- SYS_STORAGE_THRESHOLD_PERCENTAGE = "sys_storage_threshold_percentage";
- private static final String
- SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes";
-
- // Copied from android.os.StorageManager
- private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
- private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500L * 1024 * 1024;
-
- private static AsyncTask<Void, Void, Void> sLastCacheCleanUpTask;
- private static File sBufferDir;
- private static long sStorageBufferBytes;
-
- private final long mMaxBufferSize;
-
- private static void initParamsIfNeeded(Context context, @NonNull File path) {
- // TODO: Support multi-sessions.
- SoftPreconditions.checkState(
- sBufferDir == null || sBufferDir.equals(path));
- if (path.equals(sBufferDir)) {
- return;
- }
- sBufferDir = path;
- long lowPercentage = Settings.Global.getInt(context.getContentResolver(),
- SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
- long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100;
- long maxLowBytes = Settings.Global.getLong(context.getContentResolver(),
- SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
- sStorageBufferBytes = Math.min(lowPercentageToBytes, maxLowBytes);
- }
-
- public TrickplayStorageManager(Context context, @NonNull File baseDir, long maxBufferSize) {
- initParamsIfNeeded(context, new File(baseDir, BUFFER_DIR));
- sBufferDir.mkdirs();
- mMaxBufferSize = maxBufferSize;
- clearStorage();
- }
-
- private void clearStorage() {
- long now = System.currentTimeMillis();
- if (sLastCacheCleanUpTask != null) {
- sLastCacheCleanUpTask.cancel(true);
- }
- sLastCacheCleanUpTask = new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- if (isCancelled()) {
- return null;
- }
- File files[] = sBufferDir.listFiles();
- if (files == null || files.length == 0) {
- return null;
- }
- for (File file : files) {
- if (isCancelled()) {
- break;
- }
- long lastModified = file.lastModified();
- if (lastModified != 0 && lastModified < now) {
- file.delete();
- }
- }
- return null;
- }
- };
- sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- @Override
- public File getBufferDir() {
- return sBufferDir;
- }
-
- @Override
- public boolean isPersistent() {
- return false;
- }
-
- @Override
- public boolean reachedStorageMax(long bufferSize, long pendingDelete) {
- return bufferSize - pendingDelete > mMaxBufferSize;
- }
-
- @Override
- public boolean hasEnoughBuffer(long pendingDelete) {
- return sBufferDir.getUsableSpace() + pendingDelete >= sStorageBufferBytes;
- }
-
- @Override
- public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) {
- return null;
- }
-
- @Override
- public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId) {
- return null;
- }
-
- @Override
- public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio) {
- }
-
- @Override
- public void writeIndexFile(String trackName,
- SortedMap<Long, Pair<SampleChunk, Integer>> index) {
- }
-
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java
deleted file mode 100644
index 356636cc..00000000
--- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.ffmpeg;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import android.support.annotation.MainThread;
-import android.support.annotation.WorkerThread;
-import android.support.annotation.VisibleForTesting;
-import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.Features;
-import com.android.tv.tuner.exoplayer.audio.AudioDecoder;
-
-import java.nio.ByteBuffer;
-
-/**
- * The class connects {@link FfmpegDecoderService} to decode audio samples.
- * In order to sandbox ffmpeg based decoder, {@link FfmpegDecoderService} is an isolated process
- * without any permission and connected by binder.
- */
-public class FfmpegDecoderClient extends AudioDecoder {
- private static FfmpegDecoderClient sInstance;
-
- private IFfmpegDecoder mService;
- private Boolean mIsAvailable;
-
- private static final String FFMPEG_DECODER_SERVICE_FILTER =
- "com.android.tv.tuner.exoplayer.ffmpeg.IFfmpegDecoder";
- private static final long FFMPEG_SERVICE_CONNECT_TIMEOUT_MS = 500;
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = IFfmpegDecoder.Stub.asInterface(service);
- synchronized (FfmpegDecoderClient.this) {
- try {
- mIsAvailable = mService.isAvailable();
- } catch (RemoteException e) {
- }
- FfmpegDecoderClient.this.notify();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- synchronized (FfmpegDecoderClient.this) {
- sInstance.releaseLocked();
- mIsAvailable = false;
- mService = null;
- }
- }
- };
-
- /**
- * Connects to the decoder service for future uses.
- * @param context
- * @return {@code true} when decoder service is connected.
- */
- @MainThread
- public synchronized static boolean connect(Context context) {
- if (Features.AC3_SOFTWARE_DECODE.isEnabled(context)) {
- if (sInstance == null) {
- sInstance = new FfmpegDecoderClient();
- Intent intent =
- new Intent(FFMPEG_DECODER_SERVICE_FILTER)
- .setComponent(
- new ComponentName(context, FfmpegDecoderService.class));
- if (context.bindService(intent, sInstance.mConnection, Context.BIND_AUTO_CREATE)) {
- return true;
- } else {
- sInstance = null;
- }
- }
- }
- return false;
- }
-
- /**
- * Disconnects from the decoder service and release resources.
- * @param context
- */
- @MainThread
- public synchronized static void disconnect(Context context) {
- if (sInstance != null) {
- synchronized (sInstance) {
- sInstance.releaseLocked();
- if (sInstance.mIsAvailable != null && sInstance.mIsAvailable) {
- context.unbindService(sInstance.mConnection);
- }
- sInstance.mIsAvailable = false;
- sInstance.mService = null;
- }
- sInstance = null;
- }
- }
-
- /**
- * Returns whether service is available or not.
- * Before using client, this should be used to check availability.
- */
- @WorkerThread
- public synchronized static boolean isAvailable() {
- if (sInstance != null) {
- return sInstance.available();
- }
- return false;
- }
-
- /**
- * Returns an client instance.
- */
- public synchronized static FfmpegDecoderClient getInstance() {
- if (sInstance != null) {
- sInstance.createDecoder();
- }
- return sInstance;
- }
-
- private FfmpegDecoderClient() {
- }
-
- private synchronized boolean available() {
- if (mIsAvailable == null) {
- try {
- this.wait(FFMPEG_SERVICE_CONNECT_TIMEOUT_MS);
- } catch (InterruptedException e) {
- }
- }
- return mIsAvailable != null && mIsAvailable == true;
- }
-
- private synchronized void createDecoder() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- try {
- mService.create();
- } catch (RemoteException e) {
- }
- }
-
- private void releaseLocked() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- try {
- mService.release();
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public synchronized void release() {
- releaseLocked();
- }
-
- @Override
- public synchronized void decode(SampleHolder sampleHolder) {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- byte[] sampleBytes = new byte [sampleHolder.data.limit()];
- sampleHolder.data.get(sampleBytes, 0, sampleBytes.length);
- try {
- mService.decode(sampleHolder.timeUs, sampleBytes);
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public synchronized void resetDecoderState(String mimeType) {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- try {
- mService.resetDecoderState(mimeType);
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public synchronized ByteBuffer getDecodedSample() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return null;
- }
- try {
- byte[] outputBytes = mService.getDecodedSample();
- if (outputBytes != null && outputBytes.length > 0) {
- return ByteBuffer.wrap(outputBytes);
- }
- } catch (RemoteException e) {
- }
- return null;
- }
-
- @Override
- public synchronized long getDecodedTimeUs() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return 0;
- }
- try {
- return mService.getDecodedTimeUs();
- } catch (RemoteException e) {
- }
- return 0;
- }
-
- @VisibleForTesting
- public boolean testSandboxIsolatedProcess() {
- // When testing isolated process, we will check the permission in FfmpegDecoderService.
- // If the service have any permission, an exception will be thrown.
- try {
- mService.testSandboxIsolatedProcess();
- } catch (RemoteException e) {
- return false;
- }
- return true;
- }
-
- @VisibleForTesting
- public void testSandboxMinijail() {
- // When testing minijail, we will call a system call which is blocked by minijail. In that
- // case, the FfmpegDecoderService will be disconnected, we can check the connection status
- // to make sure if the minijail works or not.
- try {
- mService.testSandboxMinijail();
- } catch (RemoteException e) {
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java
deleted file mode 100644
index 3ebdd381..00000000
--- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.ffmpeg;
-
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.os.AsyncTask;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioDecoder;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Ffmpeg based audio decoder service.
- * It should be isolatedProcess due to security reason.
- */
-public class FfmpegDecoderService extends Service {
- private static final String TAG = "FfmpegDecoderService";
- private static final boolean DEBUG = false;
-
- private static final String POLICY_FILE = "whitelist.policy";
-
- private static final long MINIJAIL_SETUP_WAIT_TIMEOUT_MS = 5000;
-
- private static boolean sLibraryLoaded = true;
-
- static {
- try {
- System.loadLibrary("minijail_jni");
- } catch (Exception | Error e) {
- Log.e(TAG, "Load minijail failed:", e);
- sLibraryLoaded = false;
- }
- }
-
- private FfmpegDecoder mBinder = new FfmpegDecoder();
- private volatile Object mMinijailSetupMonitor = new Object();
- //@GuardedBy("mMinijailSetupMonitor")
- private volatile Boolean mMinijailSetup;
-
- @Override
- public void onCreate() {
- if (sLibraryLoaded) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (mMinijailSetupMonitor) {
- int pipeFd = getPolicyPipeFd();
- if (pipeFd <= 0) {
- Log.e(TAG, "fail to open policy file");
- mMinijailSetup = false;
- } else {
- nativeSetupMinijail(pipeFd);
- mMinijailSetup = true;
- if (DEBUG) Log.d(TAG, "Minijail setup successfully");
- }
- mMinijailSetupMonitor.notify();
- }
- return null;
- }
- }.execute();
- } else {
- synchronized (mMinijailSetupMonitor) {
- mMinijailSetup = false;
- mMinijailSetupMonitor.notify();
- }
- }
- super.onCreate();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- private int getPolicyPipeFd() {
- try {
- ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
- new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
- final AssetFileDescriptor policyFile = getAssets().openFd("whitelist.policy");
- final byte[] buffer = new byte[2048];
- final FileInputStream policyStream = policyFile.createInputStream();
- while (true) {
- int bytesRead = policyStream.read(buffer);
- if (bytesRead == -1) break;
- outputStream.write(buffer, 0, bytesRead);
- }
- policyStream.close();
- outputStream.close();
- return pipe[0].detachFd();
- } catch (IOException e) {
- Log.e(TAG, "Policy file not found:" + e);
- }
- return -1;
- }
-
- private final class FfmpegDecoder extends IFfmpegDecoder.Stub {
- FfmpegAudioDecoder mDecoder;
- @Override
- public boolean isAvailable() {
- return isMinijailSetupDone() && FfmpegAudioDecoder.isAvailable();
- }
-
- @Override
- public void create() {
- mDecoder = new FfmpegAudioDecoder(FfmpegDecoderService.this);
- }
-
- @Override
- public void release() {
- if (mDecoder != null) {
- mDecoder.release();
- mDecoder = null;
- }
- }
-
- @Override
- public void decode(long timeUs, byte[] sample) {
- if (!isMinijailSetupDone()) {
- // If minijail is not setup, we don't run decode for better security.
- return;
- }
- mDecoder.decode(timeUs, sample);
- }
-
- @Override
- public void resetDecoderState(String mimetype) {
- mDecoder.resetDecoderState(mimetype);
- }
-
- @Override
- public byte[] getDecodedSample() {
- ByteBuffer decodedBuffer = mDecoder.getDecodedSample();
- byte[] ret = new byte[decodedBuffer.limit()];
- decodedBuffer.get(ret, 0, ret.length);
- return ret;
- }
-
- @Override
- public long getDecodedTimeUs() {
- return mDecoder.getDecodedTimeUs();
- }
-
- private boolean isMinijailSetupDone() {
- synchronized (mMinijailSetupMonitor) {
- if (DEBUG) Log.d(TAG, "mMinijailSetup in isAvailable(): " + mMinijailSetup);
- if (mMinijailSetup == null) {
- try {
- if (DEBUG) Log.d(TAG, "Wait till Minijail setup is done");
- mMinijailSetupMonitor.wait(MINIJAIL_SETUP_WAIT_TIMEOUT_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- return mMinijailSetup != null && mMinijailSetup;
- }
- }
-
- @Override
- public void testSandboxIsolatedProcess() {
- if (!isMinijailSetupDone()) {
- // If minijail is not setup, we return directly to make the test fail.
- return;
- }
- if (FfmpegDecoderService.this.checkSelfPermission("android.permission.INTERNET")
- == PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Shouldn't have the permission of internet");
- }
- }
-
- @Override
- public void testSandboxMinijail() {
- if (!isMinijailSetupDone()) {
- // If minijail is not setup, we return directly to make the test fail.
- return;
- }
- nativeTestMinijail();
- }
- }
-
- private native void nativeSetupMinijail(int policyFd);
- private native void nativeTestMinijail();
-}
diff --git a/src/com/android/tv/tuner/layout/ScaledLayout.java b/src/com/android/tv/tuner/layout/ScaledLayout.java
deleted file mode 100644
index 379ea70e..00000000
--- a/src/com/android/tv/tuner/layout/ScaledLayout.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.layout;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.tv.tuner.R;
-
-import java.util.Arrays;
-import java.util.Comparator;
-
-/**
- * A layout that scales its children using the given percentage value.
- */
-public class ScaledLayout extends ViewGroup {
- private static final String TAG = "ScaledLayout";
- private static final boolean DEBUG = false;
- private static final Comparator<Rect> mRectTopLeftSorter = new Comparator<Rect>() {
- @Override
- public int compare(Rect lhs, Rect rhs) {
- if (lhs.top != rhs.top) {
- return lhs.top - rhs.top;
- } else {
- return lhs.left - rhs.left;
- }
- }
- };
-
- private Rect[] mRectArray;
- private final int mMaxWidth;
- private final int mMaxHeight;
-
- public ScaledLayout(Context context) {
- this(context, null);
- }
-
- public ScaledLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ScaledLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- Point size = new Point();
- DisplayManager displayManager = (DisplayManager) getContext()
- .getSystemService(Context.DISPLAY_SERVICE);
- Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- display.getRealSize(size);
- mMaxWidth = size.x;
- mMaxHeight = size.y;
- }
-
- /**
- * ScaledLayoutParams stores the four scale factors.
- * <br>
- * Vertical coordinate system: ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) %
- * Horizontal coordinate system: ({@code scaleStartCol} * 100) % ~ ({@code scaleEndCol} * 100) %
- * <br>
- * In XML, for example,
- * <pre>
- * {@code
- * <View
- * app:layout_scaleStartRow="0.1"
- * app:layout_scaleEndRow="0.5"
- * app:layout_scaleStartCol="0.4"
- * app:layout_scaleEndCol="1" />
- * }
- * </pre>
- */
- public static class ScaledLayoutParams extends ViewGroup.LayoutParams {
- public static final float SCALE_UNSPECIFIED = -1;
- public final float scaleStartRow;
- public final float scaleEndRow;
- public final float scaleStartCol;
- public final float scaleEndCol;
-
- public ScaledLayoutParams(float scaleStartRow, float scaleEndRow,
- float scaleStartCol, float scaleEndCol) {
- super(MATCH_PARENT, MATCH_PARENT);
- this.scaleStartRow = scaleStartRow;
- this.scaleEndRow = scaleEndRow;
- this.scaleStartCol = scaleStartCol;
- this.scaleEndCol = scaleEndCol;
- }
-
- public ScaledLayoutParams(Context context, AttributeSet attrs) {
- super(MATCH_PARENT, MATCH_PARENT);
- TypedArray array =
- context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout);
- scaleStartRow =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED);
- scaleEndRow =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED);
- scaleStartCol =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED);
- scaleEndCol =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED);
- array.recycle();
- }
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new ScaledLayoutParams(getContext(), attrs);
- }
-
- @Override
- protected boolean checkLayoutParams(LayoutParams p) {
- return (p instanceof ScaledLayoutParams);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- int width = widthSpecSize - getPaddingLeft() - getPaddingRight();
- int height = heightSpecSize - getPaddingTop() - getPaddingBottom();
- if (DEBUG) {
- Log.d(TAG, String.format("onMeasure width: %d, height: %d", width, height));
- }
- int count = getChildCount();
- mRectArray = new Rect[count];
- for (int i = 0; i < count; ++i) {
- View child = getChildAt(i);
- ViewGroup.LayoutParams params = child.getLayoutParams();
- float scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol;
- if (!(params instanceof ScaledLayoutParams)) {
- throw new RuntimeException(
- "A child of ScaledLayout cannot have the UNSPECIFIED scale factors");
- }
- scaleStartRow = ((ScaledLayoutParams) params).scaleStartRow;
- scaleEndRow = ((ScaledLayoutParams) params).scaleEndRow;
- scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol;
- scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol;
- if (scaleStartRow < 0 || scaleStartRow > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleStartRow between 0 and 1");
- }
- if (scaleEndRow < scaleStartRow || scaleStartRow > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleEndRow between scaleStartRow and 1");
- }
- if (scaleEndCol < 0 || scaleEndCol > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleStartCol between 0 and 1");
- }
- if (scaleEndCol < scaleStartCol || scaleEndCol > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleEndCol between scaleStartCol and 1");
- }
- if (DEBUG) {
- Log.d(TAG, String.format("onMeasure child scaleStartRow: %f scaleEndRow: %f "
- + "scaleStartCol: %f scaleEndCol: %f",
- scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
- }
- mRectArray[i] = new Rect((int) (scaleStartCol * width), (int) (scaleStartRow * height),
- (int) (scaleEndCol * width), (int) (scaleEndRow * height));
- int scaleWidth = (int) (width * (scaleEndCol - scaleStartCol));
- int childWidthSpec = MeasureSpec.makeMeasureSpec(
- scaleWidth > mMaxWidth ? mMaxWidth : scaleWidth, MeasureSpec.EXACTLY);
- int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- child.measure(childWidthSpec, childHeightSpec);
-
- // If the height of the measured child view is bigger than the height of the calculated
- // region by the given ScaleLayoutParams, the height of the region should be increased
- // to fit the size of the child view.
- if (child.getMeasuredHeight() > mRectArray[i].height()) {
- int overflowedHeight = child.getMeasuredHeight() - mRectArray[i].height();
- overflowedHeight = (overflowedHeight + 1) / 2;
- mRectArray[i].bottom += overflowedHeight;
- mRectArray[i].top -= overflowedHeight;
- if (mRectArray[i].top < 0) {
- mRectArray[i].bottom -= mRectArray[i].top;
- mRectArray[i].top = 0;
- }
- if (mRectArray[i].bottom > height) {
- mRectArray[i].top -= mRectArray[i].bottom - height;
- mRectArray[i].bottom = height;
- }
- }
- int scaleHeight = (int) (height * (scaleEndRow - scaleStartRow));
- childHeightSpec = MeasureSpec.makeMeasureSpec(
- scaleHeight > mMaxHeight ? mMaxHeight : scaleHeight, MeasureSpec.EXACTLY);
- child.measure(childWidthSpec, childHeightSpec);
- }
-
- // Avoid overlapping rectangles.
- // Step 1. Sort rectangles by position (top-left).
- int visibleRectCount = 0;
- int[] visibleRectGroup = new int[count];
- Rect[] visibleRectArray = new Rect[count];
- for (int i = 0; i < count; ++i) {
- if (getChildAt(i).getVisibility() == View.VISIBLE) {
- visibleRectGroup[visibleRectCount] = visibleRectCount;
- visibleRectArray[visibleRectCount] = mRectArray[i];
- ++visibleRectCount;
- }
- }
- Arrays.sort(visibleRectArray, 0, visibleRectCount, mRectTopLeftSorter);
-
- // Step 2. Move down if there are overlapping rectangles.
- for (int i = 0; i < visibleRectCount - 1; ++i) {
- for (int j = i + 1; j < visibleRectCount; ++j) {
- if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) {
- visibleRectGroup[j] = visibleRectGroup[i];
- visibleRectArray[j].set(visibleRectArray[j].left,
- visibleRectArray[i].bottom,
- visibleRectArray[j].right,
- visibleRectArray[i].bottom + visibleRectArray[j].height());
- }
- }
- }
-
- // Step 3. Move up if there is any overflowed rectangle.
- for (int i = visibleRectCount - 1; i >= 0; --i) {
- if (visibleRectArray[i].bottom > height) {
- int overflowedHeight = visibleRectArray[i].bottom - height;
- for (int j = 0; j <= i; ++j) {
- if (visibleRectGroup[i] == visibleRectGroup[j]) {
- visibleRectArray[j].set(visibleRectArray[j].left,
- visibleRectArray[j].top - overflowedHeight,
- visibleRectArray[j].right,
- visibleRectArray[j].bottom - overflowedHeight);
- }
- }
- }
- }
- setMeasuredDimension(widthSpecSize, heightSpecSize);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int paddingLeft = getPaddingLeft();
- int paddingTop = getPaddingTop();
- int count = getChildCount();
- for (int i = 0; i < count; ++i) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- int childLeft = paddingLeft + mRectArray[i].left;
- int childTop = paddingTop + mRectArray[i].top;
- int childBottom = paddingLeft + mRectArray[i].bottom;
- int childRight = paddingTop + mRectArray[i].right;
- if (DEBUG) {
- Log.d(TAG, String.format("layoutChild bottom: %d left: %d right: %d top: %d",
- childBottom, childLeft,
- childRight, childTop));
- }
- child.layout(childLeft, childTop, childRight, childBottom);
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
deleted file mode 100644
index e0e21a20..00000000
--- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.setup;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import com.android.tv.common.BuildConfig;
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.R;
-
-import java.util.List;
-import java.util.TimeZone;
-
-/**
- * A fragment for connection type selection.
- */
-public class ConnectionTypeFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.ConnectionTypeFragment";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- ((TunerSetupActivity) getActivity()).generateTunerHal();
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onResume() {
- ((TunerSetupActivity) getActivity()).generateTunerHal();
- super.onResume();
- }
-
- @Override
- public void onDestroy() {
- ((TunerSetupActivity) getActivity()).clearTunerHal();
- super.onDestroy();
- }
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- return new ContentFragment();
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return false;
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- return new Guidance(getString(R.string.ut_connection_title),
- getString(R.string.ut_connection_description),
- getString(R.string.ut_setup_breadcrumb), null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String[] choices = getResources().getStringArray(R.array.ut_connection_choices);
- int length = choices.length - 1;
- int startOffset = 0;
- for (int i = 0; i < length; ++i) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(startOffset + i)
- .title(choices[i])
- .build());
- }
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/PostalCodeFragment.java b/src/com/android/tv/tuner/setup/PostalCodeFragment.java
deleted file mode 100644
index 025b9193..00000000
--- a/src/com/android/tv/tuner/setup/PostalCodeFragment.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.setup;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.text.InputFilter;
-import android.text.InputFilter.AllCaps;
-import android.view.View;
-import android.widget.TextView;
-import com.android.tv.R;
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.util.PostalCodeUtils;
-import com.android.tv.util.LocationUtils;
-import java.util.List;
-
-/**
- * A fragment for initial screen.
- */
-public class PostalCodeFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.PostalCodeFragment";
- private static final int VIEW_TYPE_EDITABLE = 1;
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- ContentFragment fragment = new ContentFragment();
- Bundle arguments = new Bundle();
- arguments.putBoolean(SetupGuidedStepFragment.KEY_THREE_PANE, true);
- fragment.setArguments(arguments);
- return fragment;
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return true;
- }
-
- @Override
- protected boolean needsSkipButton() {
- return true;
- }
-
- @Override
- protected void setOnClickAction(View view, final String category, final int actionId) {
- if (actionId == ACTION_DONE) {
- view.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- CharSequence postalCode =
- ((ContentFragment) getContentFragment()).mEditAction.getTitle();
- String region = LocationUtils.getCurrentCountry(getContext());
- if (postalCode != null && PostalCodeUtils.matches(postalCode, region)) {
- PostalCodeUtils.setLastPostalCode(
- getContext(), postalCode.toString());
- onActionClick(category, actionId);
- } else {
- ContentFragment contentFragment =
- (ContentFragment) getContentFragment();
- contentFragment.mEditAction.setDescription(
- getString(R.string.postal_code_invalid_warning));
- contentFragment.notifyActionChanged(0);
- contentFragment.mEditedActionView.performClick();
- }
- }
- });
- } else if (actionId == ACTION_SKIP) {
- super.setOnClickAction(view, category, ACTION_SKIP);
- }
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
- private GuidedAction mEditAction;
- private View mEditedActionView;
- private View mDoneActionView;
- private boolean mProceed;
-
- @Override
- public void onGuidedActionFocused(GuidedAction action) {
- if (action.equals(mEditAction)) {
- if (mProceed) {
- // "NEXT" in IME was just clicked, moves focus to Done button.
- if (mDoneActionView == null) {
- mDoneActionView = getActivity().findViewById(R.id.button_done);
- }
- mDoneActionView.requestFocus();
- mProceed = false;
- } else {
- // Directly opens IME to input postal/zip code.
- if (mEditedActionView == null) {
- int maxLength = PostalCodeUtils.getRegionMaxLength(getContext());
- mEditedActionView = getView().findViewById(R.id.guidedactions_editable);
- ((TextView) mEditedActionView.findViewById(R.id.guidedactions_item_title))
- .setFilters(
- new InputFilter[] {
- new InputFilter.LengthFilter(maxLength), new AllCaps()
- });
- }
- mEditedActionView.performClick();
- }
- }
- }
-
- @Override
- public long onGuidedActionEditedAndProceed(GuidedAction action) {
- mProceed = true;
- return 0;
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getString(R.string.postal_code_guidance_title);
- String description = getString(R.string.postal_code_guidance_description);
- String breadcrumb = getString(R.string.ut_setup_breadcrumb);
- return new Guidance(title, description, breadcrumb, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String description = getString(R.string.postal_code_action_description);
- mEditAction = new GuidedAction.Builder(getActivity()).id(0).editable(true)
- .description(description).build();
- actions.add(mEditAction);
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- public GuidedActionsStylist onCreateActionsStylist() {
- return new GuidedActionsStylist() {
- @Override
- public int getItemViewType(GuidedAction action) {
- if (action.isEditable()) {
- return VIEW_TYPE_EDITABLE;
- }
- return super.getItemViewType(action);
- }
-
- @Override
- public int onProvideItemLayoutId(int viewType) {
- if (viewType == VIEW_TYPE_EDITABLE) {
- return R.layout.guided_action_editable;
- }
- return super.onProvideItemLayoutId(viewType);
- }
- };
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/setup/ScanFragment.java b/src/com/android/tv/tuner/setup/ScanFragment.java
deleted file mode 100644
index b6936e38..00000000
--- a/src/com/android/tv/tuner/setup/ScanFragment.java
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.setup;
-
-import android.animation.LayoutTransition;
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.ListView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.ui.setup.SetupFragment;
-import com.android.tv.tuner.ChannelScanFileParser;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.PsipData;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.source.FileTsStreamer;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsStreamer;
-import com.android.tv.tuner.source.TunerTsStreamer;
-import com.android.tv.tuner.tvinput.ChannelDataManager;
-import com.android.tv.tuner.tvinput.EventDetector;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A fragment for scanning channels.
- */
-public class ScanFragment extends SetupFragment {
- private static final String TAG = "ScanFragment";
- private static final boolean DEBUG = false;
-
- // In the fake mode, the connection to antenna or cable is not necessary.
- // Instead dummy channels are added.
- private static final boolean FAKE_MODE = false;
-
- private static final String VCTLESS_CHANNEL_NAME_FORMAT = "RF%d-%d";
-
- public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ScanFragment";
- public static final int ACTION_CANCEL = 1;
- public static final int ACTION_FINISH = 2;
-
- public static final String EXTRA_FOR_CHANNEL_SCAN_FILE = "scan_file_choice";
-
- private static final long CHANNEL_SCAN_SHOW_DELAY_MS = 10000;
- private static final long CHANNEL_SCAN_PERIOD_MS = 4000;
- private static final long SHOW_PROGRESS_DIALOG_DELAY_MS = 300;
-
- // Build channels out of the locally stored TS streams.
- private static final boolean SCAN_LOCAL_STREAMS = true;
-
- private ChannelDataManager mChannelDataManager;
- private ChannelScanTask mChannelScanTask;
- private ProgressBar mProgressBar;
- private TextView mScanningMessage;
- private View mChannelHolder;
- private ChannelAdapter mAdapter;
- private volatile boolean mChannelListVisible;
- private Button mCancelButton;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreateView");
- View view = super.onCreateView(inflater, container, savedInstanceState);
- mChannelDataManager = new ChannelDataManager(getActivity());
- mChannelDataManager.checkDataVersion(getActivity());
- mAdapter = new ChannelAdapter();
- mProgressBar = (ProgressBar) view.findViewById(R.id.tune_progress);
- mScanningMessage = (TextView) view.findViewById(R.id.tune_description);
- ListView channelList = (ListView) view.findViewById(R.id.channel_list);
- channelList.setAdapter(mAdapter);
- channelList.setOnItemClickListener(null);
- ViewGroup progressHolder = (ViewGroup) view.findViewById(R.id.progress_holder);
- LayoutTransition transition = new LayoutTransition();
- transition.enableTransitionType(LayoutTransition.CHANGING);
- progressHolder.setLayoutTransition(transition);
- mChannelHolder = view.findViewById(R.id.channel_holder);
- mCancelButton = (Button) view.findViewById(R.id.tune_cancel);
- mCancelButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- finishScan(false);
- }
- });
- Bundle args = getArguments();
- int tunerType = (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0));
- // TODO: Handle the case when the fragment is restored.
- startScan(args == null ? 0 : args.getInt(EXTRA_FOR_CHANNEL_SCAN_FILE, 0));
- TextView scanTitleView = (TextView) view.findViewById(R.id.tune_title);
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- scanTitleView.setText(R.string.ut_channel_scan);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- scanTitleView.setText(R.string.nt_channel_scan);
- break;
- default:
- scanTitleView.setText(R.string.bt_channel_scan);
- }
- return view;
- }
-
- @Override
- protected int getLayoutResourceId() {
- return R.layout.ut_channel_scan;
- }
-
- @Override
- protected int[] getParentIdsForDelay() {
- return new int[] {R.id.progress_holder};
- }
-
- private void startScan(int channelMapId) {
- mChannelScanTask = new ChannelScanTask(channelMapId);
- mChannelScanTask.execute();
- }
-
- @Override
- public void onPause() {
- Log.d(TAG, "onPause");
- if (mChannelScanTask != null) {
- // Ensure scan task will stop.
- Log.w(TAG, "The activity went to the background. Stopping channel scan.");
- mChannelScanTask.stopScan();
- }
- super.onPause();
- }
-
- /**
- * Finishes the current scan thread. This fragment will be popped after the scan thread ends.
- *
- * @param cancel a flag which indicates the scan is canceled or not.
- */
- public void finishScan(boolean cancel) {
- if (mChannelScanTask != null) {
- mChannelScanTask.cancelScan(cancel);
-
- // Notifies a user of waiting to finish the scanning process.
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mChannelScanTask != null) {
- mChannelScanTask.showFinishingProgressDialog();
- }
- }
- }, SHOW_PROGRESS_DIALOG_DELAY_MS);
-
- // Hides the cancel button.
- mCancelButton.setEnabled(false);
- }
- }
-
- private class ChannelAdapter extends BaseAdapter {
- private final ArrayList<TunerChannel> mChannels;
-
- public ChannelAdapter() {
- mChannels = new ArrayList<>();
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public boolean isEnabled(int pos) {
- return false;
- }
-
- @Override
- public int getCount() {
- return mChannels.size();
- }
-
- @Override
- public Object getItem(int pos) {
- return pos;
- }
-
- @Override
- public long getItemId(int pos) {
- return pos;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
-
- if (convertView == null) {
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.ut_channel_list, parent, false);
- }
-
- TextView channelNum = (TextView) convertView.findViewById(R.id.channel_num);
- channelNum.setText(mChannels.get(position).getDisplayNumber());
-
- TextView channelName = (TextView) convertView.findViewById(R.id.channel_name);
- channelName.setText(mChannels.get(position).getName());
- return convertView;
- }
-
- public void add(TunerChannel channel) {
- mChannels.add(channel);
- notifyDataSetChanged();
- }
- }
-
- private class ChannelScanTask extends AsyncTask<Void, Integer, Void>
- implements EventDetector.EventListener, ChannelDataManager.ChannelScanListener {
- private static final int MAX_PROGRESS = 100;
-
- private final Activity mActivity;
- private final int mChannelMapId;
- private final TsStreamer mScanTsStreamer;
- private final TsStreamer mFileTsStreamer;
- private final ConditionVariable mConditionStopped;
-
- private final List<ChannelScanFileParser.ScanChannel> mScanChannelList = new ArrayList<>();
- private boolean mIsCanceled;
- private boolean mIsFinished;
- private ProgressDialog mFinishingProgressDialog;
- private CountDownLatch mLatch;
-
- public ChannelScanTask(int channelMapId) {
- mActivity = getActivity();
- mChannelMapId = channelMapId;
- if (FAKE_MODE) {
- mScanTsStreamer = new FakeTsStreamer(this);
- } else {
- TunerHal hal = ((TunerSetupActivity) mActivity).getTunerHal();
- if (hal == null) {
- throw new RuntimeException("Failed to open a DVB device");
- }
- mScanTsStreamer = new TunerTsStreamer(hal, this);
- }
- mFileTsStreamer = SCAN_LOCAL_STREAMS ? new FileTsStreamer(this, mActivity) : null;
- mConditionStopped = new ConditionVariable();
- mChannelDataManager.setChannelScanListener(this, new Handler());
- }
-
- private void maybeSetChannelListVisible() {
- mActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- int channelsFound = mAdapter.getCount();
- if (!mChannelListVisible && channelsFound > 0) {
- String format = getResources().getQuantityString(
- R.plurals.ut_channel_scan_message, channelsFound, channelsFound);
- mScanningMessage.setText(String.format(format, channelsFound));
- mChannelHolder.setVisibility(View.VISIBLE);
- mChannelListVisible = true;
- }
- }
- });
- }
-
- private void addChannel(final TunerChannel channel) {
- mActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mAdapter.add(channel);
- if (mChannelListVisible) {
- int channelsFound = mAdapter.getCount();
- String format = getResources().getQuantityString(
- R.plurals.ut_channel_scan_message, channelsFound, channelsFound);
- mScanningMessage.setText(String.format(format, channelsFound));
- }
- }
- });
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- mScanChannelList.clear();
- if (SCAN_LOCAL_STREAMS) {
- FileTsStreamer.addLocalStreamFiles(mScanChannelList);
- }
- mScanChannelList.addAll(ChannelScanFileParser.parseScanFile(
- getResources().openRawResource(mChannelMapId)));
- scanChannels();
- return null;
- }
-
- @Override
- protected void onCancelled() {
- SoftPreconditions.checkState(false, TAG, "call cancelScan instead of cancel");
- }
-
- @Override
- protected void onProgressUpdate(Integer... values) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- mProgressBar.setProgress(values[0], true);
- } else {
- mProgressBar.setProgress(values[0]);
- }
- }
-
- private void stopScan() {
- if (mLatch != null) {
- mLatch.countDown();
- }
- mConditionStopped.open();
- }
-
- private void cancelScan(boolean cancel) {
- mIsCanceled = cancel;
- stopScan();
- }
-
- private void scanChannels() {
- if (DEBUG) Log.i(TAG, "Channel scan starting");
- mChannelDataManager.notifyScanStarted();
-
- long startMs = System.currentTimeMillis();
- int i = 1;
- for (ChannelScanFileParser.ScanChannel scanChannel : mScanChannelList) {
- int frequency = scanChannel.frequency;
- String modulation = scanChannel.modulation;
- Log.i(TAG, "Tuning to " + frequency + " " + modulation);
-
- TsStreamer streamer = getStreamer(scanChannel.type);
- SoftPreconditions.checkNotNull(streamer);
- if (streamer != null && streamer.startStream(scanChannel)) {
- mLatch = new CountDownLatch(1);
- try {
- mLatch.await(CHANNEL_SCAN_PERIOD_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "The current thread is interrupted during scanChannels(). " +
- "The TS stream is stopped earlier than expected.", e);
- }
- streamer.stopStream();
-
- addChannelsWithoutVct(scanChannel);
- if (System.currentTimeMillis() > startMs + CHANNEL_SCAN_SHOW_DELAY_MS
- && !mChannelListVisible) {
- maybeSetChannelListVisible();
- }
- }
- if (mConditionStopped.block(-1)) {
- break;
- }
- publishProgress(MAX_PROGRESS * i++ / mScanChannelList.size());
- }
- mChannelDataManager.notifyScanCompleted();
- if (!mConditionStopped.block(-1)) {
- publishProgress(MAX_PROGRESS);
- }
- if (DEBUG) Log.i(TAG, "Channel scan ended");
- }
-
-
- private void addChannelsWithoutVct(ChannelScanFileParser.ScanChannel scanChannel) {
- if (scanChannel.radioFrequencyNumber == null
- || !(mScanTsStreamer instanceof TunerTsStreamer)) {
- return;
- }
- for (TunerChannel tunerChannel
- : ((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()) {
- if ((tunerChannel.getVideoPid() != TunerChannel.INVALID_PID)
- && (tunerChannel.getAudioPid() != TunerChannel.INVALID_PID)) {
- tunerChannel.setFrequency(scanChannel.frequency);
- tunerChannel.setModulation(scanChannel.modulation);
- tunerChannel.setShortName(String.format(Locale.US, VCTLESS_CHANNEL_NAME_FORMAT,
- scanChannel.radioFrequencyNumber,
- tunerChannel.getProgramNumber()));
- tunerChannel.setVirtualMajor(scanChannel.radioFrequencyNumber);
- tunerChannel.setVirtualMinor(tunerChannel.getProgramNumber());
- onChannelDetected(tunerChannel, true);
- }
- }
- }
-
- private TsStreamer getStreamer(int type) {
- switch (type) {
- case Channel.TYPE_TUNER:
- return mScanTsStreamer;
- case Channel.TYPE_FILE:
- return mFileTsStreamer;
- default:
- return null;
- }
- }
-
- @Override
- public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
- mChannelDataManager.notifyEventDetected(channel, items);
- }
-
- @Override
- public void onChannelScanDone() {
- if (mLatch != null) {
- mLatch.countDown();
- }
- }
-
- @Override
- public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- if (channelArrivedAtFirstTime) {
- Log.i(TAG, "Found channel " + channel);
- }
- if (channelArrivedAtFirstTime && channel.hasAudio()) {
- // Playbacks with video-only stream have not been tested yet.
- // No video-only channel has been found.
- addChannel(channel);
- mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
- }
- }
-
- public void showFinishingProgressDialog() {
- // Show a progress dialog to wait for the scanning process if it's not done yet.
- if (!mIsFinished && mFinishingProgressDialog == null) {
- mFinishingProgressDialog = ProgressDialog.show(mActivity, "",
- getString(R.string.ut_setup_cancel), true, false);
- }
- }
-
- @Override
- public void onChannelHandlingDone() {
- mChannelDataManager.setCurrentVersion(mActivity);
- mChannelDataManager.releaseSafely();
- mIsFinished = true;
- TunerPreferences.setScannedChannelCount(mActivity.getApplicationContext(),
- mChannelDataManager.getScannedChannelCount());
- // Cancel a previously shown notification.
- TunerSetupActivity.cancelNotification(mActivity.getApplicationContext());
- // Mark scan as done
- TunerPreferences.setScanDone(mActivity.getApplicationContext());
- // finishing will be done manually.
- if (mFinishingProgressDialog != null) {
- mFinishingProgressDialog.dismiss();
- }
- // If the fragment is not resumed, the next fragment (scan result page) can't be
- // displayed. In that case, just close the activity.
- if (isResumed()) {
- onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH);
- } else if (getActivity() != null) {
- getActivity().finish();
- }
- mChannelScanTask = null;
- }
- }
-
- private static class FakeTsStreamer implements TsStreamer {
- private final EventDetector.EventListener mEventListener;
- private int mProgramNumber = 0;
-
- FakeTsStreamer(EventDetector.EventListener eventListener) {
- mEventListener = eventListener;
- }
-
- @Override
- public boolean startStream(ChannelScanFileParser.ScanChannel channel) {
- if (++mProgramNumber % 2 == 1) {
- return true;
- }
- final String displayNumber = Integer.toString(mProgramNumber);
- final String name = "Channel-" + mProgramNumber;
- mEventListener.onChannelDetected(new TunerChannel(mProgramNumber, new ArrayList<>()) {
- @Override
- public String getDisplayNumber() {
- return displayNumber;
- }
-
- @Override
- public String getName() {
- return name;
- }
- }, true);
- return true;
- }
-
- @Override
- public boolean startStream(TunerChannel channel) {
- return false;
- }
-
- @Override
- public void stopStream() {
- }
-
- @Override
- public TsDataSource createDataSource() {
- return null;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java
deleted file mode 100644
index 3b8cd823..00000000
--- a/src/com/android/tv/tuner/setup/ScanResultFragment.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.setup;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.util.TunerInputInfoUtils;
-
-import java.util.List;
-
-/**
- * A fragment for initial screen.
- */
-public class ScanResultFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.ScanResultFragment";
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- return new ContentFragment();
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return false;
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
- private int mChannelCountOnPreference;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mChannelCountOnPreference = TunerPreferences.getScannedChannelCount(context);
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title;
- String description;
- String breadcrumb;
- if (mChannelCountOnPreference > 0) {
- Resources res = getResources();
- title = res.getQuantityString(R.plurals.ut_result_found_title,
- mChannelCountOnPreference, mChannelCountOnPreference);
- description = res.getQuantityString(R.plurals.ut_result_found_description,
- mChannelCountOnPreference, mChannelCountOnPreference);
- breadcrumb = null;
- } else {
- Bundle args = getArguments();
- int tunerType =
- (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0));
- title = getString(R.string.ut_result_not_found_title);
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- description = getString(R.string.ut_result_not_found_description);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- description = getString(R.string.nt_result_not_found_description);
- break;
- default:
- description = getString(R.string.bt_result_not_found_description);
- }
- breadcrumb = getString(R.string.ut_setup_breadcrumb);
- }
- return new Guidance(title, description, breadcrumb, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String[] choices;
- int doneActionIndex;
- if (mChannelCountOnPreference > 0) {
- choices = getResources().getStringArray(R.array.ut_result_found_choices);
- doneActionIndex = 0;
- } else {
- choices = getResources().getStringArray(R.array.ut_result_not_found_choices);
- doneActionIndex = 1;
- }
- for (int i = 0; i < choices.length; ++i) {
- if (i == doneActionIndex) {
- actions.add(new GuidedAction.Builder(getActivity()).id(ACTION_DONE)
- .title(choices[i]).build());
- } else {
- actions.add(new GuidedAction.Builder(getActivity()).id(i).title(choices[i])
- .build());
- }
- }
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java
deleted file mode 100644
index e9f3baa7..00000000
--- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.setup;
-
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.tv.TvContract;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.support.v4.app.NotificationCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.widget.Toast;
-
-import com.android.tv.Features;
-import com.android.tv.TvApplication;
-import com.android.tv.common.AutoCloseableUtils;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonConstants;
-import com.android.tv.common.TvCommonUtils;
-import com.android.tv.common.ui.setup.SetupActivity;
-import com.android.tv.common.ui.setup.SetupFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.experiments.Experiments;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-import com.android.tv.tuner.util.PostalCodeUtils;
-
-import java.util.concurrent.Executor;
-
-/**
- * An activity that serves tuner setup process.
- */
-public class TunerSetupActivity extends SetupActivity {
- private static final String TAG = "TunerSetupActivity";
- private static final boolean DEBUG = false;
-
- /**
- * Key for passing tuner type to sub-fragments.
- */
- public static final String KEY_TUNER_TYPE = "TunerSetupActivity.tunerType";
-
- // For the notification.
- private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity";
- private static final String TUNER_SET_UP_NOTIFICATION_CHANNEL_ID = "tuner_setup_channel";
- private static final String NOTIFY_TAG = "TunerSetup";
- private static final int NOTIFY_ID = 1000;
- private static final String TAG_DRAWABLE = "drawable";
- private static final String TAG_ICON = "ic_launcher_s";
- private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
-
- private static final int CHANNEL_MAP_SCAN_FILE[] = {
- R.raw.ut_us_atsc_center_frequencies_8vsb,
- R.raw.ut_us_cable_standard_center_frequencies_qam256,
- R.raw.ut_us_all,
- R.raw.ut_kr_atsc_center_frequencies_8vsb,
- R.raw.ut_kr_cable_standard_center_frequencies_qam256,
- R.raw.ut_kr_all,
- R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256,
- R.raw.ut_euro_dvbt_all,
- R.raw.ut_euro_dvbt_all,
- R.raw.ut_euro_dvbt_all
- };
-
- private ScanFragment mLastScanFragment;
- private Integer mTunerType;
- private TunerHalFactory mTunerHalFactory;
- private boolean mNeedToShowPostalCodeFragment;
- private String mPreviousPostalCode;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate");
- new AsyncTask<Void, Void, Integer>() {
- @Override
- protected Integer doInBackground(Void... arg0) {
- return TunerHal.getTunerTypeAndCount(TunerSetupActivity.this).first;
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- if (!TunerSetupActivity.this.isDestroyed()) {
- mTunerType = result;
- if (result == null) {
- finish();
- } else {
- showInitialFragment();
- }
- }
- }
- }.execute();
- TvApplication.setCurrentRunningProcess(this, false);
- super.onCreate(savedInstanceState);
- // TODO: check {@link shouldShowRequestPermissionRationale}.
- if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
- // No need to check the request result.
- requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
- PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
- }
- mTunerHalFactory = new TunerHalFactory(getApplicationContext());
- try {
- // Updating postal code takes time, therefore we called it here for "warm-up".
- mPreviousPostalCode = PostalCodeUtils.getLastPostalCode(this);
- PostalCodeUtils.setLastPostalCode(this, null);
- PostalCodeUtils.updatePostalCode(this);
- } catch (Exception e) {
- // Do nothing. If the last known postal code is null, we'll show guided fragment to
- // prompt users to input postal code before ConnectionTypeFragment is shown.
- Log.i(TAG, "Can't get postal code:" + e);
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
- if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
- && Experiments.CLOUD_EPG.get()) {
- try {
- // Updating postal code takes time, therefore we should update postal code
- // right after the permission is granted, so that the subsequent operations,
- // especially EPG fetcher, could get the newly updated postal code.
- PostalCodeUtils.updatePostalCode(this);
- } catch (Exception e) {
- // Do nothing
- }
- }
- }
- }
-
- @Override
- protected Fragment onCreateInitialFragment() {
- if (mTunerType != null) {
- SetupFragment fragment = new WelcomeFragment();
- Bundle args = new Bundle();
- args.putInt(KEY_TUNER_TYPE, mTunerType);
- fragment.setArguments(args);
- fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
- | SetupFragment.FRAGMENT_REENTER_TRANSITION);
- return fragment;
- } else {
- return null;
- }
- }
-
- @Override
- protected boolean executeAction(String category, int actionId, Bundle params) {
- switch (category) {
- case WelcomeFragment.ACTION_CATEGORY:
- switch (actionId) {
- case SetupMultiPaneFragment.ACTION_DONE:
- // If the scan was performed, then the result should be OK.
- setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK);
- finish();
- break;
- default:
- if (mNeedToShowPostalCodeFragment
- || Features.ENABLE_CLOUD_EPG_REGION.isEnabled(
- getApplicationContext())
- && TextUtils.isEmpty(
- PostalCodeUtils.getLastPostalCode(this))) {
- // We cannot get postal code automatically. Postal code input fragment
- // should always be shown even if users have input some valid postal
- // code in this activity before.
- mNeedToShowPostalCodeFragment = true;
- showPostalCodeFragment();
- } else {
- showConnectionTypeFragment();
- }
- break;
- }
- return true;
- case PostalCodeFragment.ACTION_CATEGORY:
- if (actionId == SetupMultiPaneFragment.ACTION_DONE
- || actionId == SetupMultiPaneFragment.ACTION_SKIP) {
- showConnectionTypeFragment();
- }
- return true;
- case ConnectionTypeFragment.ACTION_CATEGORY:
- if (mTunerHalFactory.getOrCreate() == null) {
- finish();
- Toast.makeText(getApplicationContext(),
- R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show();
- return true;
- }
- mLastScanFragment = new ScanFragment();
- Bundle args1 = new Bundle();
- args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
- CHANNEL_MAP_SCAN_FILE[actionId]);
- args1.putInt(KEY_TUNER_TYPE, mTunerType);
- mLastScanFragment.setArguments(args1);
- showFragment(mLastScanFragment, true);
- return true;
- case ScanFragment.ACTION_CATEGORY:
- switch (actionId) {
- case ScanFragment.ACTION_CANCEL:
- getFragmentManager().popBackStack();
- return true;
- case ScanFragment.ACTION_FINISH:
- mTunerHalFactory.clear();
- SetupFragment fragment = new ScanResultFragment();
- Bundle args2 = new Bundle();
- args2.putInt(KEY_TUNER_TYPE, mTunerType);
- fragment.setArguments(args2);
- fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
- | SetupFragment.FRAGMENT_REENTER_TRANSITION);
- showFragment(fragment, true);
- return true;
- }
- break;
- case ScanResultFragment.ACTION_CATEGORY:
- switch (actionId) {
- case SetupMultiPaneFragment.ACTION_DONE:
- setResult(RESULT_OK);
- finish();
- break;
- default:
- SetupFragment fragment = new ConnectionTypeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
- break;
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- FragmentManager manager = getFragmentManager();
- int count = manager.getBackStackEntryCount();
- if (count > 0) {
- String lastTag = manager.getBackStackEntryAt(count - 1).getName();
- if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
- // Pops fragment including ScanFragment.
- manager.popBackStack(manager.getBackStackEntryAt(count - 2).getName(),
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
- return true;
- } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) {
- mLastScanFragment.finishScan(true);
- return true;
- }
- }
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onDestroy() {
- if (mPreviousPostalCode != null && PostalCodeUtils.getLastPostalCode(this) == null) {
- PostalCodeUtils.setLastPostalCode(this, mPreviousPostalCode);
- }
- super.onDestroy();
- }
-
- /**
- * A callback to be invoked when the TvInputService is enabled or disabled.
- *
- * @param context a {@link Context} instance
- * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled;
- * otherwise {@code false}
- */
- public static void onTvInputEnabled(Context context, boolean enabled, Integer tunerType) {
- // Send a notification for tuner setup if there's no channels and the tuner TV input
- // setup has been not done.
- boolean channelScanDoneOnPreference = TunerPreferences.isScanDone(context);
- int channelCountOnPreference = TunerPreferences.getScannedChannelCount(context);
- if (enabled && !channelScanDoneOnPreference && channelCountOnPreference == 0) {
- TunerPreferences.setShouldShowSetupActivity(context, true);
- sendNotification(context, tunerType);
- } else {
- TunerPreferences.setShouldShowSetupActivity(context, false);
- cancelNotification(context);
- }
- }
-
- /**
- * Returns a {@link Intent} to launch the tuner TV input service.
- *
- * @param context a {@link Context} instance
- */
- public static Intent createSetupActivity(Context context) {
- String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(),
- TunerTvInputService.class.getName()));
-
- // Make an intent to launch the setup activity of TV tuner input.
- Intent intent = TvCommonUtils.createSetupIntent(
- new Intent(context, TunerSetupActivity.class), inputId);
- intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId);
- Intent tvActivityIntent = new Intent();
- tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME));
- intent.putExtra(TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent);
- return intent;
- }
-
- /**
- * Gets the currently used tuner HAL.
- */
- TunerHal getTunerHal() {
- return mTunerHalFactory.getOrCreate();
- }
-
- /**
- * Generates tuner HAL.
- */
- void generateTunerHal() {
- mTunerHalFactory.generate();
- }
-
- /**
- * Clears the currently used tuner HAL.
- */
- void clearTunerHal() {
- mTunerHalFactory.clear();
- }
-
- /**
- * Returns a {@link PendingIntent} to launch the tuner TV input service.
- *
- * @param context a {@link Context} instance
- */
- private static PendingIntent createPendingIntentForSetupActivity(Context context) {
- return PendingIntent.getActivity(context, 0, createSetupActivity(context),
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- private static void sendNotification(Context context, Integer tunerType) {
- SoftPreconditions.checkState(tunerType != null, TAG,
- "tunerType is null when send notification");
- if (tunerType == null) {
- return;
- }
- Resources resources = context.getResources();
- String contentTitle = resources.getString(R.string.ut_setup_notification_content_title);
- int contentTextId = 0;
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_BUILT_IN:
- contentTextId = R.string.bt_setup_notification_content_text;
- break;
- case TunerHal.TUNER_TYPE_USB:
- contentTextId = R.string.ut_setup_notification_content_text;
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- contentTextId = R.string.nt_setup_notification_content_text;
- break;
- }
- String contentText = resources.getString(contentTextId);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- sendNotificationInternal(context, contentTitle, contentText);
- } else {
- Bitmap largeIcon = BitmapFactory.decodeResource(resources,
- R.drawable.recommendation_antenna);
- sendRecommendationCard(context, contentTitle, contentText, largeIcon);
- }
- }
-
- /**
- * Sends the recommendation card to start the tuner TV input setup activity.
- *
- * @param context a {@link Context} instance
- */
- private static void sendRecommendationCard(Context context, String contentTitle,
- String contentText, Bitmap largeIcon) {
- // Build and send the notification.
- Notification notification = new NotificationCompat.BigPictureStyle(
- new NotificationCompat.Builder(context)
- .setAutoCancel(false)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setContentInfo(contentText)
- .setCategory(Notification.CATEGORY_RECOMMENDATION)
- .setLargeIcon(largeIcon)
- .setSmallIcon(context.getResources().getIdentifier(
- TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
- .setContentIntent(createPendingIntentForSetupActivity(context)))
- .build();
- NotificationManager notificationManager = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
- }
-
- private static void sendNotificationInternal(Context context, String contentTitle,
- String contentText) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(
- Context.NOTIFICATION_SERVICE);
- notificationManager.createNotificationChannel(new NotificationChannel(
- TUNER_SET_UP_NOTIFICATION_CHANNEL_ID,
- context.getResources().getString(R.string.ut_setup_notification_channel_name),
- NotificationManager.IMPORTANCE_HIGH));
- Notification notification = new Notification.Builder(
- context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setSmallIcon(context.getResources().getIdentifier(
- TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
- .setContentIntent(createPendingIntentForSetupActivity(context))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .extend(new Notification.TvExtender())
- .build();
- notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
- }
-
- private void showPostalCodeFragment() {
- SetupFragment fragment = new PostalCodeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
- }
-
- private void showConnectionTypeFragment() {
- SetupFragment fragment = new ConnectionTypeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
- }
-
- /**
- * Cancels the previously shown notification.
- *
- * @param context a {@link Context} instance
- */
- public static void cancelNotification(Context context) {
- NotificationManager notificationManager = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID);
- }
-
- @VisibleForTesting
- static class TunerHalFactory {
- private Context mContext;
- @VisibleForTesting
- TunerHal mTunerHal;
- private GenerateTunerHalTask mGenerateTunerHalTask;
- private final Executor mExecutor;
-
- TunerHalFactory(Context context) {
- this(context, AsyncTask.SERIAL_EXECUTOR);
- }
-
- TunerHalFactory(Context context, Executor executor) {
- mContext = context;
- mExecutor = executor;
- }
-
- /**
- * Returns tuner HAL currently used. If it's {@code null} and tuner HAL is not generated
- * before, tries to generate it synchronously.
- */
- @WorkerThread
- TunerHal getOrCreate() {
- if (mGenerateTunerHalTask != null
- && mGenerateTunerHalTask.getStatus() != AsyncTask.Status.FINISHED) {
- try {
- return mGenerateTunerHalTask.get();
- } catch (Exception e) {
- Log.e(TAG, "Cannot get Tuner HAL: " + e);
- }
- } else if (mGenerateTunerHalTask == null && mTunerHal == null) {
- mTunerHal = createInstance();
- }
- return mTunerHal;
- }
-
- /**
- * Generates tuner hal for scanning with asynchronous tasks.
- */
- @MainThread
- void generate() {
- if (mGenerateTunerHalTask == null && mTunerHal == null) {
- mGenerateTunerHalTask = new GenerateTunerHalTask();
- mGenerateTunerHalTask.executeOnExecutor(mExecutor);
- }
- }
-
- /**
- * Clears the currently used tuner hal.
- */
- @MainThread
- void clear() {
- if (mGenerateTunerHalTask != null) {
- mGenerateTunerHalTask.cancel(true);
- mGenerateTunerHalTask = null;
- }
- if (mTunerHal != null) {
- AutoCloseableUtils.closeQuietly(mTunerHal);
- mTunerHal = null;
- }
- }
-
- @WorkerThread
- protected TunerHal createInstance() {
- return TunerHal.createInstance(mContext);
- }
-
- class GenerateTunerHalTask extends AsyncTask<Void, Void, TunerHal> {
- @Override
- protected TunerHal doInBackground(Void... args) {
- return createInstance();
- }
-
- @Override
- protected void onPostExecute(TunerHal tunerHal) {
- mTunerHal = tunerHal;
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/setup/WelcomeFragment.java b/src/com/android/tv/tuner/setup/WelcomeFragment.java
deleted file mode 100644
index feae1ec9..00000000
--- a/src/com/android/tv/tuner/setup/WelcomeFragment.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.setup;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import java.util.List;
-
-/**
- * A fragment for initial screen.
- */
-public class WelcomeFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.WelcomeFragment";
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- ContentFragment fragment = new ContentFragment();
- fragment.setArguments(getArguments());
- return fragment;
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return false;
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
- private int mChannelCountOnPreference;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- mChannelCountOnPreference =
- TunerPreferences.getScannedChannelCount(getActivity().getApplicationContext());
- super.onCreate(savedInstanceState);
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title;
- String description;
- int tunerType = getArguments().getInt(TunerSetupActivity.KEY_TUNER_TYPE,
- TunerHal.TUNER_TYPE_BUILT_IN);
- if (mChannelCountOnPreference == 0) {
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- title = getString(R.string.ut_setup_new_title);
- description = getString(R.string.ut_setup_new_description);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- title = getString(R.string.nt_setup_new_title);
- description = getString(R.string.nt_setup_new_description);
- break;
- default:
- title = getString(R.string.bt_setup_new_title);
- description = getString(R.string.bt_setup_new_description);
- }
- } else {
- title = getString(R.string.bt_setup_again_title);
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- description = getString(R.string.ut_setup_again_description);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- description = getString(R.string.nt_setup_again_description);
- break;
- default:
- description = getString(R.string.bt_setup_again_description);
- }
- }
- return new Guidance(title, description, null, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String[] choices = getResources().getStringArray(mChannelCountOnPreference == 0
- ? R.array.ut_setup_new_choices : R.array.ut_setup_again_choices);
- for (int i = 0; i < choices.length - 1; ++i) {
- actions.add(new GuidedAction.Builder(getActivity()).id(i).title(choices[i])
- .build());
- }
- actions.add(new GuidedAction.Builder(getActivity()).id(ACTION_DONE)
- .title(choices[choices.length - 1]).build());
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java
deleted file mode 100644
index f17dd46b..00000000
--- a/src/com/android/tv/tuner/source/FileTsStreamer.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import android.content.Context;
-import android.os.Environment;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
-import com.android.tv.Features;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.ChannelScanFileParser.ScanChannel;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.ts.TsParser;
-import com.android.tv.tuner.tvinput.EventDetector;
-import com.android.tv.tuner.tvinput.FileSourceEventDetector;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Provides MPEG-2 TS stream sources for both channel scanning and channel playing from a local file
- * generated by capturing TV signal.
- */
-public class FileTsStreamer implements TsStreamer {
- private static final String TAG = "FileTsStreamer";
-
- private static final int TS_PACKET_SIZE = 188;
- private static final int TS_SYNC_BYTE = 0x47;
- private static final int MIN_READ_UNIT = TS_PACKET_SIZE * 10;
- private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~20KB
- private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 4000; // ~ 8MB
- private static final int PADDING_SIZE = MIN_READ_UNIT * 1000; // ~2MB
- private static final int READ_TIMEOUT_MS = 10000; // 10 secs.
- private static final int BUFFER_UNDERRUN_SLEEP_MS = 10;
- private static final String FILE_DIR =
- new File(Environment.getExternalStorageDirectory(), "Streams").getAbsolutePath();
-
- // Virtual frequency base used for file-based source
- public static final int FREQ_BASE = 100;
-
- private final Object mCircularBufferMonitor = new Object();
- private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE];
- private final FileSourceEventDetector mEventDetector;
- private final Context mContext;
-
- private long mBytesFetched;
- private long mLastReadPosition;
- private boolean mStreaming;
-
- private Thread mStreamingThread;
- private StreamProvider mSource;
-
- public static class FileDataSource extends TsDataSource {
- private final FileTsStreamer mTsStreamer;
- private final AtomicLong mLastReadPosition = new AtomicLong(0);
- private long mStartBufferedPosition;
-
- private FileDataSource(FileTsStreamer tsStreamer) {
- mTsStreamer = tsStreamer;
- mStartBufferedPosition = tsStreamer.getBufferedPosition();
- }
-
- @Override
- public long getBufferedPosition() {
- return mTsStreamer.getBufferedPosition() - mStartBufferedPosition;
- }
-
- @Override
- public long getLastReadPosition() {
- return mLastReadPosition.get();
- }
-
- @Override
- public void shiftStartPosition(long offset) {
- SoftPreconditions.checkState(mLastReadPosition.get() == 0);
- SoftPreconditions.checkArgument(0 <= offset && offset <= getBufferedPosition());
- mStartBufferedPosition += offset;
- }
-
- @Override
- public long open(DataSpec dataSpec) throws IOException {
- mLastReadPosition.set(0);
- return C.LENGTH_UNBOUNDED;
- }
-
- @Override
- public void close() {
- }
-
- @Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
- int ret = mTsStreamer.readAt(mStartBufferedPosition + mLastReadPosition.get(), buffer,
- offset, readLength);
- if (ret > 0) {
- mLastReadPosition.addAndGet(ret);
- }
- return ret;
- }
- }
-
- /**
- * Creates {@link TsStreamer} for scanning & playing MPEG-2 TS file.
- * @param eventListener the listener for channel & program information
- */
- public FileTsStreamer(EventDetector.EventListener eventListener, Context context) {
- mEventDetector =
- new FileSourceEventDetector(
- eventListener, Features.ENABLE_FILE_DVB.isEnabled(context));
- mContext = context;
- }
-
- @Override
- public boolean startStream(ScanChannel channel) {
- String filepath = new File(FILE_DIR, channel.filename).getAbsolutePath();
- mSource = new StreamProvider(filepath);
- if (!mSource.isReady()) {
- return false;
- }
- mEventDetector.start(mSource, FileSourceEventDetector.ALL_PROGRAM_NUMBERS);
- mSource.addPidFilter(TsParser.PAT_PID);
- mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID);
- if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) {
- mSource.addPidFilter(TsParser.DVB_EIT_PID);
- mSource.addPidFilter(TsParser.DVB_SDT_PID);
- }
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- return true;
- }
- mStreaming = true;
- }
-
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
-
- @Override
- public boolean startStream(TunerChannel channel) {
- Log.i(TAG, "tuneToChannel with: " + channel.getFilepath());
- mSource = new StreamProvider(channel.getFilepath());
- if (!mSource.isReady()) {
- return false;
- }
- mEventDetector.start(mSource, channel.getProgramNumber());
- mSource.addPidFilter(channel.getVideoPid());
- for (Integer i : channel.getAudioPids()) {
- mSource.addPidFilter(i);
- }
- mSource.addPidFilter(channel.getPcrPid());
- mSource.addPidFilter(TsParser.PAT_PID);
- mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID);
- if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) {
- mSource.addPidFilter(TsParser.DVB_EIT_PID);
- mSource.addPidFilter(TsParser.DVB_SDT_PID);
- }
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- return true;
- }
- mStreaming = true;
- }
-
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
-
- /**
- * Blocks the current thread until the streaming thread stops. In rare cases when the tuner
- * device is overloaded this can take a while, but usually it returns pretty quickly.
- */
- @Override
- public void stopStream() {
- synchronized (mCircularBufferMonitor) {
- mStreaming = false;
- mCircularBufferMonitor.notify();
- }
-
- try {
- if (mStreamingThread != null) {
- mStreamingThread.join();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- @Override
- public TsDataSource createDataSource() {
- return new FileDataSource(this);
- }
-
- /**
- * Returns the current buffered position from the file.
- * @return the current buffered position
- */
- public long getBufferedPosition() {
- synchronized (mCircularBufferMonitor) {
- return mBytesFetched;
- }
- }
-
- /**
- * Provides MPEG-2 transport stream from a local file. Stream can be filtered by PID.
- */
- public static class StreamProvider {
- private final String mFilepath;
- private final SparseBooleanArray mPids = new SparseBooleanArray();
- private final byte[] mPreBuffer = new byte[READ_BUFFER_SIZE];
-
- private BufferedInputStream mInputStream;
-
- private StreamProvider(String filepath) {
- mFilepath = filepath;
- open(filepath);
- }
-
- private void open(String filepath) {
- try {
- mInputStream = new BufferedInputStream(new FileInputStream(filepath));
- } catch (IOException e) {
- Log.e(TAG, "Error opening input stream", e);
- mInputStream = null;
- }
- }
-
- private boolean isReady() {
- return mInputStream != null;
- }
-
- /**
- * Returns the file path of the MPEG-2 TS file.
- */
- public String getFilepath() {
- return mFilepath;
- }
-
- /**
- * Adds a pid for filtering from the MPEG-2 TS file.
- */
- public void addPidFilter(int pid) {
- mPids.put(pid, true);
- }
-
- /**
- * Returns whether the current pid filter is empty or not.
- */
- public boolean isFilterEmpty() {
- return mPids.size() == 0;
- }
-
- /**
- * Clears the current pid filter.
- */
- public void clearPidFilter() {
- mPids.clear();
- }
-
- /**
- * Returns whether a pid is in the pid filter or not.
- * @param pid the pid to check
- */
- public boolean isInFilter(int pid) {
- return mPids.get(pid);
- }
-
- /**
- * Reads from the MPEG-2 TS file to buffer.
- *
- * @param inputBuffer to read
- * @return the number of read bytes
- */
- private int read(byte[] inputBuffer) {
- int readSize = readInternal();
- if (readSize <= 0) {
- // Reached the end of stream. Restart from the beginning.
- close();
- open(mFilepath);
- if (mInputStream == null) {
- return -1;
- }
- readSize = readInternal();
- }
-
- if (mPreBuffer[0] != TS_SYNC_BYTE) {
- Log.e(TAG, "Error reading input stream - no TS sync found");
- return -1;
- }
- int filteredSize = 0;
- for (int i = 0, destPos = 0; i < readSize; i += TS_PACKET_SIZE) {
- if (mPreBuffer[i] == TS_SYNC_BYTE) {
- int pid = ((mPreBuffer[i + 1] & 0x1f) << 8) + (mPreBuffer[i + 2] & 0xff);
- if (mPids.get(pid)) {
- System.arraycopy(mPreBuffer, i, inputBuffer, destPos, TS_PACKET_SIZE);
- destPos += TS_PACKET_SIZE;
- filteredSize += TS_PACKET_SIZE;
- }
- }
- }
- return filteredSize;
- }
-
- private int readInternal() {
- int readSize;
- try {
- readSize = mInputStream.read(mPreBuffer, 0, mPreBuffer.length);
- } catch (IOException e) {
- Log.e(TAG, "Error reading input stream", e);
- return -1;
- }
- return readSize;
- }
-
- private void close() {
- try {
- mInputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "Error closing input stream:", e);
- }
- mInputStream = null;
- }
- }
-
- /**
- * Reads data from internal buffer.
- * @param pos the position to read from
- * @param buffer to read
- * @param offset start position of the read buffer
- * @param amount number of bytes to read
- * @return number of read bytes when successful, {@code -1} otherwise
- * @throws IOException
- */
- public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException {
- synchronized (mCircularBufferMonitor) {
- long initialBytesFetched = mBytesFetched;
- while (mBytesFetched < pos + amount && mStreaming) {
- try {
- mCircularBufferMonitor.wait(READ_TIMEOUT_MS);
- } catch (InterruptedException e) {
- // Wait again.
- Thread.currentThread().interrupt();
- }
- if (initialBytesFetched == mBytesFetched) {
- Log.w(TAG, "No data update for " + READ_TIMEOUT_MS + "ms. returning -1.");
-
- // Returning -1 will make demux report EOS so that the input service can retry
- // the playback.
- return -1;
- }
- }
- if (!mStreaming) {
- Log.w(TAG, "Stream is already stopped.");
- return -1;
- }
- if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) {
- Log.e(TAG, "Demux is requesting the data which is already overwritten.");
- return -1;
- }
- int posInBuffer = (int) (pos % CIRCULAR_BUFFER_SIZE);
- int bytesToCopyInFirstPass = amount;
- if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) {
- bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer;
- }
- System.arraycopy(mCircularBuffer, posInBuffer, buffer, offset, bytesToCopyInFirstPass);
- if (bytesToCopyInFirstPass < amount) {
- System.arraycopy(mCircularBuffer, 0, buffer, offset + bytesToCopyInFirstPass,
- amount - bytesToCopyInFirstPass);
- }
- mLastReadPosition = pos + amount;
- mCircularBufferMonitor.notify();
- return amount;
- }
- }
-
- /**
- * Adds {@link ScanChannel} instance for local files.
- *
- * @param output a list of channels where the results will be placed in
- */
- public static void addLocalStreamFiles(List<ScanChannel> output) {
- File dir = new File(FILE_DIR);
- if (!dir.exists()) return;
-
- File[] tsFiles = dir.listFiles();
- if (tsFiles == null) return;
- int freq = FileTsStreamer.FREQ_BASE;
- for (File file : tsFiles) {
- if (!file.isFile()) continue;
- output.add(ScanChannel.forFile(freq, file.getName()));
- freq += 100;
- }
- }
-
- /**
- * A thread managing a circular buffer that holds stream data to be consumed by player.
- * Keeps reading data in from a {@link StreamProvider} to hold enough amount for buffering.
- * Started and stopped by {@link #startStream()} and {@link #stopStream()}, respectively.
- */
- private class StreamingThread extends Thread {
- @Override
- public void run() {
- byte[] dataBuffer = new byte[READ_BUFFER_SIZE];
-
- synchronized (mCircularBufferMonitor) {
- mBytesFetched = 0;
- mLastReadPosition = 0;
- }
-
- while (true) {
- synchronized (mCircularBufferMonitor) {
- while ((mBytesFetched - mLastReadPosition + PADDING_SIZE) > CIRCULAR_BUFFER_SIZE
- && mStreaming) {
- try {
- mCircularBufferMonitor.wait();
- } catch (InterruptedException e) {
- // Wait again.
- Thread.currentThread().interrupt();
- }
- }
- if (!mStreaming) {
- break;
- }
- }
-
- int bytesWritten = mSource.read(dataBuffer);
- if (bytesWritten <= 0) {
- try {
- // When buffer is underrun, we sleep for short time to prevent
- // unnecessary CPU draining.
- sleep(BUFFER_UNDERRUN_SLEEP_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- continue;
- }
-
- mEventDetector.feedTSStream(dataBuffer, 0, bytesWritten);
-
- synchronized (mCircularBufferMonitor) {
- int posInBuffer = (int) (mBytesFetched % CIRCULAR_BUFFER_SIZE);
- int bytesToCopyInFirstPass = bytesWritten;
- if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) {
- bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer;
- }
- System.arraycopy(dataBuffer, 0, mCircularBuffer, posInBuffer,
- bytesToCopyInFirstPass);
- if (bytesToCopyInFirstPass < bytesWritten) {
- System.arraycopy(dataBuffer, bytesToCopyInFirstPass, mCircularBuffer, 0,
- bytesWritten - bytesToCopyInFirstPass);
- }
- mBytesFetched += bytesWritten;
- mCircularBufferMonitor.notify();
- }
- }
-
- Log.i(TAG, "Streaming stopped");
- mSource.close();
- }
- }
-}
diff --git a/src/com/android/tv/tuner/source/TsDataSource.java b/src/com/android/tv/tuner/source/TsDataSource.java
deleted file mode 100644
index 2ce3e670..00000000
--- a/src/com/android/tv/tuner/source/TsDataSource.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import com.google.android.exoplayer.upstream.DataSource;
-
-/**
- * {@link DataSource} for MPEG-TS stream, which will be used by {@link TsExtractor}.
- */
-public abstract class TsDataSource implements DataSource {
-
- /**
- * Returns the number of bytes being buffered by {@link TsStreamer} so far.
- *
- * @return the buffered position
- */
- public long getBufferedPosition() {
- return 0;
- }
-
- /**
- * Returns the offset position where the last {@link DataSource#read} read.
- *
- * @return the last read position
- */
- public long getLastReadPosition() {
- return 0;
- }
-
- /**
- * Shifts start position by the specified offset.
- * Do not call this method when the class already provided MPEG-TS stream to the extractor.
- * @param offset 0 <= offset <= buffered position
- */
- public void shiftStartPosition(long offset) { }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java
deleted file mode 100644
index 16be7582..00000000
--- a/src/com/android/tv/tuner/source/TsDataSourceManager.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import android.content.Context;
-import android.support.annotation.VisibleForTesting;
-
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.tvinput.EventDetector;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Manages {@link DataSource} for playback and recording.
- * The class hides handling of {@link TunerHal} and {@link TsStreamer} from other classes.
- * One TsDataSourceManager should be created for per session.
- */
-public class TsDataSourceManager {
- private static final Object sLock = new Object();
- private static final Map<TsDataSource, TsStreamer> sTsStreamers =
- new ConcurrentHashMap<>();
-
- private static int sSequenceId;
-
- private final int mId;
- private final boolean mIsRecording;
- private final TunerTsStreamerManager mTunerStreamerManager =
- TunerTsStreamerManager.getInstance();
-
- private boolean mKeepTuneStatus;
-
- /**
- * Creates TsDataSourceManager to create and release {@link DataSource} which will be
- * used for playing and recording.
- * @param isRecording {@code true} when for recording, {@code false} otherwise
- * @return {@link TsDataSourceManager}
- */
- public static TsDataSourceManager createSourceManager(boolean isRecording) {
- int id;
- synchronized (sLock) {
- id = ++sSequenceId;
- }
- return new TsDataSourceManager(id, isRecording);
- }
-
- private TsDataSourceManager(int id, boolean isRecording) {
- mId = id;
- mIsRecording = isRecording;
- mKeepTuneStatus = true;
- }
-
- /**
- * Creates or retrieves {@link TsDataSource} for playing or recording
- * @param context a {@link Context} instance
- * @param channel to play or record
- * @param eventListener for program information which will be scanned from MPEG2-TS stream
- * @return {@link TsDataSource} which will provide the specified channel stream
- */
- public TsDataSource createDataSource(Context context, TunerChannel channel,
- EventDetector.EventListener eventListener) {
- if (channel.getType() == Channel.TYPE_FILE) {
- // MPEG2 TS captured stream file recording is not supported.
- if (mIsRecording) {
- return null;
- }
- FileTsStreamer streamer = new FileTsStreamer(eventListener, context);
- if (streamer.startStream(channel)) {
- TsDataSource source = streamer.createDataSource();
- sTsStreamers.put(source, streamer);
- return source;
- }
- return null;
- }
- return mTunerStreamerManager.createDataSource(context, channel, eventListener,
- mId, !mIsRecording && mKeepTuneStatus);
- }
-
- /**
- * Releases the specified {@link TsDataSource} and underlying {@link TunerHal}.
- * @param source to release
- */
- public void releaseDataSource(TsDataSource source) {
- if (source instanceof TunerTsStreamer.TunerDataSource) {
- mTunerStreamerManager.releaseDataSource(
- source, mId, !mIsRecording && mKeepTuneStatus);
- } else if (source instanceof FileTsStreamer.FileDataSource) {
- FileTsStreamer streamer = (FileTsStreamer) sTsStreamers.get(source);
- if (streamer != null) {
- sTsStreamers.remove(source);
- streamer.stopStream();
- }
- }
- }
-
- /**
- * Indicates that the current session has pending tunes.
- */
- public void setHasPendingTune() {
- mTunerStreamerManager.setHasPendingTune(mId);
- }
-
- /**
- * Indicates whether the underlying {@link TunerHal} should be kept or not when data source
- * is being released.
- * TODO: If b/30750953 is fixed, we can remove this function.
- * @param keepTuneStatus underlying {@link TunerHal} will be reused when data source releasing.
- */
- public void setKeepTuneStatus(boolean keepTuneStatus) {
- mKeepTuneStatus = keepTuneStatus;
- }
-
- /**
- * Add tuner hal into TunerTsStreamerManager for test.
- */
- @VisibleForTesting
- public void addTunerHalForTest(TunerHal tunerHal) {
- mTunerStreamerManager.addTunerHal(tunerHal, mId);
- }
-
- /**
- * Releases persistent resources.
- */
- public void release() {
- mTunerStreamerManager.release(mId);
- }
-}
diff --git a/src/com/android/tv/tuner/source/TsStreamWriter.java b/src/com/android/tv/tuner/source/TsStreamWriter.java
deleted file mode 100644
index 30650555..00000000
--- a/src/com/android/tv/tuner/source/TsStreamWriter.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import android.content.Context;
-import android.util.Log;
-import com.android.tv.tuner.data.TunerChannel;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Stores TS files to the disk for debugging.
- */
-public class TsStreamWriter {
- private static final String TAG = "TsStreamWriter";
- private static final boolean DEBUG = false;
-
- private static final long TIME_LIMIT_MS = 10000; // 10s
- private static final int NO_INSTANCE_ID = 0;
- private static final int MAX_GET_ID_RETRY_COUNT = 5;
- private static final int MAX_INSTANCE_ID = 10000;
- private static final String SEPARATOR = "_";
-
- private FileOutputStream mFileOutputStream;
- private long mFileStartTimeMs;
- private String mFileName = null;
- private final String mDirectoryPath;
- private final File mDirectory;
- private final int mInstanceId;
- private TunerChannel mChannel;
-
- public TsStreamWriter(Context context) {
- File externalFilesDir = context.getExternalFilesDir(null);
- if (externalFilesDir == null || !externalFilesDir.isDirectory()) {
- mDirectoryPath = null;
- mDirectory = null;
- mInstanceId = NO_INSTANCE_ID;
- if (DEBUG) {
- Log.w(TAG, "Fail to get external files dir!");
- }
- } else {
- mDirectoryPath = externalFilesDir.getPath() + "/EngTsStream";
- mDirectory = new File(mDirectoryPath);
- if (!mDirectory.exists()) {
- boolean madeDir = mDirectory.mkdir();
- if (!madeDir) {
- Log.w(TAG, "Error. Fail to create folder!");
- }
- }
- mInstanceId = generateInstanceId();
- }
- }
-
- /**
- * Sets the current channel.
- *
- * @param channel curren channel of the stream
- */
- public void setChannel(TunerChannel channel) {
- mChannel = channel;
- }
-
- /**
- * Opens a file to store TS data.
- */
- public void openFile() {
- if (mChannel == null || mDirectoryPath == null) {
- return;
- }
- mFileStartTimeMs = System.currentTimeMillis();
- mFileName = mChannel.getDisplayNumber() + SEPARATOR + mFileStartTimeMs + SEPARATOR
- + mInstanceId + ".ts";
- String filePath = mDirectoryPath + "/" + mFileName;
- try {
- mFileOutputStream = new FileOutputStream(filePath, false);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Cannot open file: " + filePath, e);
- }
- }
-
- /**
- * Closes the file and stops storing TS data.
- *
- * @param calledWhenStopStream {@code true} if this method is called when the stream is stopped
- * {@code false} otherwise
- */
- public void closeFile(boolean calledWhenStopStream) {
- if (mFileOutputStream == null) {
- return;
- }
- try {
- mFileOutputStream.close();
- deleteOutdatedFiles(calledWhenStopStream);
- mFileName = null;
- mFileOutputStream = null;
- } catch (IOException e) {
- Log.w(TAG, "Error on closing file.", e);
- }
- }
-
- /**
- * Writes the data to the file.
- *
- * @param buffer the data to be written
- * @param bytesWritten number of bytes written
- */
- public void writeToFile(byte[] buffer, int bytesWritten) {
- if (mFileOutputStream == null) {
- return;
- }
- if (System.currentTimeMillis() - mFileStartTimeMs > TIME_LIMIT_MS) {
- closeFile(false);
- openFile();
- }
- try {
- mFileOutputStream.write(buffer, 0, bytesWritten);
- } catch (IOException e) {
- Log.w(TAG, "Error on writing TS stream.", e);
- }
- }
-
- /**
- * Deletes outdated files to save storage.
- *
- * @param deleteAll {@code true} if all the files with the relative ID should be deleted
- * {@code false} if the most recent file should not be deleted
- */
- private void deleteOutdatedFiles(boolean deleteAll) {
- if (mFileName == null) {
- return;
- }
- if (mDirectory == null || !mDirectory.isDirectory()) {
- Log.e(TAG, "Error. The folder doesn't exist!");
- return;
- }
- if (mFileName == null) {
- Log.e(TAG, "Error. The current file name is null!");
- return;
- }
- for (File file : mDirectory.listFiles()) {
- if (file.isFile() && getFileId(file) == mInstanceId
- && (deleteAll || !mFileName.equals(file.getName()))) {
- boolean deleted = file.delete();
- if (DEBUG && !deleted) {
- Log.w(TAG, "Failed to delete " + file.getName());
- }
- }
- }
- }
-
- /**
- * Generates a unique instance ID.
- *
- * @return a unique instance ID
- */
- private int generateInstanceId() {
- if (mDirectory == null) {
- return NO_INSTANCE_ID;
- }
- Set<Integer> idSet = getExistingIds();
- if (idSet == null) {
- return NO_INSTANCE_ID;
- }
- for (int i = 0; i < MAX_GET_ID_RETRY_COUNT; i++) {
- // Range [1, MAX_INSTANCE_ID]
- int id = (int)Math.floor(Math.random() * MAX_INSTANCE_ID) + 1;
- if (!idSet.contains(id)) {
- return id;
- }
- }
- return NO_INSTANCE_ID;
- }
-
- /**
- * Gets all existing instance IDs.
- *
- * @return a set of all existing instance IDs
- */
- private Set<Integer> getExistingIds() {
- if (mDirectory == null || !mDirectory.isDirectory()) {
- return null;
- }
-
- Set<Integer> idSet = new HashSet<>();
- for (File file : mDirectory.listFiles()) {
- int id = getFileId(file);
- if(id != NO_INSTANCE_ID) {
- idSet.add(id);
- }
- }
- return idSet;
- }
-
- /**
- * Gets the instance ID of a given file.
- *
- * @param file the file whose TsStreamWriter ID is returned
- * @return the TsStreamWriter ID of the file or NO_INSTANCE_ID if not available
- */
- private static int getFileId(File file) {
- if (file == null || !file.isFile()) {
- return NO_INSTANCE_ID;
- }
- String fileName = file.getName();
- int lastSeparator = fileName.lastIndexOf(SEPARATOR);
- if (!fileName.endsWith(".ts") || lastSeparator == -1) {
- return NO_INSTANCE_ID;
- }
- try {
- return Integer.parseInt(fileName.substring(lastSeparator + 1, fileName.length() - 3));
- } catch (NumberFormatException e) {
- if (DEBUG) {
- Log.e(TAG, fileName + " is not a valid file name.");
- }
- }
- return NO_INSTANCE_ID;
- }
-}
diff --git a/src/com/android/tv/tuner/source/TsStreamer.java b/src/com/android/tv/tuner/source/TsStreamer.java
deleted file mode 100644
index 1ac950bb..00000000
--- a/src/com/android/tv/tuner/source/TsStreamer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import com.android.tv.tuner.ChannelScanFileParser;
-import com.android.tv.tuner.data.TunerChannel;
-
-/**
- * Interface definition for a stream generator. The interface will provide streams
- * for scanning channels and/or playback.
- */
-public interface TsStreamer {
- /**
- * Starts streaming the data for channel scanning process.
- *
- * @param channel {@link ChannelScanFileParser.ScanChannel} to be scanned
- * @return {@code true} if ready to stream, otherwise {@code false}
- */
- boolean startStream(ChannelScanFileParser.ScanChannel channel);
-
- /**
- * Starts streaming the data for channel playing or recording.
- *
- * @param channel {@link TunerChannel} to tune
- * @return {@code true} if ready to stream, otherwise {@code false}
- */
- boolean startStream(TunerChannel channel);
-
- /**
- * Stops streaming the data.
- */
- void stopStream();
-
- /**
- * Creates {@link TsDataSource} which will provide MPEG-2 TS stream for
- * {@link android.media.MediaExtractor}. The source will start from the position
- * where it is created.
- *
- * @return {@link TsDataSource}
- */
- TsDataSource createDataSource();
-}
diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java
deleted file mode 100644
index 843cbdb7..00000000
--- a/src/com/android/tv/tuner/source/TunerTsStreamer.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import android.content.Context;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.ChannelScanFileParser;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.tvinput.EventDetector;
-import com.android.tv.tuner.tvinput.EventDetector.EventListener;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Provides MPEG-2 TS stream sources for channel playing from an underlying tuner device.
- */
-public class TunerTsStreamer implements TsStreamer {
- private static final String TAG = "TunerTsStreamer";
-
- private static final int MIN_READ_UNIT = 1500;
- private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~15KB
- private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000; // ~ 30MB
- private static final int TS_PACKET_SIZE = 188;
-
- private static final int READ_TIMEOUT_MS = 5000; // 5 secs.
- private static final int BUFFER_UNDERRUN_SLEEP_MS = 10;
- private static final int READ_ERROR_STREAMING_ENDED = -1;
- private static final int READ_ERROR_BUFFER_OVERWRITTEN = -2;
-
- private final Object mCircularBufferMonitor = new Object();
- private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE];
- private long mBytesFetched;
- private final AtomicLong mLastReadPosition = new AtomicLong();
- private boolean mStreaming;
-
- private final TunerHal mTunerHal;
- private TunerChannel mChannel;
- private Thread mStreamingThread;
- private final EventDetector mEventDetector;
- private final List<Pair<EventListener, Boolean>> mEventListenerActions = new ArrayList<>();
-
- private final TsStreamWriter mTsStreamWriter;
- private String mChannelNumber;
-
- public static class TunerDataSource extends TsDataSource {
- private final TunerTsStreamer mTsStreamer;
- private final AtomicLong mLastReadPosition = new AtomicLong(0);
- private long mStartBufferedPosition;
-
- private TunerDataSource(TunerTsStreamer tsStreamer) {
- mTsStreamer = tsStreamer;
- mStartBufferedPosition = tsStreamer.getBufferedPosition();
- }
-
- @Override
- public long getBufferedPosition() {
- return mTsStreamer.getBufferedPosition() - mStartBufferedPosition;
- }
-
- @Override
- public long getLastReadPosition() {
- return mLastReadPosition.get();
- }
-
- @Override
- public void shiftStartPosition(long offset) {
- SoftPreconditions.checkState(mLastReadPosition.get() == 0);
- SoftPreconditions.checkArgument(0 <= offset && offset <= getBufferedPosition());
- mStartBufferedPosition += offset;
- }
-
- @Override
- public long open(DataSpec dataSpec) throws IOException {
- mLastReadPosition.set(0);
- return C.LENGTH_UNBOUNDED;
- }
-
- @Override
- public void close() {
- }
-
- @Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
- int ret = mTsStreamer.readAt(mStartBufferedPosition + mLastReadPosition.get(), buffer,
- offset, readLength);
- if (ret > 0) {
- mLastReadPosition.addAndGet(ret);
- } else if (ret == READ_ERROR_BUFFER_OVERWRITTEN) {
- long currentPosition = mStartBufferedPosition + mLastReadPosition.get();
- long endPosition = mTsStreamer.getBufferedPosition();
- long diff = ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE)
- * TS_PACKET_SIZE;
- Log.w(TAG, "Demux position jump by overwritten buffer: " + diff);
- mStartBufferedPosition = currentPosition + diff;
- mLastReadPosition.set(0);
- return 0;
- }
- return ret;
- }
- }
- /**
- * Creates {@link TsStreamer} for playing or recording the specified channel.
- * @param tunerHal the HAL for tuner device
- * @param eventListener the listener for channel & program information
- */
- public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) {
- mTunerHal = tunerHal;
- mEventDetector = new EventDetector(mTunerHal);
- if (eventListener != null) {
- mEventDetector.registerListener(eventListener);
- }
- mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ?
- new TsStreamWriter(context) : null;
- }
-
- public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener) {
- this(tunerHal, eventListener, null);
- }
-
- @Override
- public boolean startStream(TunerChannel channel) {
- if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(),
- channel.getDisplayNumber(false))) {
- if (channel.hasVideo()) {
- mTunerHal.addPidFilter(channel.getVideoPid(),
- TunerHal.FILTER_TYPE_VIDEO);
- }
- boolean audioFilterSet = false;
- for (Integer audioPid : channel.getAudioPids()) {
- if (!audioFilterSet) {
- mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_AUDIO);
- audioFilterSet = true;
- } else {
- // FILTER_TYPE_AUDIO overrides the previous filter for audio. We use
- // FILTER_TYPE_OTHER from the secondary one to get the all audio tracks.
- mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_OTHER);
- }
- }
- mTunerHal.addPidFilter(channel.getPcrPid(),
- TunerHal.FILTER_TYPE_PCR);
- if (mEventDetector != null) {
- mEventDetector.startDetecting(channel.getFrequency(), channel.getModulation(),
- channel.getProgramNumber());
- }
- mChannel = channel;
- mChannelNumber = channel.getDisplayNumber();
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- Log.w(TAG, "Streaming should be stopped before start streaming");
- return true;
- }
- mStreaming = true;
- mBytesFetched = 0;
- mLastReadPosition.set(0L);
- }
- if (mTsStreamWriter != null) {
- mTsStreamWriter.setChannel(mChannel);
- mTsStreamWriter.openFile();
- }
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
- return false;
- }
-
- @Override
- public boolean startStream(ChannelScanFileParser.ScanChannel channel) {
- if (mTunerHal.tune(channel.frequency, channel.modulation, null)) {
- mEventDetector.startDetecting(
- channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS);
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- Log.w(TAG, "Streaming should be stopped before start streaming");
- return true;
- }
- mStreaming = true;
- mBytesFetched = 0;
- mLastReadPosition.set(0L);
- }
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
- return false;
- }
-
- /**
- * Blocks the current thread until the streaming thread stops. In rare cases when the tuner
- * device is overloaded this can take a while, but usually it returns pretty quickly.
- */
- @Override
- public void stopStream() {
- mChannel = null;
- synchronized (mCircularBufferMonitor) {
- mStreaming = false;
- mCircularBufferMonitor.notifyAll();
- }
-
- try {
- if (mStreamingThread != null) {
- mStreamingThread.join();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- if (mTsStreamWriter != null) {
- mTsStreamWriter.closeFile(true);
- mTsStreamWriter.setChannel(null);
- }
- }
-
- @Override
- public TsDataSource createDataSource() {
- return new TunerDataSource(this);
- }
-
- /**
- * Returns incomplete channel lists which was scanned so far. Incomplete channel means
- * the channel whose channel information is not complete or is not well-formed.
- * @return {@link List} of {@link TunerChannel}
- */
- public List<TunerChannel> getMalFormedChannels() {
- return mEventDetector.getMalFormedChannels();
- }
-
- /**
- * Returns the current {@link TunerHal} which provides MPEG-TS stream for TunerTsStreamer.
- * @return {@link TunerHal}
- */
- public TunerHal getTunerHal() {
- return mTunerHal;
- }
-
- /**
- * Returns the current tuned channel for TunerTsStreamer.
- * @return {@link TunerChannel}
- */
- public TunerChannel getChannel() {
- return mChannel;
- }
-
- /**
- * Returns the current buffered position from tuner.
- * @return the current buffered position
- */
- public long getBufferedPosition() {
- synchronized (mCircularBufferMonitor) {
- return mBytesFetched;
- }
- }
-
- public String getStreamerInfo() {
- return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming;
- }
-
- public void registerListener(EventListener listener) {
- if (mEventDetector != null && listener != null) {
- synchronized (mEventListenerActions) {
- mEventListenerActions.add(new Pair<>(listener, true));
- }
- }
- }
-
- public void unregisterListener(EventListener listener) {
- if (mEventDetector != null) {
- synchronized (mEventListenerActions) {
- mEventListenerActions.add(new Pair(listener, false));
- }
- }
- }
-
- private class StreamingThread extends Thread {
- @Override
- public void run() {
- // Buffers for streaming data from the tuner and the internal buffer.
- byte[] dataBuffer = new byte[READ_BUFFER_SIZE];
-
- while (true) {
- synchronized (mCircularBufferMonitor) {
- if (!mStreaming) {
- break;
- }
- }
-
- if (mEventDetector != null) {
- synchronized (mEventListenerActions) {
- for (Pair listenerAction : mEventListenerActions) {
- EventListener listener = (EventListener) listenerAction.first;
- if ((boolean) listenerAction.second) {
- mEventDetector.registerListener(listener);
- } else {
- mEventDetector.unregisterListener(listener);
- }
- }
- mEventListenerActions.clear();
- }
- }
-
- int bytesWritten = mTunerHal.readTsStream(dataBuffer, dataBuffer.length);
- if (bytesWritten <= 0) {
- try {
- // When buffer is underrun, we sleep for short time to prevent
- // unnecessary CPU draining.
- sleep(BUFFER_UNDERRUN_SLEEP_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- continue;
- }
-
- if (mTsStreamWriter != null) {
- mTsStreamWriter.writeToFile(dataBuffer, bytesWritten);
- }
-
- if (mEventDetector != null) {
- mEventDetector.feedTSStream(dataBuffer, 0, bytesWritten);
- }
- synchronized (mCircularBufferMonitor) {
- int posInBuffer = (int) (mBytesFetched % CIRCULAR_BUFFER_SIZE);
- int bytesToCopyInFirstPass = bytesWritten;
- if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) {
- bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer;
- }
- System.arraycopy(dataBuffer, 0, mCircularBuffer, posInBuffer,
- bytesToCopyInFirstPass);
- if (bytesToCopyInFirstPass < bytesWritten) {
- System.arraycopy(dataBuffer, bytesToCopyInFirstPass, mCircularBuffer, 0,
- bytesWritten - bytesToCopyInFirstPass);
- }
- mBytesFetched += bytesWritten;
- mCircularBufferMonitor.notifyAll();
- }
- }
-
- Log.i(TAG, "Streaming stopped");
- }
- }
-
- /**
- * Reads data from internal buffer.
- * @param pos the position to read from
- * @param buffer to read
- * @param offset start position of the read buffer
- * @param amount number of bytes to read
- * @return number of read bytes when successful, {@code -1} otherwise
- * @throws IOException
- */
- public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException {
- while (true) {
- synchronized (mCircularBufferMonitor) {
- if (!mStreaming) {
- return READ_ERROR_STREAMING_ENDED;
- }
- if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) {
- Log.w(TAG, "Demux is requesting the data which is already overwritten.");
- return READ_ERROR_BUFFER_OVERWRITTEN;
- }
- if (mBytesFetched < pos + amount) {
- try {
- mCircularBufferMonitor.wait(READ_TIMEOUT_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- // Try again to prevent starvation.
- // Give chances to read from other threads.
- continue;
- }
- int startPos = (int) (pos % CIRCULAR_BUFFER_SIZE);
- int endPos = (int) ((pos + amount) % CIRCULAR_BUFFER_SIZE);
- int firstLength = (startPos > endPos ? CIRCULAR_BUFFER_SIZE : endPos) - startPos;
- System.arraycopy(mCircularBuffer, startPos, buffer, offset, firstLength);
- if (firstLength < amount) {
- System.arraycopy(mCircularBuffer, 0, buffer, offset + firstLength,
- amount - firstLength);
- }
- mCircularBufferMonitor.notifyAll();
- return amount;
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
deleted file mode 100644
index 258a4d86..00000000
--- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import android.content.Context;
-
-import com.android.tv.common.AutoCloseableUtils;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.tvinput.EventDetector;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Manages {@link TunerTsStreamer} for playback and recording.
- * The class hides handling of {@link TunerHal} from other classes.
- * This class is used by {@link TsDataSourceManager}. Don't use this class directly.
- */
-class TunerTsStreamerManager {
- // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator
- // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from
- // the same session.
- private final Object mCancelLock = new Object();
- private final StreamerFinder mStreamerFinder = new StreamerFinder();
- private final Map<Integer, TsStreamerCreator> mCreators = new HashMap<>();
- private final Map<Integer, EventDetector.EventListener> mListeners = new HashMap<>();
- private final Map<TsDataSource, TunerTsStreamer> mSourceToStreamerMap = new HashMap<>();
- private final TunerHalManager mTunerHalManager = new TunerHalManager();
- private static TunerTsStreamerManager sInstance;
-
- /**
- * Returns the singleton instance for the class
- * @return TunerTsStreamerManager
- */
- static synchronized TunerTsStreamerManager getInstance() {
- if (sInstance == null) {
- sInstance = new TunerTsStreamerManager();
- }
- return sInstance;
- }
-
- private TunerTsStreamerManager() { }
-
- synchronized TsDataSource createDataSource(
- Context context, TunerChannel channel, EventDetector.EventListener listener,
- int sessionId, boolean reuse) {
- TsStreamerCreator creator;
- synchronized (mCancelLock) {
- if (mStreamerFinder.containsLocked(channel)) {
- mStreamerFinder.appendSessionLocked(channel, sessionId);
- TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel);
- TsDataSource source = streamer.createDataSource();
- mListeners.put(sessionId, listener);
- streamer.registerListener(listener);
- mSourceToStreamerMap.put(source, streamer);
- return source;
- }
- creator = new TsStreamerCreator(context, channel, listener);
- mCreators.put(sessionId, creator);
- }
- TunerTsStreamer streamer = creator.create(sessionId, reuse);
- synchronized (mCancelLock) {
- mCreators.remove(sessionId);
- if (streamer == null) {
- return null;
- }
- if (!creator.isCancelledLocked()) {
- mStreamerFinder.putLocked(channel, sessionId, streamer);
- TsDataSource source = streamer.createDataSource();
- mListeners.put(sessionId, listener);
- mSourceToStreamerMap.put(source, streamer);
- return source;
- }
- }
- // Created streamer was cancelled by a new tune request.
- streamer.stopStream();
- TunerHal hal = streamer.getTunerHal();
- hal.setHasPendingTune(false);
- mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
- return null;
- }
-
- synchronized void releaseDataSource(TsDataSource source, int sessionId,
- boolean reuse) {
- TunerTsStreamer streamer;
- synchronized (mCancelLock) {
- streamer = mSourceToStreamerMap.get(source);
- mSourceToStreamerMap.remove(source);
- if (streamer == null) {
- return;
- }
- EventDetector.EventListener listener = mListeners.remove(sessionId);
- streamer.unregisterListener(listener);
- TunerChannel channel = streamer.getChannel();
- SoftPreconditions.checkState(channel != null);
- mStreamerFinder.removeSessionLocked(channel, sessionId);
- if (mStreamerFinder.containsLocked(channel)) {
- return;
- }
- }
- streamer.stopStream();
- TunerHal hal = streamer.getTunerHal();
- hal.setHasPendingTune(false);
- mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
- }
-
- void setHasPendingTune(int sessionId) {
- synchronized (mCancelLock) {
- if (mCreators.containsKey(sessionId)) {
- mCreators.get(sessionId).cancelLocked();
- }
- }
- }
-
- /**
- * Add tuner hal into TunerHalManager for test.
- */
- void addTunerHal(TunerHal tunerHal, int sessionId) {
- mTunerHalManager.addTunerHal(tunerHal, sessionId);
- }
-
- synchronized void release(int sessionId) {
- mTunerHalManager.releaseCachedHal(sessionId);
- }
-
- private class StreamerFinder {
- private final Map<TunerChannel, Set<Integer>> mSessions = new HashMap<>();
- private final Map<TunerChannel, TunerTsStreamer> mStreamers = new HashMap<>();
-
- // @GuardedBy("mCancelLock")
- private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) {
- Set<Integer> sessions = new HashSet<>();
- sessions.add(sessionId);
- mSessions.put(channel, sessions);
- mStreamers.put(channel, streamer);
- }
-
- // @GuardedBy("mCancelLock")
- private void appendSessionLocked(TunerChannel channel, int sessionId) {
- if (mSessions.containsKey(channel)) {
- mSessions.get(channel).add(sessionId);
- }
- }
-
- // @GuardedBy("mCancelLock")
- private void removeSessionLocked(TunerChannel channel, int sessionId) {
- Set<Integer> sessions = mSessions.get(channel);
- sessions.remove(sessionId);
- if (sessions.size() == 0) {
- mSessions.remove(channel);
- mStreamers.remove(channel);
- }
- }
-
- // @GuardedBy("mCancelLock")
- private boolean containsLocked(TunerChannel channel) {
- return mSessions.containsKey(channel);
- }
-
- // @GuardedBy("mCancelLock")
- private TunerTsStreamer getStreamerLocked(TunerChannel channel) {
- return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null;
- }
- }
-
- /**
- * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same
- * session. The class supports the cancellation in creating new {@link TunerTsStreamer}.
- */
- private class TsStreamerCreator {
- private final Context mContext;
- private final TunerChannel mChannel;
- private final EventDetector.EventListener mEventListener;
- // mCancelled will be {@code true} if a new tune request for the same session
- // cancels create().
- private boolean mCancelled;
- private TunerHal mTunerHal;
-
- private TsStreamerCreator(Context context, TunerChannel channel,
- EventDetector.EventListener listener) {
- mContext = context;
- mChannel = channel;
- mEventListener = listener;
- }
-
- private TunerTsStreamer create(int sessionId, boolean reuse) {
- TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId);
- if (hal == null) {
- return null;
- }
- boolean canceled = false;
- synchronized (mCancelLock) {
- if (!mCancelled) {
- mTunerHal = hal;
- } else {
- canceled = true;
- }
- }
- if (!canceled) {
- TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext);
- if (tsStreamer.startStream(mChannel)) {
- return tsStreamer;
- }
- synchronized (mCancelLock) {
- mTunerHal = null;
- }
- }
- hal.setHasPendingTune(false);
- // Since TunerTsStreamer is not properly created, closes TunerHal.
- // And do not re-use TunerHal when it is not cancelled.
- mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse);
- return null;
- }
-
- // @GuardedBy("mCancelLock")
- private void cancelLocked() {
- if (mCancelled) {
- return;
- }
- mCancelled = true;
- if (mTunerHal != null) {
- mTunerHal.setHasPendingTune(true);
- }
- }
-
- // @GuardedBy("mCancelLock")
- private boolean isCancelledLocked() {
- return mCancelled;
- }
- }
-
- /**
- * Supports sharing {@link TunerHal} among multiple sessions.
- * The class also supports session affinity for {@link TunerHal} allocation.
- */
- private class TunerHalManager {
- private final Map<Integer, TunerHal> mTunerHals = new HashMap<>();
-
- private TunerHal getOrCreateTunerHal(Context context, int sessionId) {
- // Handles session affinity.
- TunerHal hal = mTunerHals.get(sessionId);
- if (hal != null) {
- mTunerHals.remove(sessionId);
- return hal;
- }
- // Finds a TunerHal which is cached for other sessions.
- Iterator it = mTunerHals.keySet().iterator();
- if (it.hasNext()) {
- Integer key = (Integer) it.next();
- hal = mTunerHals.get(key);
- mTunerHals.remove(key);
- return hal;
- }
- return TunerHal.createInstance(context);
- }
-
- private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) {
- if (!reuse || !hal.isReusable()) {
- AutoCloseableUtils.closeQuietly(hal);
- return;
- }
- TunerHal cachedHal = mTunerHals.get(sessionId);
- if (cachedHal != hal) {
- mTunerHals.put(sessionId, hal);
- if (cachedHal != null) {
- AutoCloseableUtils.closeQuietly(cachedHal);
- }
- }
- }
-
- private void releaseCachedHal(int sessionId) {
- TunerHal hal = mTunerHals.get(sessionId);
- if (hal != null) {
- mTunerHals.remove(sessionId);
- }
- if (hal != null) {
- AutoCloseableUtils.closeQuietly(hal);
- }
- }
-
- private void addTunerHal(TunerHal tunerHal, int sessionId) {
- mTunerHals.put(sessionId, tunerHal);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/ts/SectionParser.java b/src/com/android/tv/tuner/ts/SectionParser.java
deleted file mode 100644
index e1f890f3..00000000
--- a/src/com/android/tv/tuner/ts/SectionParser.java
+++ /dev/null
@@ -1,1759 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.ts;
-
-import android.media.tv.TvContentRating;
-import android.media.tv.TvContract.Programs.Genres;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.tv.tuner.data.PsiData.PatItem;
-import com.android.tv.tuner.data.PsiData.PmtItem;
-import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor;
-import com.android.tv.tuner.data.PsipData.CaptionServiceDescriptor;
-import com.android.tv.tuner.data.PsipData.ContentAdvisoryDescriptor;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.EttItem;
-import com.android.tv.tuner.data.PsipData.ExtendedChannelNameDescriptor;
-import com.android.tv.tuner.data.PsipData.GenreDescriptor;
-import com.android.tv.tuner.data.PsipData.Iso639LanguageDescriptor;
-import com.android.tv.tuner.data.PsipData.MgtItem;
-import com.android.tv.tuner.data.PsipData.ParentalRatingDescriptor;
-import com.android.tv.tuner.data.PsipData.PsipSection;
-import com.android.tv.tuner.data.PsipData.RatingRegion;
-import com.android.tv.tuner.data.PsipData.RegionalRating;
-import com.android.tv.tuner.data.PsipData.SdtItem;
-import com.android.tv.tuner.data.PsipData.ServiceDescriptor;
-import com.android.tv.tuner.data.PsipData.ShortEventDescriptor;
-import com.android.tv.tuner.data.PsipData.TsDescriptor;
-import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.ByteArrayBuffer;
-
-import com.android.tv.tuner.util.ConvertUtils;
-import com.ibm.icu.text.UnicodeDecompressor;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.Calendar;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Parses ATSC PSIP sections.
- */
-public class SectionParser {
- private static final String TAG = "SectionParser";
- private static final boolean DEBUG = false;
-
- private static final byte TABLE_ID_PAT = (byte) 0x00;
- private static final byte TABLE_ID_PMT = (byte) 0x02;
- private static final byte TABLE_ID_MGT = (byte) 0xc7;
- private static final byte TABLE_ID_TVCT = (byte) 0xc8;
- private static final byte TABLE_ID_CVCT = (byte) 0xc9;
- private static final byte TABLE_ID_EIT = (byte) 0xcb;
- private static final byte TABLE_ID_ETT = (byte) 0xcc;
-
- // Table id for DVB
- private static final byte TABLE_ID_SDT = (byte) 0x42;
- private static final byte TABLE_ID_DVB_ACTUAL_P_F_EIT = (byte) 0x4e;
- private static final byte TABLE_ID_DVB_OTHER_P_F_EIT = (byte) 0x4f;
- private static final byte TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT = (byte) 0x50;
- private static final byte TABLE_ID_DVB_OTHER_SCHEDULE_EIT = (byte) 0x60;
-
- // For details of the structure for the tags of descriptors, see ATSC A/65 Table 6.25.
- public static final int DESCRIPTOR_TAG_ISO639LANGUAGE = 0x0a;
- public static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
- public static final int DESCRIPTOR_TAG_CONTENT_ADVISORY = 0x87;
- public static final int DESCRIPTOR_TAG_AC3_AUDIO_STREAM = 0x81;
- public static final int DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = 0xa0;
- public static final int DESCRIPTOR_TAG_GENRE = 0xab;
-
- // For details of the structure for the tags of DVB descriptors, see DVB Document A038 Table 12.
- public static final int DVB_DESCRIPTOR_TAG_SERVICE = 0x48;
- public static final int DVB_DESCRIPTOR_TAG_SHORT_EVENT = 0X4d;
- public static final int DVB_DESCRIPTOR_TAG_CONTENT = 0x54;
- public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55;
-
- private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00;
- private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff
- private static final byte MODE_UTF16 = (byte) 0x3f;
- private static final byte MODE_SCSU = (byte) 0x3e;
- private static final int MAX_SHORT_NAME_BYTES = 14;
-
- // See ANSI/CEA-766-C.
- private static final int RATING_REGION_US_TV = 1;
- private static final int RATING_REGION_KR_TV = 4;
-
- // The following values are defined in the live channels app.
- // See https://developer.android.com/reference/android/media/tv/TvContentRating.html.
- private static final String RATING_DOMAIN = "com.android.tv";
- private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV";
- private static final String RATING_REGION_RATING_SYSTEM_US_MV = "US_MV";
- private static final String RATING_REGION_RATING_SYSTEM_KR_TV = "KR_TV";
-
- private static final String[] RATING_REGION_TABLE_US_TV = {
- "US_TV_Y", "US_TV_Y7", "US_TV_G", "US_TV_PG", "US_TV_14", "US_TV_MA"
- };
-
- private static final String[] RATING_REGION_TABLE_US_MV = {
- "US_MV_G", "US_MV_PG", "US_MV_PG13", "US_MV_R", "US_MV_NC17"
- };
-
- private static final String[] RATING_REGION_TABLE_KR_TV = {
- "KR_TV_ALL", "KR_TV_7", "KR_TV_12", "KR_TV_15", "KR_TV_19"
- };
-
- private static final String[] RATING_REGION_TABLE_US_TV_SUBRATING = {
- "US_TV_D", "US_TV_L", "US_TV_S", "US_TV_V", "US_TV_FV"
- };
-
- // According to ANSI-CEA-766-D
- private static final int VALUE_US_TV_Y = 1;
- private static final int VALUE_US_TV_Y7 = 2;
- private static final int VALUE_US_TV_NONE = 1;
- private static final int VALUE_US_TV_G = 2;
- private static final int VALUE_US_TV_PG = 3;
- private static final int VALUE_US_TV_14 = 4;
- private static final int VALUE_US_TV_MA = 5;
-
- private static final int DIMENSION_US_TV_RATING = 0;
- private static final int DIMENSION_US_TV_D = 1;
- private static final int DIMENSION_US_TV_L = 2;
- private static final int DIMENSION_US_TV_S = 3;
- private static final int DIMENSION_US_TV_V = 4;
- private static final int DIMENSION_US_TV_Y = 5;
- private static final int DIMENSION_US_TV_FV = 6;
- private static final int DIMENSION_US_MV_RATING = 7;
-
- private static final int VALUE_US_MV_G = 2;
- private static final int VALUE_US_MV_PG = 3;
- private static final int VALUE_US_MV_PG13 = 4;
- private static final int VALUE_US_MV_R = 5;
- private static final int VALUE_US_MV_NC17 = 6;
- private static final int VALUE_US_MV_X = 7;
-
- private static final String STRING_US_TV_Y = "US_TV_Y";
- private static final String STRING_US_TV_Y7 = "US_TV_Y7";
- private static final String STRING_US_TV_FV = "US_TV_FV";
-
-
- /*
- * The following CRC table is from the code generated by the following command.
- * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c
- * To see the details of pycrc, visit http://www.tty1.net/pycrc/index_en.html
- */
- public static final int[] CRC_TABLE = {
- 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
- 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
- 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
- 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
- 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
- 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
- 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
- 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
- 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
- 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
- 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
- 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
- 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
- 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
- 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
- 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
- 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
- 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
- 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
- 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
- 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
- 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
- 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
- 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
- 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
- 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
- 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
- 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
- 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
- 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
- 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
- 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
- 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
- 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
- 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
- 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
- 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
- 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
- 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
- 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
- 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
- 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
- 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
- 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
- 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
- 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
- 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
- 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
- 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
- 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
- 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
- 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
- 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
- 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
- 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
- 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
- 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
- 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
- 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
- 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
- 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
- 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
- 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
- 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
- };
-
- // A table which maps ATSC genres to TIF genres.
- // See ATSC/65 Table 6.20.
- private static final String[] CANONICAL_GENRES_TABLE = {
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- Genres.EDUCATION, Genres.ENTERTAINMENT, Genres.MOVIES, Genres.NEWS,
- Genres.LIFE_STYLE, Genres.SPORTS, null, Genres.MOVIES,
- null,
- Genres.FAMILY_KIDS, Genres.DRAMA, null, Genres.ENTERTAINMENT, Genres.SPORTS,
- Genres.SPORTS,
- null, null,
- Genres.MUSIC, Genres.EDUCATION,
- null,
- Genres.COMEDY,
- null,
- Genres.MUSIC,
- null, null,
- Genres.MOVIES, Genres.ENTERTAINMENT, Genres.NEWS, Genres.DRAMA,
- Genres.EDUCATION, Genres.MOVIES, Genres.SPORTS, Genres.MOVIES,
- null,
- Genres.LIFE_STYLE, Genres.ARTS, Genres.LIFE_STYLE, Genres.SPORTS,
- null, null,
- Genres.GAMING, Genres.LIFE_STYLE, Genres.SPORTS,
- null,
- Genres.LIFE_STYLE, Genres.EDUCATION, Genres.EDUCATION, Genres.LIFE_STYLE,
- Genres.SPORTS, Genres.LIFE_STYLE, Genres.MOVIES, Genres.NEWS,
- null, null, null,
- Genres.EDUCATION,
- null, null, null,
- Genres.EDUCATION,
- null, null, null,
- Genres.DRAMA, Genres.MUSIC, Genres.MOVIES,
- null,
- Genres.ANIMAL_WILDLIFE,
- null, null,
- Genres.PREMIER,
- null, null, null, null,
- Genres.SPORTS, Genres.ARTS,
- null, null, null,
- Genres.MOVIES, Genres.TECH_SCIENCE, Genres.DRAMA,
- null,
- Genres.SHOPPING, Genres.DRAMA,
- null,
- Genres.MOVIES, Genres.ENTERTAINMENT, Genres.TECH_SCIENCE, Genres.SPORTS,
- Genres.TRAVEL, Genres.ENTERTAINMENT, Genres.ARTS, Genres.NEWS,
- null,
- Genres.ARTS, Genres.SPORTS, Genres.SPORTS, Genres.NEWS,
- Genres.SPORTS, Genres.SPORTS, Genres.SPORTS, Genres.FAMILY_KIDS,
- Genres.FAMILY_KIDS, Genres.MOVIES,
- null,
- Genres.TECH_SCIENCE, Genres.MUSIC,
- null,
- Genres.SPORTS, Genres.FAMILY_KIDS, Genres.NEWS, Genres.SPORTS,
- Genres.NEWS, Genres.SPORTS, Genres.ANIMAL_WILDLIFE,
- null,
- Genres.MUSIC, Genres.NEWS, Genres.SPORTS,
- null,
- Genres.NEWS, Genres.NEWS, Genres.NEWS, Genres.NEWS,
- Genres.SPORTS, Genres.MOVIES, Genres.ARTS, Genres.ANIMAL_WILDLIFE,
- Genres.MUSIC, Genres.MUSIC, Genres.MOVIES, Genres.EDUCATION,
- Genres.DRAMA, Genres.SPORTS, Genres.SPORTS, Genres.SPORTS,
- Genres.SPORTS,
- null,
- Genres.SPORTS, Genres.SPORTS,
- };
-
- // A table which contains ATSC categorical genre code assignments.
- // See ATSC/65 Table 6.20.
- private static final String[] BROADCAST_GENRES_TABLE = new String[] {
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- "Education", "Entertainment", "Movie", "News",
- "Religious", "Sports", "Other", "Action",
- "Advertisement", "Animated", "Anthology", "Automobile",
- "Awards", "Baseball", "Basketball", "Bulletin",
- "Business", "Classical", "College", "Combat",
- "Comedy", "Commentary", "Concert", "Consumer",
- "Contemporary", "Crime", "Dance", "Documentary",
- "Drama", "Elementary", "Erotica", "Exercise",
- "Fantasy", "Farm", "Fashion", "Fiction",
- "Food", "Football", "Foreign", "Fund Raiser",
- "Game/Quiz", "Garden", "Golf", "Government",
- "Health", "High School", "History", "Hobby",
- "Hockey", "Home", "Horror", "Information",
- "Instruction", "International", "Interview", "Language",
- "Legal", "Live", "Local", "Math",
- "Medical", "Meeting", "Military", "Miniseries",
- "Music", "Mystery", "National", "Nature",
- "Police", "Politics", "Premier", "Prerecorded",
- "Product", "Professional", "Public", "Racing",
- "Reading", "Repair", "Repeat", "Review",
- "Romance", "Science", "Series", "Service",
- "Shopping", "Soap Opera", "Special", "Suspense",
- "Talk", "Technical", "Tennis", "Travel",
- "Variety", "Video", "Weather", "Western",
- "Art", "Auto Racing", "Aviation", "Biography",
- "Boating", "Bowling", "Boxing", "Cartoon",
- "Children", "Classic Film", "Community", "Computers",
- "Country Music", "Court", "Extreme Sports", "Family",
- "Financial", "Gymnastics", "Headlines", "Horse Racing",
- "Hunting/Fishing/Outdoors", "Independent", "Jazz", "Magazine",
- "Motorcycle Racing", "Music/Film/Books", "News-International", "News-Local",
- "News-National", "News-Regional", "Olympics", "Original",
- "Performing Arts", "Pets/Animals", "Pop", "Rock & Roll",
- "Sci-Fi", "Self Improvement", "Sitcom", "Skating",
- "Skiing", "Soccer", "Track/Field", "True",
- "Volleyball", "Wrestling",
- };
-
- // Audio language code map from ISO 639-2/B to 639-2/T, in order to show correct audio language.
- private static final HashMap<String, String> ISO_LANGUAGE_CODE_MAP;
- static {
- ISO_LANGUAGE_CODE_MAP = new HashMap<>();
- ISO_LANGUAGE_CODE_MAP.put("alb", "sqi");
- ISO_LANGUAGE_CODE_MAP.put("arm", "hye");
- ISO_LANGUAGE_CODE_MAP.put("baq", "eus");
- ISO_LANGUAGE_CODE_MAP.put("bur", "mya");
- ISO_LANGUAGE_CODE_MAP.put("chi", "zho");
- ISO_LANGUAGE_CODE_MAP.put("cze", "ces");
- ISO_LANGUAGE_CODE_MAP.put("dut", "nld");
- ISO_LANGUAGE_CODE_MAP.put("fre", "fra");
- ISO_LANGUAGE_CODE_MAP.put("geo", "kat");
- ISO_LANGUAGE_CODE_MAP.put("ger", "deu");
- ISO_LANGUAGE_CODE_MAP.put("gre", "ell");
- ISO_LANGUAGE_CODE_MAP.put("ice", "isl");
- ISO_LANGUAGE_CODE_MAP.put("mac", "mkd");
- ISO_LANGUAGE_CODE_MAP.put("mao", "mri");
- ISO_LANGUAGE_CODE_MAP.put("may", "msa");
- ISO_LANGUAGE_CODE_MAP.put("per", "fas");
- ISO_LANGUAGE_CODE_MAP.put("rum", "ron");
- ISO_LANGUAGE_CODE_MAP.put("slo", "slk");
- ISO_LANGUAGE_CODE_MAP.put("tib", "bod");
- ISO_LANGUAGE_CODE_MAP.put("wel", "cym");
- ISO_LANGUAGE_CODE_MAP.put("esl", "spa"); // Special entry for channel 9-1 KQED in bay area.
- }
-
- // Containers to store the last version numbers of the PSIP sections.
- private final HashMap<PsipSection, Integer> mSectionVersionMap = new HashMap<>();
- private final SparseArray<List<EttItem>> mParsedEttItems = new SparseArray<>();
-
- public interface OutputListener {
- void onPatParsed(List<PatItem> items);
- void onPmtParsed(int programNumber, List<PmtItem> items);
- void onMgtParsed(List<MgtItem> items);
- void onVctParsed(List<VctItem> items, int sectionNumber, int lastSectionNumber);
- void onEitParsed(int sourceId, List<EitItem> items);
- void onEttParsed(int sourceId, List<EttItem> descriptions);
- void onSdtParsed(List<SdtItem> items);
- }
-
- private final OutputListener mListener;
-
- public SectionParser(OutputListener listener) {
- mListener = listener;
- }
-
- public void parseSections(ByteArrayBuffer data) {
- int pos = 0;
- while (pos + 3 <= data.length()) {
- if ((data.byteAt(pos) & 0xff) == 0xff) {
- // Clear stuffing bytes according to H222.0 section 2.4.4.
- data.setLength(0);
- break;
- }
- int sectionLength =
- (((data.byteAt(pos + 1) & 0x0f) << 8) | (data.byteAt(pos + 2) & 0xff)) + 3;
- if (pos + sectionLength > data.length()) {
- break;
- }
- if (DEBUG) {
- Log.d(TAG, "parseSections 0x" + Integer.toHexString(data.byteAt(pos) & 0xff));
- }
- parseSection(Arrays.copyOfRange(data.buffer(), pos, pos + sectionLength));
- pos += sectionLength;
- }
- if (mListener != null) {
- for (int i = 0; i < mParsedEttItems.size(); ++i) {
- int sourceId = mParsedEttItems.keyAt(i);
- List<EttItem> descriptions = mParsedEttItems.valueAt(i);
- mListener.onEttParsed(sourceId, descriptions);
- }
- }
- mParsedEttItems.clear();
- }
-
- public void resetVersionNumbers() {
- mSectionVersionMap.clear();
- }
-
- private void parseSection(byte[] data) {
- if (!checkSanity(data)) {
- Log.d(TAG, "Bad CRC!");
- return;
- }
- PsipSection section = PsipSection.create(data);
- if (section == null) {
- return;
- }
-
- // The currentNextIndicator indicates that the section sent is currently applicable.
- if (!section.getCurrentNextIndicator()) {
- return;
- }
- int versionNumber = (data[5] & 0x3e) >> 1;
- Integer oldVersionNumber = mSectionVersionMap.get(section);
-
- // The versionNumber shall be incremented when a change in the information carried within
- // the section occurs.
- if (oldVersionNumber != null && versionNumber == oldVersionNumber) {
- return;
- }
- boolean result = false;
- switch (data[0]) {
- case TABLE_ID_PAT:
- result = parsePAT(data);
- break;
- case TABLE_ID_PMT:
- result = parsePMT(data);
- break;
- case TABLE_ID_MGT:
- result = parseMGT(data);
- break;
- case TABLE_ID_TVCT:
- case TABLE_ID_CVCT:
- result = parseVCT(data);
- break;
- case TABLE_ID_EIT:
- result = parseEIT(data);
- break;
- case TABLE_ID_ETT:
- result = parseETT(data);
- break;
- case TABLE_ID_SDT:
- result = parseSDT(data);
- break;
- case TABLE_ID_DVB_ACTUAL_P_F_EIT:
- case TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT:
- result = parseDVBEIT(data);
- break;
- default:
- break;
- }
- if (result) {
- mSectionVersionMap.put(section, versionNumber);
- }
- }
-
- private boolean parsePAT(byte[] data) {
- if (DEBUG) {
- Log.d(TAG, "PAT is discovered.");
- }
- int pos = 8;
-
- List<PatItem> results = new ArrayList<>();
- for (; pos < data.length - 4; pos = pos + 4) {
- if (pos > data.length - 4 - 4) {
- Log.e(TAG, "Broken PAT.");
- return false;
- }
- int programNo = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
- int pmtPid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff);
- results.add(new PatItem(programNo, pmtPid));
- }
- if (mListener != null) {
- mListener.onPatParsed(results);
- }
- return true;
- }
-
- private boolean parsePMT(byte[] data) {
- int table_id_ext = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- if (DEBUG) {
- Log.d(TAG, "PMT is discovered. programNo = " + table_id_ext);
- }
- if (data.length <= 11) {
- Log.e(TAG, "Broken PMT.");
- return false;
- }
- int pcrPid = (data[8] & 0x1f) << 8 | data[9];
- int programInfoLen = (data[10] & 0x0f) << 8 | data[11];
- int pos = 12;
- List<TsDescriptor> descriptors = parseDescriptors(data, pos, pos + programInfoLen);
- pos += programInfoLen;
- if (DEBUG) {
- Log.d(TAG, "PMT descriptors size: " + descriptors.size());
- }
- List<PmtItem> results = new ArrayList<>();
- for (; pos < data.length - 4;) {
- if (pos < 0) {
- Log.e(TAG, "Broken PMT.");
- return false;
- }
- int streamType = data[pos] & 0xff;
- int esPid = (data[pos + 1] & 0x1f) << 8 | (data[pos + 2] & 0xff);
- int esInfoLen = (data[pos + 3] & 0xf) << 8 | (data[pos + 4] & 0xff);
- if (data.length < pos + esInfoLen + 5) {
- Log.e(TAG, "Broken PMT.");
- return false;
- }
- descriptors = parseDescriptors(data, pos + 5, pos + 5 + esInfoLen);
- List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
- List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
- PmtItem pmtItem = new PmtItem(streamType, esPid, audioTracks, captionTracks);
- if (DEBUG) {
- Log.d(TAG, "PMT " + pmtItem + " descriptors size: " + descriptors.size());
- }
- results.add(pmtItem);
- pos = pos + esInfoLen + 5;
- }
- results.add(new PmtItem(PmtItem.ES_PID_PCR, pcrPid, null, null));
- if (mListener != null) {
- mListener.onPmtParsed(table_id_ext, results);
- }
- return true;
- }
-
- private boolean parseMGT(byte[] data) {
- // For details of the structure for MGT, see ATSC A/65 Table 6.2.
- if (DEBUG) {
- Log.d(TAG, "MGT is discovered.");
- }
- if (data.length <= 10) {
- Log.e(TAG, "Broken MGT.");
- return false;
- }
- int tablesDefined = ((data[9] & 0xff) << 8) | (data[10] & 0xff);
- int pos = 11;
- List<MgtItem> results = new ArrayList<>();
- for (int i = 0; i < tablesDefined; ++i) {
- if (data.length <= pos + 10) {
- Log.e(TAG, "Broken MGT.");
- return false;
- }
- int tableType = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
- int tableTypePid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff);
- int descriptorsLength = ((data[pos + 9] & 0x0f) << 8) | (data[pos + 10] & 0xff);
- pos += 11 + descriptorsLength;
- results.add(new MgtItem(tableType, tableTypePid));
- }
- // Skip the remaining descriptor part which we don't use.
-
- if (mListener != null) {
- mListener.onMgtParsed(results);
- }
- return true;
- }
-
- private boolean parseVCT(byte[] data) {
- // For details of the structure for VCT, see ATSC A/65 Table 6.4 and 6.8.
- if (DEBUG) {
- Log.d(TAG, "VCT is discovered.");
- }
- if (data.length <= 9) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- int numChannelsInSection = (data[9] & 0xff);
- int sectionNumber = (data[6] & 0xff);
- int lastSectionNumber = (data[7] & 0xff);
- if (sectionNumber > lastSectionNumber) {
- // According to section 6.3.1 of the spec ATSC A/65,
- // last section number is the largest section number.
- Log.w(TAG, "Invalid VCT. Section Number " + sectionNumber + " > Last Section Number "
- + lastSectionNumber);
- return false;
- }
- int pos = 10;
- List<VctItem> results = new ArrayList<>();
- for (int i = 0; i < numChannelsInSection; ++i) {
- if (data.length <= pos + 31) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- String shortName = "";
- int shortNameSize = getShortNameSize(data, pos);
- try {
- shortName = new String(
- Arrays.copyOfRange(data, pos, pos + shortNameSize), "UTF-16");
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Broken VCT.", e);
- return false;
- }
- if ((data[pos + 14] & 0xf0) != 0xf0) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- int majorNumber = ((data[pos + 14] & 0x0f) << 6) | ((data[pos + 15] & 0xff) >> 2);
- int minorNumber = ((data[pos + 15] & 0x03) << 8) | (data[pos + 16] & 0xff);
- if ((majorNumber & 0x3f0) == 0x3f0) {
- // If the six MSBs are 111111, these indicate that there is only one-part channel
- // number. To see details, refer A/65 Section 6.3.2.
- majorNumber = ((majorNumber & 0xf) << 10) + minorNumber;
- minorNumber = 0;
- }
- int channelTsid = ((data[pos + 22] & 0xff) << 8) | (data[pos + 23] & 0xff);
- int programNumber = ((data[pos + 24] & 0xff) << 8) | (data[pos + 25] & 0xff);
- boolean accessControlled = (data[pos + 26] & 0x20) != 0;
- boolean hidden = (data[pos + 26] & 0x10) != 0;
- int serviceType = (data[pos + 27] & 0x3f);
- int sourceId = ((data[pos + 28] & 0xff) << 8) | (data[pos + 29] & 0xff);
- int descriptorsPos = pos + 32;
- int descriptorsLength = ((data[pos + 30] & 0x03) << 8) | (data[pos + 31] & 0xff);
- pos += 32 + descriptorsLength;
- if (data.length < pos) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- List<TsDescriptor> descriptors = parseDescriptors(
- data, descriptorsPos, descriptorsPos + descriptorsLength);
- String longName = null;
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof ExtendedChannelNameDescriptor) {
- ExtendedChannelNameDescriptor extendedChannelNameDescriptor =
- (ExtendedChannelNameDescriptor) descriptor;
- longName = extendedChannelNameDescriptor.getLongChannelName();
- break;
- }
- }
- if (DEBUG) {
- Log.d(TAG, String.format(
- "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d "
- + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d",
- shortName, longName, serviceType, channelTsid, programNumber, majorNumber,
- minorNumber, accessControlled, hidden, descriptors.size()));
- }
- if (!accessControlled && !hidden && (serviceType == Channel.SERVICE_TYPE_ATSC_AUDIO ||
- serviceType == Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION ||
- serviceType == Channel.SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) {
- // Hide hidden, encrypted, or unsupported ATSC service type channels
- results.add(new VctItem(shortName, longName, serviceType, channelTsid,
- programNumber, majorNumber, minorNumber, sourceId));
- }
- }
- // Skip the remaining descriptor part which we don't use.
-
- if (mListener != null) {
- mListener.onVctParsed(results, sectionNumber, lastSectionNumber);
- }
- return true;
- }
-
- private boolean parseEIT(byte[] data) {
- // For details of the structure for EIT, see ATSC A/65 Table 6.11.
- if (DEBUG) {
- Log.d(TAG, "EIT is discovered.");
- }
- if (data.length <= 9) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- int numEventsInSection = (data[9] & 0xff);
-
- int pos = 10;
- List<EitItem> results = new ArrayList<>();
- for (int i = 0; i < numEventsInSection; ++i) {
- if (data.length <= pos + 9) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- if ((data[pos] & 0xc0) != 0xc0) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- int eventId = ((data[pos] & 0x3f) << 8) + (data[pos + 1] & 0xff);
- long startTime = ((data[pos + 2] & (long) 0xff) << 24) | ((data[pos + 3] & 0xff) << 16)
- | ((data[pos + 4] & 0xff) << 8) | (data[pos + 5] & 0xff);
- int lengthInSecond = ((data[pos + 6] & 0x0f) << 16)
- | ((data[pos + 7] & 0xff) << 8) | (data[pos + 8] & 0xff);
- int titleLength = (data[pos + 9] & 0xff);
- if (data.length <= pos + 10 + titleLength + 1) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- String titleText = "";
- if (titleLength > 0) {
- titleText = extractText(data, pos + 10);
- }
- if ((data[pos + 10 + titleLength] & 0xf0) != 0xf0) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- int descriptorsLength = ((data[pos + 10 + titleLength] & 0x0f) << 8)
- | (data[pos + 10 + titleLength + 1] & 0xff);
- int descriptorsPos = pos + 10 + titleLength + 2;
- if (data.length < descriptorsPos + descriptorsLength) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- List<TsDescriptor> descriptors = parseDescriptors(
- data, descriptorsPos, descriptorsPos + descriptorsLength);
- if (DEBUG) {
- Log.d(TAG, String.format("EIT descriptors size: %d", descriptors.size()));
- }
- String contentRating = generateContentRating(descriptors);
- String broadcastGenre = generateBroadcastGenre(descriptors);
- String canonicalGenre = generateCanonicalGenre(descriptors);
- List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
- List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
- pos += 10 + titleLength + 2 + descriptorsLength;
- results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText,
- startTime, lengthInSecond, contentRating, audioTracks, captionTracks,
- broadcastGenre, canonicalGenre, null));
- }
- if (mListener != null) {
- mListener.onEitParsed(sourceId, results);
- }
- return true;
- }
-
- private boolean parseETT(byte[] data) {
- // For details of the structure for ETT, see ATSC A/65 Table 6.13.
- if (DEBUG) {
- Log.d(TAG, "ETT is discovered.");
- }
- if (data.length <= 12) {
- Log.e(TAG, "Broken ETT.");
- return false;
- }
- int sourceId = ((data[9] & 0xff) << 8) | (data[10] & 0xff);
- int eventId = (((data[11] & 0xff) << 8) | (data[12] & 0xff)) >> 2;
- String text = extractText(data, 13);
- List<EttItem> ettItems = mParsedEttItems.get(sourceId);
- if (ettItems == null) {
- ettItems = new ArrayList<>();
- mParsedEttItems.put(sourceId, ettItems);
- }
- ettItems.add(new EttItem(eventId, text));
- return true;
- }
-
- private boolean parseSDT(byte[] data) {
- // For details of the structure for SDT, see DVB Document A038 Table 5.
- if (DEBUG) {
- Log.d(TAG, "SDT id discovered");
- }
- if (data.length <= 11) {
- Log.e(TAG, "Broken SDT.");
- return false;
- }
- if ((data[1] & 0x80) >> 7 != 1) {
- Log.e(TAG, "Broken SDT, section syntax indicator error.");
- return false;
- }
- int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff);
- int transportStreamId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- int originalNetworkId = ((data[8] & 0xff) << 8) | (data[9] & 0xff);
- int pos = 11;
- if (sectionLength + 3 > data.length) {
- Log.e(TAG, "Broken SDT.");
- }
- List<SdtItem> sdtItems = new ArrayList<>();
- while (pos + 9 < data.length) {
- int serviceId = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
- int descriptorsLength = ((data[pos + 3] & 0x0f) << 8) | (data[pos + 4] & 0xff);
- pos += 5;
- List<TsDescriptor> descriptors = parseDescriptors(data, pos, pos + descriptorsLength);
- List<ServiceDescriptor> serviceDescriptors = generateServiceDescriptors(descriptors);
- String serviceName = "";
- String serviceProviderName = "";
- int serviceType = 0;
- for (ServiceDescriptor serviceDescriptor : serviceDescriptors) {
- serviceName = serviceDescriptor.getServiceName();
- serviceProviderName = serviceDescriptor.getServiceProviderName();
- serviceType = serviceDescriptor.getServiceType();
- }
- if (serviceDescriptors.size() > 0) {
- sdtItems.add(new SdtItem(serviceName, serviceProviderName, serviceType, serviceId,
- originalNetworkId));
- }
- pos += descriptorsLength;
- }
- if (mListener != null) {
- mListener.onSdtParsed(sdtItems);
- }
- return true;
- }
-
- private boolean parseDVBEIT(byte[] data) {
- // For details of the structure for DVB ETT, see DVB Document A038 Table 7.
- if (DEBUG) {
- Log.d(TAG, "DVB EIT is discovered.");
- }
- if (data.length < 18) {
- Log.e(TAG, "Broken DVB EIT.");
- return false;
- }
- int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff);
- int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- int transportStreamId = ((data[8] & 0xff) << 8) | (data[9] & 0xff);
- int originalNetworkId = ((data[10] & 0xff) << 8) | (data[11] & 0xff);
-
- int pos = 14;
- List<EitItem> results = new ArrayList<>();
- while (pos + 12 < data.length) {
- int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff);
- float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff);
- int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f);
- int mjdMonth = (int) ((modifiedJulianDate - 14956.1f
- - (int) (startYear * 365.25f)) / 30.6001f);
- int startDay = (int) modifiedJulianDate - 14956 - (int) (startYear * 365.25f)
- - (int) (mjdMonth * 30.6001f);
- int startMonth = mjdMonth - 1;
- if (mjdMonth == 14 || mjdMonth == 15) {
- startYear += 1;
- startMonth -= 12;
- }
- int startHour = ((data[pos + 4] & 0xf0) >> 4) * 10 + (data[pos + 4] & 0x0f);
- int startMinute = ((data[pos + 5] & 0xf0) >> 4) * 10 + (data[pos + 5] & 0x0f);
- int startSecond = ((data[pos + 6] & 0xf0) >> 4) * 10 + (data[pos + 6] & 0x0f);
- Calendar calendar = Calendar.getInstance();
- startYear += 1900;
- calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond);
- long startTime = ConvertUtils.convertUnixEpochToGPSTime(
- calendar.getTimeInMillis() / 1000);
- int durationInSecond = (((data[pos + 7] & 0xf0) >> 4) * 10
- + (data[pos + 7] & 0x0f)) * 3600
- + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60
- + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f));
- int descriptorsLength = ((data[pos + 10] & 0x0f) << 8)
- | (data[pos + 10 + 1] & 0xff);
- int descriptorsPos = pos + 10 + 2;
- if (data.length < descriptorsPos + descriptorsLength) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- List<TsDescriptor> descriptors = parseDescriptors(
- data, descriptorsPos, descriptorsPos + descriptorsLength);
- if (DEBUG) {
- Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size()));
- }
- // TODO: Add logic to generating content rating for dvb. See DVB document 6.2.28 for
- // details. Content rating here will be null
- String contentRating = generateContentRating(descriptors);
- // TODO: Add logic for generating genre for dvb. See DVB document 6.2.9 for details.
- // Genre here will be null here.
- String broadcastGenre = generateBroadcastGenre(descriptors);
- String canonicalGenre = generateCanonicalGenre(descriptors);
- String titleText = generateShortEventName(descriptors);
- List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
- List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
- pos += 12 + descriptorsLength;
- results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText,
- startTime, durationInSecond, contentRating, audioTracks, captionTracks,
- broadcastGenre, canonicalGenre, null));
- }
- if (mListener != null) {
- mListener.onEitParsed(sourceId, results);
- }
- return true;
- }
-
- private static List<AtscAudioTrack> generateAudioTracks(List<TsDescriptor> descriptors) {
- // The list of audio tracks sent is located at both AC3 Audio descriptor and ISO 639
- // Language descriptor.
- List<AtscAudioTrack> ac3Tracks = new ArrayList<>();
- List<AtscAudioTrack> iso639LanguageTracks = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof Ac3AudioDescriptor) {
- Ac3AudioDescriptor audioDescriptor =
- (Ac3AudioDescriptor) descriptor;
- AtscAudioTrack audioTrack = new AtscAudioTrack();
- if (audioDescriptor.getLanguage() != null) {
- audioTrack.language = audioDescriptor.getLanguage();
- }
- if (audioTrack.language == null) {
- audioTrack.language = "";
- }
- audioTrack.audioType = AtscAudioTrack.AUDIOTYPE_UNDEFINED;
- audioTrack.channelCount = audioDescriptor.getNumChannels();
- audioTrack.sampleRate = audioDescriptor.getSampleRate();
- ac3Tracks.add(audioTrack);
- }
- }
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof Iso639LanguageDescriptor) {
- Iso639LanguageDescriptor iso639LanguageDescriptor =
- (Iso639LanguageDescriptor) descriptor;
- iso639LanguageTracks.addAll(iso639LanguageDescriptor.getAudioTracks());
- }
- }
-
- // An AC3 audio stream descriptor only has a audio channel count and a audio sample rate
- // while a ISO 639 Language descriptor only has a audio type, which describes a main use
- // case of its audio track.
- // Some channels contain only AC3 audio stream descriptors with valid language values.
- // Other channels contain both an AC3 audio stream descriptor and a ISO 639 Language
- // descriptor per audio track, and those AC3 audio stream descriptors often have a null
- // value of language field.
- // Combines two descriptors into one in order to gather more audio track specific
- // information as much as possible.
- List<AtscAudioTrack> tracks = new ArrayList<>();
- if (!ac3Tracks.isEmpty() && !iso639LanguageTracks.isEmpty()
- && ac3Tracks.size() != iso639LanguageTracks.size()) {
- // This shouldn't be happen. In here, it handles two cases. The first case is that the
- // only one type of descriptors arrives. The second case is that the two types of
- // descriptors have the same number of tracks.
- Log.e(TAG, "AC3 audio stream descriptors size != ISO 639 Language descriptors size");
- return tracks;
- }
- int size = Math.max(ac3Tracks.size(), iso639LanguageTracks.size());
- for (int i = 0; i < size; ++i) {
- AtscAudioTrack audioTrack = null;
- if (i < ac3Tracks.size()) {
- audioTrack = ac3Tracks.get(i);
- }
- if (i < iso639LanguageTracks.size()) {
- if (audioTrack == null) {
- audioTrack = iso639LanguageTracks.get(i);
- } else {
- AtscAudioTrack iso639LanguageTrack = iso639LanguageTracks.get(i);
- if (audioTrack.language == null || TextUtils.equals(audioTrack.language, "")) {
- audioTrack.language = iso639LanguageTrack.language;
- }
- audioTrack.audioType = iso639LanguageTrack.audioType;
- }
- }
- String language = ISO_LANGUAGE_CODE_MAP.get(audioTrack.language);
- if (language != null) {
- audioTrack.language = language;
- }
- tracks.add(audioTrack);
- }
- return tracks;
- }
-
- private static List<AtscCaptionTrack> generateCaptionTracks(List<TsDescriptor> descriptors) {
- List<AtscCaptionTrack> services = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof CaptionServiceDescriptor) {
- CaptionServiceDescriptor captionServiceDescriptor =
- (CaptionServiceDescriptor) descriptor;
- services.addAll(captionServiceDescriptor.getCaptionTracks());
- }
- }
- return services;
- }
-
- @VisibleForTesting
- static String generateContentRating(List<TsDescriptor> descriptors) {
- Set<String> contentRatings = new ArraySet<>();
- List<RatingRegion> usRatingRegions = getRatingRegions(descriptors, RATING_REGION_US_TV);
- List<RatingRegion> krRatingRegions = getRatingRegions(descriptors, RATING_REGION_KR_TV);
- for (RatingRegion region : usRatingRegions) {
- String contentRating = getUsRating(region);
- if (contentRating != null) {
- contentRatings.add(contentRating);
- }
- }
- for (RatingRegion region : krRatingRegions) {
- String contentRating = getKrRating(region);
- if (contentRating != null) {
- contentRatings.add(contentRating);
- }
- }
- return TextUtils.join(",", contentRatings);
- }
-
- /**
- * Gets a list of {@link RatingRegion} in the specific region.
- *
- * @param descriptors {@link TsDescriptor} list which may contains rating information
- * @param region the specific region
- * @return a list of {@link RatingRegion} in the specific region
- */
- private static List<RatingRegion> getRatingRegions(List<TsDescriptor> descriptors, int region) {
- List<RatingRegion> ratingRegions = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (!(descriptor instanceof ContentAdvisoryDescriptor)) {
- continue;
- }
- ContentAdvisoryDescriptor contentAdvisoryDescriptor =
- (ContentAdvisoryDescriptor) descriptor;
- for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) {
- if (ratingRegion.getName() == region) {
- ratingRegions.add(ratingRegion);
- }
- }
- }
- return ratingRegions;
- }
-
- /**
- * Gets US content rating and subratings (if any).
- *
- * @param ratingRegion a {@link RatingRegion} instance which may contain rating information.
- * @return A string representing the US content rating and subratings. The format of the string
- * is defined in {@link TvContentRating}. null, if no such a string exists.
- */
- private static String getUsRating(RatingRegion ratingRegion) {
- if (ratingRegion.getName() != RATING_REGION_US_TV) {
- return null;
- }
- List<RegionalRating> regionalRatings = ratingRegion.getRegionalRatings();
- String rating = null;
- int ratingIndex = VALUE_US_TV_NONE;
- List<String> subratings = new ArrayList<>();
- for (RegionalRating index : regionalRatings) {
- // See Table 3 of ANSI-CEA-766-D
- int dimension = index.getDimension();
- int value = index.getRating();
- switch (dimension) {
- // According to Table 6.27 of ATSC A65,
- // the dimensions shall be in increasing order.
- // Therefore, rating and ratingIndex are assigned before any corresponding
- // subrating.
- case DIMENSION_US_TV_RATING:
- if (value >= VALUE_US_TV_G && value < RATING_REGION_TABLE_US_TV.length) {
- rating = RATING_REGION_TABLE_US_TV[value];
- ratingIndex = value;
- }
- break;
- case DIMENSION_US_TV_D:
- if (value == 1
- && (ratingIndex == VALUE_US_TV_PG || ratingIndex == VALUE_US_TV_14)) {
- // US_TV_D is applicable to US_TV_PG and US_TV_14
- subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]);
- }
- break;
- case DIMENSION_US_TV_L:
- case DIMENSION_US_TV_S:
- case DIMENSION_US_TV_V:
- if (value == 1
- && ratingIndex >= VALUE_US_TV_PG
- && ratingIndex <= VALUE_US_TV_MA) {
- // US_TV_L, US_TV_S, and US_TV_V are applicable to
- // US_TV_PG, US_TV_14 and US_TV_MA
- subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]);
- }
- break;
- case DIMENSION_US_TV_Y:
- if (rating == null) {
- if (value == VALUE_US_TV_Y) {
- rating = STRING_US_TV_Y;
- } else if (value == VALUE_US_TV_Y7) {
- rating = STRING_US_TV_Y7;
- }
- }
- break;
- case DIMENSION_US_TV_FV:
- if (STRING_US_TV_Y7.equals(rating) && value == 1) {
- // US_TV_FV is applicable to US_TV_Y7
- subratings.add(STRING_US_TV_FV);
- }
- break;
- case DIMENSION_US_MV_RATING:
- if (value >= VALUE_US_MV_G && value <= VALUE_US_MV_X) {
- if (value == VALUE_US_MV_X) {
- // US_MV_X was replaced by US_MV_NC17 in 1990,
- // and it's not supported by TvContentRating
- value = VALUE_US_MV_NC17;
- }
- if (rating != null) {
- // According to Table 3 of ANSI-CEA-766-D,
- // DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING shall not be
- // present in the same descriptor.
- Log.w(
- TAG,
- "DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING are "
- + "present in the same descriptor");
- } else {
- return TvContentRating.createRating(
- RATING_DOMAIN,
- RATING_REGION_RATING_SYSTEM_US_MV,
- RATING_REGION_TABLE_US_MV[value - 2])
- .flattenToString();
- }
- }
- break;
-
- default:
- break;
- }
- }
- if (rating == null) {
- return null;
- }
-
- String[] subratingArray = subratings.toArray(new String[subratings.size()]);
- return TvContentRating.createRating(
- RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_US_TV, rating, subratingArray)
- .flattenToString();
- }
-
- /**
- * Gets KR(South Korea) content rating.
- *
- * @param ratingRegion a {@link RatingRegion} instance which may contain rating information.
- * @return A string representing the KR content rating. The format of the string is defined in
- * {@link TvContentRating}. null, if no such a string exists.
- */
- private static String getKrRating(RatingRegion ratingRegion) {
- if (ratingRegion.getName() != RATING_REGION_KR_TV) {
- return null;
- }
- List<RegionalRating> regionalRatings = ratingRegion.getRegionalRatings();
- String rating = null;
- for (RegionalRating index : regionalRatings) {
- if (index.getDimension() == 0
- && index.getRating() >= 0
- && index.getRating() < RATING_REGION_TABLE_KR_TV.length) {
- rating = RATING_REGION_TABLE_KR_TV[index.getRating()];
- break;
- }
- }
- if (rating == null) {
- return null;
- }
- return TvContentRating.createRating(
- RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_KR_TV, rating)
- .flattenToString();
- }
-
- private static String generateBroadcastGenre(List<TsDescriptor> descriptors) {
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof GenreDescriptor) {
- GenreDescriptor genreDescriptor =
- (GenreDescriptor) descriptor;
- return TextUtils.join(",", genreDescriptor.getBroadcastGenres());
- }
- }
- return null;
- }
-
- private static String generateCanonicalGenre(List<TsDescriptor> descriptors) {
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof GenreDescriptor) {
- GenreDescriptor genreDescriptor =
- (GenreDescriptor) descriptor;
- return Genres.encode(genreDescriptor.getCanonicalGenres());
- }
- }
- return null;
- }
-
- private static List<ServiceDescriptor> generateServiceDescriptors(
- List<TsDescriptor> descriptors) {
- List<ServiceDescriptor> serviceDescriptors = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof ServiceDescriptor) {
- ServiceDescriptor serviceDescriptor = (ServiceDescriptor) descriptor;
- serviceDescriptors.add(serviceDescriptor);
- }
- }
- return serviceDescriptors;
- }
-
- private static String generateShortEventName(List<TsDescriptor> descriptors) {
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof ShortEventDescriptor) {
- ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor) descriptor;
- return shortEventDescriptor.getEventName();
- }
- }
- return "";
- }
-
- private static List<TsDescriptor> parseDescriptors(byte[] data, int offset, int limit) {
- // For details of the structure for descriptors, see ATSC A/65 Section 6.9.
- List<TsDescriptor> descriptors = new ArrayList<>();
- if (data.length < limit) {
- return descriptors;
- }
- int pos = offset;
- while (pos + 1 < limit) {
- int tag = data[pos] & 0xff;
- int length = data[pos + 1] & 0xff;
- if (length <= 0) {
- break;
- }
- if (limit < pos + length + 2) {
- break;
- }
- if (DEBUG) {
- Log.d(TAG, String.format("Descriptor tag: %02x", tag));
- }
- TsDescriptor descriptor = null;
- switch (tag) {
- case DESCRIPTOR_TAG_CONTENT_ADVISORY:
- descriptor = parseContentAdvisory(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_CAPTION_SERVICE:
- descriptor = parseCaptionService(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME:
- descriptor = parseLongChannelName(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_GENRE:
- descriptor = parseGenre(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_AC3_AUDIO_STREAM:
- descriptor = parseAc3AudioStream(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_ISO639LANGUAGE:
- descriptor = parseIso639Language(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_SERVICE:
- descriptor = parseDvbService(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_SHORT_EVENT:
- descriptor = parseDvbShortEvent(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_CONTENT:
- descriptor = parseDvbContent(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_PARENTAL_RATING:
- descriptor = parseDvbParentalRating(data, pos, pos + length + 2);
- break;
-
- default:
- }
- if (descriptor != null) {
- if (DEBUG) {
- Log.d(TAG, "Descriptor parsed: " + descriptor);
- }
- descriptors.add(descriptor);
- }
- pos += length + 2;
- }
- return descriptors;
- }
-
- private static Iso639LanguageDescriptor parseIso639Language(byte[] data, int pos, int limit) {
- // For the details of the structure of ISO 639 language descriptor,
- // see ISO13818-1 second edition Section 2.6.18.
- pos += 2;
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- while (pos + 4 <= limit) {
- if (limit <= pos + 3) {
- Log.e(TAG, "Broken Iso639Language.");
- return null;
- }
- String language = new String(data, pos, 3);
- int audioType = data[pos + 3] & 0xff;
- AtscAudioTrack audioTrack = new AtscAudioTrack();
- audioTrack.language = language;
- audioTrack.audioType = audioType;
- audioTracks.add(audioTrack);
- pos += 4;
- }
- return new Iso639LanguageDescriptor(audioTracks);
- }
-
- private static CaptionServiceDescriptor parseCaptionService(byte[] data, int pos, int limit) {
- // For the details of the structure of caption service descriptor,
- // see ATSC A/65 Section 6.9.2.
- if (limit <= pos + 2) {
- Log.e(TAG, "Broken CaptionServiceDescriptor.");
- return null;
- }
- List<AtscCaptionTrack> services = new ArrayList<>();
- pos += 2;
- int numberServices = data[pos] & 0x1f;
- ++pos;
- if (limit < pos + numberServices * 6) {
- Log.e(TAG, "Broken CaptionServiceDescriptor.");
- return null;
- }
- for (int i = 0; i < numberServices; ++i) {
- String language = new String(Arrays.copyOfRange(data, pos, pos + 3));
- pos += 3;
- boolean ccType = (data[pos] & 0x80) != 0;
- if (!ccType) {
- pos +=3;
- continue;
- }
- int captionServiceNumber = data[pos] & 0x3f;
- ++pos;
- boolean easyReader = (data[pos] & 0x80) != 0;
- boolean wideAspectRatio = (data[pos] & 0x40) != 0;
- byte[] reserved = new byte[2];
- reserved[0] = (byte) (data[pos] << 2);
- reserved[0] |= (byte) ((data[pos + 1] & 0xc0) >>> 6);
- reserved[1] = (byte) ((data[pos + 1] & 0x3f) << 2);
- pos += 2;
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.language = language;
- captionTrack.serviceNumber = captionServiceNumber;
- captionTrack.easyReader = easyReader;
- captionTrack.wideAspectRatio = wideAspectRatio;
- services.add(captionTrack);
- }
- return new CaptionServiceDescriptor(services);
- }
-
- private static ContentAdvisoryDescriptor parseContentAdvisory(byte[] data, int pos, int limit) {
- // For details of the structure for content advisory descriptor, see A/65 Table 6.27.
- if (limit <= pos + 2) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- int count = data[pos + 2] & 0x3f;
- pos += 3;
- List<RatingRegion> ratingRegions = new ArrayList<>();
- for (int i = 0; i < count; ++i) {
- if (limit <= pos + 1) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- List<RegionalRating> indices = new ArrayList<>();
- int ratingRegion = data[pos] & 0xff;
- int dimensionCount = data[pos + 1] & 0xff;
- pos += 2;
- int previousDimension = -1;
- for (int j = 0; j < dimensionCount; ++j) {
- if (limit <= pos + 1) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- int dimensionIndex = data[pos] & 0xff;
- int ratingValue = data[pos + 1] & 0x0f;
- if (dimensionIndex <= previousDimension) {
- // According to Table 6.27 of ATSC A65,
- // the indices shall be in increasing order.
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- previousDimension = dimensionIndex;
- pos += 2;
- indices.add(new RegionalRating(dimensionIndex, ratingValue));
- }
- if (limit <= pos) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- int ratingDescriptionLength = data[pos] & 0xff;
- ++pos;
- if (limit < pos + ratingDescriptionLength) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- String ratingDescription = extractText(data, pos);
- pos += ratingDescriptionLength;
- ratingRegions.add(new RatingRegion(ratingRegion, ratingDescription, indices));
- }
- return new ContentAdvisoryDescriptor(ratingRegions);
- }
-
- private static ExtendedChannelNameDescriptor parseLongChannelName(byte[] data, int pos,
- int limit) {
- if (limit <= pos + 2) {
- Log.e(TAG, "Broken ExtendedChannelName.");
- return null;
- }
- pos += 2;
- String text = extractText(data, pos);
- if (text == null) {
- Log.e(TAG, "Broken ExtendedChannelName.");
- return null;
- }
- return new ExtendedChannelNameDescriptor(text);
- }
-
- private static GenreDescriptor parseGenre(byte[] data, int pos, int limit) {
- pos += 2;
- int attributeCount = data[pos] & 0x1f;
- if (limit <= pos + attributeCount) {
- Log.e(TAG, "Broken Genre.");
- return null;
- }
- HashSet<String> broadcastGenreSet = new HashSet<>();
- HashSet<String> canonicalGenreSet = new HashSet<>();
- for (int i = 0; i < attributeCount; ++i) {
- ++pos;
- int genreCode = data[pos] & 0xff;
- if (genreCode < BROADCAST_GENRES_TABLE.length) {
- String broadcastGenre = BROADCAST_GENRES_TABLE[genreCode];
- if (broadcastGenre != null && !broadcastGenreSet.contains(broadcastGenre)) {
- broadcastGenreSet.add(broadcastGenre);
- }
- }
- if (genreCode < CANONICAL_GENRES_TABLE.length) {
- String canonicalGenre = CANONICAL_GENRES_TABLE[genreCode];
- if (canonicalGenre != null && !canonicalGenreSet.contains(canonicalGenre)) {
- canonicalGenreSet.add(canonicalGenre);
- }
- }
- }
- return new GenreDescriptor(broadcastGenreSet.toArray(new String[broadcastGenreSet.size()]),
- canonicalGenreSet.toArray(new String[canonicalGenreSet.size()]));
- }
-
- private static TsDescriptor parseAc3AudioStream(byte[] data, int pos, int limit) {
- // For details of the AC3 audio stream descriptor, see A/52 Table A4.1.
- if (limit <= pos + 5) {
- Log.e(TAG, "Broken AC3 audio stream descriptor.");
- return null;
- }
- pos += 2;
- byte sampleRateCode = (byte) ((data[pos] & 0xe0) >> 5);
- byte bsid = (byte) (data[pos] & 0x1f);
- ++pos;
- byte bitRateCode = (byte) ((data[pos] & 0xfc) >> 2);
- byte surroundMode = (byte) (data[pos] & 0x03);
- ++pos;
- byte bsmod = (byte) ((data[pos] & 0xe0) >> 5);
- int numChannels = (data[pos] & 0x1e) >> 1;
- boolean fullSvc = (data[pos] & 0x01) != 0;
- ++pos;
- byte langCod = data[pos];
- byte langCod2 = 0;
- if (numChannels == 0) {
- if (limit <= pos) {
- Log.e(TAG, "Broken AC3 audio stream descriptor.");
- return null;
- }
- ++pos;
- langCod2 = data[pos];
- }
- if (limit <= pos + 1) {
- Log.e(TAG, "Broken AC3 audio stream descriptor.");
- return null;
- }
- byte mainId = 0;
- byte priority = 0;
- byte asvcflags = 0;
- ++pos;
- if (bsmod < 2) {
- mainId = (byte) ((data[pos] & 0xe0) >> 5);
- priority = (byte) ((data[pos] & 0x18) >> 3);
- if ((data[pos] & 0x07) != 0x07) {
- Log.e(TAG, "Broken AC3 audio stream descriptor reserved failed");
- return null;
- }
- } else {
- asvcflags = data[pos];
- }
-
- // See A/52B Table A3.6 num_channels.
- int numEncodedChannels;
- switch (numChannels) {
- case 1:
- case 8:
- numEncodedChannels = 1;
- break;
- case 2:
- case 9:
- numEncodedChannels = 2;
- break;
- case 3:
- case 4:
- case 10:
- numEncodedChannels = 3;
- break;
- case 5:
- case 6:
- case 11:
- numEncodedChannels = 4;
- break;
- case 7:
- case 12:
- numEncodedChannels = 5;
- break;
- case 13:
- numEncodedChannels = 6;
- break;
- default:
- numEncodedChannels = 0;
- break;
- }
-
- if (limit <= pos + 1) {
- Log.w(TAG, "Missing text and language fields on AC3 audio stream descriptor.");
- return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod,
- numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags,
- null, null, null);
- }
- ++pos;
- int textLen = (data[pos] & 0xfe) >> 1;
- boolean textCode = (data[pos] & 0x01) != 0;
- ++pos;
- String text = "";
- if (textLen > 0) {
- if (limit < pos + textLen) {
- Log.e(TAG, "Broken AC3 audio stream descriptor");
- return null;
- }
- if (textCode) {
- text = new String(data, pos, textLen);
- } else {
- text = new String(data, pos, textLen, Charset.forName("UTF-16"));
- }
- pos += textLen;
- }
- String language = null;
- String language2 = null;
- if (pos < limit) {
- // Many AC3 audio stream descriptors skip the language fields.
- boolean languageFlag1 = (data[pos] & 0x80) != 0;
- boolean languageFlag2 = (data[pos] & 0x40) != 0;
- if ((data[pos] & 0x3f) != 0x3f) {
- Log.e(TAG, "Broken AC3 audio stream descriptor");
- return null;
- }
- if (pos + (languageFlag1 ? 3 : 0) + (languageFlag2 ? 3 : 0) > limit) {
- Log.e(TAG, "Broken AC3 audio stream descriptor");
- return null;
- }
- ++pos;
- if (languageFlag1) {
- language = new String(data, pos, 3);
- pos += 3;
- }
- if (languageFlag2) {
- language2 = new String(data, pos, 3);
- }
- }
-
- return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod,
- numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags, text,
- language, language2);
- }
-
- private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) {
- // For details of DVB service descriptors, see DVB Document A038 Table 86.
- if (limit < pos + 5) {
- Log.e(TAG, "Broken service descriptor.");
- return null;
- }
- pos += 2;
- int serviceType = data[pos] & 0xff;
- pos++;
- int serviceProviderNameLength = data[pos] & 0xff;
- pos++;
- String serviceProviderName = extractTextFromDvb(data, pos, serviceProviderNameLength);
- pos += serviceProviderNameLength;
- int serviceNameLength = data[pos] & 0xff;
- pos++;
- String serviceName = extractTextFromDvb(data, pos, serviceNameLength);
- return new ServiceDescriptor(serviceType, serviceProviderName, serviceName);
- }
-
- private static TsDescriptor parseDvbShortEvent(byte[] data, int pos, int limit) {
- // For details of DVB service descriptors, see DVB Document A038 Table 91.
- if (limit < pos + 7) {
- Log.e(TAG, "Broken short event descriptor.");
- return null;
- }
- pos += 2;
- String language = new String(data, pos, 3);
- int eventNameLength = data[pos + 3] & 0xff;
- pos += 4;
- if (pos + eventNameLength > limit) {
- Log.e(TAG, "Broken short event descriptor.");
- return null;
- }
- String eventName = new String(data, pos, eventNameLength);
- pos += eventNameLength;
- int textLength = data[pos] & 0xff;
- if (pos + textLength > limit) {
- Log.e(TAG, "Broken short event descriptor.");
- return null;
- }
- pos++;
- String text = new String(data, pos, textLength);
- return new ShortEventDescriptor(language, eventName, text);
- }
-
- private static TsDescriptor parseDvbContent(byte[] data, int pos, int limit) {
- // TODO: According to DVB Document A038 Table 27 to add a parser for content descriptor to
- // get content genre.
- return null;
- }
-
- private static TsDescriptor parseDvbParentalRating(byte[] data, int pos, int limit) {
- // For details of DVB service descriptors, see DVB Document A038 Table 81.
- HashMap<String, Integer> ratings = new HashMap<>();
- pos += 2;
- while (pos + 4 <= limit) {
- String countryCode = new String(data, pos, 3);
- int rating = data[pos + 3] & 0xff;
- pos += 4;
- if (rating > 15) {
- // Rating > 15 means that the ratings is defined by broadcaster.
- continue;
- }
- ratings.put(countryCode, rating + 3);
- }
- return new ParentalRatingDescriptor(ratings);
- }
-
- private static int getShortNameSize(byte[] data, int offset) {
- for (int i = 0; i < MAX_SHORT_NAME_BYTES; i += 2) {
- if (data[offset + i] == 0 && data[offset + i + 1] == 0) {
- return i;
- }
- }
- return MAX_SHORT_NAME_BYTES;
- }
-
- private static String extractText(byte[] data, int pos) {
- if (data.length < pos) {
- return null;
- }
- int numStrings = data[pos] & 0xff;
- pos++;
- for (int i = 0; i < numStrings; ++i) {
- if (data.length <= pos + 3) {
- Log.e(TAG, "Broken text.");
- return null;
- }
- int numSegments = data[pos + 3] & 0xff;
- pos += 4;
- for (int j = 0; j < numSegments; ++j) {
- if (data.length <= pos + 2) {
- Log.e(TAG, "Broken text.");
- return null;
- }
- int compressionType = data[pos] & 0xff;
- int mode = data[pos + 1] & 0xff;
- int numBytes = data[pos + 2] & 0xff;
- if (data.length < pos + 3 + numBytes) {
- Log.e(TAG, "Broken text.");
- return null;
- }
- byte[] bytes = Arrays.copyOfRange(data, pos + 3, pos + 3 + numBytes);
- if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION) {
- try {
- switch (mode) {
- case MODE_SELECTED_UNICODE_RANGE_1:
- return new String(bytes, "ISO-8859-1");
- case MODE_SCSU:
- return UnicodeDecompressor.decompress(bytes);
- case MODE_UTF16:
- return new String(bytes, "UTF-16");
- }
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Unsupported text format.", e);
- }
- }
- pos += 3 + numBytes;
- }
- }
- return null;
- }
-
- private static String extractTextFromDvb(byte[] data, int pos, int length) {
- // For details of DVB character set selection, see DVB Document A038 Annex A.
- if (data.length < pos + length) {
- return null;
- }
- try {
- String charsetPrefix = "ISO-8859-";
- switch (data[0]) {
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- case 0x09:
- case 0x0A:
- case 0x0B:
- String charset = charsetPrefix + String.valueOf(data[0] & 0xff + 4);
- return new String(data, pos, length, charset);
- case 0x10:
- if (length < 3) {
- Log.e(TAG, "Broken DVB text");
- return null;
- }
- int codeTable = data[pos + 2] & 0xff;
- if (data[pos + 1] == 0 && codeTable > 0 && codeTable < 15) {
- return new String(
- data, pos, length, charsetPrefix + String.valueOf(codeTable));
- } else {
- return new String(data, pos, length, "ISO-8859-1");
- }
- case 0x11:
- case 0x14:
- case 0x15:
- return new String(data, pos, length, "UTF-16BE");
- case 0x12:
- return new String(data, pos, length, "EUC-KR");
- case 0x13:
- return new String(data, pos, length, "GB2312");
- default:
- return new String(data, pos, length, "ISO-8859-1");
- }
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Unsupported text format.", e);
- }
- return new String(data, pos, length);
- }
-
- private static boolean checkSanity(byte[] data) {
- if (data.length <= 1) {
- return false;
- }
- boolean hasCRC = (data[1] & 0x80) != 0; // section_syntax_indicator
- if (hasCRC) {
- int crc = 0xffffffff;
- for(byte b : data) {
- int index = ((crc >> 24) ^ (b & 0xff)) & 0xff;
- crc = CRC_TABLE[index] ^ (crc << 8);
- }
- if(crc != 0){
- return false;
- }
- }
- return true;
- }
-}
diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java
deleted file mode 100644
index 7cdb534e..00000000
--- a/src/com/android/tv/tuner/ts/TsParser.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.ts;
-
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.tv.tuner.data.PsiData.PatItem;
-import com.android.tv.tuner.data.PsiData.PmtItem;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.EttItem;
-import com.android.tv.tuner.data.PsipData.MgtItem;
-import com.android.tv.tuner.data.PsipData.SdtItem;
-import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.ts.SectionParser.OutputListener;
-import com.android.tv.tuner.util.ByteArrayBuffer;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeSet;
-
-/**
- * Parses MPEG-2 TS packets.
- */
-public class TsParser {
- private static final String TAG = "TsParser";
- private static final boolean DEBUG = false;
-
- public static final int ATSC_SI_BASE_PID = 0x1ffb;
- public static final int PAT_PID = 0x0000;
- public static final int DVB_SDT_PID = 0x0011;
- public static final int DVB_EIT_PID = 0x0012;
- private static final int TS_PACKET_START_CODE = 0x47;
- private static final int TS_PACKET_TEI_MASK = 0x80;
- private static final int TS_PACKET_SIZE = 188;
-
- /*
- * Using a SparseArray removes the need to auto box the int key for mStreamMap
- * in feedTdPacket which is called 100 times a second. This greatly reduces the
- * number of objects created and the frequency of garbage collection.
- * Other maps might be suitable for a SparseArray, but the performance
- * trade offs must be considered carefully.
- * mStreamMap is the only one called at such a high rate.
- */
- private final SparseArray<Stream> mStreamMap = new SparseArray<>();
- private final Map<Integer, VctItem> mSourceIdToVctItemMap = new HashMap<>();
- private final Map<Integer, String> mSourceIdToVctItemDescriptionMap = new HashMap<>();
- private final Map<Integer, VctItem> mProgramNumberToVctItemMap = new HashMap<>();
- private final Map<Integer, List<PmtItem>> mProgramNumberToPMTMap = new HashMap<>();
- private final Map<Integer, List<EitItem>> mSourceIdToEitMap = new HashMap<>();
- private final Map<Integer, SdtItem> mProgramNumberToSdtItemMap = new HashMap<>();
- private final Map<EventSourceEntry, List<EitItem>> mEitMap = new HashMap<>();
- private final Map<EventSourceEntry, List<EttItem>> mETTMap = new HashMap<>();
- private final TreeSet<Integer> mEITPids = new TreeSet<>();
- private final TreeSet<Integer> mETTPids = new TreeSet<>();
- private final SparseBooleanArray mProgramNumberHandledStatus = new SparseBooleanArray();
- private final SparseBooleanArray mVctItemHandledStatus = new SparseBooleanArray();
- private final TsOutputListener mListener;
- private final boolean mIsDvbSignal;
-
- private int mVctItemCount;
- private int mHandledVctItemCount;
- private int mVctSectionParsedCount;
- private boolean[] mVctSectionParsed;
-
- public interface TsOutputListener {
- void onPatDetected(List<PatItem> items);
- void onEitPidDetected(int pid);
- void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems);
- void onEitItemParsed(VctItem channel, List<EitItem> items);
- void onEttPidDetected(int pid);
- void onAllVctItemsParsed();
- void onSdtItemParsed(SdtItem channel, List<PmtItem> pmtItems);
- }
-
- private abstract class Stream {
- private static final int INVALID_CONTINUITY_COUNTER = -1;
- private static final int NUM_CONTINUITY_COUNTER = 16;
-
- protected int mContinuityCounter = INVALID_CONTINUITY_COUNTER;
- protected final ByteArrayBuffer mPacket = new ByteArrayBuffer(TS_PACKET_SIZE);
-
- public void feedData(byte[] data, int continuityCounter, boolean startIndicator) {
- if ((mContinuityCounter + 1) % NUM_CONTINUITY_COUNTER != continuityCounter) {
- mPacket.setLength(0);
- }
- mContinuityCounter = continuityCounter;
- handleData(data, startIndicator);
- }
-
- protected abstract void handleData(byte[] data, boolean startIndicator);
- protected abstract void resetDataVersions();
- }
-
- private class SectionStream extends Stream {
- private final SectionParser mSectionParser;
- private final int mPid;
-
- public SectionStream(int pid) {
- mPid = pid;
- mSectionParser = new SectionParser(mSectionListener);
- }
-
- @Override
- protected void handleData(byte[] data, boolean startIndicator) {
- int startPos = 0;
- if (mPacket.length() == 0) {
- if (startIndicator) {
- startPos = (data[0] & 0xff) + 1;
- } else {
- // Don't know where the section starts yet. Wait until start indicator is on.
- return;
- }
- } else {
- if (startIndicator) {
- startPos = 1;
- }
- }
-
- // When a broken packet is encountered, parsing will stop and return right away.
- if (startPos >= data.length) {
- mPacket.setLength(0);
- return;
- }
- mPacket.append(data, startPos, data.length - startPos);
- mSectionParser.parseSections(mPacket);
- }
-
- @Override
- protected void resetDataVersions() {
- mSectionParser.resetVersionNumbers();
- }
-
- private final OutputListener mSectionListener = new OutputListener() {
- @Override
- public void onPatParsed(List<PatItem> items) {
- for (PatItem i : items) {
- startListening(i.getPmtPid());
- }
- if (mListener != null) {
- mListener.onPatDetected(items);
- }
- }
-
- @Override
- public void onPmtParsed(int programNumber, List<PmtItem> items) {
- mProgramNumberToPMTMap.put(programNumber, items);
- if (DEBUG) {
- Log.d(TAG, "onPMTParsed, programNo " + programNumber + " handledStatus is "
- + mProgramNumberHandledStatus.get(programNumber, false));
- }
- int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber);
- if (statusIndex < 0) {
- mProgramNumberHandledStatus.put(programNumber, false);
- }
- if (!mProgramNumberHandledStatus.get(programNumber)) {
- VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber);
- if (vctItem != null) {
- // When PMT is parsed later than VCT.
- mProgramNumberHandledStatus.put(programNumber, true);
- handleVctItem(vctItem, items);
- mHandledVctItemCount++;
- if (mHandledVctItemCount >= mVctItemCount
- && mVctSectionParsedCount >= mVctSectionParsed.length
- && mListener != null) {
- mListener.onAllVctItemsParsed();
- }
- }
- SdtItem sdtItem = mProgramNumberToSdtItemMap.get(programNumber);
- if (sdtItem != null) {
- // When PMT is parsed later than SDT.
- mProgramNumberHandledStatus.put(programNumber, true);
- handleSdtItem(sdtItem, items);
- }
- }
- }
-
- @Override
- public void onMgtParsed(List<MgtItem> items) {
- for (MgtItem i : items) {
- if (mStreamMap.get(i.getTableTypePid()) != null) {
- continue;
- }
- if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START
- && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) {
- startListening(i.getTableTypePid());
- mEITPids.add(i.getTableTypePid());
- if (mListener != null) {
- mListener.onEitPidDetected(i.getTableTypePid());
- }
- } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT ||
- (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START
- && i.getTableType() <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) {
- startListening(i.getTableTypePid());
- mETTPids.add(i.getTableTypePid());
- if (mListener != null) {
- mListener.onEttPidDetected(i.getTableTypePid());
- }
- }
- }
- }
-
- @Override
- public void onVctParsed(List<VctItem> items, int sectionNumber, int lastSectionNumber) {
- if (mVctSectionParsed == null) {
- mVctSectionParsed = new boolean[lastSectionNumber + 1];
- } else if (mVctSectionParsed[sectionNumber]) {
- // The current section was handled before.
- if (DEBUG) {
- Log.d(TAG, "Duplicate VCT section found.");
- }
- return;
- }
- mVctSectionParsed[sectionNumber] = true;
- mVctSectionParsedCount++;
- mVctItemCount += items.size();
- for (VctItem i : items) {
- if (DEBUG) Log.d(TAG, "onVCTParsed " + i);
- if (i.getSourceId() != 0) {
- mSourceIdToVctItemMap.put(i.getSourceId(), i);
- i.setDescription(mSourceIdToVctItemDescriptionMap.get(i.getSourceId()));
- }
- int programNumber = i.getProgramNumber();
- mProgramNumberToVctItemMap.put(programNumber, i);
- List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
- if (pmtList != null) {
- mProgramNumberHandledStatus.put(programNumber, true);
- handleVctItem(i, pmtList);
- mHandledVctItemCount++;
- if (mHandledVctItemCount >= mVctItemCount
- && mVctSectionParsedCount >= mVctSectionParsed.length
- && mListener != null) {
- mListener.onAllVctItemsParsed();
- }
- } else {
- mProgramNumberHandledStatus.put(programNumber, false);
- Log.i(TAG, "onVCTParsed, but PMT for programNo " + programNumber
- + " is not found yet.");
- }
- }
- }
-
- @Override
- public void onEitParsed(int sourceId, List<EitItem> items) {
- if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId);
- EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
- mEitMap.put(entry, items);
- handleEvents(sourceId);
- }
-
- @Override
- public void onEttParsed(int sourceId, List<EttItem> descriptions) {
- if (DEBUG) {
- Log.d(TAG, String.format("onETTParsed sourceId: %d, descriptions.size(): %d",
- sourceId, descriptions.size()));
- }
- for (EttItem item : descriptions) {
- if (item.eventId == 0) {
- // Channel description
- mSourceIdToVctItemDescriptionMap.put(sourceId, item.text);
- VctItem vctItem = mSourceIdToVctItemMap.get(sourceId);
- if (vctItem != null) {
- vctItem.setDescription(item.text);
- List<PmtItem> pmtItems =
- mProgramNumberToPMTMap.get(vctItem.getProgramNumber());
- if (pmtItems != null) {
- handleVctItem(vctItem, pmtItems);
- }
- }
- }
- }
-
- // Event Information description
- EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
- mETTMap.put(entry, descriptions);
- handleEvents(sourceId);
- }
-
- @Override
- public void onSdtParsed(List<SdtItem> sdtItems) {
- for (SdtItem sdtItem : sdtItems) {
- if (DEBUG) Log.d(TAG, "onSdtParsed " + sdtItem);
- int programNumber = sdtItem.getServiceId();
- mProgramNumberToSdtItemMap.put(programNumber, sdtItem);
- List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
- if (pmtList != null) {
- mProgramNumberHandledStatus.put(programNumber, true);
- handleSdtItem(sdtItem, pmtList);
- } else {
- mProgramNumberHandledStatus.put(programNumber, false);
- Log.i(TAG, "onSdtParsed, but PMT for programNo " + programNumber
- + " is not found yet.");
- }
- }
- }
- };
- }
-
- private static class EventSourceEntry {
- public final int pid;
- public final int sourceId;
-
- public EventSourceEntry(int pid, int sourceId) {
- this.pid = pid;
- this.sourceId = sourceId;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + pid;
- result = 31 * result + sourceId;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof EventSourceEntry) {
- EventSourceEntry another = (EventSourceEntry) obj;
- return pid == another.pid && sourceId == another.sourceId;
- }
- return false;
- }
- }
-
- private void handleVctItem(VctItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "handleVctItem " + channel);
- }
- if (mListener != null) {
- mListener.onVctItemParsed(channel, pmtItems);
- }
- int sourceId = channel.getSourceId();
- int statusIndex = mVctItemHandledStatus.indexOfKey(sourceId);
- if (statusIndex < 0) {
- mVctItemHandledStatus.put(sourceId, false);
- return;
- }
- if (!mVctItemHandledStatus.valueAt(statusIndex)) {
- List<EitItem> eitItems = mSourceIdToEitMap.get(sourceId);
- if (eitItems != null) {
- // When VCT is parsed later than EIT.
- mVctItemHandledStatus.put(sourceId, true);
- handleEitItems(channel, eitItems);
- }
- }
- }
-
- private void handleEitItems(VctItem channel, List<EitItem> items) {
- if (mListener != null) {
- mListener.onEitItemParsed(channel, items);
- }
- }
-
- private void handleSdtItem(SdtItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "handleSdtItem " + channel);
- }
- if (mListener != null) {
- mListener.onSdtItemParsed(channel, pmtItems);
- }
- }
-
- private void handleEvents(int sourceId) {
- Map<Integer, EitItem> itemSet = new HashMap<>();
- for (int pid : mEITPids) {
- List<EitItem> eitItems = mEitMap.get(new EventSourceEntry(pid, sourceId));
- if (eitItems != null) {
- for (EitItem item : eitItems) {
- item.setDescription(null);
- itemSet.put(item.getEventId(), item);
- }
- }
- }
- for (int pid : mETTPids) {
- List<EttItem> ettItems = mETTMap.get(new EventSourceEntry(pid, sourceId));
- if (ettItems != null) {
- for (EttItem ettItem : ettItems) {
- if (ettItem.eventId != 0) {
- EitItem item = itemSet.get(ettItem.eventId);
- if (item != null) {
- item.setDescription(ettItem.text);
- }
- }
- }
- }
- }
- List<EitItem> items = new ArrayList<>(itemSet.values());
- mSourceIdToEitMap.put(sourceId, items);
- VctItem channel = mSourceIdToVctItemMap.get(sourceId);
- if (channel != null && mProgramNumberHandledStatus.get(channel.getProgramNumber())) {
- mVctItemHandledStatus.put(sourceId, true);
- handleEitItems(channel, items);
- } else {
- mVctItemHandledStatus.put(sourceId, false);
- if (!mIsDvbSignal) {
- // Log only when zapping to non-DVB channels, since there is not VCT in DVB signal.
- Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet.");
- }
- }
- }
-
- /**
- * Creates MPEG-2 TS parser.
- *
- * @param listener TsOutputListener
- */
- public TsParser(TsOutputListener listener, boolean isDvbSignal) {
- startListening(PAT_PID);
- startListening(ATSC_SI_BASE_PID);
- mIsDvbSignal = isDvbSignal;
- if (isDvbSignal) {
- startListening(DVB_EIT_PID);
- startListening(DVB_SDT_PID);
- }
- mListener = listener;
- }
-
- private void startListening(int pid) {
- mStreamMap.put(pid, new SectionStream(pid));
- }
-
- private boolean feedTSPacket(byte[] tsData, int pos) {
- if (tsData.length < pos + TS_PACKET_SIZE) {
- if (DEBUG) Log.d(TAG, "Data should include a single TS packet.");
- return false;
- }
- if (tsData[pos] != TS_PACKET_START_CODE) {
- if (DEBUG) Log.d(TAG, "Invalid ts packet.");
- return false;
- }
- if ((tsData[pos + 1] & TS_PACKET_TEI_MASK) != 0) {
- if (DEBUG) Log.d(TAG, "Erroneous ts packet.");
- return false;
- }
-
- // For details for the structure of TS packet, see H.222.0 Table 2-2.
- int pid = ((tsData[pos + 1] & 0x1f) << 8) | (tsData[pos + 2] & 0xff);
- boolean hasAdaptation = (tsData[pos + 3] & 0x20) != 0;
- boolean hasPayload = (tsData[pos + 3] & 0x10) != 0;
- boolean payloadStartIndicator = (tsData[pos + 1] & 0x40) != 0;
- int continuityCounter = tsData[pos + 3] & 0x0f;
- Stream stream = mStreamMap.get(pid);
- int payloadPos = pos;
- payloadPos += hasAdaptation ? 5 + (tsData[pos + 4] & 0xff) : 4;
- if (!hasPayload || stream == null) {
- // We are not interested in this packet.
- return false;
- }
- if (payloadPos >= pos + TS_PACKET_SIZE) {
- if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet.");
- return false;
- }
- stream.feedData(Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE),
- continuityCounter, payloadStartIndicator);
- return true;
- }
-
- /**
- * Feeds MPEG-2 TS data to parse.
- * @param tsData buffer for ATSC TS stream
- * @param pos the offset where buffer starts
- * @param length The length of available data
- */
- public void feedTSData(byte[] tsData, int pos, int length) {
- for (; pos <= length - TS_PACKET_SIZE; pos += TS_PACKET_SIZE) {
- feedTSPacket(tsData, pos);
- }
- }
-
- /**
- * Retrieves the channel information regardless of being well-formed.
- * @return {@link List} of {@link TunerChannel}
- */
- public List<TunerChannel> getMalFormedChannels() {
- List<TunerChannel> incompleteChannels = new ArrayList<>();
- for (int i = 0; i < mProgramNumberHandledStatus.size(); i++) {
- if (!mProgramNumberHandledStatus.valueAt(i)) {
- int programNumber = mProgramNumberHandledStatus.keyAt(i);
- List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
- if (pmtList != null) {
- TunerChannel tunerChannel = new TunerChannel(programNumber, pmtList);
- incompleteChannels.add(tunerChannel);
- }
- }
- }
- return incompleteChannels;
- }
-
- /**
- * Reset the versions so that data with old version number can be handled.
- */
- public void resetDataVersions() {
- for (int eitPid : mEITPids) {
- Stream stream = mStreamMap.get(eitPid);
- if (stream != null) {
- stream.resetDataVersions();
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java
deleted file mode 100644
index d2b4998a..00000000
--- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java
+++ /dev/null
@@ -1,734 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.content.ComponentName;
-import android.content.ContentProviderOperation;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.RemoteException;
-import android.support.annotation.Nullable;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.util.ConvertUtils;
-import com.android.tv.util.PermissionUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Manages the channel info and EPG data through {@link TvInputManager}.
- */
-public class ChannelDataManager implements Handler.Callback {
- private static final String TAG = "ChannelDataManager";
-
- private static final String[] ALL_PROGRAMS_SELECTION_ARGS = new String[] {
- TvContract.Programs._ID,
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_BROADCAST_GENRE,
- TvContract.Programs.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_VERSION_NUMBER };
- private static final String[] CHANNEL_DATA_SELECTION_ARGS = new String[] {
- TvContract.Channels._ID,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1};
-
- private static final int MSG_HANDLE_EVENTS = 1;
- private static final int MSG_HANDLE_CHANNEL = 2;
- private static final int MSG_BUILD_CHANNEL_MAP = 3;
- private static final int MSG_REQUEST_PROGRAMS = 4;
- private static final int MSG_CLEAR_CHANNELS = 6;
- private static final int MSG_CHECK_VERSION = 7;
-
- // Throttle the batch operations to avoid TransactionTooLargeException.
- private static final int BATCH_OPERATION_COUNT = 100;
- // At most 16 days of program information is delivered through an EIT,
- // according to the Chapter 6.4 of ATSC Recommended Practice A/69.
- private static final long PROGRAM_QUERY_DURATION = TimeUnit.DAYS.toMillis(16);
-
- /**
- * A version number to enforce consistency of the channel data.
- *
- * WARNING: If a change in the database serialization lead to breaking the backward
- * compatibility, you must increment this value so that the old data are purged,
- * and the user is requested to perform the auto-scan again to generate the new data set.
- */
- private static final int VERSION = 6;
-
- private final Context mContext;
- private final String mInputId;
- private ProgramInfoListener mListener;
- private ChannelScanListener mChannelScanListener;
- private Handler mChannelScanHandler;
- private final HandlerThread mHandlerThread;
- private final Handler mHandler;
- private final ConcurrentHashMap<Long, TunerChannel> mTunerChannelMap;
- private final ConcurrentSkipListMap<TunerChannel, Long> mTunerChannelIdMap;
- private final Uri mChannelsUri;
-
- // Used for scanning
- private final ConcurrentSkipListSet<TunerChannel> mScannedChannels;
- private final ConcurrentSkipListSet<TunerChannel> mPreviousScannedChannels;
- private final AtomicBoolean mIsScanning;
- private final AtomicBoolean scanCompleted = new AtomicBoolean();
-
- public interface ProgramInfoListener {
-
- /**
- * Invoked when a request for getting programs of a channel has been processed and passes
- * the requested channel and the programs retrieved from database to the listener.
- */
- void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs);
-
- /**
- * Invoked when programs of a channel have been arrived and passes the arrived channel and
- * programs to the listener.
- */
- void onProgramsArrived(TunerChannel channel, List<EitItem> programs);
-
- /**
- * Invoked when a channel has been arrived and passes the arrived channel to the listener.
- */
- void onChannelArrived(TunerChannel channel);
-
- /**
- * Invoked when the database schema has been changed and the old-format channels have been
- * deleted. A receiver should notify to a user that re-scanning channels is necessary.
- */
- void onRescanNeeded();
- }
-
- public interface ChannelScanListener {
- /**
- * Invoked when all pending channels have been handled.
- */
- void onChannelHandlingDone();
- }
-
- public ChannelDataManager(Context context) {
- mContext = context;
- mInputId = TvContract.buildInputId(new ComponentName(mContext.getPackageName(),
- TunerTvInputService.class.getName()));
- mChannelsUri = TvContract.buildChannelsUriForInput(mInputId);
- mTunerChannelMap = new ConcurrentHashMap<>();
- mTunerChannelIdMap = new ConcurrentSkipListMap<>();
- mHandlerThread = new HandlerThread("TvInputServiceBackgroundThread");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper(), this);
- mIsScanning = new AtomicBoolean();
- mScannedChannels = new ConcurrentSkipListSet<>();
- mPreviousScannedChannels = new ConcurrentSkipListSet<>();
- }
-
- // Public methods
- public void checkDataVersion(Context context) {
- int version = TunerPreferences.getChannelDataVersion(context);
- Log.d(TAG, "ChannelDataManager.VERSION=" + VERSION + " (current=" + version + ")");
- if (version == VERSION) {
- // Everything is awesome. Return and continue.
- return;
- }
- setCurrentVersion(context);
-
- if (version == TunerPreferences.CHANNEL_DATA_VERSION_NOT_SET) {
- mHandler.sendEmptyMessage(MSG_CHECK_VERSION);
- } else {
- // The stored channel data seem outdated. Delete them all.
- mHandler.sendEmptyMessage(MSG_CLEAR_CHANNELS);
- }
- }
-
- public void setCurrentVersion(Context context) {
- TunerPreferences.setChannelDataVersion(context, VERSION);
- }
-
- public void setListener(ProgramInfoListener listener) {
- mListener = listener;
- }
-
- public void setChannelScanListener(ChannelScanListener listener, Handler handler) {
- mChannelScanListener = listener;
- mChannelScanHandler = handler;
- }
-
- public void release() {
- mHandler.removeCallbacksAndMessages(null);
- releaseSafely();
- }
-
- public void releaseSafely() {
- mHandlerThread.quitSafely();
- mListener = null;
- mChannelScanListener = null;
- mChannelScanHandler = null;
- }
-
- public TunerChannel getChannel(long channelId) {
- TunerChannel channel = mTunerChannelMap.get(channelId);
- if (channel != null) {
- return channel;
- }
- mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP);
- byte[] data = null;
- try (Cursor cursor = mContext.getContentResolver().query(TvContract.buildChannelUri(
- channelId), CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- data = cursor.getBlob(1);
- }
- }
- if (data == null) {
- return null;
- }
- channel = TunerChannel.parseFrom(data);
- if (channel == null) {
- return null;
- }
- channel.setChannelId(channelId);
- return channel;
- }
-
- public void requestProgramsData(TunerChannel channel) {
- mHandler.removeMessages(MSG_REQUEST_PROGRAMS);
- mHandler.obtainMessage(MSG_REQUEST_PROGRAMS, channel).sendToTarget();
- }
-
- public void notifyEventDetected(TunerChannel channel, List<EitItem> items) {
- mHandler.obtainMessage(MSG_HANDLE_EVENTS, new ChannelEvent(channel, items)).sendToTarget();
- }
-
- public void notifyChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- if (mIsScanning.get()) {
- // During scanning, channels should be handle first to improve scan time.
- // EIT items can be handled in background after channel scan.
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, channel));
- } else {
- mHandler.obtainMessage(MSG_HANDLE_CHANNEL, channel).sendToTarget();
- }
- }
-
- // For scanning process
- /**
- * Invoked when starting a scanning mode. This method gets the previous channels to detect the
- * obsolete channels after scanning and initializes the variables used for scanning.
- */
- public void notifyScanStarted() {
- mScannedChannels.clear();
- mPreviousScannedChannels.clear();
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- long channelId = cursor.getLong(0);
- byte[] data = cursor.getBlob(1);
- TunerChannel channel = TunerChannel.parseFrom(data);
- if (channel != null) {
- channel.setChannelId(channelId);
- mPreviousScannedChannels.add(channel);
- }
- } while (cursor.moveToNext());
- }
- }
- mIsScanning.set(true);
- }
-
- /**
- * Invoked when completing the scanning mode. Passes {@code MSG_SCAN_COMPLETED} to the handler
- * in order to wait for finishing the remaining messages in the handler queue. Then removes the
- * obsolete channels, which are previously scanned but are not in the current scanned result.
- */
- public void notifyScanCompleted() {
- // Send a dummy message to check whether there is any MSG_HANDLE_CHANNEL in queue
- // and avoid race conditions.
- scanCompleted.set(true);
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, null));
- }
-
- public void scannedChannelHandlingCompleted() {
- mIsScanning.set(false);
- if (!mPreviousScannedChannels.isEmpty()) {
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- for (TunerChannel channel : mPreviousScannedChannels) {
- ops.add(ContentProviderOperation.newDelete(
- TvContract.buildChannelUri(channel.getChannelId())).build());
- }
- try {
- mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
- } catch (RemoteException | OperationApplicationException e) {
- Log.e(TAG, "Error deleting obsolete channels", e);
- }
- }
- if (mChannelScanListener != null && mChannelScanHandler != null) {
- mChannelScanHandler.post(new Runnable() {
- @Override
- public void run() {
- mChannelScanListener.onChannelHandlingDone();
- }
- });
- } else {
- Log.e(TAG, "Error. mChannelScanListener is null.");
- }
- }
-
- /**
- * Returns the number of scanned channels in the scanning mode.
- */
- public int getScannedChannelCount() {
- return mScannedChannels.size();
- }
-
- /**
- * Removes all callbacks and messages in handler to avoid previous messages from last channel.
- */
- public void removeAllCallbacksAndMessages() {
- mHandler.removeCallbacksAndMessages(null);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_HANDLE_EVENTS: {
- ChannelEvent event = (ChannelEvent) msg.obj;
- handleEvents(event.channel, event.eitItems);
- return true;
- }
- case MSG_HANDLE_CHANNEL: {
- TunerChannel channel = (TunerChannel) msg.obj;
- if (channel != null) {
- handleChannel(channel);
- }
- if (scanCompleted.get() && mIsScanning.get()
- && !mHandler.hasMessages(MSG_HANDLE_CHANNEL)) {
- // Complete the scan when all found channels have already been handled.
- scannedChannelHandlingCompleted();
- }
- return true;
- }
- case MSG_BUILD_CHANNEL_MAP: {
- mHandler.removeMessages(MSG_BUILD_CHANNEL_MAP);
- buildChannelMap();
- return true;
- }
- case MSG_REQUEST_PROGRAMS: {
- if (mHandler.hasMessages(MSG_REQUEST_PROGRAMS)) {
- return true;
- }
- TunerChannel channel = (TunerChannel) msg.obj;
- if (mListener != null) {
- mListener.onRequestProgramsResponse(channel, getAllProgramsForChannel(channel));
- }
- return true;
- }
- case MSG_CLEAR_CHANNELS: {
- clearChannels();
- return true;
- }
- case MSG_CHECK_VERSION: {
- checkVersion();
- return true;
- }
- }
- return false;
- }
-
- // Private methods
- private void handleEvents(TunerChannel channel, List<EitItem> items) {
- long channelId = getChannelId(channel);
- if (channelId <= 0) {
- return;
- }
- channel.setChannelId(channelId);
-
- // Schedule the audio and caption tracks of the current program and the programs being
- // listed after the current one into TIS.
- if (mListener != null) {
- mListener.onProgramsArrived(channel, items);
- }
-
- long currentTime = System.currentTimeMillis();
- List<EitItem> oldItems = getAllProgramsForChannel(channel, currentTime,
- currentTime + PROGRAM_QUERY_DURATION);
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- // TODO: Find a right way to check if the programs are added outside.
- boolean addedOutside = false;
- for (EitItem item : oldItems) {
- if (item.getEventId() == 0) {
- // The event has been added outside TV tuner.
- addedOutside = true;
- break;
- }
- }
-
- // Inserting programs only when there is no overlapping with existing data assuming that:
- // 1. external EPG is more accurate and rich and
- // 2. the data we add here will be updated when we apply external EPG.
- if (addedOutside) {
- // oldItemCount cannot be 0 if addedOutside is true.
- int oldItemCount = oldItems.size();
- for (EitItem newItem : items) {
- if (newItem.getEndTimeUtcMillis() < currentTime) {
- continue;
- }
- long newItemStartTime = newItem.getStartTimeUtcMillis();
- long newItemEndTime = newItem.getEndTimeUtcMillis();
- if (newItemStartTime < oldItems.get(0).getStartTimeUtcMillis()) {
- // Start time smaller than that of any old items. Insert if no overlap.
- if (newItemEndTime > oldItems.get(0).getStartTimeUtcMillis()) continue;
- } else if (newItemStartTime
- > oldItems.get(oldItemCount - 1).getStartTimeUtcMillis()) {
- // Start time larger than that of any old item. Insert if no overlap.
- if (newItemStartTime
- < oldItems.get(oldItemCount - 1).getEndTimeUtcMillis()) continue;
- } else {
- int pos = Collections.binarySearch(oldItems, newItem,
- new Comparator<EitItem>() {
- @Override
- public int compare(EitItem lhs, EitItem rhs) {
- return Long.compare(lhs.getStartTimeUtcMillis(),
- rhs.getStartTimeUtcMillis());
- }
- });
- if (pos >= 0) {
- // Same start Time found. Overlapped.
- continue;
- }
- int insertPoint = -1 - pos;
- // Check the two adjacent items.
- if (newItemStartTime < oldItems.get(insertPoint - 1).getEndTimeUtcMillis()
- || newItemEndTime > oldItems.get(insertPoint).getStartTimeUtcMillis()) {
- continue;
- }
- }
- ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert(
- TvContract.Programs.CONTENT_URI), newItem, channel));
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- }
- applyBatch(channel.getName(), ops);
- return;
- }
-
- List<EitItem> outdatedOldItems = new ArrayList<>();
- Map<Integer, EitItem> newEitItemMap = new HashMap<>();
- for (EitItem item : items) {
- newEitItemMap.put(item.getEventId(), item);
- }
- for (EitItem oldItem : oldItems) {
- EitItem item = newEitItemMap.get(oldItem.getEventId());
- if (item == null) {
- outdatedOldItems.add(oldItem);
- continue;
- }
-
- // Since program descriptions arrive at different time, the older one may have the
- // correct program description while the newer one has no clue what value is.
- if (oldItem.getDescription() != null && item.getDescription() == null
- && oldItem.getEventId() == item.getEventId()
- && oldItem.getStartTime() == item.getStartTime()
- && oldItem.getLengthInSecond() == item.getLengthInSecond()
- && Objects.equals(oldItem.getContentRating(), item.getContentRating())
- && Objects.equals(oldItem.getBroadcastGenre(), item.getBroadcastGenre())
- && Objects.equals(oldItem.getCanonicalGenre(), item.getCanonicalGenre())) {
- item.setDescription(oldItem.getDescription());
- }
- if (item.compareTo(oldItem) != 0) {
- ops.add(buildContentProviderOperation(ContentProviderOperation.newUpdate(
- TvContract.buildProgramUri(oldItem.getProgramId())), item, null));
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- }
- newEitItemMap.remove(item.getEventId());
- }
- for (EitItem unverifiedOldItems : outdatedOldItems) {
- if (unverifiedOldItems.getStartTimeUtcMillis() > currentTime) {
- // The given new EIT item list covers partial time span of EPG. Here, we delete old
- // item only when it has an overlapping with the new EIT item list.
- long startTime = unverifiedOldItems.getStartTimeUtcMillis();
- long endTime = unverifiedOldItems.getEndTimeUtcMillis();
- for (EitItem item : newEitItemMap.values()) {
- long newItemStartTime = item.getStartTimeUtcMillis();
- long newItemEndTime = item.getEndTimeUtcMillis();
- if ((startTime >= newItemStartTime && startTime < newItemEndTime)
- || (endTime > newItemStartTime && endTime <= newItemEndTime)) {
- ops.add(ContentProviderOperation.newDelete(TvContract.buildProgramUri(
- unverifiedOldItems.getProgramId())).build());
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- break;
- }
- }
- }
- }
- for (EitItem item : newEitItemMap.values()) {
- if (item.getEndTimeUtcMillis() < currentTime) {
- continue;
- }
- ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert(
- TvContract.Programs.CONTENT_URI), item, channel));
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- }
-
- applyBatch(channel.getName(), ops);
- }
-
- private ContentProviderOperation buildContentProviderOperation(
- ContentProviderOperation.Builder builder, EitItem item, TunerChannel channel) {
- if (channel != null) {
- builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- builder.withValue(TvContract.Programs.COLUMN_RECORDING_PROHIBITED,
- channel.isRecordingProhibited() ? 1 : 0);
- }
- }
- if (item != null) {
- builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText())
- .withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- item.getStartTimeUtcMillis())
- .withValue(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- item.getEndTimeUtcMillis())
- .withValue(TvContract.Programs.COLUMN_CONTENT_RATING,
- item.getContentRating())
- .withValue(TvContract.Programs.COLUMN_AUDIO_LANGUAGE,
- item.getAudioLanguage())
- .withValue(TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- item.getDescription())
- .withValue(TvContract.Programs.COLUMN_VERSION_NUMBER,
- item.getEventId());
- }
- return builder.build();
- }
-
- private void applyBatch(String channelName, ArrayList<ContentProviderOperation> operations) {
- try {
- mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, operations);
- } catch (RemoteException | OperationApplicationException e) {
- Log.e(TAG, "Error updating EPG " + channelName, e);
- }
- }
-
- private void handleChannel(TunerChannel channel) {
- long channelId = getChannelId(channel);
- ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_NETWORK_AFFILIATION, channel.getShortName());
- values.put(TvContract.Channels.COLUMN_SERVICE_TYPE, channel.getServiceTypeName());
- values.put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.getTsid());
- values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.getDisplayNumber());
- values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.getName());
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, channel.toByteArray());
- values.put(TvContract.Channels.COLUMN_DESCRIPTION, channel.getDescription());
- values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.getVideoFormat());
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, VERSION);
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2,
- channel.isRecordingProhibited() ? 1 : 0);
-
- if (channelId <= 0) {
- values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
- values.put(TvContract.Channels.COLUMN_TYPE, "QAM256".equals(channel.getModulation())
- ? TvContract.Channels.TYPE_ATSC_C : TvContract.Channels.TYPE_ATSC_T);
- values.put(TvContract.Channels.COLUMN_SERVICE_ID, channel.getProgramNumber());
-
- // ATSC doesn't have original_network_id
- values.put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.getFrequency());
-
- Uri channelUri = mContext.getContentResolver().insert(TvContract.Channels.CONTENT_URI,
- values);
- channelId = ContentUris.parseId(channelUri);
- } else {
- mContext.getContentResolver().update(
- TvContract.buildChannelUri(channelId), values, null, null);
- }
- channel.setChannelId(channelId);
- mTunerChannelMap.put(channelId, channel);
- mTunerChannelIdMap.put(channel, channelId);
- if (mIsScanning.get()) {
- mScannedChannels.add(channel);
- mPreviousScannedChannels.remove(channel);
- }
- if (mListener != null) {
- mListener.onChannelArrived(channel);
- }
- }
-
- private void clearChannels() {
- int count = mContext.getContentResolver().delete(mChannelsUri, null, null);
- if (count > 0) {
- // We have just deleted obsolete data. Now tell the user that he or she needs
- // to perform the auto-scan again.
- if (mListener != null) {
- mListener.onRescanNeeded();
- }
- }
- }
-
- private void checkVersion() {
- if (PermissionUtils.hasAccessAllEpg(mContext)) {
- String selection = TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + "<>?";
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, selection,
- new String[] {Integer.toString(VERSION)}, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- // The stored channel data seem outdated. Delete them all.
- clearChannels();
- }
- }
- } else {
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- new String[] { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 },
- null, null, null)) {
- if (cursor != null) {
- while (cursor.moveToNext()) {
- int version = cursor.getInt(0);
- if (version != VERSION) {
- clearChannels();
- break;
- }
- }
- }
- }
- }
- }
-
- private long getChannelId(TunerChannel channel) {
- Long channelId = mTunerChannelIdMap.get(channel);
- if (channelId != null) {
- return channelId;
- }
- mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP);
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- channelId = cursor.getLong(0);
- byte[] providerData = cursor.getBlob(1);
- TunerChannel tunerChannel = TunerChannel.parseFrom(providerData);
- if (tunerChannel != null && tunerChannel.compareTo(channel) == 0) {
- channel.setChannelId(channelId);
- mTunerChannelIdMap.put(channel, channelId);
- mTunerChannelMap.put(channelId, channel);
- return channelId;
- }
- } while (cursor.moveToNext());
- }
- }
- return -1;
- }
-
- private List<EitItem> getAllProgramsForChannel(TunerChannel channel) {
- return getAllProgramsForChannel(channel, null, null);
- }
-
- private List<EitItem> getAllProgramsForChannel(TunerChannel channel, @Nullable Long startTimeMs,
- @Nullable Long endTimeMs) {
- List<EitItem> items = new ArrayList<>();
- long channelId = channel.getChannelId();
- Uri programsUri = (startTimeMs == null || endTimeMs == null) ?
- TvContract.buildProgramsUriForChannel(channelId) :
- TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs);
- try (Cursor cursor = mContext.getContentResolver().query(programsUri,
- ALL_PROGRAMS_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- long id = cursor.getLong(0);
- String titleText = cursor.getString(1);
- long startTime = ConvertUtils.convertUnixEpochToGPSTime(
- cursor.getLong(2) / DateUtils.SECOND_IN_MILLIS);
- long endTime = ConvertUtils.convertUnixEpochToGPSTime(
- cursor.getLong(3) / DateUtils.SECOND_IN_MILLIS);
- int lengthInSecond = (int) (endTime - startTime);
- String contentRating = cursor.getString(4);
- String broadcastGenre = cursor.getString(5);
- String canonicalGenre = cursor.getString(6);
- String description = cursor.getString(7);
- int eventId = cursor.getInt(8);
- EitItem eitItem = new EitItem(id, eventId, titleText, startTime, lengthInSecond,
- contentRating, null, null, broadcastGenre, canonicalGenre, description);
- items.add(eitItem);
- } while (cursor.moveToNext());
- }
- }
- return items;
- }
-
- private void buildChannelMap() {
- ArrayList<TunerChannel> channels = new ArrayList<>();
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- long channelId = cursor.getLong(0);
- byte[] data = cursor.getBlob(1);
- TunerChannel channel = TunerChannel.parseFrom(data);
- if (channel != null) {
- channel.setChannelId(channelId);
- channels.add(channel);
- }
- } while (cursor.moveToNext());
- }
- }
- mTunerChannelMap.clear();
- mTunerChannelIdMap.clear();
- for (TunerChannel channel : channels) {
- mTunerChannelMap.put(channel.getChannelId(), channel);
- mTunerChannelIdMap.put(channel, channel.getChannelId());
- }
- }
-
- private static class ChannelEvent {
- public final TunerChannel channel;
- public final List<EitItem> eitItems;
-
- public ChannelEvent(TunerChannel channel, List<EitItem> eitItems) {
- this.channel = channel;
- this.eitItems = eitItems;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java
deleted file mode 100644
index dc99118a..00000000
--- a/src/com/android/tv/tuner/tvinput/EventDetector.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.ts.TsParser;
-import com.android.tv.tuner.data.PsiData;
-import com.android.tv.tuner.data.PsipData;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Detects channels and programs that are emerged or changed while parsing ATSC PSIP information.
- */
-public class EventDetector {
- private static final String TAG = "EventDetector";
- private static final boolean DEBUG = false;
- public static final int ALL_PROGRAM_NUMBERS = -1;
-
- private final TunerHal mTunerHal;
-
- private TsParser mTsParser;
- private final Set<Integer> mPidSet = new HashSet<>();
-
- // To prevent channel duplication
- private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
- private final Set<Integer> mSdtProgramNumberSet = new HashSet<>();
- private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
- private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
- private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
- private final List<EventListener> mEventListeners = new ArrayList<>();
- private int mFrequency;
- private String mModulation;
- private int mProgramNumber = ALL_PROGRAM_NUMBERS;
-
- private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() {
- @Override
- public void onPatDetected(List<PsiData.PatItem> items) {
- for (PsiData.PatItem i : items) {
- if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) {
- mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER);
- }
- }
- }
-
- @Override
- public void onEitPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onEitItemParsed(PsipData.VctItem channel, List<PsipData.EitItem> items) {
- TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
- if (DEBUG) {
- Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
- + channel.getProgramNumber());
- }
- int channelSourceId = channel.getSourceId();
-
- // Source id 0 is useful for cases where a cable operator wishes to define a channel for
- // which no EPG data is currently available.
- // We don't handle such a case.
- if (channelSourceId == 0) {
- return;
- }
-
- // If at least a one caption track have been found in EIT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
- for (PsipData.EitItem item : items) {
- if (captionTracksFound) {
- break;
- }
- List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
- if (captionTracks != null && !captionTracks.isEmpty()) {
- captionTracksFound = true;
- }
- }
- mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
- if (captionTracksFound) {
- for (PsipData.EitItem item : items) {
- item.setHasCaptionTrack();
- }
- }
- if (tunerChannel != null && !mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onEventDetected(tunerChannel, items);
- }
- }
- }
-
- @Override
- public void onEttPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onAllVctItemsParsed() {
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelScanDone();
- }
- }
- }
-
- @Override
- public void onVctItemParsed(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onVctItemParsed VCT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PsiData.PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getProgramNumber();
-
- // If at least a one caption track have been found in VCT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
- || !captionTracks.isEmpty();
- mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
- if (captionTracksFound) {
- tunerChannel.setHasCaptionTrack();
- }
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
- tunerChannel.setFrequency(mFrequency);
- tunerChannel.setModulation(mModulation);
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mVctProgramNumberSet.add(channelProgramNumber);
- }
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelDetected(tunerChannel, !found);
- }
- }
- }
-
- @Override
- public void onSdtItemParsed(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onSdtItemParsed SDT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PsiData.PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getServiceId();
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
- tunerChannel.setFrequency(mFrequency);
- tunerChannel.setModulation(mModulation);
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mSdtProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mSdtProgramNumberSet.add(channelProgramNumber);
- }
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelDetected(tunerChannel, !found);
- }
- }
- }
- };
-
- /**
- * Listener for detecting ATSC TV channels and receiving EPG data.
- */
- public interface EventListener {
-
- /**
- * Fired when new information of an ATSC TV channel arrived.
- *
- * @param channel an ATSC TV channel
- * @param channelArrivedAtFirstTime tells whether this channel arrived at first time
- */
- void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime);
-
- /**
- * Fired when new program events of an ATSC TV channel arrived.
- *
- * @param channel an ATSC TV channel
- * @param items a list of EIT items that were received
- */
- void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items);
-
- /**
- * Fired when information of all detectable ATSC TV channels in current frequency arrived.
- */
- void onChannelScanDone();
- }
-
- /**
- * Creates a detector for ATSC TV channles and program information.
- *
- * @param usbTunerInteface {@link TunerHal}
- */
- public EventDetector(TunerHal usbTunerInteface) {
- mTunerHal = usbTunerInteface;
- }
-
- private void reset() {
- // TODO: Use TsParser.reset()
- int deliverySystemType = mTunerHal.getDeliverySystemType();
- mTsParser =
- new TsParser(
- mTsOutputListener,
- TunerHal.isDvbDeliverySystem(mTunerHal.getDeliverySystemType()));
- mPidSet.clear();
- mVctProgramNumberSet.clear();
- mSdtProgramNumberSet.clear();
- mVctCaptionTracksFound.clear();
- mEitCaptionTracksFound.clear();
- mChannelMap.clear();
- }
-
- /**
- * Starts detecting channel and program information.
- *
- * @param frequency The frequency to listen to.
- * @param modulation The modulation type.
- * @param programNumber The program number if this is for handling tune request. For scanning
- * purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
- */
- public void startDetecting(int frequency, String modulation, int programNumber) {
- reset();
- mFrequency = frequency;
- mModulation = modulation;
- mProgramNumber = programNumber;
- }
-
- private void startListening(int pid) {
- if (mPidSet.contains(pid)) {
- return;
- }
- mPidSet.add(pid);
- mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER);
- }
-
- /**
- * Feeds ATSC TS stream to detect channel and program information.
- * @param data buffer for ATSC TS stream
- * @param startOffset the offset where buffer starts
- * @param length The length of available data
- */
- public void feedTSStream(byte[] data, int startOffset, int length) {
- if (mPidSet.isEmpty()) {
- startListening(TsParser.ATSC_SI_BASE_PID);
- }
- if (mTsParser != null) {
- mTsParser.feedTSData(data, startOffset, length);
- }
- }
-
- /**
- * Retrieves the channel information regardless of being well-formed.
- * @return {@link List} of {@link TunerChannel}
- */
- public List<TunerChannel> getMalFormedChannels() {
- return mTsParser.getMalFormedChannels();
- }
-
- /**
- * Registers an EventListener.
- * @param eventListener the listener to be registered
- */
- public void registerListener(EventListener eventListener) {
- if (mTsParser != null) {
- // Resets the version numbers so that the new listener can receive the EIT items.
- // Otherwise, each EIT session is handled only once unless there is a new version.
- mTsParser.resetDataVersions();
- }
- mEventListeners.add(eventListener);
- }
-
- /**
- * Unregisters an EventListener.
- * @param eventListener the listener to be unregistered
- */
- public void unregisterListener(EventListener eventListener) {
- boolean removed = mEventListeners.remove(eventListener);
- if (!removed && DEBUG) {
- Log.d(TAG, "Cannot unregister a non-registered listener!");
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java
deleted file mode 100644
index 99222bf8..00000000
--- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.tv.tuner.data.PsiData.PatItem;
-import com.android.tv.tuner.data.PsiData.PmtItem;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.SdtItem;
-import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.source.FileTsStreamer;
-import com.android.tv.tuner.ts.TsParser;
-import com.android.tv.tuner.tvinput.EventDetector.EventListener;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * PSIP event detector for a file source.
- *
- * <p>Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports
- * various PSIP-related events via {@link TsParser.TsOutputListener}.
- */
-public class FileSourceEventDetector {
- private static final String TAG = "FileSourceEventDetector";
- private static final boolean DEBUG = true;
- public static final int ALL_PROGRAM_NUMBERS = 0;
-
- private TsParser mTsParser;
- private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
- private final Set<Integer> mSdtProgramNumberSet = new HashSet<>();
- private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
- private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
- private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
- private final EventListener mEventListener;
- private final boolean mEnableDvbSignal;
- private FileTsStreamer.StreamProvider mStreamProvider;
- private int mProgramNumber = ALL_PROGRAM_NUMBERS;
-
- public FileSourceEventDetector(EventDetector.EventListener listener, boolean enableDvbSignal) {
- mEventListener = listener;
- mEnableDvbSignal = enableDvbSignal;
- }
-
- /**
- * Starts detecting channel and program information.
- *
- * @param provider MPEG-2 transport stream source.
- * @param programNumber The program number if this is for handling tune request. For scanning
- * purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
- */
- public void start(FileTsStreamer.StreamProvider provider, int programNumber) {
- mStreamProvider = provider;
- mProgramNumber = programNumber;
- reset();
- }
-
- private void reset() {
- mTsParser = new TsParser(mTsOutputListener, mEnableDvbSignal); // TODO: Use TsParser.reset()
- mStreamProvider.clearPidFilter();
- mVctProgramNumberSet.clear();
- mSdtProgramNumberSet.clear();
- mVctCaptionTracksFound.clear();
- mEitCaptionTracksFound.clear();
- mChannelMap.clear();
- }
-
- public void feedTSStream(byte[] data, int startOffset, int length) {
- if (mStreamProvider.isFilterEmpty()) {
- startListening(TsParser.ATSC_SI_BASE_PID);
- startListening(TsParser.PAT_PID);
- }
- if (mTsParser != null) {
- mTsParser.feedTSData(data, startOffset, length);
- }
- }
-
- private void startListening(int pid) {
- if (mStreamProvider.isInFilter(pid)) {
- return;
- }
- mStreamProvider.addPidFilter(pid);
- }
-
- private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() {
- @Override
- public void onPatDetected(List<PatItem> items) {
- for (PatItem i : items) {
- if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) {
- mStreamProvider.addPidFilter(i.getPmtPid());
- }
- }
- }
-
- @Override
- public void onEitPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onEitItemParsed(VctItem channel, List<EitItem> items) {
- TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
- if (DEBUG) {
- Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
- + channel.getProgramNumber());
- }
- int channelSourceId = channel.getSourceId();
-
- // Source id 0 is useful for cases where a cable operator wishes to define a channel for
- // which no EPG data is currently available.
- // We don't handle such a case.
- if (channelSourceId == 0) {
- return;
- }
-
- // If at least a one caption track have been found in EIT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
- for (EitItem item : items) {
- if (captionTracksFound) {
- break;
- }
- List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
- if (captionTracks != null && !captionTracks.isEmpty()) {
- captionTracksFound = true;
- }
- }
- mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
- if (captionTracksFound) {
- for (EitItem item : items) {
- item.setHasCaptionTrack();
- }
- }
- if (tunerChannel != null && mEventListener != null) {
- mEventListener.onEventDetected(tunerChannel, items);
- }
- }
-
- @Override
- public void onEttPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onAllVctItemsParsed() {
- // do nothing.
- }
-
- @Override
- public void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onVctItemParsed VCT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getProgramNumber();
-
- // If at least a one caption track have been found in VCT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
- || !captionTracks.isEmpty();
- mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
- if (captionTracksFound) {
- tunerChannel.setHasCaptionTrack();
- }
- tunerChannel.setFilepath(mStreamProvider.getFilepath());
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
-
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mVctProgramNumberSet.add(channelProgramNumber);
- }
- if (mEventListener != null) {
- mEventListener.onChannelDetected(tunerChannel, !found);
- }
- }
-
- @Override
- public void onSdtItemParsed(SdtItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onSdtItemParsed SDT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = TunerChannel.forDvbFile(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getServiceId();
- tunerChannel.setFilepath(mStreamProvider.getFilepath());
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mSdtProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mSdtProgramNumberSet.add(channelProgramNumber);
- }
- if (mEventListener != null) {
- mEventListener.onChannelDetected(tunerChannel, !found);
- }
- }
- };
-}
diff --git a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java b/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java
deleted file mode 100644
index 3908fe6c..00000000
--- a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-/**
- * The listener for buffer events occurred during playback.
- */
-public interface PlaybackBufferListener {
-
- /**
- * Invoked when the start position of the buffer has been changed.
- *
- * @param startTimeMs the new start time of the buffer in millisecond
- */
- void onBufferStartTimeChanged(long startTimeMs);
-
- /**
- * Invoked when the state of the buffer has been changed.
- *
- * @param available whether the buffer is available or not
- */
- void onBufferStateChanged(boolean available);
-
- /**
- * Invoked when the disk speed is too slow to write the buffers.
- */
- void onDiskTooSlow();
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerDebug.java b/src/com/android/tv/tuner/tvinput/TunerDebug.java
deleted file mode 100644
index 2ddc946a..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerDebug.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * A class to maintain various debugging information.
- */
-public class TunerDebug {
- private static final String TAG = "TunerDebug";
- public static final boolean ENABLED = false;
-
- private int mVideoFrameDrop;
- private int mBytesInQueue;
-
- private long mAudioPositionUs;
- private long mAudioPtsUs;
- private long mVideoPtsUs;
-
- private long mLastAudioPositionUs;
- private long mLastAudioPtsUs;
- private long mLastVideoPtsUs;
- private long mLastCheckTimestampMs;
-
- private long mAudioPositionUsRate;
- private long mAudioPtsUsRate;
- private long mVideoPtsUsRate;
-
- private TunerDebug() {
- mVideoFrameDrop = 0;
- mLastCheckTimestampMs = SystemClock.elapsedRealtime();
- }
-
- private static class LazyHolder {
- private static final TunerDebug INSTANCE = new TunerDebug();
- }
-
- public static TunerDebug getInstance() {
- return LazyHolder.INSTANCE;
- }
-
- public static void notifyVideoFrameDrop(int count, long delta) {
- // TODO: provide timestamp mismatch information using delta
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mVideoFrameDrop += count;
- }
-
- public static int getVideoFrameDrop() {
- TunerDebug sTunerDebug = getInstance();
- int videoFrameDrop = sTunerDebug.mVideoFrameDrop;
- if (videoFrameDrop > 0) {
- Log.d(TAG, "Dropped video frame: " + videoFrameDrop);
- }
- sTunerDebug.mVideoFrameDrop = 0;
- return videoFrameDrop;
- }
-
- public static void setBytesInQueue(int bytesInQueue) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mBytesInQueue = bytesInQueue;
- }
-
- public static int getBytesInQueue() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mBytesInQueue;
- }
-
- public static void setAudioPositionUs(long audioPositionUs) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mAudioPositionUs = audioPositionUs;
- }
-
- public static long getAudioPositionUs() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPositionUs;
- }
-
- public static void setAudioPtsUs(long audioPtsUs) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mAudioPtsUs = audioPtsUs;
- }
-
- public static long getAudioPtsUs() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPtsUs;
- }
-
- public static void setVideoPtsUs(long videoPtsUs) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mVideoPtsUs = videoPtsUs;
- }
-
- public static long getVideoPtsUs() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mVideoPtsUs;
- }
-
- public static void calculateDiff() {
- TunerDebug sTunerDebug = getInstance();
- long currentTime = SystemClock.elapsedRealtime();
- long duration = currentTime - sTunerDebug.mLastCheckTimestampMs;
- if (duration != 0) {
- sTunerDebug.mAudioPositionUsRate =
- (sTunerDebug.mAudioPositionUs - sTunerDebug.mLastAudioPositionUs) * 1000
- / duration;
- sTunerDebug.mAudioPtsUsRate =
- (sTunerDebug.mAudioPtsUs - sTunerDebug.mLastAudioPtsUs) * 1000
- / duration;
- sTunerDebug.mVideoPtsUsRate =
- (sTunerDebug.mVideoPtsUs - sTunerDebug.mLastVideoPtsUs) * 1000
- / duration;
- }
-
- sTunerDebug.mLastAudioPositionUs = sTunerDebug.mAudioPositionUs;
- sTunerDebug.mLastAudioPtsUs = sTunerDebug.mAudioPtsUs;
- sTunerDebug.mLastVideoPtsUs = sTunerDebug.mVideoPtsUs;
- sTunerDebug.mLastCheckTimestampMs = currentTime;
- }
-
- public static long getAudioPositionUsRate() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPositionUsRate;
- }
-
- public static long getAudioPtsUsRate() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPtsUsRate;
- }
-
- public static long getVideoPtsUsRate() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mVideoPtsUsRate;
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
deleted file mode 100644
index acdd149f..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.content.Context;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-
-/**
- * Processes DVR recordings, and deletes the previously recorded contents.
- */
-public class TunerRecordingSession extends TvInputService.RecordingSession {
- private static final String TAG = "TunerRecordingSession";
- private static final boolean DEBUG = false;
-
- private final TunerRecordingSessionWorker mSessionWorker;
-
- public TunerRecordingSession(Context context, String inputId,
- ChannelDataManager channelDataManager) {
- super(context);
- mSessionWorker = new TunerRecordingSessionWorker(context, inputId, channelDataManager,
- this);
- }
-
- // RecordingSession
- @MainThread
- @Override
- public void onTune(Uri channelUri) {
- // TODO(dvr): support calling more than once, http://b/27171225
- if (DEBUG) {
- Log.d(TAG, "Requesting recording session tune: " + channelUri);
- }
- mSessionWorker.tune(channelUri);
- }
-
- @MainThread
- @Override
- public void onRelease() {
- if (DEBUG) {
- Log.d(TAG, "Requesting recording session release.");
- }
- mSessionWorker.release();
- }
-
- @MainThread
- @Override
- public void onStartRecording(@Nullable Uri programUri) {
- if (DEBUG) {
- Log.d(TAG, "Requesting start recording.");
- }
- mSessionWorker.startRecording(programUri);
- }
-
- @MainThread
- @Override
- public void onStopRecording() {
- if (DEBUG) {
- Log.d(TAG, "Requesting stop recording.");
- }
- mSessionWorker.stopRecording();
- }
-
- // Called from TunerRecordingSessionImpl in a worker thread.
- @WorkerThread
- public void onTuned(Uri channelUri) {
- if (DEBUG) {
- Log.d(TAG, "Notifying recording session tuned.");
- }
- notifyTuned(channelUri);
- }
-
- @WorkerThread
- public void onRecordFinished(final Uri recordedProgramUri) {
- if (DEBUG) {
- Log.d(TAG, "Notifying record successfully finished.");
- }
- notifyRecordingStopped(recordedProgramUri);
- }
-
- @WorkerThread
- public void onError(int reason) {
- Log.w(TAG, "Notifying recording error: " + reason);
- notifyError(reason);
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
deleted file mode 100644
index 34013bf1..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
+++ /dev/null
@@ -1,662 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.support.annotation.IntDef;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import android.util.Pair;
-import com.google.android.exoplayer.C;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.recording.RecordingCapability;
-import com.android.tv.dvr.DvrStorageStatusManager;
-import com.android.tv.dvr.data.RecordedProgram;
-import com.android.tv.tuner.DvbDeviceAccessor;
-import com.android.tv.tuner.data.PsipData;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.util.Utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Implements a DVR feature.
- */
-public class TunerRecordingSessionWorker implements PlaybackBufferListener,
- EventDetector.EventListener, SampleExtractor.OnCompletionListener,
- Handler.Callback {
- private static final String TAG = "TunerRecordingSessionW";
- private static final boolean DEBUG = false;
-
- private static final String SORT_BY_TIME = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS
- + ", " + TvContract.Programs.COLUMN_CHANNEL_ID + ", "
- + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS;
- private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
- private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
- private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
- private static final long PREPARE_RECORDER_POLL_MS = 50;
- private static final int MSG_TUNE = 1;
- private static final int MSG_START_RECORDING = 2;
- private static final int MSG_PREPARE_RECODER = 3;
- private static final int MSG_STOP_RECORDING = 4;
- private static final int MSG_MONITOR_STORAGE_STATUS = 5;
- private static final int MSG_RELEASE = 6;
- private static final int MSG_UPDATE_CC_INFO = 7;
- private final RecordingCapability mCapabilities;
-
- public RecordingCapability getCapabilities() {
- return mCapabilities;
- }
-
- @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DvrSessionState {}
- private static final int STATE_IDLE = 1;
- private static final int STATE_TUNING = 2;
- private static final int STATE_TUNED = 3;
- private static final int STATE_RECORDING = 4;
-
- private static final long CHANNEL_ID_NONE = -1;
- private static final int MAX_TUNING_RETRY = 6;
-
- private final Context mContext;
- private final ChannelDataManager mChannelDataManager;
- private final DvrStorageStatusManager mDvrStorageStatusManager;
- private final Handler mHandler;
- private final TsDataSourceManager mSourceManager;
- private final Random mRandom = new Random();
-
- private TsDataSource mTunerSource;
- private TunerChannel mChannel;
- private File mStorageDir;
- private long mRecordStartTime;
- private long mRecordEndTime;
- private boolean mRecorderRunning;
- private SampleExtractor mRecorder;
- private final TunerRecordingSession mSession;
- @DvrSessionState private int mSessionState = STATE_IDLE;
- private final String mInputId;
- private Uri mProgramUri;
-
- private PsipData.EitItem mCurrenProgram;
- private List<AtscCaptionTrack> mCaptionTracks;
- private DvrStorageManager mDvrStorageManager;
-
- public TunerRecordingSessionWorker(Context context, String inputId,
- ChannelDataManager dataManager, TunerRecordingSession session) {
- mRandom.setSeed(System.nanoTime());
- mContext = context;
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper(), this);
- mDvrStorageStatusManager =
- TvApplication.getSingletons(context).getDvrStorageStatusManager();
- mChannelDataManager = dataManager;
- mChannelDataManager.checkDataVersion(context);
- mSourceManager = TsDataSourceManager.createSourceManager(true);
- mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId);
- mInputId = inputId;
- if (DEBUG) Log.d(TAG, mCapabilities.toString());
- mSession = session;
- }
-
- // PlaybackBufferListener
- @Override
- public void onBufferStartTimeChanged(long startTimeMs) { }
-
- @Override
- public void onBufferStateChanged(boolean available) { }
-
- @Override
- public void onDiskTooSlow() { }
-
- // EventDetector.EventListener
- @Override
- public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- if (mChannel == null || mChannel.compareTo(channel) != 0) {
- return;
- }
- mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
- }
-
- @Override
- public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
- if (mChannel == null || mChannel.compareTo(channel) != 0) {
- return;
- }
- mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget();
- mChannelDataManager.notifyEventDetected(channel, items);
- }
-
- @Override
- public void onChannelScanDone() {
- // do nothing.
- }
-
- // SampleExtractor.OnCompletionListener
- @Override
- public void onCompletion(boolean success, long lastExtractedPositionUs) {
- onRecordingResult(success, lastExtractedPositionUs);
- reset();
- }
-
- /**
- * Tunes to {@code channelUri}.
- */
- @MainThread
- public void tune(Uri channelUri) {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget();
- }
-
- /**
- * Starts recording.
- */
- @MainThread
- public void startRecording(@Nullable Uri programUri) {
- mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget();
- }
-
- /**
- * Stops recording.
- */
- @MainThread
- public void stopRecording() {
- mHandler.sendEmptyMessage(MSG_STOP_RECORDING);
- }
-
- /**
- * Releases all resources.
- */
- @MainThread
- public void release() {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.sendEmptyMessage(MSG_RELEASE);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_TUNE: {
- Uri channelUri = (Uri) msg.obj;
- int retryCount = msg.arg1;
- if (DEBUG) Log.d(TAG, "Tune to " + channelUri);
- if (doTune(channelUri)) {
- if (mSessionState == STATE_TUNED) {
- mSession.onTuned(channelUri);
- } else {
- Log.w(TAG, "Tuner stream cannot be created due to resource shortage.");
- if (retryCount < MAX_TUNING_RETRY) {
- Message tuneMsg =
- mHandler.obtainMessage(MSG_TUNE, retryCount + 1, 0, channelUri);
- mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS);
- } else {
- mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY);
- reset();
- }
- }
- }
- return true;
- }
- case MSG_START_RECORDING: {
- if (DEBUG) Log.d(TAG, "Start recording");
- if (!doStartRecording((Uri) msg.obj)) {
- reset();
- }
- return true;
- }
- case MSG_PREPARE_RECODER: {
- if (DEBUG) Log.d(TAG, "Preparing recorder");
- if (!mRecorderRunning) {
- return true;
- }
- try {
- if (!mRecorder.prepare()) {
- mHandler.sendEmptyMessageDelayed(MSG_PREPARE_RECODER,
- PREPARE_RECORDER_POLL_MS);
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor");
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- reset();
- }
- return true;
- }
- case MSG_STOP_RECORDING: {
- if (DEBUG) Log.d(TAG, "Stop recording");
- if (mSessionState != STATE_RECORDING) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- reset();
- return true;
- }
- if (mRecorderRunning) {
- stopRecorder();
- }
- return true;
- }
- case MSG_MONITOR_STORAGE_STATUS: {
- if (mSessionState != STATE_RECORDING) {
- return true;
- }
- if (!mDvrStorageStatusManager.isStorageSufficient()) {
- if (mRecorderRunning) {
- stopRecorder();
- }
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- reset();
- } else {
- mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS,
- STORAGE_MONITOR_INTERVAL_MS);
- }
- return true;
- }
- case MSG_RELEASE: {
- // Since release was requested, current recording will be cancelled
- // without notification.
- reset();
- mSourceManager.release();
- mHandler.removeCallbacksAndMessages(null);
- mHandler.getLooper().quitSafely();
- return true;
- }
- case MSG_UPDATE_CC_INFO: {
- Pair<TunerChannel, List<EitItem>> pair =
- (Pair<TunerChannel, List<EitItem>>) msg.obj;
- updateCaptionTracks(pair.first, pair.second);
- return true;
- }
- }
- return false;
- }
-
- @Nullable
- private TunerChannel getChannel(Uri channelUri) {
- if (channelUri == null) {
- return null;
- }
- long channelId;
- try {
- channelId = ContentUris.parseId(channelUri);
- } catch (UnsupportedOperationException | NumberFormatException e) {
- channelId = CHANNEL_ID_NONE;
- }
- return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId);
- }
-
- private String getStorageKey() {
- long prefix = System.currentTimeMillis();
- int suffix = mRandom.nextInt();
- return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix);
- }
-
- private void reset() {
- if (mRecorder != null) {
- mRecorder.release();
- mRecorder = null;
- }
- if (mTunerSource != null) {
- mSourceManager.releaseDataSource(mTunerSource);
- mTunerSource = null;
- }
- mDvrStorageManager = null;
- mSessionState = STATE_IDLE;
- mRecorderRunning = false;
- }
-
- private boolean doTune(Uri channelUri) {
- if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Tuning was requested from wrong status.");
- return false;
- }
- mChannel = getChannel(channelUri);
- if (mChannel == null) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel);
- return false;
- } else if (mChannel.isRecordingProhibited()) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel);
- return false;
- }
- if (!mDvrStorageStatusManager.isStorageSufficient()) {
- mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- Log.w(TAG, "Tuning failed due to insufficient storage.");
- return false;
- }
- mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this);
- if (mTunerSource == null) {
- // Retry tuning in this case.
- mSessionState = STATE_TUNING;
- return true;
- }
- mSessionState = STATE_TUNED;
- return true;
- }
-
- private boolean doStartRecording(@Nullable Uri programUri) {
- if (mSessionState != STATE_TUNED) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Recording session status abnormal");
- return false;
- }
- mStorageDir = mDvrStorageStatusManager.isStorageSufficient() ?
- new File(mDvrStorageStatusManager.getRecordingRootDataDirectory(),
- getStorageKey()) : null;
- if (mStorageDir == null) {
- mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- Log.w(TAG, "Failed to start recording due to insufficient storage.");
- return false;
- }
- // Since tuning might be happened a while ago, shifts the start position of tuned source.
- mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition());
- mRecordStartTime = System.currentTimeMillis();
- mDvrStorageManager = new DvrStorageManager(mStorageDir, true);
- mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource,
- new BufferManager(mDvrStorageManager), this, true);
- mRecorder.setOnCompletionListener(this, mHandler);
- mProgramUri = programUri;
- mSessionState = STATE_RECORDING;
- mRecorderRunning = true;
- mHandler.sendEmptyMessage(MSG_PREPARE_RECODER);
- mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
- mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS,
- STORAGE_MONITOR_INTERVAL_MS);
- return true;
- }
-
- private void stopRecorder() {
- // Do not change session status.
- if (mRecorder != null) {
- mRecorder.release();
- mRecordEndTime = System.currentTimeMillis();
- mRecorder = null;
- }
- mRecorderRunning = false;
- mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
- Log.i(TAG, "Recording stopped");
- }
-
- private void updateCaptionTracks(TunerChannel channel, List<PsipData.EitItem> items) {
- if (mChannel == null || channel == null || mChannel.compareTo(channel) != 0
- || items == null || items.isEmpty()) {
- return;
- }
- PsipData.EitItem currentProgram = getCurrentProgram(items);
- if (currentProgram == null || !currentProgram.hasCaptionTrack()
- || mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0) {
- return;
- }
- mCurrenProgram = currentProgram;
- mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks());
- if (DEBUG) {
- Log.d(TAG, "updated " + mCaptionTracks.size() + " caption tracks for "
- + currentProgram);
- }
- }
-
- private PsipData.EitItem getCurrentProgram(List<PsipData.EitItem> items) {
- for (PsipData.EitItem item : items) {
- if (mRecordStartTime >= item.getStartTimeUtcMillis()
- && mRecordStartTime < item.getEndTimeUtcMillis()) {
- return item;
- }
- }
- return null;
- }
-
- private static class Program {
- private final long mChannelId;
- private final String mTitle;
- private String mSeriesId;
- private final String mSeasonTitle;
- private final String mEpisodeTitle;
- private final String mSeasonNumber;
- private final String mEpisodeNumber;
- private final String mDescription;
- private final String mPosterArtUri;
- private final String mThumbnailUri;
- private final String mCanonicalGenres;
- private final String mContentRatings;
- private final long mStartTimeUtcMillis;
- private final long mEndTimeUtcMillis;
- private final int mVideoWidth;
- private final int mVideoHeight;
- private final byte[] mInternalProviderData;
-
- private static final String[] PROJECTION = {
- TvContract.Programs.COLUMN_CHANNEL_ID,
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_SEASON_TITLE,
- TvContract.Programs.COLUMN_EPISODE_TITLE,
- TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_POSTER_ART_URI,
- TvContract.Programs.COLUMN_THUMBNAIL_URI,
- TvContract.Programs.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_VIDEO_WIDTH,
- TvContract.Programs.COLUMN_VIDEO_HEIGHT,
- TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
- };
-
- public Program(Cursor cursor) {
- int index = 0;
- mChannelId = cursor.getLong(index++);
- mTitle = cursor.getString(index++);
- mSeasonTitle = cursor.getString(index++);
- mEpisodeTitle = cursor.getString(index++);
- mSeasonNumber = cursor.getString(index++);
- mEpisodeNumber = cursor.getString(index++);
- mDescription = cursor.getString(index++);
- mPosterArtUri = cursor.getString(index++);
- mThumbnailUri = cursor.getString(index++);
- mCanonicalGenres = cursor.getString(index++);
- mContentRatings = cursor.getString(index++);
- mStartTimeUtcMillis = cursor.getLong(index++);
- mEndTimeUtcMillis = cursor.getLong(index++);
- mVideoWidth = cursor.getInt(index++);
- mVideoHeight = cursor.getInt(index++);
- mInternalProviderData = cursor.getBlob(index++);
- SoftPreconditions.checkArgument(index == PROJECTION.length);
- }
-
- public Program(long channelId) {
- mChannelId = channelId;
- mTitle = "Unknown";
- mSeasonTitle = "";
- mEpisodeTitle = "";
- mSeasonNumber = "";
- mEpisodeNumber = "";
- mDescription = "Unknown";
- mPosterArtUri = null;
- mThumbnailUri = null;
- mCanonicalGenres = null;
- mContentRatings = null;
- mStartTimeUtcMillis = 0;
- mEndTimeUtcMillis = 0;
- mVideoWidth = 0;
- mVideoHeight = 0;
- mInternalProviderData = null;
- }
-
- public static Program onQuery(Cursor c) {
- Program program = null;
- if (c != null && c.moveToNext()) {
- program = new Program(c);
- }
- return program;
- }
-
- public ContentValues buildValues() {
- ContentValues values = new ContentValues();
- int index = 0;
- values.put(PROJECTION[index++], mChannelId);
- values.put(PROJECTION[index++], mTitle);
- values.put(PROJECTION[index++], mSeasonTitle);
- values.put(PROJECTION[index++], mEpisodeTitle);
- values.put(PROJECTION[index++], mSeasonNumber);
- values.put(PROJECTION[index++], mEpisodeNumber);
- values.put(PROJECTION[index++], mDescription);
- values.put(PROJECTION[index++], mPosterArtUri);
- values.put(PROJECTION[index++], mThumbnailUri);
- values.put(PROJECTION[index++], mCanonicalGenres);
- values.put(PROJECTION[index++], mContentRatings);
- values.put(PROJECTION[index++], mStartTimeUtcMillis);
- values.put(PROJECTION[index++], mEndTimeUtcMillis);
- values.put(PROJECTION[index++], mVideoWidth);
- values.put(PROJECTION[index++], mVideoHeight);
- values.put(PROJECTION[index++], mInternalProviderData);
- SoftPreconditions.checkArgument(index == PROJECTION.length);
- return values;
- }
- }
-
- private Program getRecordedProgram() {
- ContentResolver resolver = mContext.getContentResolver();
- Uri programUri = mProgramUri;
- if (mProgramUri == null) {
- long avg = mRecordStartTime / 2 + mRecordEndTime / 2;
- programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg);
- }
- try (Cursor c = resolver.query(programUri, Program.PROJECTION, null, null, SORT_BY_TIME)) {
- if (c != null) {
- Program result = Program.onQuery(c);
- if (DEBUG) {
- Log.v(TAG, "Finished query for " + this);
- }
- return result;
- } else {
- if (c == null) {
- Log.e(TAG, "Unknown query error for " + this);
- } else {
- if (DEBUG) Log.d(TAG, "Canceled query for " + this);
- }
- return null;
- }
- }
- }
-
- private Uri insertRecordedProgram(Program program, long channelId, String storageUri,
- long totalBytes, long startTime, long endTime) {
- // TODO: Set title even though program is null.
- RecordedProgram recordedProgram = RecordedProgram.builder()
- .setInputId(mInputId)
- .setChannelId(channelId)
- .setDataUri(storageUri)
- .setDurationMillis(endTime - startTime)
- .setDataBytes(totalBytes)
- // startTime and endTime could be overridden by program's start and end value.
- .setStartTimeUtcMillis(startTime)
- .setEndTimeUtcMillis(endTime)
- .build();
- ContentValues values = RecordedProgram.toValues(recordedProgram);
- if (program != null) {
- values.putAll(program.buildValues());
- }
- return mContext.getContentResolver().insert(TvContract.RecordedPrograms.CONTENT_URI,
- values);
- }
-
- private void onRecordingResult(boolean success, long lastExtractedPositionUs) {
- if (mSessionState != STATE_RECORDING) {
- // Error notification is not needed.
- Log.e(TAG, "Recording session status abnormal");
- return;
- }
- if (mRecorderRunning) {
- // In case of recorder not being stopped, because of premature termination of recording.
- stopRecorder();
- }
- if (!success && lastExtractedPositionUs <
- TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) {
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.w(TAG, "Recording failed during recording");
- return;
- }
- Log.i(TAG, "recording finished " + (success ? "completely" : "partially"));
- long recordEndTime =
- (lastExtractedPositionUs == C.UNKNOWN_TIME_US)
- ? System.currentTimeMillis()
- : mRecordStartTime + lastExtractedPositionUs / 1000;
- Uri uri =
- insertRecordedProgram(
- getRecordedProgram(),
- mChannel.getChannelId(),
- Uri.fromFile(mStorageDir).toString(),
- 1024 * 1024,
- mRecordStartTime,
- recordEndTime);
- if (uri == null) {
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Inserting a recording to DB failed");
- return;
- }
- mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks);
- mSession.onRecordFinished(uri);
- }
-
- private static class DeleteRecordingTask extends AsyncTask<File, Void, Void> {
-
- @Override
- public Void doInBackground(File... files) {
- if (files == null || files.length == 0) {
- return null;
- }
- for(File file : files) {
- Utils.deleteDirOrFile(file);
- }
- return null;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java
deleted file mode 100644
index 44bae908..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerSession.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.media.PlaybackParams;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.text.Html;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener;
-import com.android.tv.tuner.cc.CaptionLayout;
-import com.android.tv.tuner.cc.CaptionTrackRenderer;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.GlobalSettingsUtils;
-import com.android.tv.tuner.util.StatusTextUtils;
-import com.android.tv.tuner.util.SystemPropertiesProxy;
-
-/**
- * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions
- * are implemented in {@link TunerSessionWorker}.
- */
-public class TunerSession extends TvInputService.Session implements
- Handler.Callback, TunerPreferencesChangedListener {
- private static final String TAG = "TunerSession";
- private static final boolean DEBUG = false;
- private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug";
-
- public static final int MSG_UI_SHOW_MESSAGE = 1;
- public static final int MSG_UI_HIDE_MESSAGE = 2;
- public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3;
- public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4;
- public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5;
- public static final int MSG_UI_START_CAPTION_TRACK = 6;
- public static final int MSG_UI_STOP_CAPTION_TRACK = 7;
- public static final int MSG_UI_RESET_CAPTION_TRACK = 8;
- public static final int MSG_UI_CLEAR_CAPTION_RENDERER = 9;
- public static final int MSG_UI_SET_STATUS_TEXT = 10;
- public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11;
-
- private final Context mContext;
- private final Handler mUiHandler;
- private final View mOverlayView;
- private final TextView mMessageView;
- private final TextView mStatusView;
- private final TextView mAudioStatusView;
- private final ViewGroup mMessageLayout;
- private final CaptionTrackRenderer mCaptionTrackRenderer;
- private final TunerSessionWorker mSessionWorker;
- private boolean mReleased = false;
- private boolean mPlayPaused;
- private long mTuneStartTimestamp;
-
- public TunerSession(Context context, ChannelDataManager channelDataManager) {
- super(context);
- mContext = context;
- mUiHandler = new Handler(this);
- LayoutInflater inflater = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null);
- mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout);
- mMessageLayout.setVisibility(View.INVISIBLE);
- mMessageView = (TextView) mOverlayView.findViewById(R.id.message);
- mStatusView = (TextView) mOverlayView.findViewById(R.id.tuner_status);
- boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false);
- mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE);
- mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status);
- mAudioStatusView.setVisibility(View.INVISIBLE);
- CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption);
- mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout);
- mSessionWorker = new TunerSessionWorker(context, channelDataManager, this);
- TunerPreferences.setTunerPreferencesChangedListener(this);
- }
-
- public boolean isReleased() {
- return mReleased;
- }
-
- @Override
- public View onCreateOverlayView() {
- return mOverlayView;
- }
-
- @Override
- public boolean onSelectTrack(int type, String trackId) {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId);
- return false;
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- mSessionWorker.setCaptionEnabled(enabled);
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- mSessionWorker.setStreamVolume(volume);
- }
-
- @Override
- public boolean onSetSurface(Surface surface) {
- mSessionWorker.setSurface(surface);
- return true;
- }
-
- @Override
- public void onTimeShiftPause() {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE);
- mPlayPaused = true;
- }
-
- @Override
- public void onTimeShiftResume() {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME);
- mPlayPaused = false;
- }
-
- @Override
- public void onTimeShiftSeekTo(long timeMs) {
- if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000);
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO,
- mPlayPaused ? 1 : 0, 0, timeMs);
- }
-
- @Override
- public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
- mSessionWorker.sendMessage(
- TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params);
- }
-
- @Override
- public long onTimeShiftGetStartPosition() {
- return mSessionWorker.getStartPosition();
- }
-
- @Override
- public long onTimeShiftGetCurrentPosition() {
- return mSessionWorker.getCurrentPosition();
- }
-
- @Override
- public boolean onTune(Uri channelUri) {
- if (DEBUG) {
- Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : "");
- }
- if (channelUri == null) {
- Log.w(TAG, "onTune() is failed due to null channelUri.");
- mSessionWorker.stopTune();
- return false;
- }
- mTuneStartTimestamp = SystemClock.elapsedRealtime();
- mSessionWorker.tune(channelUri);
- mPlayPaused = false;
- return true;
- }
-
- @TargetApi(Build.VERSION_CODES.N)
- @Override
- public void onTimeShiftPlay(Uri recordUri) {
- if (recordUri == null) {
- Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri.");
- mSessionWorker.stopTune();
- return;
- }
- mTuneStartTimestamp = SystemClock.elapsedRealtime();
- mSessionWorker.tune(recordUri);
- mPlayPaused = false;
- }
-
- @Override
- public void onUnblockContent(TvContentRating unblockedRating) {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING,
- unblockedRating);
- }
-
- @Override
- public void onRelease() {
- if (DEBUG) {
- Log.d(TAG, "onRelease");
- }
- mReleased = true;
- mSessionWorker.release();
- mUiHandler.removeCallbacksAndMessages(null);
- TunerPreferences.setTunerPreferencesChangedListener(null);
- }
-
- /**
- * Sets {@link AudioCapabilities}.
- */
- public void setAudioCapabilities(AudioCapabilities audioCapabilities) {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED,
- audioCapabilities);
- }
-
- @Override
- public void notifyVideoAvailable() {
- super.notifyVideoAvailable();
- if (mTuneStartTimestamp != 0) {
- Log.i(TAG, "[Profiler] Video available in "
- + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms");
- mTuneStartTimestamp = 0;
- }
- }
-
- @Override
- public void notifyVideoUnavailable(int reason) {
- super.notifyVideoUnavailable(reason);
- if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
- && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) {
- notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
- }
- }
-
- public void sendUiMessage(int message) {
- mUiHandler.sendEmptyMessage(message);
- }
-
- public void sendUiMessage(int message, Object object) {
- mUiHandler.obtainMessage(message, object).sendToTarget();
- }
-
- public void sendUiMessage(int message, int arg1, int arg2, Object object) {
- mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget();
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UI_SHOW_MESSAGE: {
- mMessageView.setText((String) msg.obj);
- mMessageLayout.setVisibility(View.VISIBLE);
- return true;
- }
- case MSG_UI_HIDE_MESSAGE: {
- mMessageLayout.setVisibility(View.INVISIBLE);
- return true;
- }
- case MSG_UI_SHOW_AUDIO_UNPLAYABLE: {
- // Showing message of enabling surround sound only when global surround sound
- // setting is "never".
- final int value = GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext);
- if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) {
- mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
- mContext.getString(R.string.ut_surround_sound_disabled))));
- } else {
- mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
- mContext.getString(R.string.audio_passthrough_not_supported))));
- }
- mAudioStatusView.setVisibility(View.VISIBLE);
- return true;
- }
- case MSG_UI_HIDE_AUDIO_UNPLAYABLE: {
- mAudioStatusView.setVisibility(View.INVISIBLE);
- return true;
- }
- case MSG_UI_PROCESS_CAPTION_TRACK: {
- mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj);
- return true;
- }
- case MSG_UI_START_CAPTION_TRACK: {
- mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj);
- return true;
- }
- case MSG_UI_STOP_CAPTION_TRACK: {
- mCaptionTrackRenderer.stop();
- return true;
- }
- case MSG_UI_RESET_CAPTION_TRACK: {
- mCaptionTrackRenderer.reset();
- return true;
- }
- case MSG_UI_CLEAR_CAPTION_RENDERER: {
- mCaptionTrackRenderer.clear();
- return true;
- }
- case MSG_UI_SET_STATUS_TEXT: {
- mStatusView.setText((CharSequence) msg.obj);
- return true;
- }
- case MSG_UI_TOAST_RESCAN_NEEDED: {
- Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show();
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void onTunerPreferencesChanged() {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED);
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
deleted file mode 100644
index e7eb017e..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
+++ /dev/null
@@ -1,1754 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.MediaFormat;
-import android.media.PlaybackParams;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvTrackInfo;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.SystemClock;
-import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
-import android.support.annotation.WorkerThread;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.Surface;
-import android.view.accessibility.CaptioningManager;
-
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.google.android.exoplayer.ExoPlayer;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvContentRatingCache;
-import com.android.tv.customization.TvCustomizationManager;
-import com.android.tv.customization.TvCustomizationManager.TRICKPLAY_MODE;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.TunerPreferences.TrickplaySetting;
-import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.TvTracksInterface;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager;
-import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer;
-import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
-import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.util.StatusTextUtils;
-import com.android.tv.tuner.util.SystemPropertiesProxy;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs
- * such as handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on.
- */
-@WorkerThread
-public class TunerSessionWorker implements PlaybackBufferListener,
- MpegTsPlayer.VideoEventListener, MpegTsPlayer.Listener, EventDetector.EventListener,
- ChannelDataManager.ProgramInfoListener, Handler.Callback {
- private static final String TAG = "TunerSessionWorker";
- private static final boolean DEBUG = false;
- private static final boolean ENABLE_PROFILER = true;
- private static final String PLAY_FROM_CHANNEL = "channel";
- private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes";
- private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB
- private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB
-
- // Public messages
- public static final int MSG_SELECT_TRACK = 1;
- public static final int MSG_UPDATE_CAPTION_TRACK = 2;
- public static final int MSG_SET_STREAM_VOLUME = 3;
- public static final int MSG_TIMESHIFT_PAUSE = 4;
- public static final int MSG_TIMESHIFT_RESUME = 5;
- public static final int MSG_TIMESHIFT_SEEK_TO = 6;
- public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7;
- public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8;
- public static final int MSG_UNBLOCKED_RATING = 9;
- public static final int MSG_TUNER_PREFERENCES_CHANGED = 10;
-
- // Private messages
- private static final int MSG_TUNE = 1000;
- private static final int MSG_RELEASE = 1001;
- private static final int MSG_RETRY_PLAYBACK = 1002;
- private static final int MSG_START_PLAYBACK = 1003;
- private static final int MSG_UPDATE_PROGRAM = 1008;
- private static final int MSG_SCHEDULE_OF_PROGRAMS = 1009;
- private static final int MSG_UPDATE_CHANNEL_INFO = 1010;
- private static final int MSG_TRICKPLAY_BY_SEEK = 1011;
- private static final int MSG_SMOOTH_TRICKPLAY_MONITOR = 1012;
- private static final int MSG_PARENTAL_CONTROLS = 1015;
- private static final int MSG_RESCHEDULE_PROGRAMS = 1016;
- private static final int MSG_BUFFER_START_TIME_CHANGED = 1017;
- private static final int MSG_CHECK_SIGNAL = 1018;
- private static final int MSG_DISCOVER_CAPTION_SERVICE_NUMBER = 1019;
- private static final int MSG_RESET_PLAYBACK = 1020;
- private static final int MSG_BUFFER_STATE_CHANGED = 1021;
- private static final int MSG_PROGRAM_DATA_RESULT = 1022;
- private static final int MSG_STOP_TUNE = 1023;
- private static final int MSG_SET_SURFACE = 1024;
- private static final int MSG_NOTIFY_AUDIO_TRACK_UPDATED = 1025;
-
- private static final int TS_PACKET_SIZE = 188;
- private static final int CHECK_NO_SIGNAL_INITIAL_DELAY_MS = 4000;
- private static final int CHECK_NO_SIGNAL_PERIOD_MS = 500;
- private static final int RECOVER_STOPPED_PLAYBACK_PERIOD_MS = 2500;
- private static final int PARENTAL_CONTROLS_INTERVAL_MS = 5000;
- private static final int RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS = 4000;
- private static final int RESCHEDULE_PROGRAMS_INTERVAL_MS = 10000;
- private static final int RESCHEDULE_PROGRAMS_TOLERANCE_MS = 2000;
- // The following 3s is defined empirically. This should be larger than 2s considering video
- // key frame interval in the TS stream.
- private static final int PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS = 3000;
- private static final int PLAYBACK_RETRY_DELAY_MS = 5000;
- private static final int MAX_IMMEDIATE_RETRY_COUNT = 5;
- private static final long INVALID_TIME = -1;
-
- // Some examples of the track ids of the audio tracks, "a0", "a1", "a2".
- // The number after prefix is being used for indicating a index of the given audio track.
- private static final String AUDIO_TRACK_PREFIX = "a";
-
- // Some examples of the tracks id of the caption tracks, "s1", "s2", "s3".
- // The number after prefix is being used for indicating a index of a caption service number
- // of the given caption track.
- private static final String SUBTITLE_TRACK_PREFIX = "s";
- private static final int TRACK_PREFIX_SIZE = 1;
- private static final String VIDEO_TRACK_ID = "v";
- private static final long BUFFER_UNDERFLOW_BUFFER_MS = 5000;
-
- // Actual interval would be divided by the speed.
- private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500;
- private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20;
- private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250;
- private static final int RELEASE_WAIT_INTERVAL_MS = 50;
- private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14);
-
- // Since release() is done asynchronously, synchronization between multiple TunerSessionWorker
- // creation/release is required.
- // This is used to guarantee that at most one active TunerSessionWorker exists at any give time.
- private static Semaphore sActiveSessionSemaphore = new Semaphore(1);
-
- private final Context mContext;
- private final ChannelDataManager mChannelDataManager;
- private final TsDataSourceManager mSourceManager;
- private final int mMaxTrickplayBufferSizeMb;
- private final File mTrickplayBufferDir;
- private final @TRICKPLAY_MODE int mTrickplayModeCustomization;
- private volatile Surface mSurface;
- private volatile float mVolume = 1.0f;
- private volatile boolean mCaptionEnabled;
- private volatile MpegTsPlayer mPlayer;
- private volatile TunerChannel mChannel;
- private volatile Long mRecordingDuration;
- private volatile long mRecordStartTimeMs;
- private volatile long mBufferStartTimeMs;
- private volatile boolean mTrickplayDisabledByStorageIssue;
- private @TrickplaySetting int mTrickplaySetting;
- private long mTrickplayExpiredMs;
- private String mRecordingId;
- private final Handler mHandler;
- private int mRetryCount;
- private final ArrayList<TvTrackInfo> mTvTracks;
- private final SparseArray<AtscAudioTrack> mAudioTrackMap;
- private final SparseArray<AtscCaptionTrack> mCaptionTrackMap;
- private AtscCaptionTrack mCaptionTrack;
- private PlaybackParams mPlaybackParams = new PlaybackParams();
- private boolean mPlayerStarted = false;
- private boolean mReportedDrawnToSurface = false;
- private boolean mReportedWeakSignal = false;
- private EitItem mProgram;
- private List<EitItem> mPrograms;
- private final TvInputManager mTvInputManager;
- private boolean mChannelBlocked;
- private TvContentRating mUnblockedContentRating;
- private long mLastPositionMs;
- private AudioCapabilities mAudioCapabilities;
- private long mLastLimitInBytes;
- private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
- private final TunerSession mSession;
- private final boolean mHasSoftwareAudioDecoder;
- private int mPlayerState = ExoPlayer.STATE_IDLE;
- private long mPreparingStartTimeMs;
- private long mBufferingStartTimeMs;
- private long mReadyStartTimeMs;
- private boolean mIsActiveSession;
- private boolean mReleaseRequested; // Guarded by mReleaseLock
- private final Object mReleaseLock = new Object();
-
- public TunerSessionWorker(Context context, ChannelDataManager channelDataManager,
- TunerSession tunerSession) {
- if (DEBUG) Log.d(TAG, "TunerSessionWorker created");
- mContext = context;
-
- // HandlerThread should be set up before it is registered as a listener in the all other
- // components.
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper(), this);
- mSession = tunerSession;
- mChannelDataManager = channelDataManager;
- mChannelDataManager.setListener(this);
- mChannelDataManager.checkDataVersion(mContext);
- mSourceManager = TsDataSourceManager.createSourceManager(false);
- mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- mTvTracks = new ArrayList<>();
- mAudioTrackMap = new SparseArray<>();
- mCaptionTrackMap = new SparseArray<>();
- CaptioningManager captioningManager =
- (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
- mCaptionEnabled = captioningManager.isEnabled();
- mPlaybackParams.setSpeed(1.0f);
- mMaxTrickplayBufferSizeMb =
- SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
- mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context);
- if (mTrickplayModeCustomization ==
- TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
- boolean useExternalStorage =
- Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
- Environment.isExternalStorageRemovable();
- mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null;
- } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) {
- mTrickplayBufferDir = context.getCacheDir();
- } else {
- mTrickplayBufferDir = null;
- }
- mTrickplayDisabledByStorageIssue = mTrickplayBufferDir == null;
- mTrickplaySetting = TunerPreferences.getTrickplaySetting(context);
- if (mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_NOT_SET
- && mTrickplayModeCustomization
- == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
- // Consider the case of Customization package updates the value of trickplay mode
- // to TRICKPLAY_MODE_USE_EXTERNAL_STORAGE after install.
- mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_NOT_SET;
- TunerPreferences.setTrickplaySetting(context, mTrickplaySetting);
- TunerPreferences.setTrickplayExpiredMs(context, 0);
- }
- mTrickplayExpiredMs = TunerPreferences.getTrickplayExpiredMs(context);
- mPreparingStartTimeMs = INVALID_TIME;
- mBufferingStartTimeMs = INVALID_TIME;
- mReadyStartTimeMs = INVALID_TIME;
- // NOTE: We assume that TunerSessionWorker instance will be at most one.
- // Only one TunerSessionWorker can be connected to FfmpegDecoderClient at any given time.
- // connect() will return false, if there is a connected TunerSessionWorker already.
- mHasSoftwareAudioDecoder = FfmpegDecoderClient.connect(context);
- }
-
- // Public methods
- @MainThread
- public void tune(Uri channelUri) {
- mHandler.removeCallbacksAndMessages(null);
- mSourceManager.setHasPendingTune();
- sendMessage(MSG_TUNE, channelUri);
- }
-
- @MainThread
- public void stopTune() {
- mHandler.removeCallbacksAndMessages(null);
- sendMessage(MSG_STOP_TUNE);
- }
-
- /**
- * Sets {@link Surface}.
- */
- @MainThread
- public void setSurface(Surface surface) {
- if (surface != null && !surface.isValid()) {
- Log.w(TAG, "Ignoring invalid surface.");
- return;
- }
- // mSurface is kept even when tune is called right after. But, messages can be deleted by
- // tune or updateChannelBlockStatus. So mSurface should be stored here, not through message.
- mSurface = surface;
- mHandler.sendEmptyMessage(MSG_SET_SURFACE);
- }
-
- /**
- * Sets volume.
- */
- @MainThread
- public void setStreamVolume(float volume) {
- // mVolume is kept even when tune is called right after. But, messages can be deleted by
- // tune or updateChannelBlockStatus. So mVolume is stored here and mPlayer.setVolume will be
- // called in MSG_SET_STREAM_VOLUME.
- mVolume = volume;
- mHandler.sendEmptyMessage(MSG_SET_STREAM_VOLUME);
- }
-
- /**
- * Sets if caption is enabled or disabled.
- */
- @MainThread
- public void setCaptionEnabled(boolean captionEnabled) {
- // mCaptionEnabled is kept even when tune is called right after. But, messages can be
- // deleted by tune or updateChannelBlockStatus. So mCaptionEnabled is stored here and
- // start/stopCaptionTrack will be called in MSG_UPDATE_CAPTION_STATUS.
- mCaptionEnabled = captionEnabled;
- mHandler.sendEmptyMessage(MSG_UPDATE_CAPTION_TRACK);
- }
-
- public TunerChannel getCurrentChannel() {
- return mChannel;
- }
-
- @MainThread
- public long getStartPosition() {
- return mBufferStartTimeMs;
- }
-
-
- private String getRecordingPath() {
- return Uri.parse(mRecordingId).getPath();
- }
-
- private Long getDurationForRecording(String recordingId) {
- DvrStorageManager storageManager =
- new DvrStorageManager(new File(getRecordingPath()), false);
- List<BufferManager.TrackFormat> trackFormatList =
- storageManager.readTrackInfoFiles(false);
- if (trackFormatList.isEmpty()) {
- trackFormatList = storageManager.readTrackInfoFiles(true);
- }
- if (!trackFormatList.isEmpty()) {
- BufferManager.TrackFormat trackFormat = trackFormatList.get(0);
- Long durationUs = trackFormat.format.getLong(MediaFormat.KEY_DURATION);
- // we need duration by milli for trickplay notification.
- return durationUs != null ? durationUs / 1000 : null;
- }
- Log.e(TAG, "meta file for recording was not found: " + recordingId);
- return null;
- }
-
- @MainThread
- public long getCurrentPosition() {
- // TODO: More precise time may be necessary.
- MpegTsPlayer mpegTsPlayer = mPlayer;
- long currentTime = mpegTsPlayer != null
- ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() : mRecordStartTimeMs;
- if (mChannel == null && mPlayerState == ExoPlayer.STATE_ENDED) {
- currentTime = mRecordingDuration + mRecordStartTimeMs;
- }
- if (DEBUG) {
- long systemCurrentTime = System.currentTimeMillis();
- Log.d(TAG, "currentTime = " + currentTime
- + " ; System.currentTimeMillis() = " + systemCurrentTime
- + " ; diff = " + (currentTime - systemCurrentTime));
- }
- return currentTime;
- }
-
- @AnyThread
- public void sendMessage(int messageType) {
- mHandler.sendEmptyMessage(messageType);
- }
-
- @AnyThread
- public void sendMessage(int messageType, Object object) {
- mHandler.obtainMessage(messageType, object).sendToTarget();
- }
-
- @AnyThread
- public void sendMessage(int messageType, int arg1, int arg2, Object object) {
- mHandler.obtainMessage(messageType, arg1, arg2, object).sendToTarget();
- }
-
- @MainThread
- public void release() {
- if (DEBUG) Log.d(TAG, "release()");
- synchronized (mReleaseLock) {
- mReleaseRequested = true;
- }
- if (mHasSoftwareAudioDecoder) {
- FfmpegDecoderClient.disconnect(mContext);
- }
- mChannelDataManager.setListener(null);
- mHandler.removeCallbacksAndMessages(null);
- mHandler.sendEmptyMessage(MSG_RELEASE);
- }
-
- // MpegTsPlayer.Listener
- // Called in the same thread as mHandler.
- @Override
- public void onStateChanged(boolean playWhenReady, int playbackState) {
- if (DEBUG) Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady);
- if (playbackState == mPlayerState) {
- return;
- }
- mReadyStartTimeMs = INVALID_TIME;
- mPreparingStartTimeMs = INVALID_TIME;
- mBufferingStartTimeMs = INVALID_TIME;
- if (playbackState == ExoPlayer.STATE_READY) {
- if (DEBUG) Log.d(TAG, "ExoPlayer ready");
- if (!mPlayerStarted) {
- sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer));
- }
- mReadyStartTimeMs = SystemClock.elapsedRealtime();
- } else if (playbackState == ExoPlayer.STATE_PREPARING) {
- mPreparingStartTimeMs = SystemClock.elapsedRealtime();
- } else if (playbackState == ExoPlayer.STATE_BUFFERING) {
- mBufferingStartTimeMs = SystemClock.elapsedRealtime();
- } else if (playbackState == ExoPlayer.STATE_ENDED) {
- // Final status
- // notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards.
- Log.i(TAG, "Player ended: end of stream");
- if (mChannel != null) {
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
- }
- }
- mPlayerState = playbackState;
- }
-
- @Override
- public void onError(Exception e) {
- if (TunerPreferences.getStoreTsStream(mContext)) {
- // Crash intentionally to capture the error causing TS file.
- Log.e(TAG, "Crash intentionally to capture the error causing TS file. "
- + e.getMessage());
- SoftPreconditions.checkState(false);
- }
- // There maybe some errors that finally raise ExoPlaybackException and will be handled here.
- // If we are playing live stream, retrying playback maybe helpful. But for recorded stream,
- // retrying playback is not helpful.
- if (mChannel != null) {
- mHandler.obtainMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer))
- .sendToTarget();
- }
- }
-
- @Override
- public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) {
- if (mChannel != null && mChannel.hasVideo()) {
- updateVideoTrack(width, height);
- }
- if (mRecordingId != null) {
- updateVideoTrack(width, height);
- }
- }
-
- @Override
- public void onDrawnToSurface(MpegTsPlayer player, Surface surface) {
- if (mSurface != null && mPlayerStarted) {
- if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE");
- if (mRecordingId != null) {
- // Workaround of b/33298048: set it to 1 instead of 0.
- mBufferStartTimeMs = mRecordStartTimeMs = 1;
- } else {
- mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
- }
- notifyVideoAvailable();
- mReportedDrawnToSurface = true;
-
- // If surface is drawn successfully, it means that the playback was brought back
- // to normal and therefore, the playback recovery status will be reset through
- // setting a zero value to the retry count.
- // TODO: Consider audio only channels for detecting playback status changes to
- // be normal.
- mRetryCount = 0;
- if (mCaptionEnabled && mCaptionTrack != null) {
- startCaptionTrack();
- } else {
- stopCaptionTrack();
- }
- mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED);
- }
- }
-
- @Override
- public void onSmoothTrickplayForceStopped() {
- if (mPlayer == null || !mHandler.hasMessages(MSG_SMOOTH_TRICKPLAY_MONITOR)) {
- return;
- }
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- doTrickplayBySeek((int) mPlayer.getCurrentPosition());
- }
-
- @Override
- public void onAudioUnplayable() {
- if (mPlayer == null) {
- return;
- }
- Log.i(TAG, "AC3 audio cannot be played due to device limitation");
- mSession.sendUiMessage(
- TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE);
- }
-
- // MpegTsPlayer.VideoEventListener
- @Override
- public void onEmitCaptionEvent(Cea708Data.CaptionEvent event) {
- mSession.sendUiMessage(TunerSession.MSG_UI_PROCESS_CAPTION_TRACK, event);
- }
-
- @Override
- public void onClearCaptionEvent() {
- mSession.sendUiMessage(TunerSession.MSG_UI_CLEAR_CAPTION_RENDERER);
- }
-
- @Override
- public void onDiscoverCaptionServiceNumber(int serviceNumber) {
- sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber);
- }
-
- // ChannelDataManager.ProgramInfoListener
- @Override
- public void onProgramsArrived(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs));
- }
-
- @Override
- public void onChannelArrived(TunerChannel channel) {
- sendMessage(MSG_UPDATE_CHANNEL_INFO, channel);
- }
-
- @Override
- public void onRescanNeeded() {
- mSession.sendUiMessage(TunerSession.MSG_UI_TOAST_RESCAN_NEEDED);
- }
-
- @Override
- public void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs));
- }
-
- // PlaybackBufferListener
- @Override
- public void onBufferStartTimeChanged(long startTimeMs) {
- sendMessage(MSG_BUFFER_START_TIME_CHANGED, startTimeMs);
- }
-
- @Override
- public void onBufferStateChanged(boolean available) {
- sendMessage(MSG_BUFFER_STATE_CHANGED, available);
- }
-
- @Override
- public void onDiskTooSlow() {
- mTrickplayDisabledByStorageIssue = true;
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
- }
-
- // EventDetector.EventListener
- @Override
- public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
- }
-
- @Override
- public void onEventDetected(TunerChannel channel, List<EitItem> items) {
- mChannelDataManager.notifyEventDetected(channel, items);
- }
-
- @Override
- public void onChannelScanDone() {
- // do nothing.
- }
-
- private long parseChannel(Uri uri) {
- try {
- List<String> paths = uri.getPathSegments();
- if (paths.size() > 1 && paths.get(0).equals(PLAY_FROM_CHANNEL)) {
- return ContentUris.parseId(uri);
- }
- } catch (UnsupportedOperationException | NumberFormatException e) {
- }
- return -1;
- }
-
- private static class RecordedProgram {
- private final long mChannelId;
- private final String mDataUri;
-
- private static final String[] PROJECTION = {
- TvContract.Programs.COLUMN_CHANNEL_ID,
- TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI,
- };
-
- public RecordedProgram(Cursor cursor) {
- int index = 0;
- mChannelId = cursor.getLong(index++);
- mDataUri = cursor.getString(index++);
- }
-
- public RecordedProgram(long channelId, String dataUri) {
- mChannelId = channelId;
- mDataUri = dataUri;
- }
-
- public static RecordedProgram onQuery(Cursor c) {
- RecordedProgram recording = null;
- if (c != null && c.moveToNext()) {
- recording = new RecordedProgram(c);
- }
- return recording;
- }
-
- public String getDataUri() {
- return mDataUri;
- }
- }
-
- private RecordedProgram getRecordedProgram(Uri recordedUri) {
- ContentResolver resolver = mContext.getContentResolver();
- try(Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) {
- if (c != null) {
- RecordedProgram result = RecordedProgram.onQuery(c);
- if (DEBUG) {
- Log.d(TAG, "Finished query for " + this);
- }
- return result;
- } else {
- if (c == null) {
- Log.e(TAG, "Unknown query error for " + this);
- } else {
- if (DEBUG) Log.d(TAG, "Canceled query for " + this);
- }
- return null;
- }
- }
- }
-
- private String parseRecording(Uri uri) {
- RecordedProgram recording = getRecordedProgram(uri);
- if (recording != null) {
- return recording.getDataUri();
- }
- return null;
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_TUNE: {
- if (DEBUG) Log.d(TAG, "MSG_TUNE");
-
- // When sequential tuning messages arrived, it skips middle tuning messages in order
- // to change to the last requested channel quickly.
- if (mHandler.hasMessages(MSG_TUNE)) {
- return true;
- }
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
- if (!mIsActiveSession) {
- // Wait until release is finished if there is a pending release.
- try {
- while (!sActiveSessionSemaphore.tryAcquire(
- RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) {
- synchronized (mReleaseLock) {
- if (mReleaseRequested) {
- return true;
- }
- }
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- synchronized (mReleaseLock) {
- if (mReleaseRequested) {
- sActiveSessionSemaphore.release();
- return true;
- }
- }
- mIsActiveSession = true;
- }
- Uri channelUri = (Uri) msg.obj;
- String recording = null;
- long channelId = parseChannel(channelUri);
- TunerChannel channel = (channelId == -1) ? null
- : mChannelDataManager.getChannel(channelId);
- if (channelId == -1) {
- recording = parseRecording(channelUri);
- }
- if (channel == null && recording == null) {
- Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri);
- stopTune();
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
- return true;
- }
- clearCallbacksAndMessagesSafely();
- mChannelDataManager.removeAllCallbacksAndMessages();
- if (channel != null) {
- mChannelDataManager.requestProgramsData(channel);
- }
- prepareTune(channel, recording);
- // TODO: Need to refactor. notifyContentAllowed() should not be called if parental
- // control is turned on.
- mSession.notifyContentAllowed();
- resetTvTracks();
- resetPlayback();
- mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
- RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
- return true;
- }
- case MSG_STOP_TUNE: {
- if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE");
- mChannel = null;
- stopPlayback(true);
- stopCaptionTrack();
- resetTvTracks();
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
- return true;
- }
- case MSG_RELEASE: {
- if (DEBUG) Log.d(TAG, "MSG_RELEASE");
- mHandler.removeCallbacksAndMessages(null);
- stopPlayback(true);
- stopCaptionTrack();
- mSourceManager.release();
- mHandler.getLooper().quitSafely();
- if (mIsActiveSession) {
- sActiveSessionSemaphore.release();
- }
- return true;
- }
- case MSG_RETRY_PLAYBACK: {
- if (System.identityHashCode(mPlayer) == (int) msg.obj) {
- Log.i(TAG, "Retrying the playback for channel: " + mChannel);
- mHandler.removeMessages(MSG_RETRY_PLAYBACK);
- // When there is a request of retrying playback, don't reuse TunerHal.
- mSourceManager.setKeepTuneStatus(false);
- mRetryCount++;
- if (DEBUG) {
- Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount);
- }
- mChannelDataManager.removeAllCallbacksAndMessages();
- if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) {
- resetPlayback();
- } else {
- // When it reaches this point, it may be due to an error that occurred in
- // the tuner device. Calling stopPlayback() resets the tuner device
- // to recover from the error.
- stopPlayback(false);
- stopCaptionTrack();
-
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- Log.i(TAG, "Notify weak signal since fail to retry playback");
-
- // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen
- // value before recovering the playback.
- mHandler.sendEmptyMessageDelayed(MSG_RESET_PLAYBACK,
- RECOVER_STOPPED_PLAYBACK_PERIOD_MS);
- }
- }
- return true;
- }
- case MSG_RESET_PLAYBACK: {
- if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK");
- mChannelDataManager.removeAllCallbacksAndMessages();
- resetPlayback();
- return true;
- }
- case MSG_START_PLAYBACK: {
- if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK");
- if (mChannel != null || mRecordingId != null) {
- startPlayback((int) msg.obj);
- }
- return true;
- }
- case MSG_UPDATE_PROGRAM: {
- if (mChannel != null) {
- EitItem program = (EitItem) msg.obj;
- updateTvTracks(program, false);
- mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
- }
- return true;
- }
- case MSG_SCHEDULE_OF_PROGRAMS: {
- mHandler.removeMessages(MSG_UPDATE_PROGRAM);
- Pair<TunerChannel, List<EitItem>> pair =
- (Pair<TunerChannel, List<EitItem>>) msg.obj;
- TunerChannel channel = pair.first;
- if (mChannel == null) {
- return true;
- }
- if (mChannel != null && mChannel.compareTo(channel) != 0) {
- return true;
- }
- mPrograms = pair.second;
- EitItem currentProgram = getCurrentProgram();
- if (currentProgram == null) {
- mProgram = null;
- }
- long currentTimeMs = getCurrentPosition();
- if (mPrograms != null) {
- for (EitItem item : mPrograms) {
- if (currentProgram != null && currentProgram.compareTo(item) == 0) {
- if (DEBUG) {
- Log.d(TAG, "Update current TvTracks " + item);
- }
- if (mProgram != null && mProgram.compareTo(item) == 0) {
- continue;
- }
- mProgram = item;
- updateTvTracks(item, false);
- } else if (item.getStartTimeUtcMillis() > currentTimeMs) {
- if (DEBUG) {
- Log.d(TAG, "Update next TvTracks " + item + " "
- + (item.getStartTimeUtcMillis() - currentTimeMs));
- }
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item),
- item.getStartTimeUtcMillis() - currentTimeMs);
- }
- }
- }
- mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
- return true;
- }
- case MSG_UPDATE_CHANNEL_INFO: {
- TunerChannel channel = (TunerChannel) msg.obj;
- if (mChannel != null && mChannel.compareTo(channel) == 0) {
- updateChannelInfo(channel);
- }
- return true;
- }
- case MSG_PROGRAM_DATA_RESULT: {
- TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first;
-
- // If there already exists, skip it since real-time data is a top priority,
- if (mChannel != null && mChannel.compareTo(channel) == 0
- && mPrograms == null && mProgram == null) {
- sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj);
- }
- return true;
- }
- case MSG_TRICKPLAY_BY_SEEK: {
- if (mPlayer == null) {
- return true;
- }
- doTrickplayBySeek(msg.arg1);
- return true;
- }
- case MSG_SMOOTH_TRICKPLAY_MONITOR: {
- if (mPlayer == null) {
- return true;
- }
- long systemCurrentTime = System.currentTimeMillis();
- long position = getCurrentPosition();
- if (mRecordingId == null) {
- // Checks if the position exceeds the upper bound when forwarding,
- // or exceed the lower bound when rewinding.
- // If the direction is not checked, there can be some issues.
- // (See b/29939781 for more details.)
- if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L)
- || (position < mBufferStartTimeMs && mPlaybackParams.getSpeed() < 0L)) {
- doTimeShiftResume();
- return true;
- }
- } else {
- if (position > mRecordingDuration || position < 0) {
- doTimeShiftPause();
- return true;
- }
- }
- mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
- TRICKPLAY_MONITOR_INTERVAL_MS);
- return true;
- }
- case MSG_RESCHEDULE_PROGRAMS: {
- if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) {
- mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS);
- } else {
- doReschedulePrograms();
- }
- return true;
- }
- case MSG_PARENTAL_CONTROLS: {
- doParentalControls();
- mHandler.removeMessages(MSG_PARENTAL_CONTROLS);
- mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS,
- PARENTAL_CONTROLS_INTERVAL_MS);
- return true;
- }
- case MSG_UNBLOCKED_RATING: {
- mUnblockedContentRating = (TvContentRating) msg.obj;
- doParentalControls();
- mHandler.removeMessages(MSG_PARENTAL_CONTROLS);
- mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS,
- PARENTAL_CONTROLS_INTERVAL_MS);
- return true;
- }
- case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: {
- int serviceNumber = (int) msg.obj;
- doDiscoverCaptionServiceNumber(serviceNumber);
- return true;
- }
- case MSG_SELECT_TRACK: {
- if (mChannel != null || mRecordingId != null) {
- doSelectTrack(msg.arg1, (String) msg.obj);
- }
- return true;
- }
- case MSG_UPDATE_CAPTION_TRACK: {
- if (mCaptionEnabled) {
- startCaptionTrack();
- } else {
- stopCaptionTrack();
- }
- return true;
- }
- case MSG_TIMESHIFT_PAUSE: {
- if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE");
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftPause();
- return true;
- }
- case MSG_TIMESHIFT_RESUME: {
- if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME");
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftResume();
- return true;
- }
- case MSG_TIMESHIFT_SEEK_TO: {
- long position = (long) msg.obj;
- if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")");
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftSeekTo(position);
- return true;
- }
- case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: {
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj);
- return true;
- }
- case MSG_AUDIO_CAPABILITIES_CHANGED: {
- AudioCapabilities capabilities = (AudioCapabilities) msg.obj;
- if (DEBUG) {
- Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities);
- }
- if (capabilities == null) {
- return true;
- }
- if (!capabilities.equals(mAudioCapabilities)) {
- // HDMI supported encodings are changed. restart player.
- mAudioCapabilities = capabilities;
- resetPlayback();
- }
- return true;
- }
- case MSG_SET_STREAM_VOLUME: {
- if (mPlayer != null && mPlayer.isPlaying()) {
- mPlayer.setVolume(mVolume);
- }
- return true;
- }
- case MSG_TUNER_PREFERENCES_CHANGED: {
- mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED);
- @TrickplaySetting int trickplaySetting =
- TunerPreferences.getTrickplaySetting(mContext);
- if (trickplaySetting != mTrickplaySetting) {
- boolean wasTrcikplayEnabled =
- mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- boolean isTrickplayEnabled =
- trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- mTrickplaySetting = trickplaySetting;
- if (isTrickplayEnabled != wasTrcikplayEnabled) {
- sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer));
- }
- }
- return true;
- }
- case MSG_BUFFER_START_TIME_CHANGED: {
- if (mPlayer == null) {
- return true;
- }
- mBufferStartTimeMs = (long) msg.obj;
- if (!hasEnoughBackwardBuffer()
- && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) {
- mPlayer.setPlayWhenReady(true);
- mPlayer.setAudioTrackAndClosedCaption(true);
- mPlaybackParams.setSpeed(1.0f);
- }
- return true;
- }
- case MSG_BUFFER_STATE_CHANGED: {
- boolean available = (boolean) msg.obj;
- mSession.notifyTimeShiftStatusChanged(available
- ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
- : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
- return true;
- }
- case MSG_CHECK_SIGNAL: {
- if (mChannel == null || mPlayer == null) {
- return true;
- }
- TsDataSource source = mPlayer.getDataSource();
- long limitInBytes = source != null ? source.getBufferedPosition() : 0L;
- if (TunerDebug.ENABLED) {
- TunerDebug.calculateDiff();
- mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT,
- Html.fromHtml(
- StatusTextUtils.getStatusWarningInHTML(
- (limitInBytes - mLastLimitInBytes)
- / TS_PACKET_SIZE,
- TunerDebug.getVideoFrameDrop(),
- TunerDebug.getBytesInQueue(),
- TunerDebug.getAudioPositionUs(),
- TunerDebug.getAudioPositionUsRate(),
- TunerDebug.getAudioPtsUs(),
- TunerDebug.getAudioPtsUsRate(),
- TunerDebug.getVideoPtsUs(),
- TunerDebug.getVideoPtsUsRate()
- )));
- }
- mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
- long currentTime = SystemClock.elapsedRealtime();
- long bufferingTimeMs = mBufferingStartTimeMs != INVALID_TIME
- ? currentTime - mBufferingStartTimeMs : mBufferingStartTimeMs;
- long preparingTimeMs = mPreparingStartTimeMs != INVALID_TIME
- ? currentTime - mPreparingStartTimeMs : mPreparingStartTimeMs;
- boolean isBufferingTooLong =
- bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- boolean isPreparingTooLong =
- preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- boolean isWeakSignal = source != null
- && mChannel.getType() != Channel.TYPE_FILE
- && (isBufferingTooLong || isPreparingTooLong);
- if (isWeakSignal && !mReportedWeakSignal) {
- if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
- System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
- }
- if (mPlayer != null) {
- mPlayer.setAudioTrackAndClosedCaption(false);
- }
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- Log.i(TAG, "Notify weak signal due to signal check, " + String.format(
- "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " +
- "videoFrameDrop:%d",
- (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE,
- bufferingTimeMs,
- preparingTimeMs,
- TunerDebug.getVideoFrameDrop()
- ));
- } else if (!isWeakSignal && mReportedWeakSignal) {
- boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME
- && currentTime - mReadyStartTimeMs
- > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- if (!isPlaybackStable) {
- // Wait until playback becomes stable.
- } else if (mReportedDrawnToSurface) {
- mHandler.removeMessages(MSG_RETRY_PLAYBACK);
- notifyVideoAvailable();
- mPlayer.setAudioTrackAndClosedCaption(true);
- }
- }
- mLastLimitInBytes = limitInBytes;
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS);
- return true;
- }
- case MSG_SET_SURFACE: {
- if (mPlayer != null) {
- mPlayer.setSurface(mSurface);
- } else {
- // TODO: Since surface is dynamically set, we can remove the dependency of
- // playback start on mSurface nullity.
- resetPlayback();
- }
- return true;
- }
- case MSG_NOTIFY_AUDIO_TRACK_UPDATED: {
- notifyAudioTracksUpdated();
- return true;
- }
- default: {
- Log.w(TAG, "Unhandled message code: " + msg.what);
- return false;
- }
- }
- }
-
- // Private methods
- private void doSelectTrack(int type, String trackId) {
- int numTrackId = trackId != null
- ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1;
- if (type == TvTrackInfo.TYPE_AUDIO) {
- if (trackId == null) {
- return;
- }
- if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) {
- mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId);
- }
- mSession.notifyTrackSelected(type, trackId);
- } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
- if (trackId == null) {
- mSession.notifyTrackSelected(type, null);
- mCaptionTrack = null;
- stopCaptionTrack();
- return;
- }
- for (TvTrackInfo track : mTvTracks) {
- if (track.getId().equals(trackId)) {
- // The service number of the caption service is used for track id of a
- // subtitle track. Passes the following track id on to TsParser.
- mSession.notifyTrackSelected(type, trackId);
- mCaptionTrack = mCaptionTrackMap.get(numTrackId);
- startCaptionTrack();
- return;
- }
- }
- }
- }
-
- private void setTrickplayEnabledIfNeeded() {
- if (mChannel == null ||
- mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) {
- return;
- }
- if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) {
- mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED;
- TunerPreferences.setTrickplaySetting(
- mContext, mTrickplaySetting);
- }
- }
-
- private MpegTsPlayer createPlayer(AudioCapabilities capabilities) {
- if (capabilities == null) {
- Log.w(TAG, "No Audio Capabilities");
- }
- long now = System.currentTimeMillis();
- if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED
- && mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) {
- if (mTrickplayExpiredMs == 0) {
- mTrickplayExpiredMs = now + TRICKPLAY_OFF_DURATION_MS;
- TunerPreferences.setTrickplayExpiredMs(mContext, mTrickplayExpiredMs);
- } else {
- if (mTrickplayExpiredMs < now) {
- mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting);
- }
- }
- }
- BufferManager bufferManager = null;
- if (mRecordingId != null) {
- StorageManager storageManager =
- new DvrStorageManager(new File(getRecordingPath()), false);
- bufferManager = new BufferManager(storageManager);
- updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles());
- } else if (!mTrickplayDisabledByStorageIssue
- && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED
- && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) {
- bufferManager = new BufferManager(new TrickplayStorageManager(mContext,
- mTrickplayBufferDir, 1024L * 1024 * mMaxTrickplayBufferSizeMb));
- } else {
- Log.w(TAG, "Trickplay is disabled.");
- }
- MpegTsPlayer player = new MpegTsPlayer(
- new MpegTsRendererBuilder(mContext, bufferManager, this),
- mHandler, mSourceManager, capabilities, this);
- Log.i(TAG, "Passthrough AC3 renderer");
- if (DEBUG) Log.d(TAG, "ExoPlayer created");
- return player;
- }
-
- private void startCaptionTrack() {
- if (mCaptionEnabled && mCaptionTrack != null) {
- mSession.sendUiMessage(
- TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack);
- if (mPlayer != null) {
- mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber);
- }
- }
- }
-
- private void stopCaptionTrack() {
- if (mPlayer != null) {
- mPlayer.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
- }
- mSession.sendUiMessage(TunerSession.MSG_UI_STOP_CAPTION_TRACK);
- }
-
- private void resetTvTracks() {
- mTvTracks.clear();
- mAudioTrackMap.clear();
- mCaptionTrackMap.clear();
- mSession.sendUiMessage(TunerSession.MSG_UI_RESET_CAPTION_TRACK);
- mSession.notifyTracksChanged(mTvTracks);
- }
-
- private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) {
- synchronized (tvTracksInterface) {
- if (DEBUG) {
- Log.d(TAG, "UpdateTvTracks " + tvTracksInterface);
- }
- List<AtscAudioTrack> audioTracks = tvTracksInterface.getAudioTracks();
- List<AtscCaptionTrack> captionTracks = tvTracksInterface.getCaptionTracks();
- // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio
- // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio
- // track info in PMT more and use info in EIT only when we have nothing.
- if (audioTracks != null && !audioTracks.isEmpty()
- && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) {
- updateAudioTracks(audioTracks);
- }
- if (captionTracks == null || captionTracks.isEmpty()) {
- if (tvTracksInterface.hasCaptionTrack()) {
- updateCaptionTracks(captionTracks);
- }
- } else {
- updateCaptionTracks(captionTracks);
- }
- }
- }
-
- private void removeTvTracks(int trackType) {
- Iterator<TvTrackInfo> iterator = mTvTracks.iterator();
- while (iterator.hasNext()) {
- TvTrackInfo tvTrackInfo = iterator.next();
- if (tvTrackInfo.getType() == trackType) {
- iterator.remove();
- }
- }
- }
-
- private void updateVideoTrack(int width, int height) {
- removeTvTracks(TvTrackInfo.TYPE_VIDEO);
- mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID)
- .setVideoWidth(width).setVideoHeight(height).build());
- mSession.notifyTracksChanged(mTvTracks);
- mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID);
- }
-
- private void updateAudioTracks(List<AtscAudioTrack> audioTracks) {
- if (DEBUG) {
- Log.d(TAG, "Update AudioTracks " + audioTracks);
- }
- mAudioTrackMap.clear();
- if (audioTracks != null) {
- int index = 0;
- for (AtscAudioTrack audioTrack : audioTracks) {
- audioTrack.index = index;
- mAudioTrackMap.put(index, audioTrack);
- ++index;
- }
- }
- mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED);
- }
-
- private void notifyAudioTracksUpdated() {
- if (mPlayer == null) {
- // Audio tracks will be updated later once player initialization is done.
- return;
- }
- int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO);
- removeTvTracks(TvTrackInfo.TYPE_AUDIO);
- for (int i = 0; i < audioTrackCount; i++) {
- // We use language information from EIT/VCT only when the player does not provide
- // languages.
- com.google.android.exoplayer.MediaFormat infoFromPlayer =
- mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i);
- AtscAudioTrack infoFromEit = mAudioTrackMap.get(i);
- AtscAudioTrack infoFromVct = (mChannel != null
- && mChannel.getAudioTracks().size() == mAudioTrackMap.size()
- && i < mChannel.getAudioTracks().size())
- ? mChannel.getAudioTracks().get(i) : null;
- String language = !TextUtils.isEmpty(infoFromPlayer.language) ? infoFromPlayer.language
- : (infoFromEit != null && infoFromEit.language != null) ? infoFromEit.language
- : (infoFromVct != null && infoFromVct.language != null)
- ? infoFromVct.language : null;
- TvTrackInfo.Builder builder = new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
- builder.setLanguage(language);
- builder.setAudioChannelCount(infoFromPlayer.channelCount);
- builder.setAudioSampleRate(infoFromPlayer.sampleRate);
- TvTrackInfo track = builder.build();
- mTvTracks.add(track);
- }
- mSession.notifyTracksChanged(mTvTracks);
- }
-
- private void updateCaptionTracks(List<AtscCaptionTrack> captionTracks) {
- if (DEBUG) {
- Log.d(TAG, "Update CaptionTrack " + captionTracks);
- }
- removeTvTracks(TvTrackInfo.TYPE_SUBTITLE);
- mCaptionTrackMap.clear();
- if (captionTracks != null) {
- for (AtscCaptionTrack captionTrack : captionTracks) {
- if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) {
- continue;
- }
- String language = captionTrack.language;
-
- // The service number of the caption service is used for track id of a subtitle.
- // Later, when a subtitle is chosen, track id will be passed on to TsParser.
- TvTrackInfo.Builder builder =
- new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE,
- SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber);
- builder.setLanguage(language);
- mTvTracks.add(builder.build());
- mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack);
- }
- }
- mSession.notifyTracksChanged(mTvTracks);
- }
-
- private void updateChannelInfo(TunerChannel channel) {
- if (DEBUG) {
- Log.d(TAG, String.format("Channel Info (old) videoPid: %d audioPid: %d " +
- "audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(),
- mChannel.getAudioPids().size()));
- }
-
- // The list of the audio tracks resided in a channel is often changed depending on a
- // program being on the air. So, we should update the streaming PIDs and types of the
- // tuned channel according to the newly received channel data.
- int oldVideoPid = mChannel.getVideoPid();
- int oldAudioPid = mChannel.getAudioPid();
- List<Integer> audioPids = channel.getAudioPids();
- List<Integer> audioStreamTypes = channel.getAudioStreamTypes();
- int size = audioPids.size();
- mChannel.setVideoPid(channel.getVideoPid());
- mChannel.setAudioPids(audioPids);
- mChannel.setAudioStreamTypes(audioStreamTypes);
- updateTvTracks(channel, true);
- int index = audioPids.isEmpty() ? -1 : 0;
- for (int i = 0; i < size; ++i) {
- if (audioPids.get(i) == oldAudioPid) {
- index = i;
- break;
- }
- }
- mChannel.selectAudioTrack(index);
- mSession.notifyTrackSelected(TvTrackInfo.TYPE_AUDIO,
- index == -1 ? null : AUDIO_TRACK_PREFIX + index);
-
- // Reset playback if there is a change in the listening streaming PIDs.
- if (oldVideoPid != mChannel.getVideoPid()
- || oldAudioPid != mChannel.getAudioPid()) {
- // TODO: Implement a switching between tracks more smoothly.
- resetPlayback();
- }
- if (DEBUG) {
- Log.d(TAG, String.format("Channel Info (new) videoPid: %d audioPid: %d " +
- " audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(),
- mChannel.getAudioPids().size()));
- }
- }
-
- private void stopPlayback(boolean removeChannelDataCallbacks) {
- if (removeChannelDataCallbacks) {
- mChannelDataManager.removeAllCallbacksAndMessages();
- }
- if (mPlayer != null) {
- mPlayer.setPlayWhenReady(false);
- mPlayer.release();
- mPlayer = null;
- mPlayerState = ExoPlayer.STATE_IDLE;
- mPlaybackParams.setSpeed(1.0f);
- mPlayerStarted = false;
- mReportedDrawnToSurface = false;
- mPreparingStartTimeMs = INVALID_TIME;
- mBufferingStartTimeMs = INVALID_TIME;
- mReadyStartTimeMs = INVALID_TIME;
- mLastLimitInBytes = 0L;
- mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE);
- mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
- }
- }
-
- private void startPlayback(int playerHashCode) {
- // TODO: provide hasAudio()/hasVideo() for play recordings.
- if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) {
- return;
- }
- if (mChannel != null && !mChannel.hasAudio()) {
- if (DEBUG) Log.d(TAG, "Channel " + mChannel + " does not have audio.");
- // Playbacks with video-only stream have not been tested yet.
- // No video-only channel has been found.
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
- return;
- }
- if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio())
- || (mChannel.hasVideo() && !mPlayer.hasVideo()))
- && mChannel.getType() != Channel.TYPE_NETWORK) {
- // If the channel is from network, skip this part since the video and audio tracks
- // information for channels from network are more reliable in the extractor. Otherwise,
- // tracks haven't been detected in the extractor. Try again.
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
- return;
- }
- // Since mSurface is volatile, we define a local variable surface to keep the same value
- // inside this method.
- Surface surface = mSurface;
- if (surface != null && !mPlayerStarted) {
- mPlayer.setSurface(surface);
- mPlayer.setPlayWhenReady(true);
- mPlayer.setVolume(mVolume);
- if (mChannel != null && mPlayer.hasAudio() && !mPlayer.hasVideo()) {
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY);
- } else if (!mReportedWeakSignal) {
- // Doesn't show buffering during weak signal.
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);
- }
- mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
- mPlayerStarted = true;
- }
- }
-
- private void preparePlayback() {
- SoftPreconditions.checkState(mPlayer == null);
- if (mChannel == null && mRecordingId == null) {
- return;
- }
- mSourceManager.setKeepTuneStatus(true);
- MpegTsPlayer player = createPlayer(mAudioCapabilities);
- player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
- player.setVideoEventListener(this);
- player.setCaptionServiceNumber(mCaptionTrack != null ?
- mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER);
- if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) {
- mSourceManager.setKeepTuneStatus(false);
- player.release();
- if (!mHandler.hasMessages(MSG_TUNE)) {
- // When prepare failed, there may be some errors related to hardware. In that
- // case, retry playback immediately may not help.
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- Log.i(TAG, "Notify weak signal due to player preparation failure");
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
- System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
- }
- } else {
- mPlayer = player;
- mPlayerStarted = false;
- mHandler.removeMessages(MSG_CHECK_SIGNAL);
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
- }
- }
-
- private void resetPlayback() {
- long timestamp, oldTimestamp;
- timestamp = SystemClock.elapsedRealtime();
- stopPlayback(false);
- stopCaptionTrack();
- if (ENABLE_PROFILER) {
- oldTimestamp = timestamp;
- timestamp = SystemClock.elapsedRealtime();
- Log.i(TAG, "[Profiler] stopPlayback() takes " + (timestamp - oldTimestamp) + " ms");
- }
- if (mChannelBlocked || mSurface == null) {
- return;
- }
- preparePlayback();
- }
-
- private void prepareTune(TunerChannel channel, String recording) {
- mChannelBlocked = false;
- mUnblockedContentRating = null;
- mRetryCount = 0;
- mChannel = channel;
- mRecordingId = recording;
- mRecordingDuration = recording != null ? getDurationForRecording(recording) : null;
- mProgram = null;
- mPrograms = null;
- if (mRecordingId != null) {
- // Workaround of b/33298048: set it to 1 instead of 0.
- mBufferStartTimeMs = mRecordStartTimeMs = 1;
- } else {
- mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
- }
- mLastPositionMs = 0;
- mCaptionTrack = null;
- mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
- }
-
- private void doReschedulePrograms() {
- long currentPositionMs = getCurrentPosition();
- long forwardDifference = Math.abs(currentPositionMs - mLastPositionMs
- - RESCHEDULE_PROGRAMS_INTERVAL_MS);
- mLastPositionMs = currentPositionMs;
-
- // A gap is measured as the time difference between previous and next current position
- // periodically. If the gap has a significant difference with an interval of a period,
- // this means that there is a change of playback status and the programs of the current
- // channel should be rescheduled to new playback timeline.
- if (forwardDifference > RESCHEDULE_PROGRAMS_TOLERANCE_MS) {
- if (DEBUG) {
- Log.d(TAG, "reschedule programs size:"
- + (mPrograms != null ? mPrograms.size() : 0) + " current program: "
- + getCurrentProgram());
- }
- mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms))
- .sendToTarget();
- }
- mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS);
- mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
- RESCHEDULE_PROGRAMS_INTERVAL_MS);
- }
-
- private int getTrickPlaySeekIntervalMs() {
- return Math.max(EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()),
- MIN_TRICKPLAY_SEEK_INTERVAL_MS);
- }
-
- private void doTrickplayBySeek(int seekPositionMs) {
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- if (mPlaybackParams.getSpeed() == 1.0f || !mPlayer.isPrepared()) {
- return;
- }
- if (seekPositionMs < mBufferStartTimeMs - mRecordStartTimeMs) {
- if (mPlaybackParams.getSpeed() > 1.0f) {
- // If fast forwarding, the seekPositionMs can be out of the buffered range
- // because of chuck evictions.
- seekPositionMs = (int) (mBufferStartTimeMs - mRecordStartTimeMs);
- } else {
- mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setAudioTrackAndClosedCaption(true);
- return;
- }
- } else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) {
- // Stops trickplay when FF requested the position later than current position.
- // If RW trickplay requested the position later than current position,
- // continue trickplay.
- if (mPlaybackParams.getSpeed() > 0.0f) {
- mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setAudioTrackAndClosedCaption(true);
- return;
- }
- }
-
- long delayForNextSeek = getTrickPlaySeekIntervalMs();
- if (!mPlayer.isBuffering()) {
- mPlayer.seekTo(seekPositionMs);
- } else {
- delayForNextSeek = MIN_TRICKPLAY_SEEK_INTERVAL_MS;
- }
- seekPositionMs += mPlaybackParams.getSpeed() * delayForNextSeek;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek);
- }
-
- private void doTimeShiftPause() {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- if (!hasEnoughBackwardBuffer()) {
- return;
- }
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setPlayWhenReady(false);
- mPlayer.setAudioTrackAndClosedCaption(true);
- }
-
- private void doTimeShiftResume() {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setPlayWhenReady(true);
- mPlayer.setAudioTrackAndClosedCaption(true);
- }
-
- private void doTimeShiftSeekTo(long timeMs) {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlayer.seekTo((int) (timeMs - mRecordStartTimeMs));
- }
-
- private void doTimeShiftSetPlaybackParams(PlaybackParams params) {
- if (!hasEnoughBackwardBuffer() && params.getSpeed() < 1.0f) {
- return;
- }
- mPlaybackParams = params;
- float speed = mPlaybackParams.getSpeed();
- if (speed == 1.0f) {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- doTimeShiftResume();
- } else if (mPlayer.supportSmoothTrickPlay(speed)) {
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlayer.setAudioTrackAndClosedCaption(false);
- mPlayer.startSmoothTrickplay(mPlaybackParams);
- mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
- TRICKPLAY_MONITOR_INTERVAL_MS);
- } else {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) {
- mPlayer.setAudioTrackAndClosedCaption(false);
- mPlayer.setPlayWhenReady(false);
- // Initiate trickplay
- mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK,
- (int) (mPlayer.getCurrentPosition()
- + speed * getTrickPlaySeekIntervalMs()), 0));
- }
- }
- }
-
- private EitItem getCurrentProgram() {
- if (mPrograms == null || mPrograms.isEmpty()) {
- return null;
- }
- if (mChannel.getType() == Channel.TYPE_FILE) {
- // For the playback from the local file, we use the first one from the given program.
- EitItem first = mPrograms.get(0);
- if (first != null && (mProgram == null
- || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) {
- return first;
- }
- return null;
- }
- long currentTimeMs = getCurrentPosition();
- for (EitItem item : mPrograms) {
- if (item.getStartTimeUtcMillis() <= currentTimeMs
- && item.getEndTimeUtcMillis() >= currentTimeMs) {
- return item;
- }
- }
- return null;
- }
-
- private void doParentalControls() {
- boolean isParentalControlsEnabled = mTvInputManager.isParentalControlsEnabled();
- if (isParentalControlsEnabled) {
- TvContentRating blockContentRating = getContentRatingOfCurrentProgramBlocked();
- if (DEBUG) {
- if (blockContentRating != null) {
- Log.d(TAG, "Check parental controls: blocked by content rating - "
- + blockContentRating);
- } else {
- Log.d(TAG, "Check parental controls: available");
- }
- }
- updateChannelBlockStatus(blockContentRating != null, blockContentRating);
- } else {
- if (DEBUG) {
- Log.d(TAG, "Check parental controls: available");
- }
- updateChannelBlockStatus(false, null);
- }
- }
-
- private void doDiscoverCaptionServiceNumber(int serviceNumber) {
- int index = mCaptionTrackMap.indexOfKey(serviceNumber);
- if (index < 0) {
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.serviceNumber = serviceNumber;
- captionTrack.wideAspectRatio = false;
- captionTrack.easyReader = false;
- mCaptionTrackMap.put(serviceNumber, captionTrack);
- mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE,
- SUBTITLE_TRACK_PREFIX + serviceNumber).build());
- mSession.notifyTracksChanged(mTvTracks);
- }
- }
-
- private TvContentRating getContentRatingOfCurrentProgramBlocked() {
- EitItem currentProgram = getCurrentProgram();
- if (currentProgram == null) {
- return null;
- }
- TvContentRating[] ratings = mTvContentRatingCache
- .getRatings(currentProgram.getContentRating());
- if (ratings == null || ratings.length == 0) {
- ratings = new TvContentRating[] {TvContentRating.UNRATED};
- }
- for (TvContentRating rating : ratings) {
- if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager
- .isRatingBlocked(rating)) {
- return rating;
- }
- }
- return null;
- }
-
- private void updateChannelBlockStatus(boolean channelBlocked,
- TvContentRating contentRating) {
- if (mChannelBlocked == channelBlocked) {
- return;
- }
- mChannelBlocked = channelBlocked;
- if (mChannelBlocked) {
- clearCallbacksAndMessagesSafely();
- stopPlayback(true);
- resetTvTracks();
- if (contentRating != null) {
- mSession.notifyContentBlocked(contentRating);
- }
- mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS);
- } else {
- clearCallbacksAndMessagesSafely();
- resetPlayback();
- mSession.notifyContentAllowed();
- mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
- RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
- mHandler.removeMessages(MSG_CHECK_SIGNAL);
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
- }
- }
-
- @WorkerThread
- private void clearCallbacksAndMessagesSafely() {
- // If MSG_RELEASE is removed, TunerSessionWorker will hang forever.
- // Do not remove messages, after release is requested from MainThread.
- synchronized (mReleaseLock) {
- if (!mReleaseRequested) {
- mHandler.removeCallbacksAndMessages(null);
- }
- }
- }
-
- private boolean hasEnoughBackwardBuffer() {
- return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS
- >= mBufferStartTimeMs - mRecordStartTimeMs;
- }
-
- private void notifyVideoUnavailable(final int reason) {
- mReportedWeakSignal = (reason == TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- if (mSession != null) {
- mSession.notifyVideoUnavailable(reason);
- }
- }
-
- private void notifyVideoAvailable() {
- mReportedWeakSignal = false;
- if (mSession != null) {
- mSession.notifyVideoAvailable();
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java
deleted file mode 100644
index 6ad00daa..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-
-import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrStorageStatusManager;
-import com.android.tv.util.Utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Creates {@link JobService} to clean up recorded program files which are not referenced
- * from database.
- */
-public class TunerStorageCleanUpService extends JobService {
- private static final String TAG = "TunerStorageCleanUpService";
-
- private CleanUpStorageTask mTask;
-
- @Override
- public void onCreate() {
- if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) {
- Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
- this.stopSelf();
- return;
- }
- TvApplication.setCurrentRunningProcess(this, false);
- super.onCreate();
- mTask = new CleanUpStorageTask(this, this);
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- return false;
- }
-
- /**
- * Cleans up recorded program files which are not referenced from database.
- * Cleaning up will be done periodically.
- */
- public static class CleanUpStorageTask extends AsyncTask<JobParameters, Void, JobParameters[]> {
- private final static String[] mProjection = {
- TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME,
- TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI
- };
- private final static long ELAPSED_MILLIS_TO_DELETE = TimeUnit.DAYS.toMillis(1);
-
- private final Context mContext;
- private final DvrStorageStatusManager mDvrStorageStatusManager;
- private final JobService mJobService;
- private final ContentResolver mContentResolver;
-
- /**
- * Creates a recurring storage cleaning task.
- *
- * @param context {@link Context}
- * @param jobService {@link JobService}
- */
- public CleanUpStorageTask(Context context, JobService jobService) {
- mContext = context;
- mDvrStorageStatusManager =
- TvApplication.getSingletons(mContext).getDvrStorageStatusManager();
- mJobService = jobService;
- mContentResolver = mContext.getContentResolver();
- }
-
- private Set<String> getRecordedProgramsDirs() {
- try (Cursor c = mContentResolver.query(
- TvContract.RecordedPrograms.CONTENT_URI, mProjection, null, null, null)) {
- if (c == null) {
- return null;
- }
- Set<String> recordedProgramDirs = new HashSet<>();
- while (c.moveToNext()) {
- String packageName = c.getString(0);
- String dataUriString = c.getString(1);
- if (dataUriString == null) {
- continue;
- }
- Uri dataUri = Uri.parse(dataUriString);
- if (!Utils.isInBundledPackageSet(packageName)
- || dataUri == null || dataUri.getPath() == null
- || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) {
- continue;
- }
- File recordedProgramDir = new File(dataUri.getPath());
- try {
- recordedProgramDirs.add(recordedProgramDir.getCanonicalPath());
- } catch (IOException | SecurityException e) {
- }
- }
- return recordedProgramDirs;
- }
- }
-
- @Override
- protected JobParameters[] doInBackground(JobParameters... params) {
- if (mDvrStorageStatusManager.getDvrStorageStatus()
- == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
- return params;
- }
- File dvrRecordingDir = mDvrStorageStatusManager.getRecordingRootDataDirectory();
- if (dvrRecordingDir == null || !dvrRecordingDir.isDirectory()) {
- return params;
- }
- Set<String> recordedProgramDirs = getRecordedProgramsDirs();
- if (recordedProgramDirs == null) {
- return params;
- }
- File[] files = dvrRecordingDir.listFiles();
- if (files == null || files.length == 0) {
- return params;
- }
- for (File recordingDir : files) {
- try {
- if (!recordedProgramDirs.contains(recordingDir.getCanonicalPath())) {
- long lastModified = recordingDir.lastModified();
- long now = System.currentTimeMillis();
- if (lastModified != 0
- && lastModified < now - ELAPSED_MILLIS_TO_DELETE) {
- // To prevent current recordings from being deleted,
- // deletes recordings which was not modified for long enough time.
- Utils.deleteDirOrFile(recordingDir);
- }
- }
- } catch (IOException | SecurityException e) {
- // would not happen
- }
- }
- return params;
- }
-
- @Override
- protected void onPostExecute(JobParameters[] params) {
- for (JobParameters param : params) {
- mJobService.jobFinished(param, false);
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
deleted file mode 100644
index 2725ddfc..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.tvinput;
-
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputService;
-import android.util.Log;
-
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
-import com.android.tv.TvApplication;
-import com.android.tv.common.feature.CommonFeatures;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * {@link TunerTvInputService} serves TV channels coming from a tuner device.
- */
-public class TunerTvInputService extends TvInputService
- implements AudioCapabilitiesReceiver.Listener{
- private static final String TAG = "TunerTvInputService";
- private static final boolean DEBUG = false;
-
- private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100;
-
- // WeakContainer for {@link TvInputSessionImpl}
- private final Set<TunerSession> mTunerSessions = Collections.newSetFromMap(new WeakHashMap<>());
- private ChannelDataManager mChannelDataManager;
- private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
- private AudioCapabilities mAudioCapabilities;
-
- @Override
- public void onCreate() {
- if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) {
- Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
- this.stopSelf();
- return;
- }
- TvApplication.setCurrentRunningProcess(this, false);
- super.onCreate();
- if (DEBUG) Log.d(TAG, "onCreate");
- mChannelDataManager = new ChannelDataManager(getApplicationContext());
- mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this);
- mAudioCapabilitiesReceiver.register();
- if (CommonFeatures.DVR.isEnabled(this)) {
- JobScheduler jobScheduler =
- (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- JobInfo pendingJob = jobScheduler.getPendingJob(DVR_STORAGE_CLEANUP_JOB_ID);
- if (pendingJob != null) {
- // storage cleaning job is already scheduled.
- } else {
- JobInfo job = new JobInfo.Builder(DVR_STORAGE_CLEANUP_JOB_ID,
- new ComponentName(this, TunerStorageCleanUpService.class))
- .setPersisted(true).setPeriodic(TimeUnit.DAYS.toMillis(1)).build();
- jobScheduler.schedule(job);
- }
- }
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy");
- super.onDestroy();
- mChannelDataManager.release();
- mAudioCapabilitiesReceiver.unregister();
- }
-
- @Override
- public RecordingSession onCreateRecordingSession(String inputId) {
- return new TunerRecordingSession(this, inputId, mChannelDataManager);
- }
-
- @Override
- public Session onCreateSession(String inputId) {
- if (DEBUG) Log.d(TAG, "onCreateSession");
- try {
- final TunerSession session = new TunerSession(this, mChannelDataManager);
- mTunerSessions.add(session);
- session.setAudioCapabilities(mAudioCapabilities);
- session.setOverlayViewEnabled(true);
- return session;
- } catch (RuntimeException e) {
- // There are no available DVB devices.
- Log.e(TAG, "Creating a session for " + inputId + " failed.", e);
- return null;
- }
- }
-
- @Override
- public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
- mAudioCapabilities = audioCapabilities;
- for (TunerSession session : mTunerSessions) {
- if (!session.isReleased()) {
- session.setAudioCapabilities(audioCapabilities);
- }
- }
- }
-
- public static String getInputId(Context context) {
- return TvContract.buildInputId(new ComponentName(context, TunerTvInputService.class));
- }
-}
diff --git a/src/com/android/tv/tuner/util/ByteArrayBuffer.java b/src/com/android/tv/tuner/util/ByteArrayBuffer.java
deleted file mode 100644
index da887e7d..00000000
--- a/src/com/android/tv/tuner/util/ByteArrayBuffer.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/ByteArrayBuffer.java $
- * $Revision: 496070 $
- * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.tv.tuner.util;
-
-/**
- * An expandable byte buffer built on byte array.
- */
-public final class ByteArrayBuffer {
-
- private byte[] buffer;
- private int len;
-
- public ByteArrayBuffer(int capacity) {
- super();
- if (capacity < 0) {
- throw new IllegalArgumentException("Buffer capacity may not be negative");
- }
- this.buffer = new byte[capacity];
- }
-
- private void expand(int newlen) {
- byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)];
- System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
- this.buffer = newbuffer;
- }
-
- public void append(final byte[] b, int off, int len) {
- if (b == null) {
- return;
- }
- if ((off < 0) || (off > b.length) || (len < 0) ||
- ((off + len) < 0) || ((off + len) > b.length)) {
- throw new IndexOutOfBoundsException();
- }
- if (len == 0) {
- return;
- }
- int newlen = this.len + len;
- if (newlen > this.buffer.length) {
- expand(newlen);
- }
- System.arraycopy(b, off, this.buffer, this.len, len);
- this.len = newlen;
- }
-
- public void append(int b) {
- int newlen = this.len + 1;
- if (newlen > this.buffer.length) {
- expand(newlen);
- }
- this.buffer[this.len] = (byte) b;
- this.len = newlen;
- }
-
- public void append(final char[] b, int off, int len) {
- if (b == null) {
- return;
- }
- if ((off < 0) || (off > b.length) || (len < 0) ||
- ((off + len) < 0) || ((off + len) > b.length)) {
- throw new IndexOutOfBoundsException();
- }
- if (len == 0) {
- return;
- }
- int oldlen = this.len;
- int newlen = oldlen + len;
- if (newlen > this.buffer.length) {
- expand(newlen);
- }
- for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
- this.buffer[i2] = (byte) b[i1];
- }
- this.len = newlen;
- }
-
- public void clear() {
- this.len = 0;
- }
-
- public byte[] toByteArray() {
- byte[] b = new byte[this.len];
- if (this.len > 0) {
- System.arraycopy(this.buffer, 0, b, 0, this.len);
- }
- return b;
- }
-
- public int byteAt(int i) {
- return this.buffer[i];
- }
-
- public int capacity() {
- return this.buffer.length;
- }
-
- public int length() {
- return this.len;
- }
-
- public byte[] buffer() {
- return this.buffer;
- }
-
- public void setLength(int len) {
- if (len < 0 || len > this.buffer.length) {
- throw new IndexOutOfBoundsException();
- }
- this.len = len;
- }
-
- public boolean isEmpty() {
- return this.len == 0;
- }
-
- public boolean isFull() {
- return this.len == this.buffer.length;
- }
-
-}
diff --git a/src/com/android/tv/tuner/util/ConvertUtils.java b/src/com/android/tv/tuner/util/ConvertUtils.java
deleted file mode 100644
index abf18d8c..00000000
--- a/src/com/android/tv/tuner/util/ConvertUtils.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.util;
-
-/**
- * Utility class for converting date and time.
- */
-public class ConvertUtils {
- // Time diff between 1.1.1970 00:00:00 and 6.1.1980 00:00:00
- private static final long DIFF_BETWEEN_UNIX_EPOCH_AND_GPS = 315964800;
-
- private ConvertUtils() { }
-
- public static long convertGPSTimeToUnixEpoch(long gpsTime) {
- return gpsTime + DIFF_BETWEEN_UNIX_EPOCH_AND_GPS;
- }
-
- public static long convertUnixEpochToGPSTime(long epochTime) {
- return epochTime - DIFF_BETWEEN_UNIX_EPOCH_AND_GPS;
- }
-}
diff --git a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java b/src/com/android/tv/tuner/util/GlobalSettingsUtils.java
deleted file mode 100644
index 0cefcbed..00000000
--- a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.util;
-
-import android.content.Context;
-import android.provider.Settings;
-
-/**
- * Utility class that get information of global settings.
- */
-public class GlobalSettingsUtils {
- // Since global surround setting is hided, add the related variable here for checking surround
- // sound setting when the audio is unavailable. Remove this workaround after b/31254857 fixed.
- private static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output";
- public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;
-
- private GlobalSettingsUtils () { }
-
- public static int getEncodedSurroundOutputSettings(Context context) {
- return Settings.Global.getInt(context.getContentResolver(), ENCODED_SURROUND_OUTPUT, 0);
- }
-}
diff --git a/src/com/android/tv/tuner/util/Ints.java b/src/com/android/tv/tuner/util/Ints.java
deleted file mode 100644
index 0b1be426..00000000
--- a/src/com/android/tv/tuner/util/Ints.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.android.tv.tuner.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Static utility methods pertaining to int primitives. (Referred Guava's Ints class)
- */
-public class Ints {
- private Ints() {}
-
- public static int[] toArray(List<Integer> integerList) {
- int[] intArray = new int[integerList.size()];
- int i = 0;
- for (Integer data : integerList) {
- intArray[i++] = data;
- }
- return intArray;
- }
-
- public static List<Integer> asList(int[] intArray) {
- List<Integer> integerList = new ArrayList<>(intArray.length);
- for (int data : intArray) {
- integerList.add(data);
- }
- return integerList;
- }
-}
diff --git a/src/com/android/tv/tuner/util/PostalCodeUtils.java b/src/com/android/tv/tuner/util/PostalCodeUtils.java
deleted file mode 100644
index 9eb689a7..00000000
--- a/src/com/android/tv/tuner/util/PostalCodeUtils.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.util;
-
-import android.content.Context;
-import android.location.Address;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.util.LocationUtils;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * A utility class to update, get, and set the last known postal or zip code.
- */
-public class PostalCodeUtils {
- private static final String TAG = "PostalCodeUtils";
-
- // Postcode formats, where A signifies a letter and 9 a digit:
- // US zip code format: 99999
- private static final String POSTCODE_REGEX_US = "^(\\d{5})";
- // UK postcode district formats: A9, A99, AA9, AA99
- // Full UK postcode format: Postcode District + space + 9AA
- // Should be able to handle both postcode district and full postcode
- private static final String POSTCODE_REGEX_GB =
- "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$";
- private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode
-
- private static final Map<String, Pattern> REGION_PATTERN = new HashMap<>();
- private static final Map<String, Integer> REGION_MAX_LENGTH = new HashMap<>();
-
- static {
- REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US));
- REGION_PATTERN.put(
- Locale.UK.getCountry(),
- Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR));
- REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5);
- REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8);
- }
-
- // The longest postcode number is 10-character-long.
- // Use a larger number to accommodate future changes.
- private static final int DEFAULT_MAX_LENGTH = 16;
-
- /** Returns {@code true} if postal code has been changed */
- public static boolean updatePostalCode(Context context)
- throws IOException, SecurityException, NoPostalCodeException {
- String postalCode = getPostalCode(context);
- String lastPostalCode = getLastPostalCode(context);
- if (TextUtils.isEmpty(postalCode)) {
- if (TextUtils.isEmpty(lastPostalCode)) {
- throw new NoPostalCodeException();
- }
- } else if (!TextUtils.equals(postalCode, lastPostalCode)) {
- setLastPostalCode(context, postalCode);
- return true;
- }
- return false;
- }
-
- /**
- * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or
- * input by users.
- */
- public static String getLastPostalCode(Context context) {
- return TunerPreferences.getLastPostalCode(context);
- }
-
- /**
- * Sets the last stored postal or zip code. This method will overwrite the value written by
- * calling {@link #updatePostalCode(Context)}.
- */
- public static void setLastPostalCode(Context context, String postalCode) {
- Log.i(TAG, "Set Postal Code:" + postalCode);
- TunerPreferences.setLastPostalCode(context, postalCode);
- }
-
- @Nullable
- private static String getPostalCode(Context context) throws IOException, SecurityException {
- Address address = LocationUtils.getCurrentAddress(context);
- if (address != null) {
- Log.i(TAG, "Current country and postal code is " + address.getCountryName() + ", "
- + address.getPostalCode());
- return address.getPostalCode();
- }
- return null;
- }
-
- /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */
- public static class NoPostalCodeException extends Exception {
- public NoPostalCodeException() {
- }
- }
-
- /**
- * Checks whether a postcode matches the format of the specific region.
- *
- * @return {@code false} if the region is supported and the postcode doesn't match; {@code true}
- * otherwise
- */
- public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) {
- Pattern pattern = REGION_PATTERN.get(region.toUpperCase());
- return pattern == null || pattern.matcher(postcode).matches();
- }
-
- /**
- * Gets the largest possible postcode length in the region.
- *
- * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH}
- * otherwise
- */
- public static int getRegionMaxLength(Context context) {
- Integer maxLength =
- REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase());
- return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength;
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/util/StatusTextUtils.java b/src/com/android/tv/tuner/util/StatusTextUtils.java
deleted file mode 100644
index 2633834b..00000000
--- a/src/com/android/tv/tuner/util/StatusTextUtils.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.util;
-
-import java.util.Locale;
-
-/**
- * Utility class for tuner status messages.
- */
-public class StatusTextUtils {
- private static final int PACKETS_PER_SEC_YELLOW = 1500;
- private static final int PACKETS_PER_SEC_RED = 1000;
- private static final int AUDIO_POSITION_MS_RATE_DIFF_YELLOW = 100;
- private static final int AUDIO_POSITION_MS_RATE_DIFF_RED = 200;
- private static final String COLOR_RED = "red";
- private static final String COLOR_YELLOW = "yellow";
- private static final String COLOR_GREEN = "green";
- private static final String COLOR_GRAY = "gray";
-
- private StatusTextUtils() { }
-
- /**
- * Returns tuner status warning message in HTML.
- *
- * <p>This is only called for debuging and always shown in english.</p>
- */
- public static String getStatusWarningInHTML(long packetsPerSec,
- int videoFrameDrop, int bytesInQueue,
- long audioPositionUs, long audioPositionUsRate,
- long audioPtsUs, long audioPtsUsRate,
- long videoPtsUs, long videoPtsUsRate) {
- StringBuffer buffer = new StringBuffer();
-
- // audioPosition should go in rate of 1000ms.
- long audioPositionMsRate = audioPositionUsRate / 1000;
- String audioPositionColor;
- if (Math.abs(audioPositionMsRate - 1000) > AUDIO_POSITION_MS_RATE_DIFF_RED) {
- audioPositionColor = COLOR_RED;
- } else if (Math.abs(audioPositionMsRate - 1000) > AUDIO_POSITION_MS_RATE_DIFF_YELLOW) {
- audioPositionColor = COLOR_YELLOW;
- } else {
- audioPositionColor = COLOR_GRAY;
- }
- buffer.append(String.format(Locale.US, "<font color=%s>", audioPositionColor));
- buffer.append(
- String.format(Locale.US, "audioPositionMs: %d (%d)<br>", audioPositionUs / 1000,
- audioPositionMsRate));
- buffer.append("</font>\n");
- buffer.append("<font color=" + COLOR_GRAY + ">");
- buffer.append(String.format(Locale.US, "audioPtsMs: %d (%d, %d)<br>", audioPtsUs / 1000,
- audioPtsUsRate / 1000, (audioPtsUs - audioPositionUs) / 1000));
- buffer.append(String.format(Locale.US, "videoPtsMs: %d (%d, %d)<br>", videoPtsUs / 1000,
- videoPtsUsRate / 1000, (videoPtsUs - audioPositionUs) / 1000));
- buffer.append("</font>\n");
-
- appendStatusLine(buffer, "KbytesInQueue", bytesInQueue / 1000, 1, 10);
- buffer.append("<br/>");
- appendErrorStatusLine(buffer, "videoFrameDrop", videoFrameDrop, 0, 2);
- buffer.append("<br/>");
- appendStatusLine(buffer, "packetsPerSec", packetsPerSec, PACKETS_PER_SEC_RED,
- PACKETS_PER_SEC_YELLOW);
- return buffer.toString();
- }
-
- /**
- * Returns audio unavailable warning message in HTML.
- */
- public static String getAudioWarningInHTML(String msg) {
- return String.format("<font color=%s>%s</font>\n", COLOR_YELLOW, msg);
- }
-
- private static void appendStatusLine(StringBuffer buffer, String factorName, long value,
- int minRed, int minYellow) {
- buffer.append("<font color=");
- if (value <= minRed) {
- buffer.append(COLOR_RED);
- } else if (value <= minYellow) {
- buffer.append(COLOR_YELLOW);
- } else {
- buffer.append(COLOR_GREEN);
- }
- buffer.append(">");
- buffer.append(factorName);
- buffer.append(" : ");
- buffer.append(value);
- buffer.append("</font>");
- }
-
- private static void appendErrorStatusLine(StringBuffer buffer, String factorName, int value,
- int minGreen, int minYellow) {
- buffer.append("<font color=");
- if (value <= minGreen) {
- buffer.append(COLOR_GREEN);
- } else if (value <= minYellow) {
- buffer.append(COLOR_YELLOW);
- } else {
- buffer.append(COLOR_RED);
- }
- buffer.append(">");
- buffer.append(factorName);
- buffer.append(" : ");
- buffer.append(value);
- buffer.append("</font>");
- }
-}
diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
deleted file mode 100644
index 2817ccbf..00000000
--- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.util;
-
-import android.util.Log;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Proxy class that gives an access to a hidden API {@link android.os.SystemProperties#getBoolean}.
- */
-public class SystemPropertiesProxy {
- private static final String TAG = "SystemPropertiesProxy";
-
- private SystemPropertiesProxy() { }
-
- public static boolean getBoolean(String key, boolean def)
- throws IllegalArgumentException {
- try {
- Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
- Method getBooleanMethod = SystemPropertiesClass.getDeclaredMethod("getBoolean",
- String.class, boolean.class);
- getBooleanMethod.setAccessible(true);
- return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException
- | ClassNotFoundException e) {
- Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e);
- }
- return def;
- }
-
- public static int getInt(String key, int def)
- throws IllegalArgumentException {
- try {
- Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
- Method getIntMethod = SystemPropertiesClass.getDeclaredMethod("getInt",
- String.class, int.class);
- getIntMethod.setAccessible(true);
- return (int) getIntMethod.invoke(SystemPropertiesClass, key, def);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException
- | ClassNotFoundException e) {
- Log.e(TAG, "Failed to invoke SystemProperties.getInt()", e);
- }
- return def;
- }
-
- public static String getString(String key, String def) throws IllegalArgumentException {
- try {
- Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
- Method getIntMethod =
- SystemPropertiesClass.getDeclaredMethod("get", String.class, String.class);
- getIntMethod.setAccessible(true);
- return (String) getIntMethod.invoke(SystemPropertiesClass, key, def);
- } catch (InvocationTargetException
- | IllegalAccessException
- | NoSuchMethodException
- | ClassNotFoundException e) {
- Log.e(TAG, "Failed to invoke SystemProperties.get()", e);
- }
- return def;
- }
-}
diff --git a/src/com/android/tv/tuner/util/TisConfiguration.java b/src/com/android/tv/tuner/util/TisConfiguration.java
deleted file mode 100644
index ca861d67..00000000
--- a/src/com/android/tv/tuner/util/TisConfiguration.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.android.tv.tuner.util;
-
-import android.content.Context;
-
-/**
- * A helper class of tuner configuration.
- */
-public class TisConfiguration {
- private static final String LC_PACKAGE_NAME = "com.android.tv";
-
- public static boolean isPackagedWithLiveChannels(Context context) {
- return (LC_PACKAGE_NAME.equals(context.getPackageName()));
- }
-
- public static boolean isInternalTunerTvInput(Context context) {
- return (!LC_PACKAGE_NAME.equals(context.getPackageName()));
- }
-
- public static int getTunerHwDeviceId(Context context) {
- return 0; // FIXME: Make it OEM configurable
- }
-}
diff --git a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
deleted file mode 100644
index f421bf1a..00000000
--- a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.util;
-
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.support.annotation.Nullable;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-
-/**
- * Utility class for providing tuner input info.
- */
-public class TunerInputInfoUtils {
- private static final String TAG = "TunerInputInfoUtils";
- private static final boolean DEBUG = false;
-
- /**
- * Builds tuner input's info.
- */
- @Nullable
- @TargetApi(Build.VERSION_CODES.N)
- public static TvInputInfo buildTunerInputInfo(Context context) {
- Pair<Integer, Integer> tunerTypeAndCount = TunerHal.getTunerTypeAndCount(context);
- if (tunerTypeAndCount.first == null || tunerTypeAndCount.second == 0) {
- return null;
- }
- int inputLabelId = 0;
- switch (tunerTypeAndCount.first) {
- case TunerHal.TUNER_TYPE_BUILT_IN:
- inputLabelId = R.string.bt_app_name;
- break;
- case TunerHal.TUNER_TYPE_USB:
- inputLabelId = R.string.ut_app_name;
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- inputLabelId = R.string.nt_app_name;
- break;
- }
- try {
- TvInputInfo.Builder builder = new TvInputInfo.Builder(context,
- new ComponentName(context, TunerTvInputService.class));
- return builder.setLabel(inputLabelId)
- .setCanRecord(CommonFeatures.DVR.isEnabled(context))
- .setTunerCount(tunerTypeAndCount.second)
- .build();
- } catch (IllegalArgumentException | NullPointerException e) {
- // TunerTvInputService is not enabled.
- return null;
- }
- }
-
- /**
- * Updates tuner input's info.
- *
- * @param context {@link Context} instance
- */
- public static void updateTunerInputInfo(Context context) {
- final Context appContext = context.getApplicationContext();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- new AsyncTask<Void, Void, TvInputInfo>() {
- @Override
- protected TvInputInfo doInBackground(Void... params) {
- if (DEBUG) Log.d(TAG, "updateTunerInputInfo()");
- return buildTunerInputInfo(appContext);
- }
-
- @Override
- @TargetApi(Build.VERSION_CODES.N)
- protected void onPostExecute(TvInputInfo info) {
- if (info != null) {
- ((TvInputManager) appContext.getSystemService(Context.TV_INPUT_SERVICE))
- .updateTvInputInfo(info);
- if (DEBUG) {
- Log.d(
- TAG,
- "TvInputInfo ["
- + info.loadLabel(appContext)
- + "] updated: "
- + info.toString());
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "Updating tuner input info failed. Input is not ready yet.");
- }
- }
- }
- }.execute();
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java
index 625014ea..b2be9f02 100644
--- a/src/com/android/tv/ui/AppLayerTvView.java
+++ b/src/com/android/tv/ui/AppLayerTvView.java
@@ -21,18 +21,15 @@ import android.media.tv.TvView;
import android.util.AttributeSet;
import android.view.SurfaceView;
import android.view.View;
-
-import com.android.tv.util.Debug;
-import com.android.tv.util.Utils;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.Debug;
/**
* A TvView class for application layer when multiple windows are being used in the app.
- * <p>
- * Once an app starts using additional window like SubPanel and it gets window focus, the
- * {@link android.media.tv.TvView#setMain()} does not work because its implementation assumes that
- * the app uses only application layer.
- * TODO: remove this class once the TvView.setMain() is revisited.
- * </p>
+ *
+ * <p>Once an app starts using additional window like SubPanel and it gets window focus, the {@link
+ * android.media.tv.TvView#setMain()} does not work because its implementation assumes that the app
+ * uses only application layer. TODO: remove this class once the TvView.setMain() is revisited.
*/
public class AppLayerTvView extends TvView {
public AppLayerTvView(Context context) {
@@ -56,7 +53,7 @@ public class AppLayerTvView extends TvView {
public void onViewAdded(View child) {
if (child instanceof SurfaceView) {
// Note: See b/29118070 for detail.
- ((SurfaceView) child).setSecure(!Utils.isDeveloper());
+ ((SurfaceView) child).setSecure(!CommonUtils.isDeveloper());
}
super.onViewAdded(child);
}
@@ -66,7 +63,7 @@ public class AppLayerTvView extends TvView {
super.getLocationOnScreen(outLocation);
// The TvView.MySessionCallback.onSessionCreated() will call this method indirectly.
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "AppLayerTvView.getLocationOnScreen, session created");
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log("AppLayerTvView.getLocationOnScreen, session created");
}
}
diff --git a/src/com/android/tv/ui/BlockScreenView.java b/src/com/android/tv/ui/BlockScreenView.java
index 09c167ca..6b2d9a01 100644
--- a/src/com/android/tv/ui/BlockScreenView.java
+++ b/src/com/android/tv/ui/BlockScreenView.java
@@ -29,7 +29,6 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
-
import com.android.tv.R;
import com.android.tv.ui.TunableTvView.BlockScreenType;
@@ -62,10 +61,11 @@ public class BlockScreenView extends FrameLayout {
public BlockScreenView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mSpacingNormal = getResources().getDimensionPixelOffset(
- R.dimen.tvview_block_vertical_spacing);
- mSpacingShrunken = getResources().getDimensionPixelOffset(
- R.dimen.shrunken_tvview_block_vertical_spacing);
+ mSpacingNormal =
+ getResources().getDimensionPixelOffset(R.dimen.tvview_block_vertical_spacing);
+ mSpacingShrunken =
+ getResources()
+ .getDimensionPixelOffset(R.dimen.shrunken_tvview_block_vertical_spacing);
}
@Override
@@ -78,66 +78,60 @@ public class BlockScreenView extends FrameLayout {
mSpace = findViewById(R.id.space);
mBlockingInfoTextView = (TextView) findViewById(R.id.block_screen_text);
mBackgroundImageView = (ImageView) findViewById(R.id.background_image);
- mFadeOut = AnimatorInflater.loadAnimator(getContext(),
- R.animator.tvview_block_screen_fade_out);
+ mFadeOut =
+ AnimatorInflater.loadAnimator(
+ getContext(), R.animator.tvview_block_screen_fade_out);
mFadeOut.setTarget(this);
- mFadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setVisibility(GONE);
- setBackgroundImage(null);
- setAlpha(1.0f);
- }
- });
- mInfoFadeIn = AnimatorInflater.loadAnimator(getContext(),
- R.animator.tvview_block_screen_fade_in);
+ mFadeOut.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setVisibility(GONE);
+ setBackgroundImage(null);
+ setAlpha(1.0f);
+ }
+ });
+ mInfoFadeIn =
+ AnimatorInflater.loadAnimator(getContext(), R.animator.tvview_block_screen_fade_in);
mInfoFadeIn.setTarget(mContainerView);
- mInfoFadeOut = AnimatorInflater.loadAnimator(getContext(),
- R.animator.tvview_block_screen_fade_out);
+ mInfoFadeOut =
+ AnimatorInflater.loadAnimator(
+ getContext(), R.animator.tvview_block_screen_fade_out);
mInfoFadeOut.setTarget(mContainerView);
- mInfoFadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mContainerView.setVisibility(GONE);
- }
- });
+ mInfoFadeOut.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mContainerView.setVisibility(GONE);
+ }
+ });
}
- /**
- * Sets the normal image.
- */
+ /** Sets the normal image. */
public void setIconImage(int resId) {
mNormalLockIconView.setImageResource(resId);
updateSpaceVisibility();
}
- /**
- * Sets the scale type of the normal image.
- */
+ /** Sets the scale type of the normal image. */
public void setIconScaleType(ScaleType scaleType) {
mNormalLockIconView.setScaleType(scaleType);
updateSpaceVisibility();
}
- /**
- * Show or hide the image of this view.
- */
+ /** Show or hide the image of this view. */
public void setIconVisibility(boolean visible) {
mImageContainer.setVisibility(visible ? VISIBLE : GONE);
updateSpaceVisibility();
}
- /**
- * Sets the text message.
- */
+ /** Sets the text message. */
public void setInfoText(int resId) {
mBlockingInfoTextView.setText(resId);
updateSpaceVisibility();
}
- /**
- * Sets the text message.
- */
+ /** Sets the text message. */
public void setInfoText(String text) {
mBlockingInfoTextView.setText(text);
updateSpaceVisibility();
@@ -175,19 +169,18 @@ public class BlockScreenView extends FrameLayout {
}
/**
- * Changes the spacing between the image view and the text view according to the
- * {@code blockScreenType}.
+ * Changes the spacing between the image view and the text view according to the {@code
+ * blockScreenType}.
*/
public void setSpacing(@BlockScreenType int blockScreenType) {
mSpace.getLayoutParams().height =
blockScreenType == TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW
- ? mSpacingShrunken : mSpacingNormal;
+ ? mSpacingShrunken
+ : mSpacingNormal;
requestLayout();
}
- /**
- * Changes the view layout according to the {@code blockScreenType}.
- */
+ /** Changes the view layout according to the {@code blockScreenType}. */
public void onBlockStatusChanged(@BlockScreenType int blockScreenType, boolean withAnimation) {
if (!withAnimation) {
switch (blockScreenType) {
@@ -235,25 +228,19 @@ public class BlockScreenView extends FrameLayout {
updateSpaceVisibility();
}
- /**
- * Adds a listener to the fade-in animation of info text and icons of the block screen.
- */
+ /** Adds a listener to the fade-in animation of info text and icons of the block screen. */
public void addInfoFadeInAnimationListener(AnimatorListener listener) {
mInfoFadeIn.addListener(listener);
}
- /**
- * Fades out the block screen.
- */
+ /** Fades out the block screen. */
public void fadeOut() {
if (getVisibility() == VISIBLE && !mFadeOut.isStarted()) {
mFadeOut.start();
}
}
- /**
- * Ends the currently running animations.
- */
+ /** Ends the currently running animations. */
public void endAnimations() {
if (mFadeOut != null && mFadeOut.isRunning()) {
mFadeOut.end();
diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java
index a5d897f2..28325197 100644
--- a/src/com/android/tv/ui/ChannelBannerView.java
+++ b/src/com/android/tv/ui/ChannelBannerView.java
@@ -26,7 +26,6 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
-import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.Spannable;
import android.text.SpannableString;
@@ -38,6 +37,8 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -45,46 +46,43 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.data.StreamInfo;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.parental.ContentRatingsManager;
-import com.android.tv.util.ImageCache;
-import com.android.tv.util.ImageLoader;
-import com.android.tv.util.ImageLoader.ImageLoaderCallback;
-import com.android.tv.util.ImageLoader.LoadTvInputLogoTask;
+import com.android.tv.ui.TvTransitionManager.TransitionLayout;
+import com.android.tv.ui.hideable.AutoHideScheduler;
import com.android.tv.util.Utils;
-
-/**
- * A view to render channel banner.
- */
-public class ChannelBannerView extends FrameLayout implements TvTransitionManager.TransitionLayout {
+import com.android.tv.util.images.ImageCache;
+import com.android.tv.util.images.ImageLoader;
+import com.android.tv.util.images.ImageLoader.ImageLoaderCallback;
+import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask;
+
+/** A view to render channel banner. */
+public class ChannelBannerView extends FrameLayout
+ implements TransitionLayout, AccessibilityStateChangeListener {
private static final String TAG = "ChannelBannerView";
private static final boolean DEBUG = false;
- /**
- * Show all information at the channel banner.
- */
+ /** Show all information at the channel banner. */
public static final int LOCK_NONE = 0;
/**
- * Lock program details at the channel banner.
- * This is used when a content is locked so we don't want to show program details
- * including program description text and poster art.
+ * Lock program details at the channel banner. This is used when a content is locked so we don't
+ * want to show program details including program description text and poster art.
*/
public static final int LOCK_PROGRAM_DETAIL = 1;
/**
- * Lock channel information at the channel banner.
- * This is used when a channel is locked so we only want to show input information.
+ * Lock channel information at the channel banner. This is used when a channel is locked so we
+ * only want to show input information.
*/
public static final int LOCK_CHANNEL_INFO = 2;
@@ -119,7 +117,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private Channel mCurrentChannel;
private boolean mCurrentChannelLogoExists;
private Program mLastUpdatedProgram;
- private final Handler mHandler = new Handler();
+ private final AutoHideScheduler mAutoHideScheduler;
private final DvrManager mDvrManager;
private ContentRatingsManager mContentRatingsManager;
private TvContentRating mBlockingContentRating;
@@ -134,18 +132,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private final Animator mProgramDescriptionFadeInAnimator;
private final Animator mProgramDescriptionFadeOutAnimator;
- private final Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mCurrentHeight = 0;
- mMainActivity.getOverlayManager().hideOverlays(
- TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
- }
- };
private final long mShowDurationMillis;
private final int mChannelLogoImageViewWidth;
private final int mChannelLogoImageViewHeight;
@@ -157,22 +143,23 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private final int mRecordingIconPadding;
private final Interpolator mResizeInterpolator;
- private final AnimatorListenerAdapter mResizeAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animator) {
- mProgramInfoUpdatePendingByResizing = false;
- }
+ private final AnimatorListenerAdapter mResizeAnimatorListener =
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mProgramInfoUpdatePendingByResizing = false;
+ }
- @Override
- public void onAnimationEnd(Animator animator) {
- mProgramDescriptionTextView.setAlpha(1f);
- mResizeAnimator = null;
- if (mProgramInfoUpdatePendingByResizing) {
- mProgramInfoUpdatePendingByResizing = false;
- updateProgramInfo(mLastUpdatedProgram);
- }
- }
- };
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mProgramDescriptionTextView.setAlpha(1f);
+ mResizeAnimator = null;
+ if (mProgramInfoUpdatePendingByResizing) {
+ mProgramInfoUpdatePendingByResizing = false;
+ updateProgramInfo(mLastUpdatedProgram);
+ }
+ }
+ };
public ChannelBannerView(Context context) {
this(context, null);
@@ -185,53 +172,60 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
public ChannelBannerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mResources = getResources();
-
mMainActivity = (MainActivity) context;
- mShowDurationMillis = mResources.getInteger(
- R.integer.channel_banner_show_duration);
- mChannelLogoImageViewWidth = mResources.getDimensionPixelSize(
- R.dimen.channel_banner_channel_logo_width);
- mChannelLogoImageViewHeight = mResources.getDimensionPixelSize(
- R.dimen.channel_banner_channel_logo_height);
- mChannelLogoImageViewMarginStart = mResources.getDimensionPixelSize(
- R.dimen.channel_banner_channel_logo_margin_start);
- mProgramDescriptionTextViewWidth = mResources.getDimensionPixelSize(
- R.dimen.channel_banner_program_description_width);
+ mShowDurationMillis = mResources.getInteger(R.integer.channel_banner_show_duration);
+ mChannelLogoImageViewWidth =
+ mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_width);
+ mChannelLogoImageViewHeight =
+ mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_height);
+ mChannelLogoImageViewMarginStart =
+ mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_margin_start);
+ mProgramDescriptionTextViewWidth =
+ mResources.getDimensionPixelSize(R.dimen.channel_banner_program_description_width);
mChannelBannerTextColor = mResources.getColor(R.color.channel_banner_text_color, null);
- mChannelBannerDimTextColor = mResources.getColor(R.color.channel_banner_dim_text_color,
- null);
+ mChannelBannerDimTextColor =
+ mResources.getColor(R.color.channel_banner_dim_text_color, null);
mResizeAnimDuration = mResources.getInteger(R.integer.channel_banner_fast_anim_duration);
- mRecordingIconPadding = mResources.getDimensionPixelOffset(
- R.dimen.channel_banner_recording_icon_padding);
+ mRecordingIconPadding =
+ mResources.getDimensionPixelOffset(R.dimen.channel_banner_recording_icon_padding);
- mResizeInterpolator = AnimationUtils.loadInterpolator(context,
- android.R.interpolator.linear_out_slow_in);
+ mResizeInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
- mProgramDescriptionFadeInAnimator = AnimatorInflater.loadAnimator(mMainActivity,
- R.animator.channel_banner_program_description_fade_in);
- mProgramDescriptionFadeOutAnimator = AnimatorInflater.loadAnimator(mMainActivity,
- R.animator.channel_banner_program_description_fade_out);
+ mProgramDescriptionFadeInAnimator =
+ AnimatorInflater.loadAnimator(
+ mMainActivity, R.animator.channel_banner_program_description_fade_in);
+ mProgramDescriptionFadeOutAnimator =
+ AnimatorInflater.loadAnimator(
+ mMainActivity, R.animator.channel_banner_program_description_fade_out);
if (CommonFeatures.DVR.isEnabled(mMainActivity)) {
- mDvrManager = TvApplication.getSingletons(mMainActivity).getDvrManager();
+ mDvrManager = TvSingletons.getSingletons(mMainActivity).getDvrManager();
} else {
mDvrManager = null;
}
- mContentRatingsManager = TvApplication.getSingletons(getContext())
- .getTvInputManagerHelper().getContentRatingsManager();
-
- mNoProgram = new Program.Builder()
- .setTitle(context.getString(R.string.channel_banner_no_title))
- .setDescription(EMPTY_STRING)
- .build();
- mLockedChannelProgram = new Program.Builder()
- .setTitle(context.getString(R.string.channel_banner_locked_channel_title))
- .setDescription(EMPTY_STRING)
- .build();
+ mContentRatingsManager =
+ TvSingletons.getSingletons(getContext())
+ .getTvInputManagerHelper()
+ .getContentRatingsManager();
+
+ mNoProgram =
+ new Program.Builder()
+ .setTitle(context.getString(R.string.channel_banner_no_title))
+ .setDescription(EMPTY_STRING)
+ .build();
+ mLockedChannelProgram =
+ new Program.Builder()
+ .setTitle(context.getString(R.string.channel_banner_locked_channel_title))
+ .setDescription(EMPTY_STRING)
+ .build();
if (sClosedCaptionMark == null) {
sClosedCaptionMark = context.getString(R.string.closed_caption);
}
+ mAutoHideScheduler = new AutoHideScheduler(context, this::hide);
+ context.getSystemService(AccessibilityManager.class)
+ .addAccessibilityStateChangeListener(mAutoHideScheduler);
}
@Override
@@ -260,12 +254,13 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mProgramDescriptionFadeInAnimator.setTarget(mProgramDescriptionTextView);
mProgramDescriptionFadeOutAnimator.setTarget(mProgramDescriptionTextView);
- mProgramDescriptionFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- mProgramDescriptionTextView.setText(mProgramDescriptionText);
- }
- });
+ mProgramDescriptionFadeOutAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mProgramDescriptionTextView.setText(mProgramDescriptionText);
+ }
+ });
}
@Override
@@ -274,22 +269,13 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (fromEmptyScene) {
ViewUtils.setTransitionAlpha(mChannelView, 1f);
}
- scheduleHide();
+ mAutoHideScheduler.schedule(mShowDurationMillis);
}
@Override
public void onExitAction() {
mCurrentHeight = 0;
- cancelHide();
- }
-
- private void scheduleHide() {
- cancelHide();
- mHandler.postDelayed(mHideRunnable, mShowDurationMillis);
- }
-
- private void cancelHide() {
- mHandler.removeCallbacks(mHideRunnable);
+ mAutoHideScheduler.cancel();
}
private void resetAnimationEffects() {
@@ -308,7 +294,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
* @throws IllegalArgumentException if lockType is invalid.
*/
public int setLockType(int lockType) {
- if (lockType != LOCK_NONE && lockType != LOCK_CHANNEL_INFO
+ if (lockType != LOCK_NONE
+ && lockType != LOCK_CHANNEL_INFO
&& lockType != LOCK_PROGRAM_DETAIL) {
throw new IllegalArgumentException("No such lock type " + lockType);
}
@@ -330,7 +317,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
* Update channel banner view.
*
* @param updateOnTune {@false} denotes the channel banner is updated due to other reasons than
- * tuning. The channel info will not be updated in this case.
+ * tuning. The channel info will not be updated in this case.
*/
public void updateViews(boolean updateOnTune) {
resetAnimationEffects();
@@ -338,7 +325,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mUpdateOnTune = updateOnTune;
if (mUpdateOnTune) {
if (isShown()) {
- scheduleHide();
+ mAutoHideScheduler.schedule(mShowDurationMillis);
}
mBlockingContentRating = null;
mCurrentChannel = mMainActivity.getCurrentChannel();
@@ -351,6 +338,18 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mUpdateOnTune = false;
}
+ private void hide() {
+ mCurrentHeight = 0;
+ mMainActivity
+ .getOverlayManager()
+ .hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ }
+
/**
* Update channel banner view with stream info.
*
@@ -359,14 +358,18 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
public void updateStreamInfo(StreamInfo info) {
// Update stream information in a channel.
if (mLockType != LOCK_CHANNEL_INFO && info != null) {
- updateText(mClosedCaptionTextView, info.hasClosedCaption() ? sClosedCaptionMark
- : EMPTY_STRING);
- updateText(mAspectRatioTextView,
+ updateText(
+ mClosedCaptionTextView,
+ info.hasClosedCaption() ? sClosedCaptionMark : EMPTY_STRING);
+ updateText(
+ mAspectRatioTextView,
Utils.getAspectRatioString(info.getVideoDisplayAspectRatio()));
- updateText(mResolutionTextView,
+ updateText(
+ mResolutionTextView,
Utils.getVideoDefinitionLevelString(
mMainActivity, info.getVideoDefinitionLevel()));
- updateText(mAudioChannelTextView,
+ updateText(
+ mAudioChannelTextView,
Utils.getAudioChannelString(mMainActivity, info.getAudioChannelCount()));
} else {
// Channel change has been requested. But, StreamInfo hasn't been updated yet.
@@ -415,9 +418,11 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
mChannelNumberTextView.setText(displayNumber);
mChannelNameTextView.setText(displayName);
- TvInputInfo info = mMainActivity.getTvInputManagerHelper().getTvInputInfo(
- getCurrentInputId());
- if (info == null || !ImageLoader.loadBitmap(createTvInputLogoLoaderCallback(info, this),
+ TvInputInfo info =
+ mMainActivity.getTvInputManagerHelper().getTvInputInfo(getCurrentInputId());
+ if (info == null
+ || !ImageLoader.loadBitmap(
+ createTvInputLogoLoaderCallback(info, this),
new LoadTvInputLogoTask(getContext(), ImageCache.getInstance(), info))) {
mTvInputLogoImageView.setVisibility(View.GONE);
mTvInputLogoImageView.setImageDrawable(null);
@@ -425,8 +430,11 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mChannelLogoImageView.setImageBitmap(null);
mChannelLogoImageView.setVisibility(View.GONE);
if (mCurrentChannel != null && mCurrentChannelLogoExists) {
- mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
- mChannelLogoImageViewWidth, mChannelLogoImageViewHeight,
+ mCurrentChannel.loadBitmap(
+ getContext(),
+ Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
+ mChannelLogoImageViewWidth,
+ mChannelLogoImageViewHeight,
createChannelLogoCallback(this, mCurrentChannel));
}
}
@@ -446,7 +454,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
return new ImageLoaderCallback<ChannelBannerView>(channelBannerView) {
@Override
public void onBitmapLoaded(ChannelBannerView channelBannerView, Bitmap bitmap) {
- if (bitmap != null && channelBannerView.mCurrentChannel != null
+ if (bitmap != null
+ && channelBannerView.mCurrentChannel != null
&& info.getId().equals(channelBannerView.mCurrentChannel.getInputId())) {
channelBannerView.updateTvInputLogo(bitmap);
}
@@ -485,7 +494,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
return new ImageLoaderCallback<ChannelBannerView>(channelBannerView) {
@Override
public void onBitmapLoaded(ChannelBannerView view, @Nullable Bitmap logo) {
- if (channel != view.mCurrentChannel) {
+ if (channel.equals(view.mCurrentChannel)) {
// The logo is obsolete.
return;
}
@@ -524,8 +533,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (mLastUpdatedProgram == null
|| !TextUtils.equals(program.getTitle(), mLastUpdatedProgram.getTitle())
- || !TextUtils.equals(program.getEpisodeDisplayTitle(getContext()),
- mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) {
+ || !TextUtils.equals(
+ program.getEpisodeDisplayTitle(getContext()),
+ mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) {
updateProgramTextView(program);
}
updateProgramTimeInfo(program);
@@ -548,8 +558,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mProgramDescriptionText = program.getDescription();
}
String description = mProgramDescriptionTextView.getText().toString();
- boolean programDescriptionNeedFadeAnimation = (isProgramChanged
- || !description.equals(mProgramDescriptionText)) && !mUpdateOnTune;
+ boolean programDescriptionNeedFadeAnimation =
+ (isProgramChanged || !description.equals(mProgramDescriptionText))
+ && !mUpdateOnTune;
updateBannerHeight(programDescriptionNeedFadeAnimation);
} else {
mProgramInfoUpdatePendingByResizing = true;
@@ -561,19 +572,21 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (program == null) {
return;
}
- updateProgramTextView(program == mLockedChannelProgram, program.getTitle(),
+ updateProgramTextView(
+ program.equals(mLockedChannelProgram),
+ program.getTitle(),
program.getEpisodeDisplayTitle(getContext()));
}
- private void updateProgramTextView(boolean dimText, String title,
- String episodeDisplayTitle) {
+ private void updateProgramTextView(boolean dimText, String title, String episodeDisplayTitle) {
mProgramTextView.setVisibility(View.VISIBLE);
if (dimText) {
mProgramTextView.setTextColor(mChannelBannerDimTextColor);
} else {
mProgramTextView.setTextColor(mChannelBannerTextColor);
}
- updateTextView(mProgramTextView,
+ updateTextView(
+ mProgramTextView,
R.dimen.channel_banner_program_large_text_size,
R.dimen.channel_banner_program_large_margin_top);
if (TextUtils.isEmpty(episodeDisplayTitle)) {
@@ -582,18 +595,24 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
String fullTitle = title + " " + episodeDisplayTitle;
SpannableString text = new SpannableString(fullTitle);
- text.setSpan(new TextAppearanceSpan(getContext(),
- R.style.text_appearance_channel_banner_episode_title),
- fullTitle.length() - episodeDisplayTitle.length(), fullTitle.length(),
+ text.setSpan(
+ new TextAppearanceSpan(
+ getContext(), R.style.text_appearance_channel_banner_episode_title),
+ fullTitle.length() - episodeDisplayTitle.length(),
+ fullTitle.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mProgramTextView.setText(text);
}
- int width = mProgramDescriptionTextViewWidth + (mCurrentChannelLogoExists ?
- 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
+ int width =
+ mProgramDescriptionTextViewWidth
+ + (mCurrentChannelLogoExists
+ ? 0
+ : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams();
lp.width = width;
mProgramTextView.setLayoutParams(lp);
- mProgramTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ mProgramTextView.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
boolean oneline = (mProgramTextView.getLineCount() == 1);
@@ -602,13 +621,16 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mProgramTextView,
R.dimen.channel_banner_program_medium_text_size,
R.dimen.channel_banner_program_medium_margin_top);
- mProgramTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ mProgramTextView.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
oneline = (mProgramTextView.getLineCount() == 1);
}
- updateTopMargin(mAnchorView, oneline
- ? R.dimen.channel_banner_anchor_one_line_y
- : R.dimen.channel_banner_anchor_two_line_y);
+ updateTopMargin(
+ mAnchorView,
+ oneline
+ ? R.dimen.channel_banner_anchor_one_line_y
+ : R.dimen.channel_banner_anchor_two_line_y);
}
private void updateProgramRatings(Program program) {
@@ -650,8 +672,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (mLockType != LOCK_CHANNEL_INFO && durationMs > 0 && startTimeMs > 0) {
mProgramTimeTextView.setVisibility(View.VISIBLE);
mRemainingTimeView.setVisibility(View.VISIBLE);
- mProgramTimeTextView.setText(Utils.getDurationString(
- getContext(), startTimeMs, endTimeMs, true));
+ mProgramTimeTextView.setText(
+ Utils.getDurationString(getContext(), startTimeMs, endTimeMs, true));
} else {
mProgramTimeTextView.setVisibility(View.GONE);
mRemainingTimeView.setVisibility(View.GONE);
@@ -673,8 +695,10 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
updateProgressBarAndRecIcon(program, null);
return;
}
- ScheduledRecording currentRecording = (mCurrentChannel == null) ? null
- : mDvrManager.getCurrentRecording(mCurrentChannel.getId());
+ ScheduledRecording currentRecording =
+ (mCurrentChannel == null)
+ ? null
+ : mDvrManager.getCurrentRecording(mCurrentChannel.getId());
if (DEBUG) {
Log.d(TAG, currentRecording == null ? "No Recording" : "Recording:" + currentRecording);
}
@@ -685,22 +709,23 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
}
- private void updateProgressBarAndRecIcon(Program program,
- @Nullable ScheduledRecording recording) {
+ private void updateProgressBarAndRecIcon(
+ Program program, @Nullable ScheduledRecording recording) {
long programStartTime = program.getStartTimeUtcMillis();
long programEndTime = program.getEndTimeUtcMillis();
long currentPosition = mMainActivity.getCurrentPlayingPosition();
updateRecordingIndicator(recording);
if (recording != null) {
// Recording now. Use recording-style progress bar.
- mRemainingTimeView.setProgress(getProgressPercent(recording.getStartTimeMs(),
- programStartTime, programEndTime));
- mRemainingTimeView.setSecondaryProgress(getProgressPercent(currentPosition,
- programStartTime, programEndTime));
+ mRemainingTimeView.setProgress(
+ getProgressPercent(
+ recording.getStartTimeMs(), programStartTime, programEndTime));
+ mRemainingTimeView.setSecondaryProgress(
+ getProgressPercent(currentPosition, programStartTime, programEndTime));
} else {
// No recording is going now. Recover progress bar.
- mRemainingTimeView.setProgress(getProgressPercent(currentPosition,
- programStartTime, programEndTime));
+ mRemainingTimeView.setProgress(
+ getProgressPercent(currentPosition, programStartTime, programEndTime));
mRemainingTimeView.setSecondaryProgress(0);
}
}
@@ -708,9 +733,15 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private void updateRecordingIndicator(@Nullable ScheduledRecording recording) {
if (recording != null) {
if (mRemainingTimeView.getVisibility() == View.GONE) {
- mRecordingIndicatorView.setText(mMainActivity.getResources().getString(
- R.string.dvr_recording_till_format, DateUtils.formatDateTime(mMainActivity,
- recording.getEndTimeMs(), DateUtils.FORMAT_SHOW_TIME)));
+ mRecordingIndicatorView.setText(
+ mMainActivity
+ .getResources()
+ .getString(
+ R.string.dvr_recording_till_format,
+ DateUtils.formatDateTime(
+ mMainActivity,
+ recording.getEndTimeMs(),
+ DateUtils.FORMAT_SHOW_TIME)));
mRecordingIndicatorView.setCompoundDrawablePadding(mRecordingIconPadding);
} else {
mRecordingIndicatorView.setText("");
@@ -725,10 +756,10 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private boolean isCurrentProgram(ScheduledRecording recording, Program program) {
long currentPosition = mMainActivity.getCurrentPlayingPosition();
return (recording.getType() == ScheduledRecording.TYPE_PROGRAM
- && recording.getProgramId() == program.getId())
+ && recording.getProgramId() == program.getId())
|| (recording.getType() == ScheduledRecording.TYPE_TIMED
- && currentPosition >= recording.getStartTimeMs()
- && currentPosition <= recording.getEndTimeMs());
+ && currentPosition >= recording.getStartTimeMs()
+ && currentPosition <= recording.getEndTimeMs());
}
private void setLastUpdatedProgram(Program program) {
@@ -764,18 +795,20 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private Animator createResizeAnimator(int targetHeight, boolean addFadeAnimation) {
final ValueAnimator heightAnimator = ValueAnimator.ofInt(mCurrentHeight, targetHeight);
- heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int value = (Integer) animation.getAnimatedValue();
- LayoutParams layoutParams = (LayoutParams) ChannelBannerView.this.getLayoutParams();
- if (value != layoutParams.height) {
- layoutParams.height = value;
- ChannelBannerView.this.setLayoutParams(layoutParams);
- }
- mCurrentHeight = value;
- }
- });
+ heightAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int value = (Integer) animation.getAnimatedValue();
+ LayoutParams layoutParams =
+ (LayoutParams) ChannelBannerView.this.getLayoutParams();
+ if (value != layoutParams.height) {
+ layoutParams.height = value;
+ ChannelBannerView.this.setLayoutParams(layoutParams);
+ }
+ mCurrentHeight = value;
+ }
+ });
heightAnimator.setDuration(mResizeAnimDuration);
heightAnimator.setInterpolator(mResizeInterpolator);
@@ -792,4 +825,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
animator.addListener(mResizeAnimatorListener);
return animator;
}
-} \ No newline at end of file
+
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ mAutoHideScheduler.onAccessibilityStateChanged(enabled);
+ }
+}
diff --git a/src/com/android/tv/ui/DialogUtils.java b/src/com/android/tv/ui/DialogUtils.java
index acbaf8c8..341db2e1 100644
--- a/src/com/android/tv/ui/DialogUtils.java
+++ b/src/com/android/tv/ui/DialogUtils.java
@@ -20,7 +20,6 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
-
import com.android.tv.common.SoftPreconditions;
public final class DialogUtils {
@@ -31,31 +30,28 @@ public final class DialogUtils {
* @param itemResIds String resource id for each item
* @param runnables Runnable for each item
*/
- public static void showListDialog(Context context, int[] itemResIds,
- final Runnable[] runnables) {
+ public static void showListDialog(
+ Context context, int[] itemResIds, final Runnable[] runnables) {
int size = itemResIds.length;
SoftPreconditions.checkState(size == runnables.length);
- DialogInterface.OnClickListener onClickListener
- = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, int which) {
- Runnable runnable = runnables[which];
- if (runnable != null) {
- runnable.run();
- }
- dialog.dismiss();
- }
- };
+ DialogInterface.OnClickListener onClickListener =
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, int which) {
+ Runnable runnable = runnables[which];
+ if (runnable != null) {
+ runnable.run();
+ }
+ dialog.dismiss();
+ }
+ };
CharSequence[] items = new CharSequence[itemResIds.length];
Resources res = context.getResources();
for (int i = 0; i < size; ++i) {
items[i] = res.getString(itemResIds[i]);
}
- new AlertDialog.Builder(context)
- .setItems(items, onClickListener)
- .create()
- .show();
+ new AlertDialog.Builder(context).setItems(items, onClickListener).create().show();
}
- private DialogUtils() { }
+ private DialogUtils() {}
}
diff --git a/src/com/android/tv/ui/FullscreenDialogView.java b/src/com/android/tv/ui/FullscreenDialogView.java
index e2220722..800fa85a 100644
--- a/src/com/android/tv/ui/FullscreenDialogView.java
+++ b/src/com/android/tv/ui/FullscreenDialogView.java
@@ -26,7 +26,6 @@ import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.dialog.FullscreenDialogFragment;
@@ -58,41 +57,39 @@ public class FullscreenDialogView extends FrameLayout
public FullscreenDialogView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mLinearOutSlowIn = AnimationUtils.loadInterpolator(context,
- android.R.interpolator.linear_out_slow_in);
- mFastOutLinearIn = AnimationUtils.loadInterpolator(context,
- android.R.interpolator.fast_out_linear_in);
- getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- startEnterAnimation();
- }
- });
+ mLinearOutSlowIn =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearIn =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in);
+ getViewTreeObserver()
+ .addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ startEnterAnimation();
+ }
+ });
}
protected MainActivity getActivity() {
return mActivity;
}
- /**
- * Gets the host {@link Dialog}.
- */
+ /** Gets the host {@link Dialog}. */
protected Dialog getDialog() {
return mDialog;
}
- /**
- * Dismisses the host {@link Dialog}.
- */
+ /** Dismisses the host {@link Dialog}. */
protected void dismiss() {
- startExitAnimation(new Runnable() {
- @Override
- public void run() {
- mDialog.dismiss();
- }
- });
+ startExitAnimation(
+ new Runnable() {
+ @Override
+ public void run() {
+ mDialog.dismiss();
+ }
+ });
}
@Override
@@ -102,51 +99,48 @@ public class FullscreenDialogView extends FrameLayout
}
@Override
- public void onBackPressed() { }
+ public void onBackPressed() {}
@Override
- public void onDestroy() { }
+ public void onDestroy() {}
- /**
- * Transitions to another view inside the host {@link Dialog}.
- */
+ /** Transitions to another view inside the host {@link Dialog}. */
public void transitionTo(final FullscreenDialogView v) {
mSkipExitAlphaAnimation = true;
v.mSkipEnterAlphaAnimation = true;
v.initialize(mActivity, mDialog);
- startExitAnimation(new Runnable() {
- @Override
- public void run() {
- new Handler().postDelayed(new Runnable() {
+ startExitAnimation(
+ new Runnable() {
@Override
public void run() {
- v.initialize(getActivity(), getDialog());
- getDialog().setContentView(v);
+ new Handler()
+ .postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ v.initialize(getActivity(), getDialog());
+ getDialog().setContentView(v);
+ }
+ },
+ TRANSITION_INTERVAL_MS);
}
- }, TRANSITION_INTERVAL_MS);
- }
- });
+ });
}
- /**
- * Called when an enter animation starts. Sub-view specific animation can be implemented.
- */
- protected void onStartEnterAnimation(TimeInterpolator interpolator, long duration) {
- }
+ /** Called when an enter animation starts. Sub-view specific animation can be implemented. */
+ protected void onStartEnterAnimation(TimeInterpolator interpolator, long duration) {}
- /**
- * Called when an exit animation starts. Sub-view specific animation can be implemented.
- */
- protected void onStartExitAnimation(TimeInterpolator interpolator, long duration,
- Runnable onAnimationEnded) {
- }
+ /** Called when an exit animation starts. Sub-view specific animation can be implemented. */
+ protected void onStartExitAnimation(
+ TimeInterpolator interpolator, long duration, Runnable onAnimationEnded) {}
private void startEnterAnimation() {
if (DEBUG) Log.d(TAG, "start an enter animation");
View backgroundView = findViewById(R.id.background);
if (!mSkipEnterAlphaAnimation) {
backgroundView.setAlpha(0);
- backgroundView.animate()
+ backgroundView
+ .animate()
.alpha(1.0f)
.setInterpolator(mLinearOutSlowIn)
.setDuration(FADE_IN_DURATION_MS)
@@ -160,7 +154,8 @@ public class FullscreenDialogView extends FrameLayout
if (DEBUG) Log.d(TAG, "start an exit animation");
View backgroundView = findViewById(R.id.background);
if (!mSkipExitAlphaAnimation) {
- backgroundView.animate()
+ backgroundView
+ .animate()
.alpha(0.0f)
.setInterpolator(mFastOutLinearIn)
.setDuration(FADE_OUT_DURATION_MS)
diff --git a/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java b/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java
index 39ec1279..9685b04b 100644
--- a/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java
+++ b/src/com/android/tv/ui/GuidedActionsStylistWithDivider.java
@@ -20,17 +20,13 @@ import android.content.Context;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.GuidedActionsStylist;
-
import com.android.tv.R;
-/**
- * Extended stylist class used for {@link GuidedStepFragment} with divider support.
- */
+/** Extended stylist class used for {@link GuidedStepFragment} with divider support. */
public class GuidedActionsStylistWithDivider extends GuidedActionsStylist {
- /**
- * ID used mark a divider.
- */
+ /** ID used mark a divider. */
public static final int ACTION_DIVIDER = -100;
+
private static final int VIEW_TYPE_DIVIDER = 1;
@Override
@@ -50,8 +46,8 @@ public class GuidedActionsStylistWithDivider extends GuidedActionsStylist {
}
/**
- * Creates a divider for {@link GuidedStepFragment}, targeted fragments must use
- * {@link GuidedActionsStylistWithDivider} as its actions' stylist for divider to work.
+ * Creates a divider for {@link GuidedStepFragment}, targeted fragments must use {@link
+ * GuidedActionsStylistWithDivider} as its actions' stylist for divider to work.
*/
public static GuidedAction createDividerAction(Context context) {
return new GuidedAction.Builder(context)
diff --git a/src/com/android/tv/ui/InputBannerView.java b/src/com/android/tv/ui/InputBannerView.java
index 4e254c62..5ac715bf 100644
--- a/src/com/android/tv/ui/InputBannerView.java
+++ b/src/com/android/tv/ui/InputBannerView.java
@@ -23,25 +23,27 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
public class InputBannerView extends LinearLayout implements TvTransitionManager.TransitionLayout {
private final long mShowDurationMillis;
- private final Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- ((MainActivity) getContext()).getOverlayManager().hideOverlays(
- TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
- }
- };
+ private final Runnable mHideRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ ((MainActivity) getContext())
+ .getOverlayManager()
+ .hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ }
+ };
private TextView mInputLabelTextView;
private TextView mSecondaryInputLabelTextView;
@@ -56,8 +58,8 @@ public class InputBannerView extends LinearLayout implements TvTransitionManager
public InputBannerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mShowDurationMillis = context.getResources().getInteger(
- R.integer.select_input_show_duration);
+ mShowDurationMillis =
+ context.getResources().getInteger(R.integer.select_input_show_duration);
}
@Override
@@ -73,8 +75,8 @@ public class InputBannerView extends LinearLayout implements TvTransitionManager
if (channel == null || !channel.isPassthrough()) {
return;
}
- TvInputInfo input = mainActivity.getTvInputManagerHelper().getTvInputInfo(
- channel.getInputId());
+ TvInputInfo input =
+ mainActivity.getTvInputManagerHelper().getTvInputInfo(channel.getInputId());
CharSequence customLabel = input.loadCustomLabel(getContext());
CharSequence label = input.loadLabel(getContext());
if (TextUtils.isEmpty(customLabel) || customLabel.equals(label)) {
diff --git a/src/com/android/tv/ui/IntroView.java b/src/com/android/tv/ui/IntroView.java
index 7530f283..be9fb691 100644
--- a/src/com/android/tv/ui/IntroView.java
+++ b/src/com/android/tv/ui/IntroView.java
@@ -22,7 +22,6 @@ import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
-
import com.android.tv.R;
import com.android.tv.menu.Menu;
@@ -95,20 +94,21 @@ public class IntroView extends FullscreenDialogView {
}
@Override
- protected void onStartExitAnimation(TimeInterpolator interpolator, long duration,
- final Runnable onAnimationEnded) {
+ protected void onStartExitAnimation(
+ TimeInterpolator interpolator, long duration, final Runnable onAnimationEnded) {
View v = findViewById(R.id.container);
v.animate()
.alpha(0.0f)
.setInterpolator(interpolator)
.setDuration(duration)
.withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- onAnimationEnded.run();
- }
- })
+ .withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ onAnimationEnded.run();
+ }
+ })
.start();
}
}
diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java
index ac5d841d..e2625811 100644
--- a/src/com/android/tv/ui/KeypadChannelSwitchView.java
+++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java
@@ -35,21 +35,19 @@ import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.util.DurationTimer;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.DurationTimer;
import com.android.tv.data.ChannelNumber;
-
+import com.android.tv.data.api.Channel;
import java.util.ArrayList;
import java.util.List;
-public class KeypadChannelSwitchView extends LinearLayout implements
- TvTransitionManager.TransitionLayout {
+public class KeypadChannelSwitchView extends LinearLayout
+ implements TvTransitionManager.TransitionLayout {
private static final String TAG = "KeypadChannelSwitchView";
private static final int MAX_CHANNEL_NUMBER_DIGIT = 4;
@@ -62,7 +60,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements
private final Tracker mTracker;
private final DurationTimer mViewDurationTimer = new DurationTimer();
private boolean mNavigated = false;
- @Nullable //Once mChannels is set to null it should not be used again.
+ @Nullable // Once mChannels is set to null it should not be used again.
private List<Channel> mChannels;
private TextView mChannelNumberView;
private ListView mChannelItemListView;
@@ -72,23 +70,29 @@ public class KeypadChannelSwitchView extends LinearLayout implements
private final LayoutInflater mLayoutInflater;
private Channel mSelectedChannel;
- private final Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mCurrentHeight = 0;
- if (mSelectedChannel != null) {
- mMainActivity.tuneToChannel(mSelectedChannel);
- mTracker.sendChannelNumberItemChosenByTimeout();
- } else {
- mMainActivity.getOverlayManager().hideOverlays(
- TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
- }
- }
- };
+ private final Runnable mHideRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ mCurrentHeight = 0;
+ if (mSelectedChannel != null) {
+ mMainActivity.tuneToChannel(mSelectedChannel);
+ mTracker.sendChannelNumberItemChosenByTimeout();
+ } else {
+ mMainActivity
+ .getOverlayManager()
+ .hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager
+ .FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager
+ .FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
+ | TvOverlayManager
+ .FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ }
+ }
+ };
private final long mShowDurationMillis;
private final long mRippleAnimDurationMillis;
private final int mBaseViewHeight;
@@ -112,65 +116,71 @@ public class KeypadChannelSwitchView extends LinearLayout implements
super(context, attrs, defStyleAttr);
mMainActivity = (MainActivity) context;
- mTracker = TvApplication.getSingletons(context).getTracker();
+ mTracker = TvSingletons.getSingletons(context).getTracker();
Resources resources = getResources();
mLayoutInflater = LayoutInflater.from(context);
mShowDurationMillis = resources.getInteger(R.integer.keypad_channel_switch_show_duration);
- mRippleAnimDurationMillis = resources.getInteger(
- R.integer.keypad_channel_switch_ripple_anim_duration);
- mBaseViewHeight = resources.getDimensionPixelSize(
- R.dimen.keypad_channel_switch_base_height);
+ mRippleAnimDurationMillis =
+ resources.getInteger(R.integer.keypad_channel_switch_ripple_anim_duration);
+ mBaseViewHeight =
+ resources.getDimensionPixelSize(R.dimen.keypad_channel_switch_base_height);
mItemHeight = resources.getDimensionPixelSize(R.dimen.keypad_channel_switch_item_height);
mResizeAnimDuration = resources.getInteger(R.integer.keypad_channel_switch_anim_duration);
- mResizeInterpolator = AnimationUtils.loadInterpolator(context,
- android.R.interpolator.linear_out_slow_in);
+ mResizeInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
}
@Override
- protected void onFinishInflate(){
+ protected void onFinishInflate() {
super.onFinishInflate();
mChannelNumberView = (TextView) findViewById(R.id.channel_number);
mChannelItemListView = (ListView) findViewById(R.id.channel_list);
mChannelItemListView.setAdapter(mAdapter);
- mChannelItemListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (position >= mAdapter.getCount()) {
- // It can happen during closing.
- return;
- }
- mChannelItemListView.setFocusable(false);
- final Channel channel = ((Channel) mAdapter.getItem(position));
- postDelayed(new Runnable() {
+ mChannelItemListView.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
@Override
- public void run() {
- mChannelItemListView.setFocusable(true);
- mMainActivity.tuneToChannel(channel);
- mTracker.sendChannelNumberItemClicked();
+ public void onItemClick(
+ AdapterView<?> parent, View view, int position, long id) {
+ if (position >= mAdapter.getCount()) {
+ // It can happen during closing.
+ return;
+ }
+ mChannelItemListView.setFocusable(false);
+ final Channel channel = ((Channel) mAdapter.getItem(position));
+ postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ mChannelItemListView.setFocusable(true);
+ mMainActivity.tuneToChannel(channel);
+ mTracker.sendChannelNumberItemClicked();
+ }
+ },
+ mRippleAnimDurationMillis);
+ }
+ });
+ mChannelItemListView.setOnItemSelectedListener(
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(
+ AdapterView<?> parent, View view, int position, long id) {
+ if (position >= mAdapter.getCount()) {
+ // It can happen during closing.
+ mSelectedChannel = null;
+ } else {
+ mSelectedChannel = (Channel) mAdapter.getItem(position);
+ }
+ if (position != 0 && !mNavigated) {
+ mNavigated = true;
+ mTracker.sendChannelInputNavigated();
+ }
}
- }, mRippleAnimDurationMillis);
- }
- });
- mChannelItemListView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (position >= mAdapter.getCount()) {
- // It can happen during closing.
- mSelectedChannel = null;
- } else {
- mSelectedChannel = (Channel) mAdapter.getItem(position);
- }
- if (position != 0 && !mNavigated) {
- mNavigated = true;
- mTracker.sendChannelInputNavigated();
- }
- }
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- mSelectedChannel = null;
- }
- });
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ mSelectedChannel = null;
+ }
+ });
}
@Override
@@ -276,8 +286,13 @@ public class KeypadChannelSwitchView extends LinearLayout implements
for (Channel channel : mChannels) {
ChannelNumber chNumber = ChannelNumber.parseChannelNumber(channel.getDisplayNumber());
if (chNumber == null) {
- Log.i(TAG, "Malformed channel number (name=" + channel.getDisplayName()
- + ", number=" + channel.getDisplayNumber() + ")");
+ Log.i(
+ TAG,
+ "Malformed channel number (name="
+ + channel.getDisplayName()
+ + ", number="
+ + channel.getDisplayNumber()
+ + ")");
continue;
}
if (matchChannelNumber(mTypedChannelNumber, chNumber)) {
@@ -286,7 +301,8 @@ public class KeypadChannelSwitchView extends LinearLayout implements
// Even if a user doesn't type '-', we need to match the typed number to not only
// the major number but also the minor number. For example, when a user types '111'
// without delimiter, it should be matched to '111', '1-11' and '11-1'.
- if (channel.getDisplayNumber().replaceAll(CHANNEL_DELIMITERS_REGEX, "")
+ if (channel.getDisplayNumber()
+ .replaceAll(CHANNEL_DELIMITERS_REGEX, "")
.startsWith(mTypedChannelNumber.majorNumber)) {
secondaryChannelCandidates.add(channel);
}
@@ -315,7 +331,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements
// Do not add the resize animation when the banner has not been shown before.
mCurrentHeight = targetHeight;
setViewHeight(this, targetHeight);
- } else if (mCurrentHeight != targetHeight){
+ } else if (mCurrentHeight != targetHeight) {
mResizeAnimator = createResizeAnimator(targetHeight);
mResizeAnimator.start();
}
@@ -323,21 +339,23 @@ public class KeypadChannelSwitchView extends LinearLayout implements
private Animator createResizeAnimator(int targetHeight) {
ValueAnimator animator = ValueAnimator.ofInt(mCurrentHeight, targetHeight);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int value = (Integer) animation.getAnimatedValue();
- setViewHeight(KeypadChannelSwitchView.this, value);
- mCurrentHeight = value;
- }
- });
+ animator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int value = (Integer) animation.getAnimatedValue();
+ setViewHeight(KeypadChannelSwitchView.this, value);
+ mCurrentHeight = value;
+ }
+ });
animator.setDuration(mResizeAnimDuration);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- mResizeAnimator = null;
- }
- });
+ animator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mResizeAnimator = null;
+ }
+ });
animator.setInterpolator(mResizeInterpolator);
return animator;
}
diff --git a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java
index 63ee199d..9b916afe 100644
--- a/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java
+++ b/src/com/android/tv/ui/OnRepeatedKeyInterceptListener.java
@@ -21,18 +21,15 @@ import android.support.v17.leanback.widget.VerticalGridView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
-
import com.android.tv.common.WeakHandler;
-/**
- * Listener to make focus change faster over time.
- */
+/** Listener to make focus change faster over time. */
public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInterceptListener {
private static final String TAG = "OnRepeatedKeyListener";
private static final boolean DEBUG = false;
- private static final int[] THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS = { 2000, 5000 };
- private static final int[] MAX_SKIPPED_VIEW_COUNT = { 1, 4 };
+ private static final int[] THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS = {2000, 5000};
+ private static final int[] MAX_SKIPPED_VIEW_COUNT = {1, 4};
private static final int MSG_MOVE_FOCUS = 1000;
private final VerticalGridView mView;
@@ -52,21 +49,20 @@ public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInt
@Override
public boolean onInterceptKeyEvent(KeyEvent event) {
mHandler.removeMessages(MSG_MOVE_FOCUS);
- if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_UP &&
- event.getKeyCode() != KeyEvent.KEYCODE_DPAD_DOWN) {
+ if (event.getKeyCode() != KeyEvent.KEYCODE_DPAD_UP
+ && event.getKeyCode() != KeyEvent.KEYCODE_DPAD_DOWN) {
return false;
}
long duration = event.getEventTime() - event.getDownTime();
- if (duration < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[0]
- || event.isCanceled()) {
+ if (duration < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[0] || event.isCanceled()) {
mFocusAccelerated = false;
return false;
}
- mDirection = event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP ? View.FOCUS_UP
- : View.FOCUS_DOWN;
+ mDirection =
+ event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP ? View.FOCUS_UP : View.FOCUS_DOWN;
int skippedViewCount = MAX_SKIPPED_VIEW_COUNT[0];
- for (int i = 1 ;i < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS.length; ++i) {
+ for (int i = 1; i < THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS.length; ++i) {
if (THRESHOLD_FAST_FOCUS_CHANGE_TIME_MS[i] < duration) {
skippedViewCount = MAX_SKIPPED_VIEW_COUNT[i];
} else {
@@ -83,8 +79,8 @@ public class OnRepeatedKeyInterceptListener implements VerticalGridView.OnKeyInt
mFocusAccelerated = false;
}
for (int i = 0; i < skippedViewCount; ++i) {
- mHandler.sendEmptyMessageDelayed(MSG_MOVE_FOCUS,
- mRepeatedKeyInterval * i / (skippedViewCount + 1));
+ mHandler.sendEmptyMessageDelayed(
+ MSG_MOVE_FOCUS, mRepeatedKeyInterval * i / (skippedViewCount + 1));
}
if (DEBUG) Log.d(TAG, "onInterceptKeyEvent: focused view " + mView.findFocus());
return false;
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
index dc92111c..f4949f08 100644
--- a/src/com/android/tv/ui/SelectInputView.java
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -32,23 +32,20 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.util.DurationTimer;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.DurationTimer;
+import com.android.tv.data.api.Channel;
import com.android.tv.util.TvInputManagerHelper;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class SelectInputView extends VerticalGridView implements
- TvTransitionManager.TransitionLayout {
+public class SelectInputView extends VerticalGridView
+ implements TvTransitionManager.TransitionLayout {
private static final String TAG = "SelectInputView";
private static final boolean DEBUG = false;
public static final String SCREEN_NAME = "Input selection";
@@ -59,66 +56,68 @@ public class SelectInputView extends VerticalGridView implements
private final TvInputManagerHelper.HardwareInputComparator mComparator;
private final Tracker mTracker;
private final DurationTimer mViewDurationTimer = new DurationTimer();
- private final TvInputCallback mTvInputCallback = new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- buildInputListAndNotify();
- updateSelectedPositionIfNeeded();
- }
+ private final TvInputCallback mTvInputCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
- @Override
- public void onInputRemoved(String inputId) {
- buildInputListAndNotify();
- updateSelectedPositionIfNeeded();
- }
+ @Override
+ public void onInputRemoved(String inputId) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
- @Override
- public void onInputUpdated(String inputId) {
- buildInputListAndNotify();
- updateSelectedPositionIfNeeded();
- }
+ @Override
+ public void onInputUpdated(String inputId) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
- @Override
- public void onInputStateChanged(String inputId, int state) {
- buildInputListAndNotify();
- updateSelectedPositionIfNeeded();
- }
+ @Override
+ public void onInputStateChanged(String inputId, int state) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
- private void updateSelectedPositionIfNeeded() {
- if (!isFocusable() || mSelectedInput == null) {
- return;
- }
- if (!isInputEnabled(mSelectedInput)) {
- setSelectedPosition(TUNER_INPUT_POSITION);
- return;
- }
- if (getInputPosition(mSelectedInput.getId()) != getSelectedPosition()) {
- setSelectedPosition(getInputPosition(mSelectedInput.getId()));
- }
- }
- };
+ private void updateSelectedPositionIfNeeded() {
+ if (!isFocusable() || mSelectedInput == null) {
+ return;
+ }
+ if (!isInputEnabled(mSelectedInput)) {
+ setSelectedPosition(TUNER_INPUT_POSITION);
+ return;
+ }
+ if (getInputPosition(mSelectedInput.getId()) != getSelectedPosition()) {
+ setSelectedPosition(getInputPosition(mSelectedInput.getId()));
+ }
+ }
+ };
private Channel mCurrentChannel;
private OnInputSelectedCallback mCallback;
- private final Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- if (mSelectedInput == null) {
- return;
- }
- // TODO: pass english label to tracker http://b/22355024
- final String label = mSelectedInput.loadLabel(getContext()).toString();
- mTracker.sendInputSelected(label);
- if (mCallback != null) {
- if (mSelectedInput.isPassthroughInput()) {
- mCallback.onPassthroughInputSelected(mSelectedInput);
- } else {
- mCallback.onTunerInputSelected();
+ private final Runnable mHideRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectedInput == null) {
+ return;
+ }
+ // TODO: pass english label to tracker http://b/22355024
+ final String label = mSelectedInput.loadLabel(getContext()).toString();
+ mTracker.sendInputSelected(label);
+ if (mCallback != null) {
+ if (mSelectedInput.isPassthroughInput()) {
+ mCallback.onPassthroughInputSelected(mSelectedInput);
+ } else {
+ mCallback.onTunerInputSelected();
+ }
+ }
}
- }
- }
- };
+ };
private final int mInputItemHeight;
private final long mShowDurationMillis;
@@ -144,23 +143,23 @@ public class SelectInputView extends VerticalGridView implements
super(context, attrs, defStyleAttr);
setAdapter(new InputListAdapter());
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- mTracker = appSingletons.getTracker();
- mTvInputManagerHelper = appSingletons.getTvInputManagerHelper();
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ mTracker = tvSingletons.getTracker();
+ mTvInputManagerHelper = tvSingletons.getTvInputManagerHelper();
mComparator =
new TvInputManagerHelper.HardwareInputComparator(context, mTvInputManagerHelper);
Resources resources = context.getResources();
mInputItemHeight = resources.getDimensionPixelSize(R.dimen.input_banner_item_height);
mShowDurationMillis = resources.getInteger(R.integer.select_input_show_duration);
- mRippleAnimDurationMillis = resources.getInteger(
- R.integer.select_input_ripple_anim_duration);
+ mRippleAnimDurationMillis =
+ resources.getInteger(R.integer.select_input_ripple_anim_duration);
mTextColorPrimary = resources.getColor(R.color.select_input_text_color_primary, null);
mTextColorSecondary = resources.getColor(R.color.select_input_text_color_secondary, null);
mTextColorDisabled = resources.getColor(R.color.select_input_text_color_disabled, null);
- mItemViewForMeasure = LayoutInflater.from(context).inflate(
- R.layout.select_input_item, this, false);
+ mItemViewForMeasure =
+ LayoutInflater.from(context).inflate(R.layout.select_input_item, this, false);
buildInputListAndNotify();
}
@@ -199,8 +198,10 @@ public class SelectInputView extends VerticalGridView implements
mResetTransitionAlpha = fromEmptyScene;
buildInputListAndNotify();
mTvInputManagerHelper.addCallback(mTvInputCallback);
- String currentInputId = mCurrentChannel != null && mCurrentChannel.isPassthrough() ?
- mCurrentChannel.getInputId() : null;
+ String currentInputId =
+ mCurrentChannel != null && mCurrentChannel.isPassthrough()
+ ? mCurrentChannel.getInputId()
+ : null;
if (currentInputId != null
&& !isInputEnabled(mTvInputManagerHelper.getTvInputInfo(currentInputId))) {
// If current input is disabled, the tuner input will be focused.
@@ -233,7 +234,8 @@ public class SelectInputView extends VerticalGridView implements
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = mInputItemHeight * mInputList.size();
- super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxItemWidth, MeasureSpec.EXACTLY),
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(mMaxItemWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
@@ -299,16 +301,14 @@ public class SelectInputView extends VerticalGridView implements
!= TvInputManager.INPUT_STATE_DISCONNECTED;
}
- /**
- * Sets a callback which receives the notifications of input selection.
- */
+ /** Sets a callback which receives the notifications of input selection. */
public void setOnInputSelectedCallback(OnInputSelectedCallback callback) {
mCallback = callback;
}
/**
- * Sets the current channel. The initial selection will be the input which contains the
- * {@code channel}.
+ * Sets the current channel. The initial selection will be the input which contains the {@code
+ * channel}.
*/
public void setCurrentChannel(Channel channel) {
mCurrentChannel = channel;
@@ -317,8 +317,9 @@ public class SelectInputView extends VerticalGridView implements
class InputListAdapter extends RecyclerView.Adapter<InputListAdapter.ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = LayoutInflater.from(parent.getContext()).inflate(
- R.layout.select_input_item, parent, false);
+ View v =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.select_input_item, parent, false);
return new ViewHolder(v);
}
@@ -343,25 +344,29 @@ public class SelectInputView extends VerticalGridView implements
holder.secondaryInputLabelView.setVisibility(View.GONE);
}
- holder.itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mSelectedInput = mInputList.get(position);
- // The user made a selection. Hide this view after the ripple animation. But
- // first, disable focus to avoid any further focus change during the animation.
- setFocusable(false);
- removeCallbacks(mHideRunnable);
- postDelayed(mHideRunnable, mRippleAnimDurationMillis);
- }
- });
- holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- if (hasFocus) {
- mSelectedInput = mInputList.get(position);
- }
- }
- });
+ holder.itemView.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSelectedInput = mInputList.get(position);
+ // The user made a selection. Hide this view after the ripple animation.
+ // But
+ // first, disable focus to avoid any further focus change during the
+ // animation.
+ setFocusable(false);
+ removeCallbacks(mHideRunnable);
+ postDelayed(mHideRunnable, mRippleAnimDurationMillis);
+ }
+ });
+ holder.itemView.setOnFocusChangeListener(
+ new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (hasFocus) {
+ mSelectedInput = mInputList.get(position);
+ }
+ }
+ });
if (mResetTransitionAlpha) {
ViewUtils.setTransitionAlpha(holder.itemView, 1f);
@@ -385,18 +390,12 @@ public class SelectInputView extends VerticalGridView implements
}
}
- /**
- * A callback interface for the input selection.
- */
+ /** A callback interface for the input selection. */
public interface OnInputSelectedCallback {
- /**
- * Called when the tuner input is selected.
- */
+ /** Called when the tuner input is selected. */
void onTunerInputSelected();
- /**
- * Called when the passthrough input is selected.
- */
+ /** Called when the passthrough input is selected. */
void onPassthroughInputSelected(@NonNull TvInputInfo input);
}
}
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index 48386698..bb98d974 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -57,37 +57,37 @@ import android.view.SurfaceView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.Features;
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.TvViewSession;
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.data.Program;
-import com.android.tv.data.ProgramDataManager;
-import com.android.tv.parental.ParentalControlSettings;
-import com.android.tv.util.DurationTimer;
-import com.android.tv.util.Debug;
+import com.android.tv.TvFeatures;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.BuildConfig;
+import com.android.tv.common.CommonConstants;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.Debug;
+import com.android.tv.common.util.DurationTimer;
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.WatchedHistoryManager;
+import com.android.tv.data.api.Channel;
import com.android.tv.parental.ContentRatingsManager;
+import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.recommendation.NotificationService;
-import com.android.tv.util.ImageLoader;
import com.android.tv.util.NetworkUtils;
-import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
+import com.android.tv.util.images.ImageLoader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
-public class TunableTvView extends FrameLayout implements StreamInfo {
+/** Includes the real {@link AppLayerTvView} handling tuning, block and other display events. */
+public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvViewPlayingApi {
private static final boolean DEBUG = false;
private static final String TAG = "TunableTvView";
@@ -96,20 +96,29 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
public static final int VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED = -3;
public static final int VIDEO_UNAVAILABLE_REASON_NONE = -100;
+ private OnTalkBackDpadKeyListener mOnTalkBackDpadKeyListener;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({BLOCK_SCREEN_TYPE_NO_UI, BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW, BLOCK_SCREEN_TYPE_NORMAL})
public @interface BlockScreenType {}
+
public static final int BLOCK_SCREEN_TYPE_NO_UI = 0;
public static final int BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW = 1;
public static final int BLOCK_SCREEN_TYPE_NORMAL = 2;
private static final String PERMISSION_RECEIVE_INPUT_EVENT =
- "com.android.tv.permission.RECEIVE_INPUT_EVENT";
+ CommonConstants.BASE_PACKAGE + ".permission.RECEIVE_INPUT_EVENT";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ TIME_SHIFT_STATE_NONE, TIME_SHIFT_STATE_PLAY, TIME_SHIFT_STATE_PAUSE,
- TIME_SHIFT_STATE_REWIND, TIME_SHIFT_STATE_FAST_FORWARD })
+ @IntDef({
+ TIME_SHIFT_STATE_NONE,
+ TIME_SHIFT_STATE_PLAY,
+ TIME_SHIFT_STATE_PAUSE,
+ TIME_SHIFT_STATE_REWIND,
+ TIME_SHIFT_STATE_FAST_FORWARD
+ })
private @interface TimeShiftState {}
+
private static final int TIME_SHIFT_STATE_NONE = 0;
private static final int TIME_SHIFT_STATE_PLAY = 1;
private static final int TIME_SHIFT_STATE_PAUSE = 2;
@@ -128,8 +137,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private ContentRatingsManager mContentRatingsManager;
private ParentalControlSettings mParentalControlSettings;
private ProgramDataManager mProgramDataManager;
- @Nullable
- private WatchedHistoryManager mWatchedHistoryManager;
+ @Nullable private WatchedHistoryManager mWatchedHistoryManager;
private boolean mStarted;
private String mTagetInputId;
private TvInputInfo mInputInfo;
@@ -182,230 +190,258 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private final ConnectivityManager mConnectivityManager;
private final InputSessionManager mInputSessionManager;
- private final TvInputCallback mCallback = new TvInputCallback() {
- @Override
- public void onConnectionFailed(String inputId) {
- Log.w(TAG, "Failed to bind an input");
- mTracker.sendInputConnectionFailure(inputId);
- Channel channel = mCurrentChannel;
- mCurrentChannel = null;
- mInputInfo = null;
- mCanReceiveInputEvent = false;
- if (mOnTuneListener != null) {
- // If tune is called inside onTuneFailed, mOnTuneListener will be set to
- // a new instance. In order to avoid to clear the new mOnTuneListener,
- // we copy mOnTuneListener to l and clear mOnTuneListener before
- // calling onTuneFailed.
- OnTuneListener listener = mOnTuneListener;
- mOnTuneListener = null;
- listener.onTuneFailed(channel);
- }
- }
-
- @Override
- public void onDisconnected(String inputId) {
- Log.w(TAG, "Session is released by crash");
- mTracker.sendInputDisconnected(inputId);
- Channel channel = mCurrentChannel;
- mCurrentChannel = null;
- mInputInfo = null;
- mCanReceiveInputEvent = false;
- if (mOnTuneListener != null) {
- OnTuneListener listener = mOnTuneListener;
- mOnTuneListener = null;
- listener.onUnexpectedStop(channel);
- }
- }
-
- @Override
- public void onChannelRetuned(String inputId, Uri channelUri) {
- if (DEBUG) {
- Log.d(TAG, "onChannelRetuned(inputId=" + inputId + ", channelUri="
- + channelUri + ")");
- }
- if (mOnTuneListener != null) {
- mOnTuneListener.onChannelRetuned(channelUri);
- }
- }
+ private final TvInputCallback mCallback =
+ new TvInputCallback() {
+ @Override
+ public void onConnectionFailed(String inputId) {
+ Log.w(TAG, "Failed to bind an input");
+ mTracker.sendInputConnectionFailure(inputId);
+ Channel channel = mCurrentChannel;
+ mCurrentChannel = null;
+ mInputInfo = null;
+ mCanReceiveInputEvent = false;
+ if (mOnTuneListener != null) {
+ // If tune is called inside onTuneFailed, mOnTuneListener will be set to
+ // a new instance. In order to avoid to clear the new mOnTuneListener,
+ // we copy mOnTuneListener to l and clear mOnTuneListener before
+ // calling onTuneFailed.
+ OnTuneListener listener = mOnTuneListener;
+ mOnTuneListener = null;
+ listener.onTuneFailed(channel);
+ }
+ }
- @Override
- public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
- mHasClosedCaption = false;
- for (TvTrackInfo track : tracks) {
- if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
- mHasClosedCaption = true;
- break;
+ @Override
+ public void onDisconnected(String inputId) {
+ Log.w(TAG, "Session is released by crash");
+ mTracker.sendInputDisconnected(inputId);
+ Channel channel = mCurrentChannel;
+ mCurrentChannel = null;
+ mInputInfo = null;
+ mCanReceiveInputEvent = false;
+ if (mOnTuneListener != null) {
+ OnTuneListener listener = mOnTuneListener;
+ mOnTuneListener = null;
+ listener.onUnexpectedStop(channel);
+ }
}
- }
- if (mOnTuneListener != null) {
- mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
- }
- }
- @Override
- public void onTrackSelected(String inputId, int type, String trackId) {
- if (trackId == null) {
- // A track is unselected.
- if (type == TvTrackInfo.TYPE_VIDEO) {
- mVideoWidth = 0;
- mVideoHeight = 0;
- mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
- mVideoFrameRate = 0f;
- mVideoDisplayAspectRatio = 0f;
- } else if (type == TvTrackInfo.TYPE_AUDIO) {
- mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
+ @Override
+ public void onChannelRetuned(String inputId, Uri channelUri) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onChannelRetuned(inputId="
+ + inputId
+ + ", channelUri="
+ + channelUri
+ + ")");
+ }
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onChannelRetuned(channelUri);
+ }
}
- } else {
- List<TvTrackInfo> tracks = getTracks(type);
- boolean trackFound = false;
- if (tracks != null) {
+
+ @Override
+ public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
+ mHasClosedCaption = false;
for (TvTrackInfo track : tracks) {
- if (track.getId().equals(trackId)) {
- if (type == TvTrackInfo.TYPE_VIDEO) {
- mVideoWidth = track.getVideoWidth();
- mVideoHeight = track.getVideoHeight();
- mVideoFormat = Utils.getVideoDefinitionLevelFromSize(
- mVideoWidth, mVideoHeight);
- mVideoFrameRate = track.getVideoFrameRate();
- if (mVideoWidth <= 0 || mVideoHeight <= 0) {
- mVideoDisplayAspectRatio = 0.0f;
- } else {
- float VideoPixelAspectRatio =
- track.getVideoPixelAspectRatio();
- mVideoDisplayAspectRatio = VideoPixelAspectRatio
- * mVideoWidth / mVideoHeight;
- }
- } else if (type == TvTrackInfo.TYPE_AUDIO) {
- mAudioChannelCount = track.getAudioChannelCount();
- }
- trackFound = true;
+ if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
+ mHasClosedCaption = true;
break;
}
}
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
+ }
}
- if (!trackFound) {
- Log.w(TAG, "Invalid track ID: " + trackId);
+
+ @Override
+ public void onTrackSelected(String inputId, int type, String trackId) {
+ if (trackId == null) {
+ // A track is unselected.
+ if (type == TvTrackInfo.TYPE_VIDEO) {
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
+ mVideoFrameRate = 0f;
+ mVideoDisplayAspectRatio = 0f;
+ } else if (type == TvTrackInfo.TYPE_AUDIO) {
+ mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
+ }
+ } else {
+ List<TvTrackInfo> tracks = getTracks(type);
+ boolean trackFound = false;
+ if (tracks != null) {
+ for (TvTrackInfo track : tracks) {
+ if (track.getId().equals(trackId)) {
+ if (type == TvTrackInfo.TYPE_VIDEO) {
+ mVideoWidth = track.getVideoWidth();
+ mVideoHeight = track.getVideoHeight();
+ mVideoFormat =
+ Utils.getVideoDefinitionLevelFromSize(
+ mVideoWidth, mVideoHeight);
+ mVideoFrameRate = track.getVideoFrameRate();
+ if (mVideoWidth <= 0 || mVideoHeight <= 0) {
+ mVideoDisplayAspectRatio = 0.0f;
+ } else {
+ float VideoPixelAspectRatio =
+ track.getVideoPixelAspectRatio();
+ mVideoDisplayAspectRatio =
+ VideoPixelAspectRatio
+ * mVideoWidth
+ / mVideoHeight;
+ }
+ } else if (type == TvTrackInfo.TYPE_AUDIO) {
+ mAudioChannelCount = track.getAudioChannelCount();
+ }
+ trackFound = true;
+ break;
+ }
+ }
+ }
+ if (!trackFound) {
+ Log.w(TAG, "Invalid track ID: " + trackId);
+ }
+ }
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
+ }
}
- }
- if (mOnTuneListener != null) {
- mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
- }
- }
- @Override
- public void onVideoAvailable(String inputId) {
- if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}");
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start up of Live TV ends," +
- " TunableTvView.onVideoAvailable resets timer");
- long startUpDurationTime = Debug.getTimer(Debug.TAG_START_UP_TIMER).reset();
- Debug.removeTimer(Debug.TAG_START_UP_TIMER);
- if (BuildConfig.ENG && startUpDurationTime > Debug.TIME_START_UP_DURATION_THRESHOLD) {
- showAlertDialogForLongStartUp();
- }
- mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE;
- updateBlockScreenAndMuting();
- if (mOnTuneListener != null) {
- mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
- }
- }
+ @Override
+ public void onVideoAvailable(String inputId) {
+ if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}");
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log(
+ "Start up of Live TV ends,"
+ + " TunableTvView.onVideoAvailable resets timer");
+ long startUpDurationTime = Debug.getTimer(Debug.TAG_START_UP_TIMER).reset();
+ Debug.removeTimer(Debug.TAG_START_UP_TIMER);
+ if (BuildConfig.ENG
+ && startUpDurationTime > Debug.TIME_START_UP_DURATION_THRESHOLD) {
+ showAlertDialogForLongStartUp();
+ }
+ mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE;
+ updateBlockScreenAndMuting();
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
+ }
+ }
- private void showAlertDialogForLongStartUp() {
- new AlertDialog.Builder(getContext()).setTitle(
- getContext().getString(R.string.settings_send_feedback))
- .setMessage("Because the start up time of Live channels is too long," +
- " please send feedback")
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- Intent intent = new Intent(Intent.ACTION_APP_ERROR);
- ApplicationErrorReport report = new ApplicationErrorReport();
- report.packageName = report.processName = getContext()
- .getApplicationContext().getPackageName();
- report.time = System.currentTimeMillis();
- report.type = ApplicationErrorReport.TYPE_CRASH;
-
- // Add the crash info to add title of feedback automatically.
- ApplicationErrorReport.CrashInfo crash = new
- ApplicationErrorReport.CrashInfo();
- crash.exceptionClassName =
- "Live TV start up takes long time";
- crash.exceptionMessage =
- "The start up time of Live TV is too long";
- report.crashInfo = crash;
-
- intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
- getContext().startActivity(intent);
- }
- })
- .setNegativeButton(android.R.string.no, null)
- .show();
- }
+ private void showAlertDialogForLongStartUp() {
+ new AlertDialog.Builder(getContext())
+ .setTitle(getContext().getString(R.string.settings_send_feedback))
+ .setMessage(
+ "Because the start up time of Live channels is too long,"
+ + " please send feedback")
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(
+ DialogInterface dialogInterface, int i) {
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ ApplicationErrorReport report =
+ new ApplicationErrorReport();
+ report.packageName =
+ report.processName =
+ getContext()
+ .getApplicationContext()
+ .getPackageName();
+ report.time = System.currentTimeMillis();
+ report.type = ApplicationErrorReport.TYPE_CRASH;
+
+ // Add the crash info to add title of feedback
+ // automatically.
+ ApplicationErrorReport.CrashInfo crash =
+ new ApplicationErrorReport.CrashInfo();
+ crash.exceptionClassName =
+ "Live TV start up takes long time";
+ crash.exceptionMessage =
+ "The start up time of Live TV is too long";
+ report.crashInfo = crash;
+
+ intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
+ getContext().startActivity(intent);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
- @Override
- public void onVideoUnavailable(String inputId, int reason) {
- if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING
- && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "TunableTvView.onVideoUnAvailable reason = (" + reason
- + ") and removes timer");
- Debug.removeTimer(Debug.TAG_START_UP_TIMER);
- } else {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "TunableTvView.onVideoUnAvailable reason = (" + reason + ")");
- }
- mVideoUnavailableReason = reason;
- if (closePipIfNeeded()) {
- return;
- }
- updateBlockScreenAndMuting();
- if (mOnTuneListener != null) {
- mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
- }
- switch (reason) {
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
- mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason);
- default:
- // do nothing
- }
- }
+ @Override
+ public void onVideoUnavailable(String inputId, int reason) {
+ if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING
+ && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log(
+ "TunableTvView.onVideoUnAvailable reason = ("
+ + reason
+ + ") and removes timer");
+ Debug.removeTimer(Debug.TAG_START_UP_TIMER);
+ } else {
+ Debug.getTimer(Debug.TAG_START_UP_TIMER)
+ .log("TunableTvView.onVideoUnAvailable reason = (" + reason + ")");
+ }
+ mVideoUnavailableReason = reason;
+ if (closePipIfNeeded()) {
+ return;
+ }
+ updateBlockScreenAndMuting();
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
+ }
+ switch (reason) {
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
+ mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason);
+ break;
+ default:
+ // do nothing
+ }
+ }
- @Override
- public void onContentAllowed(String inputId) {
- mBlockedContentRating = null;
- updateBlockScreenAndMuting();
- if (mOnTuneListener != null) {
- mOnTuneListener.onContentAllowed();
- }
- }
+ @Override
+ public void onContentAllowed(String inputId) {
+ mBlockedContentRating = null;
+ updateBlockScreenAndMuting();
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onContentAllowed();
+ }
+ }
- @Override
- public void onContentBlocked(String inputId, TvContentRating rating) {
- if (rating != null && rating.equals(mBlockedContentRating)) {
- return;
- }
- mBlockedContentRating = rating;
- if (closePipIfNeeded()) {
- return;
- }
- updateBlockScreenAndMuting();
- if (mOnTuneListener != null) {
- mOnTuneListener.onContentBlocked();
- }
- }
+ @Override
+ public void onContentBlocked(String inputId, TvContentRating rating) {
+ if (rating != null && rating.equals(mBlockedContentRating)) {
+ return;
+ }
+ mBlockedContentRating = rating;
+ if (closePipIfNeeded()) {
+ return;
+ }
+ updateBlockScreenAndMuting();
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onContentBlocked();
+ }
+ }
- @Override
- public void onTimeShiftStatusChanged(String inputId, int status) {
- if (DEBUG) {
- Log.d(TAG, "onTimeShiftStatusChanged: {inputId=" + inputId + ", status=" + status +
- "}");
- }
- boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE;
- setTimeShiftAvailable(available);
- }
- };
+ @Override
+ public void onTimeShiftStatusChanged(String inputId, int status) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onTimeShiftStatusChanged: {inputId="
+ + inputId
+ + ", status="
+ + status
+ + "}");
+ }
+ boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE;
+ setTimeShiftAvailable(available);
+ }
+ };
public TunableTvView(Context context) {
this(context, null);
@@ -423,49 +459,77 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
super(context, attrs, defStyleAttr, defStyleRes);
inflate(getContext(), R.layout.tunable_tv_view, this);
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
if (CommonFeatures.DVR.isEnabled(context)) {
- mInputSessionManager = appSingletons.getInputSessionManager();
+ mInputSessionManager = tvSingletons.getInputSessionManager();
} else {
mInputSessionManager = null;
}
- mInputManager = appSingletons.getTvInputManagerHelper();
- mConnectivityManager = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
+ mInputManager = tvSingletons.getTvInputManagerHelper();
+ mConnectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mCanModifyParentalControls = PermissionUtils.hasModifyParentalControls(context);
- mTracker = appSingletons.getTracker();
+ mTracker = tvSingletons.getTracker();
mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL;
mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen);
- mBlockScreenView.addInfoFadeInAnimationListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- adjustBlockScreenSpacingAndText();
- }
- });
+ mBlockScreenView.addInfoFadeInAnimationListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ adjustBlockScreenSpacingAndText();
+ }
+ });
mBufferingSpinnerView = findViewById(R.id.buffering_spinner);
- mTuningImageColorFilter = getResources()
- .getColor(R.color.tvview_block_image_color_filter, null);
+ mTuningImageColorFilter =
+ getResources().getColor(R.color.tvview_block_image_color_filter, null);
mDimScreenView = findViewById(R.id.dim_screen);
- mDimScreenView.animate().setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mActionAfterFade != null) {
- mActionAfterFade.run();
- }
- }
+ mDimScreenView
+ .animate()
+ .setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mActionAfterFade != null) {
+ mActionAfterFade.run();
+ }
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (mActionAfterFade != null) {
- mActionAfterFade.run();
- }
- }
- });
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mActionAfterFade != null) {
+ mActionAfterFade.run();
+ }
+ }
+ });
+ View placeholder = findViewById(R.id.placeholder);
+ placeholder.requestFocus();
+ findViewById(R.id.channel_up)
+ .setOnFocusChangeListener(
+ (v, hasFocus) -> {
+ if (hasFocus) {
+ placeholder.requestFocus();
+ if (mOnTalkBackDpadKeyListener != null) {
+ mOnTalkBackDpadKeyListener.onTalkBackDpadKey(
+ KeyEvent.KEYCODE_DPAD_UP);
+ }
+ }
+ });
+ findViewById(R.id.channel_down)
+ .setOnFocusChangeListener(
+ (v, hasFocus) -> {
+ if (hasFocus) {
+ placeholder.requestFocus();
+ if (mOnTalkBackDpadKeyListener != null) {
+ mOnTalkBackDpadKeyListener.onTalkBackDpadKey(
+ KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ }
+ });
}
- public void initialize(ProgramDataManager programDataManager,
- TvInputManagerHelper tvInputManagerHelper) {
+ public void initialize(
+ ProgramDataManager programDataManager, TvInputManagerHelper tvInputManagerHelper) {
mTvView = (AppLayerTvView) findViewById(R.id.tv_view);
mProgramDataManager = programDataManager;
mInputManagerHelper = tvInputManagerHelper;
@@ -482,9 +546,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mStarted = true;
}
- /**
- * Warms up the input to reduce the start time.
- */
+ /** Warms up the input to reduce the start time. */
public void warmUpInput(String inputId, Uri channelUri) {
if (!mStarted && inputId != null && channelUri != null) {
if (mTvViewSession != null) {
@@ -506,16 +568,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
long duration = mChannelViewTimer.reset();
mTracker.sendChannelViewStop(mCurrentChannel, duration);
if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) {
- mWatchedHistoryManager.logChannelViewStop(mCurrentChannel,
- System.currentTimeMillis(), duration);
+ mWatchedHistoryManager.logChannelViewStop(
+ mCurrentChannel, System.currentTimeMillis(), duration);
}
}
reset();
}
- /**
- * Releases the resources.
- */
+ /** Releases the resources. */
public void release() {
if (mInputSessionManager != null) {
mInputSessionManager.releaseTvViewSession(mTvViewSession);
@@ -523,18 +583,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
}
- /**
- * Resets TV view.
- */
+ /** Resets TV view. */
public void reset() {
resetInternal();
mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED;
updateBlockScreenAndMuting();
}
- /**
- * Resets TV view to acquire the recording session.
- */
+ /** Resets TV view to acquire the recording session. */
public void resetByRecording() {
resetInternal();
}
@@ -560,20 +616,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mWatchedHistoryManager = watchedHistoryManager;
}
- /**
- * Sets if the TunableTvView is under shrunken.
- */
+ /** Sets if the TunableTvView is under shrunken. */
public void setIsUnderShrunken(boolean isUnderShrunken) {
mIsUnderShrunken = isUnderShrunken;
}
+ @Override
public boolean isPlaying() {
return mStarted;
}
- /**
- * Called when parental control is changed.
- */
+ /** Called when parental control is changed. */
public void onParentalControlChanged(boolean enabled) {
mParentControlEnabled = enabled;
if (!enabled) {
@@ -586,8 +639,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
* Tunes to a channel with the {@code channelId}.
*
* @param params extra data to send it to TIS and store the data in TIMS.
- * @return false, if the TV input is not a proper state to tune to a channel. For example,
- * if the state is disconnected or channelId doesn't exist, it returns false.
+ * @return false, if the TV input is not a proper state to tune to a channel. For example, if
+ * the state is disconnected or channelId doesn't exist, it returns false.
*/
public boolean tuneTo(Channel channel, Bundle params, OnTuneListener listener) {
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TunableTvView.tuneTo");
@@ -603,24 +656,34 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
long duration = mChannelViewTimer.reset();
mTracker.sendChannelViewStop(mCurrentChannel, duration);
if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) {
- mWatchedHistoryManager.logChannelViewStop(mCurrentChannel,
- System.currentTimeMillis(), duration);
+ mWatchedHistoryManager.logChannelViewStop(
+ mCurrentChannel, System.currentTimeMillis(), duration);
}
}
mOnTuneListener = listener;
mCurrentChannel = channel;
- boolean tunedByRecommendation = params != null
- && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) != null;
+ boolean tunedByRecommendation =
+ params != null
+ && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE)
+ != null;
boolean needSurfaceSizeUpdate = false;
if (!inputInfo.equals(mInputInfo)) {
mTagetInputId = inputInfo.getId();
mInputInfo = inputInfo;
- mCanReceiveInputEvent = getContext().getPackageManager().checkPermission(
- PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getServiceInfo().packageName)
+ mCanReceiveInputEvent =
+ getContext()
+ .getPackageManager()
+ .checkPermission(
+ PERMISSION_RECEIVE_INPUT_EVENT,
+ mInputInfo.getServiceInfo().packageName)
== PackageManager.PERMISSION_GRANTED;
if (DEBUG) {
- Log.d(TAG, "Input \'" + mInputInfo.getId() + "\' can receive input event: "
- + mCanReceiveInputEvent);
+ Log.d(
+ TAG,
+ "Input \'"
+ + mInputInfo.getId()
+ + "\' can receive input event: "
+ + mCanReceiveInputEvent);
}
needSurfaceSizeUpdate = true;
}
@@ -671,6 +734,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mCurrentChannel = currentChannel;
}
+ @Override
public void setStreamVolume(float volume) {
if (!mStarted) {
throw new IllegalStateException("TvView isn't started");
@@ -683,13 +747,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Sets fixed size for the internal {@link android.view.Surface} of
- * {@link android.media.tv.TvView}. If either {@code width} or {@code height} is non positive,
- * the {@link android.view.Surface}'s size will be matched to the layout.
+ * Sets fixed size for the internal {@link android.view.Surface} of {@link
+ * android.media.tv.TvView}. If either {@code width} or {@code height} is non positive, the
+ * {@link android.view.Surface}'s size will be matched to the layout.
*
- * Note: Once {@link android.view.SurfaceHolder#setFixedSize} is called,
- * {@link android.view.SurfaceView} and its underlying window can be misaligned, when the size
- * of {@link android.view.SurfaceView} is changed without changing either left position or top
+ * <p>Note: Once {@link android.view.SurfaceHolder#setFixedSize} is called, {@link
+ * android.view.SurfaceView} and its underlying window can be misaligned, when the size of
+ * {@link android.view.SurfaceView} is changed without changing either left position or top
* position. For detail, please refer the codes of android.view.SurfaceView.updateWindow().
*/
public void setFixedSurfaceSize(int width, int height) {
@@ -728,10 +792,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
public interface OnTuneListener {
void onTuneFailed(Channel channel);
+
void onUnexpectedStop(Channel channel);
+
void onStreamInfoChanged(StreamInfo info);
+
void onChannelRetuned(Uri channel);
+
void onContentBlocked();
+
void onContentAllowed();
}
@@ -759,9 +828,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return mVideoFrameRate;
}
- /**
- * Returns displayed aspect ratio (video width / video height * pixel ratio).
- */
+ /** Returns displayed aspect ratio (video width / video height * pixel ratio). */
@Override
public float getVideoDisplayAspectRatio() {
return mVideoDisplayAspectRatio;
@@ -793,9 +860,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return mVideoUnavailableReason;
}
- /**
- * Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}.
- */
+ /** Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}. */
private SurfaceView getSurfaceView() {
return (SurfaceView) mTvView.getChildAt(0);
}
@@ -804,6 +869,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mTvView.setOnUnhandledInputEventListener(listener);
}
+ public void setOnTalkBackDpadKeyListener(OnTalkBackDpadKeyListener listener) {
+ mOnTalkBackDpadKeyListener = listener;
+ }
+
public void setClosedCaptionEnabled(boolean enabled) {
mTvView.setCaptionEnabled(enabled);
}
@@ -821,16 +890,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying
- * {@link TvView}, which is the actual view to play live TV videos.
+ * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying {@link TvView},
+ * which is the actual view to play live TV videos.
*/
public MarginLayoutParams getTvViewLayoutParams() {
return (MarginLayoutParams) mTvView.getLayoutParams();
}
/**
- * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying
- * {@link TvView}, which is the actual view to play live TV videos.
+ * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying {@link TvView},
+ * which is the actual view to play live TV videos.
*/
public void setTvViewLayoutParams(MarginLayoutParams layoutParams) {
mTvView.setLayoutParams(layoutParams);
@@ -851,16 +920,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return isScreenBlocked() || isContentBlocked();
}
- /**
- * Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}.
- */
+ /** Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}. */
public boolean isScreenBlocked() {
return mScreenBlocked;
}
- /**
- * Returns {@code true} if the content is blocked, otherwise {@code false}.
- */
+ /** Returns {@code true} if the content is blocked, otherwise {@code false}. */
public boolean isContentBlocked() {
return mBlockedContentRating != null;
}
@@ -869,18 +934,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mOnScreenBlockedListener = listener;
}
- /**
- * Returns currently blocked content rating. {@code null} if it's not blocked.
- */
+ /** Returns currently blocked content rating. {@code null} if it's not blocked. */
@Override
public TvContentRating getBlockedContentRating() {
return mBlockedContentRating;
}
/**
- * Blocks/unblocks current TV screen and mutes.
- * There would be black screen with lock icon in order to show that
- * screen block is intended and not an error.
+ * Blocks/unblocks current TV screen and mutes. There would be black screen with lock icon in
+ * order to show that screen block is intended and not an error.
*
* @param blockOrUnblock {@code true} to block the screen, or {@code false} to unblock.
*/
@@ -909,8 +971,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
/**
* Set the type of block screen. If {@code type} is set to {@code BLOCK_SCREEN_TYPE_NO_UI}, the
* block screen will not show any description such as a lock icon and a text for the blocked
- * reason, if {@code type} is set to {@code BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW}, the block screen
- * will show the description for shrunken tv view (Small icon and short text), and if
+ * reason, if {@code type} is set to {@code BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW}, the block
+ * screen will show the description for shrunken tv view (Small icon and short text), and if
* {@code type} is set to {@code BLOCK_SCREEN_TYPE_NORMAL}, the block screen will show the
* description for normal tv view (Big icon and long text).
*
@@ -925,14 +987,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private void updateBlockScreen(boolean animation) {
mBlockScreenView.endAnimations();
- int blockReason = (mScreenBlocked || mBlockedContentRating != null)
- && mParentControlEnabled ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED
+ int blockReason =
+ (mScreenBlocked || mBlockedContentRating != null) && mParentControlEnabled
+ ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED
: mVideoUnavailableReason;
if (blockReason != VIDEO_UNAVAILABLE_REASON_NONE) {
mBufferingSpinnerView.setVisibility(
blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
- || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING ?
- VISIBLE : GONE);
+ || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING
+ ? VISIBLE
+ : GONE);
if (!animation) {
adjustBlockScreenSpacingAndText();
}
@@ -959,7 +1023,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) {
showImageForTuningIfNeeded();
} else if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN
- && mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) {
+ && mCurrentChannel != null
+ && !mCurrentChannel.isPhysicalTunerChannel()) {
mInternetCheckTask = new InternetCheckTask();
mInternetCheckTask.execute();
}
@@ -982,8 +1047,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Returns the block screen text corresponding to the current status.
- * Note that returning {@code null} value means that the current text should not be changed.
+ * Returns the block screen text corresponding to the current status. Note that returning {@code
+ * null} value means that the current text should not be changed.
*/
private String getBlockScreenText() {
// TODO: add a test for this method
@@ -1051,7 +1116,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
private boolean closePipIfNeeded() {
- if (Features.PICTURE_IN_PICTURE.isEnabled(getContext())
+ if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(getContext())
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& ((Activity) getContext()).isInPictureInPictureMode()
&& (mScreenBlocked
@@ -1071,8 +1136,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private boolean shouldShowImageForTuning() {
if (mVideoUnavailableReason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING
- || mScreenBlocked || mBlockedContentRating != null || mCurrentChannel == null
- || mIsUnderShrunken || getWidth() == 0 || getWidth() == 0 || !isBundledInput()) {
+ || mScreenBlocked
+ || mBlockedContentRating != null
+ || mCurrentChannel == null
+ || mIsUnderShrunken
+ || getWidth() == 0
+ || getWidth() == 0
+ || !isBundledInput()) {
return false;
}
Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId());
@@ -1091,7 +1161,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId());
if (currentProgram != null) {
- currentProgram.loadPosterArt(getContext(), getWidth(), getHeight(),
+ currentProgram.loadPosterArt(
+ getContext(),
+ getWidth(),
+ getHeight(),
createProgramPosterArtCallback(mCurrentChannel.getId()));
}
}
@@ -1102,16 +1175,19 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
TvInputInfo input = mInputManager.getTvInputInfo(mTagetInputId);
Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(mTagetInputId);
if (timeMs != null) {
- return getResources().getQuantityString(R.plurals.tvview_msg_input_no_resource,
- input.getTunerCount(),
- DateUtils.formatDateTime(getContext(), timeMs, DateUtils.FORMAT_SHOW_TIME));
+ return getResources()
+ .getQuantityString(
+ R.plurals.tvview_msg_input_no_resource,
+ input.getTunerCount(),
+ DateUtils.formatDateTime(
+ getContext(), timeMs, DateUtils.FORMAT_SHOW_TIME));
}
}
return null;
}
private void updateMuteStatus() {
- // Workaround: TunerTvInputService uses AC3 pass-through implementation, which disables
+ // Workaround: BaseTunerTvInputService uses AC3 pass-through implementation, which disables
// audio tracks to enforce the mute request. We don't want to send mute request if we are
// not going to block the screen to prevent the video jankiness resulted by disabling audio
// track before the playback is started. In other way, we should send unmute request before
@@ -1119,7 +1195,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
// itself right way when the playback is going to be started, which results the initial
// jankiness, too.
boolean isBundledInput = isBundledInput();
- if ((isBundledInput || isVideoOrAudioAvailable()) && !mScreenBlocked
+ if ((isBundledInput || isVideoOrAudioAvailable())
+ && !mScreenBlocked
&& mBlockedContentRating == null) {
if (mIsMuted) {
mIsMuted = false;
@@ -1128,7 +1205,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
} else {
if (!mIsMuted) {
if ((mInputInfo == null || isBundledInput)
- && !mScreenBlocked && mBlockedContentRating == null) {
+ && !mScreenBlocked
+ && mBlockedContentRating == null) {
return;
}
mIsMuted = true;
@@ -1138,8 +1216,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
private boolean isBundledInput() {
- return mInputInfo != null && mInputInfo.getType() == TvInputInfo.TYPE_TUNER
- && Utils.isBundledInput(mInputInfo.getId());
+ return mInputInfo != null
+ && mInputInfo.getType() == TvInputInfo.TYPE_TUNER
+ && CommonUtils.isBundledInput(mInputInfo.getId());
}
/** Returns true if this view is faded out. */
@@ -1148,52 +1227,58 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/** Fade out this TunableTvView. Fade out by increasing the dimming. */
- public void fadeOut(int durationMillis, TimeInterpolator interpolator,
- final Runnable actionAfterFade) {
+ public void fadeOut(
+ int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade) {
mDimScreenView.setAlpha(0f);
mDimScreenView.setVisibility(View.VISIBLE);
- mDimScreenView.animate()
+ mDimScreenView
+ .animate()
.alpha(1f)
.setDuration(durationMillis)
.setInterpolator(interpolator)
- .withStartAction(new Runnable() {
- @Override
- public void run() {
- mFadeState = FADING_OUT;
- mActionAfterFade = actionAfterFade;
- }
- })
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mFadeState = FADED_OUT;
- }
- });
+ .withStartAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ mFadeState = FADING_OUT;
+ mActionAfterFade = actionAfterFade;
+ }
+ })
+ .withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ mFadeState = FADED_OUT;
+ }
+ });
}
/** Fade in this TunableTvView. Fade in by decreasing the dimming. */
- public void fadeIn(int durationMillis, TimeInterpolator interpolator,
- final Runnable actionAfterFade) {
+ public void fadeIn(
+ int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade) {
mDimScreenView.setAlpha(1f);
mDimScreenView.setVisibility(View.VISIBLE);
- mDimScreenView.animate()
+ mDimScreenView
+ .animate()
.alpha(0f)
.setDuration(durationMillis)
.setInterpolator(interpolator)
- .withStartAction(new Runnable() {
- @Override
- public void run() {
- mFadeState = FADING_IN;
- mActionAfterFade = actionAfterFade;
- }
- })
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mFadeState = FADED_IN;
- mDimScreenView.setVisibility(View.GONE);
- }
- });
+ .withStartAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ mFadeState = FADING_IN;
+ mActionAfterFade = actionAfterFade;
+ }
+ })
+ .withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ mFadeState = FADED_IN;
+ mDimScreenView.setVisibility(View.GONE);
+ }
+ });
}
/** Remove the fade effect. */
@@ -1208,6 +1293,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
*
* @param listener The instance of {@link TimeShiftListener}.
*/
+ @Override
public void setTimeShiftListener(TimeShiftListener listener) {
mTimeShiftListener = listener;
}
@@ -1218,20 +1304,22 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
mTimeShiftAvailable = isTimeShiftAvailable;
if (isTimeShiftAvailable) {
- mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() {
- @Override
- public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
- if (mTimeShiftListener != null && mCurrentChannel != null
- && mCurrentChannel.getInputId().equals(inputId)) {
- mTimeShiftListener.onRecordStartTimeChanged(timeMs);
- }
- }
+ mTvView.setTimeShiftPositionCallback(
+ new TvView.TimeShiftPositionCallback() {
+ @Override
+ public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
+ if (mTimeShiftListener != null
+ && mCurrentChannel != null
+ && mCurrentChannel.getInputId().equals(inputId)) {
+ mTimeShiftListener.onRecordStartTimeChanged(timeMs);
+ }
+ }
- @Override
- public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
- mTimeShiftCurrentPositionMs = timeMs;
- }
- });
+ @Override
+ public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
+ mTimeShiftCurrentPositionMs = timeMs;
+ }
+ });
} else {
mTvView.setTimeShiftPositionCallback(null);
}
@@ -1240,16 +1328,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
}
- /**
- * Returns if the time shift is available for the current channel.
- */
+ /** Returns if the time shift is available for the current channel. */
+ @Override
public boolean isTimeShiftAvailable() {
return mTimeShiftAvailable;
}
- /**
- * Plays the media, if the current input supports time-shifting.
- */
+ /** Plays the media, if the current input supports time-shifting. */
+ @Override
public void timeshiftPlay() {
if (!isTimeShiftAvailable()) {
throw new IllegalStateException("Time-shift is not supported for the current channel");
@@ -1260,9 +1346,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mTvView.timeShiftResume();
}
- /**
- * Pauses the media, if the current input supports time-shifting.
- */
+ /** Pauses the media, if the current input supports time-shifting. */
+ @Override
public void timeshiftPause() {
if (!isTimeShiftAvailable()) {
throw new IllegalStateException("Time-shift is not supported for the current channel");
@@ -1278,6 +1363,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
*
* @param speed The speed to rewind the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x.
*/
+ @Override
public void timeshiftRewind(int speed) {
if (!isTimeShiftAvailable()) {
throw new IllegalStateException("Time-shift is not supported for the current channel");
@@ -1297,6 +1383,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
*
* @param speed The speed to forward the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x.
*/
+ @Override
public void timeshiftFastForward(int speed) {
if (!isTimeShiftAvailable()) {
throw new IllegalStateException("Time-shift is not supported for the current channel");
@@ -1316,6 +1403,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
*
* @param timeMs The time in milliseconds to seek to.
*/
+ @Override
public void timeshiftSeekTo(long timeMs) {
if (!isTimeShiftAvailable()) {
throw new IllegalStateException("Time-shift is not supported for the current channel");
@@ -1323,16 +1411,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mTvView.timeShiftSeekTo(timeMs);
}
- /**
- * Returns the current playback position in milliseconds.
- */
+ /** Returns the current playback position in milliseconds. */
+ @Override
public long timeshiftGetCurrentPositionMs() {
if (!isTimeShiftAvailable()) {
throw new IllegalStateException("Time-shift is not supported for the current channel");
}
if (DEBUG) {
- Log.d(TAG, "timeshiftGetCurrentPositionMs: current position ="
- + Utils.toTimeString(mTimeShiftCurrentPositionMs));
+ Log.d(
+ TAG,
+ "timeshiftGetCurrentPositionMs: current position ="
+ + Utils.toTimeString(mTimeShiftCurrentPositionMs));
}
return mTimeShiftCurrentPositionMs;
}
@@ -1342,43 +1431,30 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return new ImageLoader.ImageLoaderCallback<BlockScreenView>(mBlockScreenView) {
@Override
public void onBitmapLoaded(BlockScreenView view, @Nullable Bitmap posterArt) {
- if (posterArt == null || getCurrentChannel() == null
+ if (posterArt == null
+ || getCurrentChannel() == null
|| channelId != getCurrentChannel().getId()
|| !shouldShowImageForTuning()) {
return;
}
Drawable drawablePosterArt = new BitmapDrawable(view.getResources(), posterArt);
- drawablePosterArt.mutate().setColorFilter(
- mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER);
+ drawablePosterArt
+ .mutate()
+ .setColorFilter(mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER);
view.setBackgroundImage(drawablePosterArt);
}
};
}
- /**
- * Used to receive the time-shift events.
- */
- public static abstract class TimeShiftListener {
- /**
- * Called when the availability of the time-shift for the current channel has been changed.
- * It should be guaranteed that this is called only when the availability is really changed.
- */
- public abstract void onAvailabilityChanged();
+ /** Listens for dpad actions that are otherwise trapped by talkback */
+ public interface OnTalkBackDpadKeyListener {
- /**
- * Called when the record start time has been changed.
- * This is not called when the recorded programs is played.
- */
- public abstract void onRecordStartTimeChanged(long recordStartTimeMs);
+ void onTalkBackDpadKey(int keycode);
}
- /**
- * A listener which receives the notification when the screen is blocked/unblocked.
- */
- public static abstract class OnScreenBlockingChangedListener {
- /**
- * Called when the screen is blocked/unblocked.
- */
+ /** A listener which receives the notification when the screen is blocked/unblocked. */
+ public abstract static class OnScreenBlockingChangedListener {
+ /** Called when the screen is blocked/unblocked. */
public abstract void onScreenBlockingChanged(boolean blocked);
}
@@ -1391,8 +1467,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
protected void onPostExecute(Boolean networkAvailable) {
mInternetCheckTask = null;
- if (!networkAvailable && isAttachedToWindow()
- && !mScreenBlocked && mBlockedContentRating == null
+ if (!networkAvailable
+ && isAttachedToWindow()
+ && !mScreenBlocked
+ && mBlockedContentRating == null
&& mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN) {
mBlockScreenView.setIconVisibility(true);
mBlockScreenView.setIconImage(R.drawable.ic_sad_cloud);
diff --git a/src/com/android/tv/ui/TunableTvViewPlayingApi.java b/src/com/android/tv/ui/TunableTvViewPlayingApi.java
new file mode 100644
index 00000000..3f19b61f
--- /dev/null
+++ b/src/com/android/tv/ui/TunableTvViewPlayingApi.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.ui;
+
+/** API to play pause and set the volume of a TunableTvView */
+public interface TunableTvViewPlayingApi {
+
+ boolean isPlaying();
+
+ void setStreamVolume(float volume);
+
+ void setTimeShiftListener(TimeShiftListener listener);
+
+ boolean isTimeShiftAvailable();
+
+ void timeshiftPlay();
+
+ void timeshiftPause();
+
+ void timeshiftRewind(int speed);
+
+ void timeshiftFastForward(int speed);
+
+ void timeshiftSeekTo(long timeMs);
+
+ long timeshiftGetCurrentPositionMs();
+
+ /** Used to receive the time-shift events. */
+ abstract class TimeShiftListener {
+ /**
+ * Called when the availability of the time-shift for the current channel has been changed.
+ * It should be guaranteed that this is called only when the availability is really changed.
+ */
+ public abstract void onAvailabilityChanged();
+
+ /**
+ * Called when the record start time has been changed. This is not called when the recorded
+ * programs is played.
+ */
+ public abstract void onRecordStartTimeChanged(long recordStartTimeMs);
+ }
+}
diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java
index 9324742e..222fcb3a 100644
--- a/src/com/android/tv/ui/TvOverlayManager.java
+++ b/src/com/android/tv/ui/TvOverlayManager.java
@@ -32,15 +32,14 @@ import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.ViewGroup;
-
-import com.android.tv.ApplicationSingletons;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import com.android.tv.ChannelTuner;
import com.android.tv.MainActivity;
import com.android.tv.MainActivity.KeyHandlerResultType;
import com.android.tv.R;
import com.android.tv.TimeShiftManager;
-import com.android.tv.TvApplication;
import com.android.tv.TvOptionsManager;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.feature.CommonFeatures;
@@ -69,7 +68,6 @@ import com.android.tv.ui.TvTransitionManager.SceneType;
import com.android.tv.ui.sidepanel.SideFragmentManager;
import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
import com.android.tv.util.TvInputManagerHelper;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -79,122 +77,111 @@ import java.util.List;
import java.util.Queue;
import java.util.Set;
-/**
- * A class responsible for the life cycle and event handling of the pop-ups over TV view.
- */
+/** A class responsible for the life cycle and event handling of the pop-ups over TV view. */
@UiThread
-public class TvOverlayManager {
+public class TvOverlayManager implements AccessibilityStateChangeListener {
private static final String TAG = "TvOverlayManager";
private static final boolean DEBUG = false;
private static final String INTRO_TRACKER_LABEL = "Intro dialog";
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true,
- value = {FLAG_HIDE_OVERLAYS_DEFAULT, FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION,
- FLAG_HIDE_OVERLAYS_KEEP_SCENE, FLAG_HIDE_OVERLAYS_KEEP_DIALOG,
- FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS, FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY,
- FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE, FLAG_HIDE_OVERLAYS_KEEP_MENU,
- FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT})
+ @IntDef(
+ flag = true,
+ value = {
+ FLAG_HIDE_OVERLAYS_DEFAULT,
+ FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION,
+ FLAG_HIDE_OVERLAYS_KEEP_SCENE,
+ FLAG_HIDE_OVERLAYS_KEEP_DIALOG,
+ FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS,
+ FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY,
+ FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE,
+ FLAG_HIDE_OVERLAYS_KEEP_MENU,
+ FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT
+ }
+ )
private @interface HideOverlayFlag {}
// FLAG_HIDE_OVERLAYs must be bitwise exclusive.
- public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000;
- public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010;
- public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100;
- public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000;
- public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000;
+ public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b000000000;
+ public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b000000010;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b000000100;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b000001000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b000010000;
public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b000100000;
- public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000;
- public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000;
- public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b001000000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_MENU = 0b010000000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT = 0b100000000;
private static final int MSG_OVERLAY_CLOSED = 1000;
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true,
- value = {OVERLAY_TYPE_NONE, OVERLAY_TYPE_MENU, OVERLAY_TYPE_SIDE_FRAGMENT,
- OVERLAY_TYPE_DIALOG, OVERLAY_TYPE_GUIDE, OVERLAY_TYPE_SCENE_CHANNEL_BANNER,
- OVERLAY_TYPE_SCENE_INPUT_BANNER, OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH,
- OVERLAY_TYPE_SCENE_SELECT_INPUT, OVERLAY_TYPE_FRAGMENT})
+ @IntDef(
+ flag = true,
+ value = {
+ OVERLAY_TYPE_NONE,
+ OVERLAY_TYPE_MENU,
+ OVERLAY_TYPE_SIDE_FRAGMENT,
+ OVERLAY_TYPE_DIALOG,
+ OVERLAY_TYPE_GUIDE,
+ OVERLAY_TYPE_SCENE_CHANNEL_BANNER,
+ OVERLAY_TYPE_SCENE_INPUT_BANNER,
+ OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH,
+ OVERLAY_TYPE_SCENE_SELECT_INPUT,
+ OVERLAY_TYPE_FRAGMENT
+ }
+ )
private @interface TvOverlayType {}
// OVERLAY_TYPEs must be bitwise exclusive.
- /**
- * The overlay type which indicates that there are no overlays.
- */
- private static final int OVERLAY_TYPE_NONE = 0b000000000;
- /**
- * The overlay type for menu.
- */
- private static final int OVERLAY_TYPE_MENU = 0b000000001;
- /**
- * The overlay type for the side fragment.
- */
- private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010;
- /**
- * The overlay type for dialog fragment.
- */
- private static final int OVERLAY_TYPE_DIALOG = 0b000000100;
- /**
- * The overlay type for program guide.
- */
- private static final int OVERLAY_TYPE_GUIDE = 0b000001000;
- /**
- * The overlay type for channel banner.
- */
- private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000;
- /**
- * The overlay type for input banner.
- */
- private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000;
- /**
- * The overlay type for keypad channel switch view.
- */
+ /** The overlay type which indicates that there are no overlays. */
+ private static final int OVERLAY_TYPE_NONE = 0b000000000;
+ /** The overlay type for menu. */
+ private static final int OVERLAY_TYPE_MENU = 0b000000001;
+ /** The overlay type for the side fragment. */
+ private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b000000010;
+ /** The overlay type for dialog fragment. */
+ private static final int OVERLAY_TYPE_DIALOG = 0b000000100;
+ /** The overlay type for program guide. */
+ private static final int OVERLAY_TYPE_GUIDE = 0b000001000;
+ /** The overlay type for channel banner. */
+ private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b000010000;
+ /** The overlay type for input banner. */
+ private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b000100000;
+ /** The overlay type for keypad channel switch view. */
private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b001000000;
- /**
- * The overlay type for select input view.
- */
- private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000;
- /**
- * The overlay type for fragment other than the side fragment and dialog fragment.
- */
- private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000;
+ /** The overlay type for select input view. */
+ private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b010000000;
+ /** The overlay type for fragment other than the side fragment and dialog fragment. */
+ private static final int OVERLAY_TYPE_FRAGMENT = 0b100000000;
// Used for the padded print of the overlay type.
private static final int NUM_OVERLAY_TYPES = 9;
@Retention(RetentionPolicy.SOURCE)
- @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE,
- UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO,
- UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK,
- UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO})
+ @IntDef({
+ UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW,
+ UPDATE_CHANNEL_BANNER_REASON_TUNE,
+ UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST,
+ UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO,
+ UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK,
+ UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO
+ })
private @interface ChannelBannerUpdateReason {}
- /**
- * Updates channel banner because the channel banner is forced to show.
- */
+ /** Updates channel banner because the channel banner is forced to show. */
public static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1;
- /**
- * Updates channel banner because of tuning.
- */
+ /** Updates channel banner because of tuning. */
public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2;
- /**
- * Updates channel banner because of fast tuning.
- */
+ /** Updates channel banner because of fast tuning. */
public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3;
- /**
- * Updates channel banner because of info updating.
- */
+ /** Updates channel banner because of info updating. */
public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4;
- /**
- * Updates channel banner because the current watched channel is locked or unlocked.
- */
+ /** Updates channel banner because the current watched channel is locked or unlocked. */
public static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5;
- /**
- * Updates channel banner because of stream info updating.
- */
+ /** Updates channel banner because of stream info updating. */
public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6;
private static final String FRAGMENT_TAG_SETUP_SOURCES = "tag_setup_sources";
private static final String FRAGMENT_TAG_NEW_SOURCES = "tag_new_sources";
private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>();
+
static {
AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(DvrHistoryDialogFragment.DIALOG_TAG);
@@ -232,14 +219,20 @@ public class TvOverlayManager {
private OnBackStackChangedListener mOnBackStackChangedListener;
- public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner,
- TunableTvView tvView, TvOptionsManager optionsManager,
- KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView,
- InputBannerView inputBannerView, SelectInputView selectInputView,
- ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment) {
+ public TvOverlayManager(
+ MainActivity mainActivity,
+ ChannelTuner channelTuner,
+ TunableTvView tvView,
+ TvOptionsManager optionsManager,
+ KeypadChannelSwitchView keypadChannelSwitchView,
+ ChannelBannerView channelBannerView,
+ InputBannerView inputBannerView,
+ SelectInputView selectInputView,
+ ViewGroup sceneContainer,
+ ProgramGuideSearchFragment searchFragment) {
mMainActivity = mainActivity;
mChannelTuner = channelTuner;
- ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity);
+ TvSingletons singletons = TvSingletons.getSingletons(mainActivity);
mChannelDataManager = singletons.getChannelDataManager();
mInputManager = singletons.getTvInputManagerHelper();
mTvView = tvView;
@@ -248,115 +241,144 @@ public class TvOverlayManager {
mSelectInputView = selectInputView;
mSearchFragment = searchFragment;
mTracker = singletons.getTracker();
- mTransitionManager = new TvTransitionManager(mainActivity, sceneContainer,
- channelBannerView, inputBannerView, mKeypadChannelSwitchView, selectInputView);
- mTransitionManager.setListener(new TvTransitionManager.Listener() {
- @Override
- public void onSceneChanged(int fromScene, int toScene) {
- // Call onOverlayOpened first so that the listener can know that a new scene
- // will be opened when the onOverlayClosed is called.
- if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
- onOverlayOpened(convertSceneToOverlayType(toScene));
- }
- if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
- onOverlayClosed(convertSceneToOverlayType(fromScene));
- }
- }
- });
- // Menu
- MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu);
- mMenu = new Menu(mainActivity, tvView, optionsManager, menuView,
- new MenuRowFactory(mainActivity, tvView),
- new Menu.OnMenuVisibilityChangeListener() {
+ mTransitionManager =
+ new TvTransitionManager(
+ mainActivity,
+ sceneContainer,
+ channelBannerView,
+ inputBannerView,
+ mKeypadChannelSwitchView,
+ selectInputView);
+ mTransitionManager.setListener(
+ new TvTransitionManager.Listener() {
@Override
- public void onMenuVisibilityChange(boolean visible) {
- if (visible) {
- onOverlayOpened(OVERLAY_TYPE_MENU);
- } else {
- onOverlayClosed(OVERLAY_TYPE_MENU);
+ public void onSceneChanged(int fromScene, int toScene) {
+ // Call onOverlayOpened first so that the listener can know that a new scene
+ // will be opened when the onOverlayClosed is called.
+ if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
+ onOverlayOpened(convertSceneToOverlayType(toScene));
+ }
+ if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
+ onOverlayClosed(convertSceneToOverlayType(fromScene));
}
}
});
+ // Menu
+ MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu);
+ mMenu =
+ new Menu(
+ mainActivity,
+ tvView,
+ optionsManager,
+ menuView,
+ new MenuRowFactory(mainActivity, tvView),
+ new Menu.OnMenuVisibilityChangeListener() {
+ @Override
+ public void onMenuVisibilityChange(boolean visible) {
+ if (visible) {
+ onOverlayOpened(OVERLAY_TYPE_MENU);
+ } else {
+ onOverlayClosed(OVERLAY_TYPE_MENU);
+ }
+ }
+ });
mMenu.setChannelTuner(mChannelTuner);
// Side Fragment
- mSideFragmentManager = new SideFragmentManager(mainActivity,
+ mSideFragmentManager =
+ new SideFragmentManager(
+ mainActivity,
+ new Runnable() {
+ @Override
+ public void run() {
+ onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT);
+ hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS);
+ }
+ },
+ new Runnable() {
+ @Override
+ public void run() {
+ showChannelBannerIfHiddenBySideFragment();
+ onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT);
+ }
+ });
+ // Program Guide
+ Runnable preShowRunnable =
new Runnable() {
@Override
public void run() {
- onOverlayOpened(OVERLAY_TYPE_SIDE_FRAGMENT);
- hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS);
+ onOverlayOpened(OVERLAY_TYPE_GUIDE);
}
- },
+ };
+ Runnable postHideRunnable =
new Runnable() {
@Override
public void run() {
- showChannelBannerIfHiddenBySideFragment();
- onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT);
+ onOverlayClosed(OVERLAY_TYPE_GUIDE);
}
- });
- // Program Guide
- Runnable preShowRunnable = new Runnable() {
- @Override
- public void run() {
- onOverlayOpened(OVERLAY_TYPE_GUIDE);
- }
- };
- Runnable postHideRunnable = new Runnable() {
- @Override
- public void run() {
- onOverlayClosed(OVERLAY_TYPE_GUIDE);
- }
- };
- DvrDataManager dvrDataManager = CommonFeatures.DVR.isEnabled(mainActivity)
- ? singletons.getDvrDataManager() : null;
- mProgramGuide = new ProgramGuide(mainActivity, channelTuner,
- singletons.getTvInputManagerHelper(), mChannelDataManager,
- singletons.getProgramDataManager(), dvrDataManager,
- singletons.getDvrScheduleManager(), singletons.getTracker(), preShowRunnable,
- postHideRunnable);
- mMainActivity.addOnActionClickListener(new OnActionClickListener() {
- @Override
- public boolean onActionClick(String category, int id, Bundle params) {
- switch (category) {
- case SetupSourcesFragment.ACTION_CATEGORY:
- switch (id) {
- case SetupMultiPaneFragment.ACTION_DONE:
- closeSetupFragment(true);
- return true;
- case SetupSourcesFragment.ACTION_ONLINE_STORE:
- mMainActivity.showMerchantCollection();
- return true;
- case SetupSourcesFragment.ACTION_SETUP_INPUT: {
- String inputId = params.getString(
- SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- mMainActivity.startSetupActivity(input, true);
- return true;
- }
- }
- break;
- case NewSourcesFragment.ACTION_CATEOGRY:
- switch (id) {
- case NewSourcesFragment.ACTION_SETUP:
- closeNewSourcesFragment(false);
- showSetupFragment();
- return true;
- case NewSourcesFragment.ACTION_SKIP:
- // Don't remove the fragment because new fragment will be replaced
- // with this fragment.
- closeNewSourcesFragment(true);
- return true;
+ };
+ DvrDataManager dvrDataManager =
+ CommonFeatures.DVR.isEnabled(mainActivity) ? singletons.getDvrDataManager() : null;
+ mProgramGuide =
+ new ProgramGuide(
+ mainActivity,
+ channelTuner,
+ singletons.getTvInputManagerHelper(),
+ mChannelDataManager,
+ singletons.getProgramDataManager(),
+ dvrDataManager,
+ singletons.getDvrScheduleManager(),
+ singletons.getTracker(),
+ preShowRunnable,
+ postHideRunnable);
+ mMainActivity.addOnActionClickListener(
+ new OnActionClickListener() {
+ @Override
+ public boolean onActionClick(String category, int id, Bundle params) {
+ switch (category) {
+ case SetupSourcesFragment.ACTION_CATEGORY:
+ switch (id) {
+ case SetupMultiPaneFragment.ACTION_DONE:
+ closeSetupFragment(true);
+ return true;
+ case SetupSourcesFragment.ACTION_ONLINE_STORE:
+ mMainActivity.showMerchantCollection();
+ return true;
+ case SetupSourcesFragment.ACTION_SETUP_INPUT:
+ {
+ String inputId =
+ params.getString(
+ SetupSourcesFragment
+ .ACTION_PARAM_KEY_INPUT_ID);
+ TvInputInfo input =
+ mInputManager.getTvInputInfo(inputId);
+ mMainActivity.startSetupActivity(input, true);
+ return true;
+ }
+ }
+ break;
+ case NewSourcesFragment.ACTION_CATEOGRY:
+ switch (id) {
+ case NewSourcesFragment.ACTION_SETUP:
+ closeNewSourcesFragment(false);
+ showSetupFragment();
+ return true;
+ case NewSourcesFragment.ACTION_SKIP:
+ // Don't remove the fragment because new fragment will be
+ // replaced
+ // with this fragment.
+ closeNewSourcesFragment(true);
+ return true;
+ }
+ break;
}
- break;
- }
- return false;
- }
- });
+ return false;
+ }
+ });
}
/**
- * A method to release all the allocated resources or unregister listeners.
- * This is called from {@link MainActivity#onDestroy}.
+ * A method to release all the allocated resources or unregister listeners. This is called from
+ * {@link MainActivity#onDestroy}.
*/
public void release() {
mMenu.release();
@@ -366,30 +388,22 @@ public class TvOverlayManager {
}
}
- /**
- * Returns the instance of {@link Menu}.
- */
+ /** Returns the instance of {@link Menu}. */
public Menu getMenu() {
return mMenu;
}
- /**
- * Returns the instance of {@link SideFragmentManager}.
- */
+ /** Returns the instance of {@link SideFragmentManager}. */
public SideFragmentManager getSideFragmentManager() {
return mSideFragmentManager;
}
- /**
- * Returns the currently opened dialog.
- */
+ /** Returns the currently opened dialog. */
public SafeDismissDialogFragment getCurrentDialog() {
return mCurrentDialog;
}
- /**
- * Checks whether the setup fragment is active or not.
- */
+ /** Checks whether the setup fragment is active or not. */
public boolean isSetupFragmentActive() {
// "getSetupSourcesFragment() != null" doesn't return the correct state. That's because,
// when we call showSetupFragment(), we need to put off showing the fragment until the side
@@ -402,9 +416,7 @@ public class TvOverlayManager {
return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_SETUP_SOURCES);
}
- /**
- * Checks whether the new sources fragment is active or not.
- */
+ /** Checks whether the new sources fragment is active or not. */
public boolean isNewSourcesFragmentActive() {
// See the comment in "isSetupFragmentActive".
return mNewSourcesFragmentActive;
@@ -414,25 +426,19 @@ public class TvOverlayManager {
return mMainActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG_NEW_SOURCES);
}
- /**
- * Returns the instance of {@link ProgramGuide}.
- */
+ /** Returns the instance of {@link ProgramGuide}. */
public ProgramGuide getProgramGuide() {
return mProgramGuide;
}
- /**
- * Shows the main menu.
- */
+ /** Shows the main menu. */
public void showMenu(@MenuShowReason int reason) {
if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) {
mMenu.show(reason);
}
}
- /**
- * Shows the play controller of the menu if the playback is paused.
- */
+ /** Shows the play controller of the menu if the playback is paused. */
public boolean showMenuWithTimeShiftPauseIfNeeded() {
if (mMainActivity.getTimeShiftManager().isPaused()) {
showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE);
@@ -441,16 +447,17 @@ public class TvOverlayManager {
return false;
}
- /**
- * Shows the given dialog.
- */
- public void showDialogFragment(String tag, SafeDismissDialogFragment dialog,
- boolean keepSidePanelHistory) {
+ /** Shows the given dialog. */
+ public void showDialogFragment(
+ String tag, SafeDismissDialogFragment dialog, boolean keepSidePanelHistory) {
showDialogFragment(tag, dialog, keepSidePanelHistory, false);
}
- public void showDialogFragment(String tag, SafeDismissDialogFragment dialog,
- boolean keepSidePanelHistory, boolean keepProgramGuide) {
+ public void showDialogFragment(
+ String tag,
+ SafeDismissDialogFragment dialog,
+ boolean keepSidePanelHistory,
+ boolean keepProgramGuide) {
int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG;
if (keepSidePanelHistory) {
flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY;
@@ -466,8 +473,8 @@ public class TvOverlayManager {
// Do not open two dialogs at the same time.
if (mCurrentDialog != null) {
- mPendingDialogActionQueue.offer(new PendingDialogAction(tag, dialog,
- keepSidePanelHistory, keepProgramGuide));
+ mPendingDialogActionQueue.offer(
+ new PendingDialogAction(tag, dialog, keepSidePanelHistory, keepProgramGuide));
return;
}
@@ -492,16 +499,17 @@ public class TvOverlayManager {
// When the side panel is closing, it closes all the fragments, so the new fragment
// should be opened after the side fragment becomes invisible.
final FragmentManager manager = mMainActivity.getFragmentManager();
- mOnBackStackChangedListener = new OnBackStackChangedListener() {
- @Override
- public void onBackStackChanged() {
- if (manager.getBackStackEntryCount() == 0) {
- manager.removeOnBackStackChangedListener(this);
- mOnBackStackChangedListener = null;
- runnable.run();
- }
- }
- };
+ mOnBackStackChangedListener =
+ new OnBackStackChangedListener() {
+ @Override
+ public void onBackStackChanged() {
+ if (manager.getBackStackEntryCount() == 0) {
+ manager.removeOnBackStackChangedListener(this);
+ mOnBackStackChangedListener = null;
+ runnable.run();
+ }
+ }
+ };
manager.addOnBackStackChangedListener(mOnBackStackChangedListener);
} else {
runnable.run();
@@ -511,46 +519,54 @@ public class TvOverlayManager {
private void showFragment(final Fragment fragment, final String tag) {
hideOverlays(FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
onOverlayOpened(OVERLAY_TYPE_FRAGMENT);
- runAfterSideFragmentsAreClosed(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "showFragment(" + fragment + ")");
- mMainActivity.getFragmentManager().beginTransaction()
- .replace(R.id.fragment_container, fragment, tag).commit();
- }
- });
+ runAfterSideFragmentsAreClosed(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "showFragment(" + fragment + ")");
+ mMainActivity
+ .getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.fragment_container, fragment, tag)
+ .commit();
+ }
+ });
}
private void closeFragment(String fragmentTagToRemove) {
if (DEBUG) Log.d(TAG, "closeFragment(" + fragmentTagToRemove + ")");
onOverlayClosed(OVERLAY_TYPE_FRAGMENT);
if (fragmentTagToRemove != null) {
- Fragment fragmentToRemove = mMainActivity.getFragmentManager()
- .findFragmentByTag(fragmentTagToRemove);
+ Fragment fragmentToRemove =
+ mMainActivity.getFragmentManager().findFragmentByTag(fragmentTagToRemove);
if (fragmentToRemove == null) {
// If the fragment has not been added to the fragment manager yet, just remove the
// listener not to add the fragment. This is needed because the side fragment is
// closed asynchronously.
- mMainActivity.getFragmentManager().removeOnBackStackChangedListener(
- mOnBackStackChangedListener);
+ mMainActivity
+ .getFragmentManager()
+ .removeOnBackStackChangedListener(mOnBackStackChangedListener);
mOnBackStackChangedListener = null;
} else {
- mMainActivity.getFragmentManager().beginTransaction().remove(fragmentToRemove)
+ mMainActivity
+ .getFragmentManager()
+ .beginTransaction()
+ .remove(fragmentToRemove)
.commit();
}
}
}
- /**
- * Shows setup dialog.
- */
+ /** Shows setup dialog. */
public void showSetupFragment() {
if (DEBUG) Log.d(TAG, "showSetupFragment");
mSetupFragmentActive = true;
SetupSourcesFragment setupFragment = new SetupSourcesFragment();
- setupFragment.enableFragmentTransition(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION
- | SetupFragment.FRAGMENT_REENTER_TRANSITION);
+ setupFragment.enableFragmentTransition(
+ SetupFragment.FRAGMENT_ENTER_TRANSITION
+ | SetupFragment.FRAGMENT_EXIT_TRANSITION
+ | SetupFragment.FRAGMENT_RETURN_TRANSITION
+ | SetupFragment.FRAGMENT_REENTER_TRANSITION);
setupFragment.setFragmentTransition(SetupFragment.FRAGMENT_EXIT_TRANSITION, Gravity.END);
showFragment(setupFragment, FRAGMENT_TAG_SETUP_SOURCES);
}
@@ -569,9 +585,7 @@ public class TvOverlayManager {
}
}
- /**
- * Shows new sources dialog.
- */
+ /** Shows new sources dialog. */
public void showNewSourcesFragment() {
if (DEBUG) Log.d(TAG, "showNewSourcesFragment");
mNewSourcesFragmentActive = true;
@@ -588,43 +602,36 @@ public class TvOverlayManager {
closeFragment(removeFragment ? FRAGMENT_TAG_NEW_SOURCES : null);
}
- /**
- * Shows DVR manager.
- */
+ /** Shows DVR manager. */
public void showDvrManager() {
Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class);
mMainActivity.startActivity(intent);
}
- /**
- * Shows intro dialog.
- */
+ /** Shows intro dialog. */
public void showIntroDialog() {
- if (DEBUG) Log.d(TAG,"showIntroDialog");
- showDialogFragment(FullscreenDialogFragment.DIALOG_TAG,
+ if (DEBUG) Log.d(TAG, "showIntroDialog");
+ showDialogFragment(
+ FullscreenDialogFragment.DIALOG_TAG,
FullscreenDialogFragment.newInstance(R.layout.intro_dialog, INTRO_TRACKER_LABEL),
false);
}
- /**
- * Shows recently watched dialog.
- */
+ /** Shows recently watched dialog. */
public void showRecentlyWatchedDialog() {
- showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG,
- new RecentlyWatchedDialogFragment(), false);
+ showDialogFragment(
+ RecentlyWatchedDialogFragment.DIALOG_TAG,
+ new RecentlyWatchedDialogFragment(),
+ false);
}
- /**
- * Shows DVR history dialog.
- */
+ /** Shows DVR history dialog. */
public void showDvrHistoryDialog() {
- showDialogFragment(DvrHistoryDialogFragment.DIALOG_TAG,
- new DvrHistoryDialogFragment(), false);
+ showDialogFragment(
+ DvrHistoryDialogFragment.DIALOG_TAG, new DvrHistoryDialogFragment(), false);
}
- /**
- * Shows banner view.
- */
+ /** Shows banner view. */
public void showBanner() {
mTransitionManager.goToChannelBannerScene();
}
@@ -636,33 +643,28 @@ public class TvOverlayManager {
*/
public void showKeypadChannelSwitch(int keyCode) {
if (mChannelTuner.areAllChannelsLoaded()) {
- hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
mTransitionManager.goToKeypadChannelSwitchScene();
mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
}
}
- /**
- * Shows select input view.
- */
+ /** Shows select input view. */
public void showSelectInputView() {
hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
mTransitionManager.goToSelectInputScene();
}
- /**
- * Initializes animators if animators are not initialized yet.
- */
+ /** Initializes animators if animators are not initialized yet. */
public void initAnimatorIfNeeded() {
mTransitionManager.initIfNeeded();
}
- /**
- * It is called when a SafeDismissDialogFragment is destroyed.
- */
+ /** It is called when a SafeDismissDialogFragment is destroyed. */
public void onDialogDestroyed() {
mCurrentDialog = null;
PendingDialogAction action = mPendingDialogActionQueue.poll();
@@ -673,16 +675,15 @@ public class TvOverlayManager {
}
}
- /**
- * Shows the program guide.
- */
+ /** Shows the program guide. */
public void showProgramGuide() {
- mProgramGuide.show(new Runnable() {
- @Override
- public void run() {
- hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE);
- }
- });
+ mProgramGuide.show(
+ new Runnable() {
+ @Override
+ public void run() {
+ hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE);
+ }
+ });
}
/**
@@ -700,9 +701,7 @@ public class TvOverlayManager {
}
}
- /**
- * Sets blocking content rating of the currently playing TV channel.
- */
+ /** Sets blocking content rating of the currently playing TV channel. */
public void setBlockingContentRating(TvContentRating rating) {
if (!mMainActivity.isChannelChangeKeyDownReceived()) {
mChannelBannerView.setBlockingContentRating(rating);
@@ -710,9 +709,11 @@ public class TvOverlayManager {
}
}
- /**
- * Hides all the opened overlays according to the flags.
- */
+ public boolean isOverlayOpened() {
+ return mOpenedOverlays != OVERLAY_TYPE_NONE;
+ }
+
+ /** Hides all the opened overlays according to the flags. */
// TODO: Add test for this method.
public void hideOverlays(@HideOverlayFlag int flags) {
if (mMainActivity.needToKeepSetupScreenWhenHidingOverlay()) {
@@ -780,9 +781,17 @@ public class TvOverlayManager {
}
}
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ // Propagate this to all elements that need it
+ mChannelBannerView.onAccessibilityStateChanged(enabled);
+ mProgramGuide.onAccessibilityStateChanged(enabled);
+ mSideFragmentManager.onAccessibilityStateChanged(enabled);
+ }
+
/**
- * Returns true, if a main view needs to hide informational text. Specifically, when overlay
- * UIs except banner is shown, the informational text needs to be hidden for clean UI.
+ * Returns true, if a main view needs to hide informational text. Specifically, when overlay UIs
+ * except banner is shown, the informational text needs to be hidden for clean UI.
*/
public boolean needHideTextOnMainView() {
return mSideFragmentManager.isActive()
@@ -793,11 +802,9 @@ public class TvOverlayManager {
|| mNewSourcesFragmentActive;
}
- /**
- * Updates and shows channel banner if it's needed.
- */
+ /** Updates and shows channel banner if it's needed. */
public void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) {
- if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")");
+ if (DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")");
if (mMainActivity.isChannelChangeKeyDownReceived()
&& reason != UPDATE_CHANNEL_BANNER_REASON_TUNE
&& reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
@@ -826,8 +833,9 @@ public class TvOverlayManager {
}
} else if (mTvView.isScreenBlocked()) {
lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
- } else if (mTvView.isContentBlocked() || (mMainActivity.getParentalControlSettings()
- .isParentalControlsEnabled() && !mTvView.isVideoOrAudioAvailable())) {
+ } else if (mTvView.isContentBlocked()
+ || (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()
+ && !mTvView.isVideoOrAudioAvailable())) {
// If the parental control is enabled, do not show the program detail until the
// video becomes available.
lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
@@ -843,19 +851,22 @@ public class TvOverlayManager {
// If parental control is enabled, we shows program description when the video is
// available, instead of tuning. Therefore we need to check it here if the program
// description is previously hidden by parental control.
- if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL &&
- lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) {
+ if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL
+ && lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) {
mChannelBannerView.updateViews(false);
}
} else {
- mChannelBannerView.updateViews(reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
- || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
+ mChannelBannerView.updateViews(
+ reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
+ || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
}
}
- boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW
- || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
- || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
- if (needToShowBanner && !mMainActivity.willShowOverlayUiWhenResume()
+ boolean needToShowBanner =
+ (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW
+ || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
+ || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
+ if (needToShowBanner
+ && !mMainActivity.willShowOverlayUiWhenResume()
&& getCurrentDialog() == null
&& !isSetupFragmentActive()
&& !isNewSourcesFragmentActive()) {
@@ -870,7 +881,8 @@ public class TvOverlayManager {
}
}
- @TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) {
+ @TvOverlayType
+ private int convertSceneToOverlayType(@SceneType int sceneType) {
switch (sceneType) {
case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER:
return OVERLAY_TYPE_SCENE_CHANNEL_BANNER;
@@ -940,13 +952,13 @@ public class TvOverlayManager {
}
private boolean isOnlyBannerOrNoneOpened() {
- return (mOpenedOverlays & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER
- & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) == 0;
+ return (mOpenedOverlays
+ & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER
+ & ~OVERLAY_TYPE_SCENE_INPUT_BANNER)
+ == 0;
}
- /**
- * Runs a given {@code action} after all the overlays are closed.
- */
+ /** Runs a given {@code action} after all the overlays are closed. */
public void runAfterOverlaysAreClosed(Runnable action) {
if (canExecuteCloseAction()) {
action.run();
@@ -955,9 +967,7 @@ public class TvOverlayManager {
}
}
- /**
- * Handles the onUserInteraction event of the {@link MainActivity}.
- */
+ /** Handles the onUserInteraction event of the {@link MainActivity}. */
public void onUserInteraction() {
if (mSideFragmentManager.isActive()) {
mSideFragmentManager.scheduleHideAll();
@@ -968,10 +978,9 @@ public class TvOverlayManager {
}
}
- /**
- * Handles the onKeyDown event of the {@link MainActivity}.
- */
- @KeyHandlerResultType public int onKeyDown(int keyCode, KeyEvent event) {
+ /** Handles the onKeyDown event of the {@link MainActivity}. */
+ @KeyHandlerResultType
+ public int onKeyDown(int keyCode, KeyEvent event) {
if (mCurrentDialog != null) {
// Consumes the keys while a Dialog is creating.
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
@@ -981,31 +990,34 @@ public class TvOverlayManager {
// Consumes the keys which may trigger system's default music player.
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
}
- if (mMenu.isActive() || mSideFragmentManager.isActive() || mProgramGuide.isActive()
- || mSetupFragmentActive || mNewSourcesFragmentActive) {
+ if (mMenu.isActive()
+ || mSideFragmentManager.isActive()
+ || mProgramGuide.isActive()
+ || mSetupFragmentActive
+ || mNewSourcesFragmentActive) {
return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
}
if (mTransitionManager.isKeypadChannelSwitchActive()) {
- return mKeypadChannelSwitchView.onKeyDown(keyCode, event) ?
- MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
+ return mKeypadChannelSwitchView.onKeyDown(keyCode, event)
+ ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
: MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
}
if (mTransitionManager.isSelectInputActive()) {
- return mSelectInputView.onKeyDown(keyCode, event) ?
- MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
+ return mSelectInputView.onKeyDown(keyCode, event)
+ ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
: MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
}
return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH;
}
- /**
- * Handles the onKeyUp event of the {@link MainActivity}.
- */
- @KeyHandlerResultType public int onKeyUp(int keyCode, KeyEvent event) {
+ /** Handles the onKeyUp event of the {@link MainActivity}. */
+ @KeyHandlerResultType
+ public int onKeyUp(int keyCode, KeyEvent event) {
// Handle media key here because it is related to the menu.
if (isMediaStartKey(keyCode)) {
// The media key should not be passed up to the system in any cases.
- if (mCurrentDialog != null || mProgramGuide.isActive()
+ if (mCurrentDialog != null
+ || mProgramGuide.isActive()
|| mSideFragmentManager.isActive()
|| mSearchFragment.isVisible()
|| mTransitionManager.isKeypadChannelSwitchActive()
@@ -1015,6 +1027,10 @@ public class TvOverlayManager {
// Do not handle media key when any pop-ups which can handle keys are active.
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
}
+ if (mTvView.isScreenBlocked()) {
+ // Do not handle media key when screen is blocked.
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager();
if (!timeShiftManager.isAvailable()) {
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
@@ -1091,9 +1107,10 @@ public class TvOverlayManager {
if (timeShiftManager.isPaused()) {
timeShiftManager.play();
}
- hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
}
if (mMenu.isActive()) {
@@ -1109,8 +1126,8 @@ public class TvOverlayManager {
mTransitionManager.goToEmptyScene(true);
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
}
- return mKeypadChannelSwitchView.onKeyUp(keyCode, event) ?
- MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
+ return mKeypadChannelSwitchView.onKeyUp(keyCode, event)
+ ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
: MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
}
if (mTransitionManager.isSelectInputActive()) {
@@ -1118,8 +1135,8 @@ public class TvOverlayManager {
mTransitionManager.goToEmptyScene(true);
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
}
- return mSelectInputView.onKeyUp(keyCode, event) ?
- MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
+ return mSelectInputView.onKeyUp(keyCode, event)
+ ? MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
: MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
}
if (mSetupFragmentActive) {
@@ -1139,9 +1156,7 @@ public class TvOverlayManager {
return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH;
}
- /**
- * Checks whether the given {@code keyCode} can start the system's music app or not.
- */
+ /** Checks whether the given {@code keyCode} can start the system's music app or not. */
private static boolean isMediaStartKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -1190,8 +1205,11 @@ public class TvOverlayManager {
private final boolean mKeepSidePanelHistory;
private final boolean mKeepProgramGuide;
- PendingDialogAction(String tag, SafeDismissDialogFragment dialog,
- boolean keepSidePanelHistory, boolean keepProgramGuide) {
+ PendingDialogAction(
+ String tag,
+ SafeDismissDialogFragment dialog,
+ boolean keepSidePanelHistory,
+ boolean keepProgramGuide) {
mTag = tag;
mDialog = dialog;
mKeepSidePanelHistory = keepSidePanelHistory;
diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java
index 628bbb72..5af3e6f2 100644
--- a/src/com/android/tv/ui/TvTransitionManager.java
+++ b/src/com/android/tv/ui/TvTransitionManager.java
@@ -30,19 +30,23 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Channel;
-
+import com.android.tv.data.api.Channel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class TvTransitionManager extends TransitionManager {
@Retention(RetentionPolicy.SOURCE)
- @IntDef({SCENE_TYPE_EMPTY, SCENE_TYPE_CHANNEL_BANNER, SCENE_TYPE_INPUT_BANNER,
- SCENE_TYPE_KEYPAD_CHANNEL_SWITCH, SCENE_TYPE_SELECT_INPUT})
+ @IntDef({
+ SCENE_TYPE_EMPTY,
+ SCENE_TYPE_CHANNEL_BANNER,
+ SCENE_TYPE_INPUT_BANNER,
+ SCENE_TYPE_KEYPAD_CHANNEL_SWITCH,
+ SCENE_TYPE_SELECT_INPUT
+ })
public @interface SceneType {}
+
public static final int SCENE_TYPE_EMPTY = 0;
public static final int SCENE_TYPE_CHANNEL_BANNER = 1;
public static final int SCENE_TYPE_INPUT_BANNER = 2;
@@ -70,17 +74,24 @@ public class TvTransitionManager extends TransitionManager {
private Listener mListener;
- public TvTransitionManager(MainActivity mainActivity, ViewGroup sceneContainer,
- ChannelBannerView channelBannerView, InputBannerView inputBannerView,
- KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView) {
+ public TvTransitionManager(
+ MainActivity mainActivity,
+ ViewGroup sceneContainer,
+ ChannelBannerView channelBannerView,
+ InputBannerView inputBannerView,
+ KeypadChannelSwitchView keypadChannelSwitchView,
+ SelectInputView selectInputView) {
mMainActivity = mainActivity;
mSceneContainer = sceneContainer;
mChannelBannerView = channelBannerView;
mInputBannerView = inputBannerView;
mKeypadChannelSwitchView = keypadChannelSwitchView;
mSelectInputView = selectInputView;
- mEmptyView = (FrameLayout) mMainActivity.getLayoutInflater().inflate(
- R.layout.empty_info_banner, sceneContainer, false);
+ mEmptyView =
+ (FrameLayout)
+ mMainActivity
+ .getLayoutInflater()
+ .inflate(R.layout.empty_info_banner, sceneContainer, false);
mCurrentSceneView = mEmptyView;
}
@@ -108,8 +119,10 @@ public class TvTransitionManager extends TransitionManager {
if (mCurrentScene != mInputBannerScene) {
// Show the input banner instead.
LayoutParams lp = (LayoutParams) mInputBannerView.getLayoutParams();
- lp.width = mCurrentScene == mSelectInputScene ? mSelectInputView.getWidth()
- : FrameLayout.LayoutParams.WRAP_CONTENT;
+ lp.width =
+ mCurrentScene == mSelectInputScene
+ ? mSelectInputView.getWidth()
+ : FrameLayout.LayoutParams.WRAP_CONTENT;
mInputBannerView.setLayoutParams(lp);
mInputBannerView.updateLabel();
transitionTo(mInputBannerScene);
@@ -154,33 +167,35 @@ public class TvTransitionManager extends TransitionManager {
if (mInitialized) {
return;
}
- mEnterAnimator = AnimatorInflater.loadAnimator(mMainActivity,
- R.animator.channel_banner_enter);
- mExitAnimator = AnimatorInflater.loadAnimator(mMainActivity,
- R.animator.channel_banner_exit);
+ mEnterAnimator =
+ AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_enter);
+ mExitAnimator =
+ AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_exit);
mEmptyScene = new Scene(mSceneContainer, (View) mEmptyView);
- mEmptyScene.setEnterAction(new Runnable() {
- @Override
- public void run() {
- FrameLayout.LayoutParams emptySceneLayoutParams =
- (FrameLayout.LayoutParams) mEmptyView.getLayoutParams();
- ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams();
- emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop();
- emptySceneLayoutParams.setMarginStart(lp.getMarginStart());
- emptySceneLayoutParams.height = mCurrentSceneView.getHeight();
- emptySceneLayoutParams.width = mCurrentSceneView.getWidth();
- mEmptyView.setLayoutParams(emptySceneLayoutParams);
- setCurrentScene(mEmptyScene, mEmptyView);
- }
- });
- mEmptyScene.setExitAction(new Runnable() {
- @Override
- public void run() {
- removeAllViewsFromOverlay();
- }
- });
+ mEmptyScene.setEnterAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ FrameLayout.LayoutParams emptySceneLayoutParams =
+ (FrameLayout.LayoutParams) mEmptyView.getLayoutParams();
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams();
+ emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop();
+ emptySceneLayoutParams.setMarginStart(lp.getMarginStart());
+ emptySceneLayoutParams.height = mCurrentSceneView.getHeight();
+ emptySceneLayoutParams.width = mCurrentSceneView.getWidth();
+ mEmptyView.setLayoutParams(emptySceneLayoutParams);
+ setCurrentScene(mEmptyScene, mEmptyView);
+ }
+ });
+ mEmptyScene.setExitAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ removeAllViewsFromOverlay();
+ }
+ });
mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView);
mInputBannerScene = buildScene(mSceneContainer, mInputBannerView);
@@ -189,18 +204,20 @@ public class TvTransitionManager extends TransitionManager {
mCurrentScene = mEmptyScene;
// Enter transitions
- TransitionSet enter = new TransitionSet()
- .addTransition(new SceneTransition(SceneTransition.ENTER))
- .addTransition(new Fade(Fade.IN));
+ TransitionSet enter =
+ new TransitionSet()
+ .addTransition(new SceneTransition(SceneTransition.ENTER))
+ .addTransition(new Fade(Fade.IN));
setTransition(mEmptyScene, mChannelBannerScene, enter);
setTransition(mEmptyScene, mInputBannerScene, enter);
setTransition(mEmptyScene, mKeypadChannelSwitchScene, enter);
setTransition(mEmptyScene, mSelectInputScene, enter);
// Exit transitions
- TransitionSet exit = new TransitionSet()
- .addTransition(new SceneTransition(SceneTransition.EXIT))
- .addTransition(new Fade(Fade.OUT));
+ TransitionSet exit =
+ new TransitionSet()
+ .addTransition(new SceneTransition(SceneTransition.EXIT))
+ .addTransition(new Fade(Fade.OUT));
setTransition(mChannelBannerScene, mEmptyScene, exit);
setTransition(mInputBannerScene, mEmptyScene, exit);
setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit);
@@ -220,10 +237,9 @@ public class TvTransitionManager extends TransitionManager {
mInitialized = true;
}
- /**
- * Returns the type of the given scene.
- */
- @SceneType public int getSceneType(Scene scene) {
+ /** Returns the type of the given scene. */
+ @SceneType
+ public int getSceneType(Scene scene) {
if (scene == mChannelBannerScene) {
return SCENE_TYPE_CHANNEL_BANNER;
} else if (scene == mInputBannerScene) {
@@ -257,21 +273,23 @@ public class TvTransitionManager extends TransitionManager {
private Scene buildScene(ViewGroup sceneRoot, final TransitionLayout layout) {
final Scene scene = new Scene(sceneRoot, (View) layout);
- scene.setEnterAction(new Runnable() {
- @Override
- public void run() {
- boolean wasEmptyScene = (mCurrentScene == mEmptyScene);
- setCurrentScene(scene, (ViewGroup) layout);
- layout.onEnterAction(wasEmptyScene);
- }
- });
- scene.setExitAction(new Runnable() {
- @Override
- public void run() {
- removeAllViewsFromOverlay();
- layout.onExitAction();
- }
- });
+ scene.setEnterAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ boolean wasEmptyScene = (mCurrentScene == mEmptyScene);
+ setCurrentScene(scene, (ViewGroup) layout);
+ layout.onEnterAction(wasEmptyScene);
+ }
+ });
+ scene.setExitAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ removeAllViewsFromOverlay();
+ layout.onExitAction();
+ }
+ });
return scene;
}
@@ -294,12 +312,10 @@ public class TvTransitionManager extends TransitionManager {
}
@Override
- public void captureStartValues(TransitionValues transitionValues) {
- }
+ public void captureStartValues(TransitionValues transitionValues) {}
@Override
- public void captureEndValues(TransitionValues transitionValues) {
- }
+ public void captureEndValues(TransitionValues transitionValues) {}
@Override
public Animator createAnimator(
@@ -311,9 +327,7 @@ public class TvTransitionManager extends TransitionManager {
}
}
- /**
- * An interface for notification of the scene transition.
- */
+ /** An interface for notification of the scene transition. */
public interface Listener {
/**
* Called when the scene changes. This method is called just before the scene transition.
diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java
index f042987a..7e354db3 100644
--- a/src/com/android/tv/ui/TvViewUiManager.java
+++ b/src/com/android/tv/ui/TvViewUiManager.java
@@ -42,16 +42,15 @@ import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
-
-import com.android.tv.Features;
import com.android.tv.R;
+import com.android.tv.TvFeatures;
import com.android.tv.TvOptionsManager;
import com.android.tv.data.DisplayMode;
import com.android.tv.util.TvSettings;
/**
- * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView.
- * It also control the settings regarding TvView UI such as display mode.
+ * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView. It
+ * also control the settings regarding TvView UI such as display mode.
*/
public class TvViewUiManager {
private static final String TAG = "TvViewManager";
@@ -94,7 +93,7 @@ public class TvViewUiManager {
mTvView.setLayoutParams(mTvViewFrame);
// Smooth PIP size change, we don't change surface size when
// isInPictureInPictureMode is true.
- if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext)
+ if (!TvFeatures.PICTURE_IN_PICTURE.isEnabled(mContext)
|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& !((Activity) mContext).isInPictureInPictureMode())) {
mTvView.setFixedSurfaceSize(
@@ -126,16 +125,19 @@ public class TvViewUiManager {
private int mAppliedTvViewEndMargin;
private float mAppliedVideoDisplayAspectRatio;
- public TvViewUiManager(Context context, TunableTvView tvView,
- FrameLayout contentView, TvOptionsManager tvOptionManager) {
+ public TvViewUiManager(
+ Context context,
+ TunableTvView tvView,
+ FrameLayout contentView,
+ TvOptionsManager tvOptionManager) {
mContext = context;
mResources = mContext.getResources();
mTvView = tvView;
mContentView = contentView;
mTvOptionsManager = tvOptionManager;
- DisplayManager displayManager = (DisplayManager) mContext
- .getSystemService(Context.DISPLAY_SERVICE);
+ DisplayManager displayManager =
+ (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
Point size = new Point();
display.getSize(size);
@@ -143,8 +145,8 @@ public class TvViewUiManager {
mWindowHeight = size.y;
// Have an assumption that TvView Shrinking happens only in full screen.
- mTvViewShrunkenStartMargin = mResources
- .getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start);
+ mTvViewShrunkenStartMargin =
+ mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start);
mTvViewShrunkenEndMargin =
mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end)
+ mResources.getDimensionPixelSize(R.dimen.side_panel_width);
@@ -152,10 +154,12 @@ public class TvViewUiManager {
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
- mLinearOutSlowIn = AnimationUtils
- .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
- mFastOutLinearIn = AnimationUtils
- .loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in);
+ mLinearOutSlowIn =
+ AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearIn =
+ AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in);
}
public void onConfigurationChanged(final int windowWidth, final int windowHeight) {
@@ -169,9 +173,9 @@ public class TvViewUiManager {
}
/**
- * Initializes animator in advance of using the animator to improve animation performance.
- * For fast first tune, it is not expected to be called in Activity.onCreate, but called
- * a few seconds later after onCreate.
+ * Initializes animator in advance of using the animator to improve animation performance. For
+ * fast first tune, it is not expected to be called in Activity.onCreate, but called a few
+ * seconds later after onCreate.
*/
public void initAnimatorIfNeeded() {
initTvAnimatorIfNeeded();
@@ -203,16 +207,14 @@ public class TvViewUiManager {
setDisplayMode(mDisplayModeBeforeShrunken, false, true);
}
- /**
- * Returns true, if TvView is shrunken.
- */
+ /** Returns true, if TvView is shrunken. */
public boolean isUnderShrunkenTvView() {
return mIsUnderShrunkenTvView;
}
/**
- * Returns true, if {@code displayMode} is available now. If screen ratio is matched to
- * video ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available.
+ * Returns true, if {@code displayMode} is available now. If screen ratio is matched to video
+ * ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available.
*/
public boolean isDisplayModeAvailable(int displayMode) {
if (displayMode == DisplayMode.MODE_NORMAL) {
@@ -226,11 +228,15 @@ public class TvViewUiManager {
if (viewWidth <= 0 || viewHeight <= 0 || videoDisplayAspectRatio <= 0f) {
Log.w(TAG, "Video size is currently unavailable");
if (DEBUG) {
- Log.d(TAG, "isDisplayModeAvailable: "
- + "viewWidth=" + viewWidth
- + ", viewHeight=" + viewHeight
- + ", videoDisplayAspectRatio=" + videoDisplayAspectRatio
- );
+ Log.d(
+ TAG,
+ "isDisplayModeAvailable: "
+ + "viewWidth="
+ + viewWidth
+ + ", viewHeight="
+ + viewHeight
+ + ", videoDisplayAspectRatio="
+ + videoDisplayAspectRatio);
}
return false;
}
@@ -239,9 +245,7 @@ public class TvViewUiManager {
return Math.abs(viewRatio - videoDisplayAspectRatio) >= DISPLAY_MODE_EPSILON;
}
- /**
- * Returns a constant defined in DisplayMode.
- */
+ /** Returns a constant defined in DisplayMode. */
public int getDisplayMode() {
if (isDisplayModeAvailable(mDisplayMode)) {
return mDisplayMode;
@@ -264,49 +268,45 @@ public class TvViewUiManager {
return prev;
}
- /**
- * Restores the display mode to the display mode stored in preference.
- */
+ /** Restores the display mode to the display mode stored in preference. */
public void restoreDisplayMode(boolean animate) {
- int displayMode = mSharedPreferences
- .getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL);
+ int displayMode =
+ mSharedPreferences.getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL);
setDisplayMode(displayMode, false, animate);
}
- /**
- * Updates TvView's aspect ratio. It should be called when video resolution is changed.
- */
+ /** Updates TvView's aspect ratio. It should be called when video resolution is changed. */
public void updateTvAspectRatio() {
applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false);
if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) {
- mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
- mFastOutLinearIn, null);
+ mTvView.fadeIn(
+ mResources.getInteger(R.integer.tvview_fade_in_duration),
+ mFastOutLinearIn,
+ null);
}
}
- /**
- * Fades in TvView.
- */
+ /** Fades in TvView. */
public void fadeInTvView() {
if (mTvView.isFadedOut()) {
- mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
- mFastOutLinearIn, null);
+ mTvView.fadeIn(
+ mResources.getInteger(R.integer.tvview_fade_in_duration),
+ mFastOutLinearIn,
+ null);
}
}
- /**
- * Fades out TvView.
- */
+ /** Fades out TvView. */
public void fadeOutTvView(Runnable postAction) {
if (!mTvView.isFadedOut()) {
- mTvView.fadeOut(mResources.getInteger(R.integer.tvview_fade_out_duration),
- mLinearOutSlowIn, postAction);
+ mTvView.fadeOut(
+ mResources.getInteger(R.integer.tvview_fade_out_duration),
+ mLinearOutSlowIn,
+ postAction);
}
}
- /**
- * This margins will be applied when applyDisplayMode is called.
- */
+ /** This margins will be applied when applyDisplayMode is called. */
private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
mTvViewStartMargin = tvViewStartMargin;
mTvViewEndMargin = tvViewEndMargin;
@@ -316,8 +316,8 @@ public class TvViewUiManager {
return mTvViewStartMargin == 0 && mTvViewEndMargin == 0;
}
- private void setBackgroundColor(int color, FrameLayout.LayoutParams targetLayoutParams,
- boolean animate) {
+ private void setBackgroundColor(
+ int color, FrameLayout.LayoutParams targetLayoutParams, boolean animate) {
if (animate) {
initBackgroundAnimatorIfNeeded();
if (mBackgroundAnimator.isStarted()) {
@@ -327,12 +327,13 @@ public class TvViewUiManager {
int decorViewWidth = mContentView.getWidth();
int decorViewHeight = mContentView.getHeight();
- boolean hasPillarBox = mTvView.getWidth() != decorViewWidth
- || mTvView.getHeight() != decorViewHeight;
- boolean willHavePillarBox = ((targetLayoutParams.width != LayoutParams.MATCH_PARENT)
- && targetLayoutParams.width != decorViewWidth) || (
- (targetLayoutParams.height != LayoutParams.MATCH_PARENT)
- && targetLayoutParams.height != decorViewHeight);
+ boolean hasPillarBox =
+ mTvView.getWidth() != decorViewWidth || mTvView.getHeight() != decorViewHeight;
+ boolean willHavePillarBox =
+ ((targetLayoutParams.width != LayoutParams.MATCH_PARENT)
+ && targetLayoutParams.width != decorViewWidth)
+ || ((targetLayoutParams.height != LayoutParams.MATCH_PARENT)
+ && targetLayoutParams.height != decorViewHeight);
if (!isTvViewFullScreen() && !hasPillarBox) {
// If there is no pillar box, no animation is needed.
@@ -351,13 +352,27 @@ public class TvViewUiManager {
mBackgroundColor = color;
}
- private void setTvViewPosition(final FrameLayout.LayoutParams layoutParams,
- FrameLayout.LayoutParams tvViewFrame, boolean animate) {
+ private void setTvViewPosition(
+ final FrameLayout.LayoutParams layoutParams,
+ FrameLayout.LayoutParams tvViewFrame,
+ boolean animate) {
if (DEBUG) {
- Log.d(TAG, "setTvViewPosition: w=" + layoutParams.width + " h=" + layoutParams.height
- + " s=" + layoutParams.getMarginStart() + " t=" + layoutParams.topMargin
- + " e=" + layoutParams.getMarginEnd() + " b=" + layoutParams.bottomMargin
- + " animate=" + animate);
+ Log.d(
+ TAG,
+ "setTvViewPosition: w="
+ + layoutParams.width
+ + " h="
+ + layoutParams.height
+ + " s="
+ + layoutParams.getMarginStart()
+ + " t="
+ + layoutParams.topMargin
+ + " e="
+ + layoutParams.getMarginEnd()
+ + " b="
+ + layoutParams.bottomMargin
+ + " animate="
+ + animate);
}
FrameLayout.LayoutParams oldTvViewFrame = mTvViewFrame;
mTvViewLayoutParams = layoutParams;
@@ -372,21 +387,25 @@ public class TvViewUiManager {
mOldTvViewFrame = new FrameLayout.LayoutParams(oldTvViewFrame);
}
mTvViewAnimator.setObjectValues(mTvView.getTvViewLayoutParams(), layoutParams);
- mTvViewAnimator.setEvaluator(new TypeEvaluator<FrameLayout.LayoutParams>() {
- FrameLayout.LayoutParams lp;
- @Override
- public FrameLayout.LayoutParams evaluate(float fraction,
- FrameLayout.LayoutParams startValue, FrameLayout.LayoutParams endValue) {
- if (lp == null) {
- lp = new FrameLayout.LayoutParams(0, 0);
- lp.gravity = startValue.gravity;
- }
- interpolateMargins(lp, startValue, endValue, fraction);
- return lp;
- }
- });
- mTvViewAnimator
- .setInterpolator(isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn);
+ mTvViewAnimator.setEvaluator(
+ new TypeEvaluator<FrameLayout.LayoutParams>() {
+ FrameLayout.LayoutParams lp;
+
+ @Override
+ public FrameLayout.LayoutParams evaluate(
+ float fraction,
+ FrameLayout.LayoutParams startValue,
+ FrameLayout.LayoutParams endValue) {
+ if (lp == null) {
+ lp = new FrameLayout.LayoutParams(0, 0);
+ lp.gravity = startValue.gravity;
+ }
+ interpolateMargins(lp, startValue, endValue, fraction);
+ return lp;
+ }
+ });
+ mTvViewAnimator.setInterpolator(
+ isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn);
mTvViewAnimator.start();
} else {
if (mTvViewAnimator != null && mTvViewAnimator.isStarted()) {
@@ -425,38 +444,42 @@ public class TvViewUiManager {
mTvViewAnimator.setProperty(
Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams"));
mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration));
- mTvViewAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCanceled = false;
+ mTvViewAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
- @Override
- public void onAnimationCancel(Animator animation) {
- mCanceled = true;
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCanceled) {
- mCanceled = false;
- return;
- }
- mHandler.post(new Runnable() {
@Override
- public void run() {
- setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false);
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) {
+ mCanceled = false;
+ return;
+ }
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false);
+ }
+ });
+ }
+ });
+ mTvViewAnimator.addUpdateListener(
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ float fraction = animator.getAnimatedFraction();
+ mLastAnimatedTvViewFrame =
+ (FrameLayout.LayoutParams) mTvView.getLayoutParams();
+ interpolateMargins(
+ mLastAnimatedTvViewFrame, mOldTvViewFrame, mTvViewFrame, fraction);
+ mTvView.setLayoutParams(mLastAnimatedTvViewFrame);
}
});
- }
- });
- mTvViewAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- float fraction = animator.getAnimatedFraction();
- mLastAnimatedTvViewFrame = (FrameLayout.LayoutParams) mTvView.getLayoutParams();
- interpolateMargins(mLastAnimatedTvViewFrame,
- mOldTvViewFrame, mTvViewFrame, fraction);
- mTvView.setLayoutParams(mLastAnimatedTvViewFrame);
- }
- });
}
private void initBackgroundAnimatorIfNeeded() {
@@ -467,31 +490,33 @@ public class TvViewUiManager {
mBackgroundAnimator = new ObjectAnimator();
mBackgroundAnimator.setTarget(mContentView);
mBackgroundAnimator.setPropertyName("backgroundColor");
- mBackgroundAnimator
- .setDuration(mResources.getInteger(R.integer.tvactivity_background_anim_duration));
- mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mHandler.post(new Runnable() {
+ mBackgroundAnimator.setDuration(
+ mResources.getInteger(R.integer.tvactivity_background_anim_duration));
+ mBackgroundAnimator.addListener(
+ new AnimatorListenerAdapter() {
@Override
- public void run() {
- mContentView.setBackgroundColor(mBackgroundColor);
+ public void onAnimationEnd(Animator animation) {
+ mHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mContentView.setBackgroundColor(mBackgroundColor);
+ }
+ });
}
});
- }
- });
}
- private void applyDisplayMode(float videoDisplayAspectRatio, boolean animate,
- boolean forceUpdate) {
+ private void applyDisplayMode(
+ float videoDisplayAspectRatio, boolean animate, boolean forceUpdate) {
if (videoDisplayAspectRatio <= 0f) {
videoDisplayAspectRatio = (float) mWindowWidth / mWindowHeight;
}
if (mAppliedDisplayedMode == mDisplayMode
&& mAppliedTvViewStartMargin == mTvViewStartMargin
&& mAppliedTvViewEndMargin == mTvViewEndMargin
- && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio) <
- DISPLAY_ASPECT_RATIO_EPSILON) {
+ && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio)
+ < DISPLAY_ASPECT_RATIO_EPSILON) {
if (!forceUpdate) {
return;
}
@@ -507,14 +532,20 @@ public class TvViewUiManager {
float availableAreaRatio = 0;
if (availableAreaWidth <= 0 || availableAreaHeight <= 0) {
displayMode = DisplayMode.MODE_FULL;
- Log.w(TAG, "Some resolution info is missing during applyDisplayMode. ("
- + "availableAreaWidth=" + availableAreaWidth + ", availableAreaHeight="
- + availableAreaHeight + ")");
+ Log.w(
+ TAG,
+ "Some resolution info is missing during applyDisplayMode. ("
+ + "availableAreaWidth="
+ + availableAreaWidth
+ + ", availableAreaHeight="
+ + availableAreaHeight
+ + ")");
} else {
availableAreaRatio = (float) availableAreaWidth / availableAreaHeight;
}
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0,
- ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity);
+ FrameLayout.LayoutParams layoutParams =
+ new FrameLayout.LayoutParams(
+ 0, 0, ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity);
switch (displayMode) {
case DisplayMode.MODE_ZOOM:
if (videoDisplayAspectRatio < availableAreaRatio) {
@@ -549,12 +580,18 @@ public class TvViewUiManager {
int marginStart = (availableAreaWidth - layoutParams.width) / 2;
layoutParams.setMarginStart(marginStart);
int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2;
- FrameLayout.LayoutParams tvViewFrame = createMarginLayoutParams(
- mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
+ FrameLayout.LayoutParams tvViewFrame =
+ createMarginLayoutParams(
+ mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
setTvViewPosition(layoutParams, tvViewFrame, animate);
- setBackgroundColor(mResources.getColor(isTvViewFullScreen()
- ? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview,
- null), layoutParams, animate);
+ setBackgroundColor(
+ mResources.getColor(
+ isTvViewFullScreen()
+ ? R.color.tvactivity_background
+ : R.color.tvactivity_background_on_shrunken_tvview,
+ null),
+ layoutParams,
+ animate);
// Update the current display mode.
mTvOptionsManager.onDisplayModeChanged(displayMode);
@@ -564,12 +601,15 @@ public class TvViewUiManager {
return (int) (start + (end - start) * fraction);
}
- private static void interpolateMargins(MarginLayoutParams out,
- MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction) {
+ private static void interpolateMargins(
+ MarginLayoutParams out,
+ MarginLayoutParams startValue,
+ MarginLayoutParams endValue,
+ float fraction) {
out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction);
out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction);
- out.setMarginStart(interpolate(startValue.getMarginStart(), endValue.getMarginStart(),
- fraction));
+ out.setMarginStart(
+ interpolate(startValue.getMarginStart(), endValue.getMarginStart(), fraction));
out.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction));
out.width = interpolate(startValue.width, endValue.width, fraction);
out.height = interpolate(startValue.height, endValue.height, fraction);
@@ -586,4 +626,4 @@ public class TvViewUiManager {
lp.height = mWindowHeight - topMargin - bottomMargin;
return lp;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/ViewUtils.java b/src/com/android/tv/ui/ViewUtils.java
index ac181752..f64a70b2 100644
--- a/src/com/android/tv/ui/ViewUtils.java
+++ b/src/com/android/tv/ui/ViewUtils.java
@@ -21,13 +21,10 @@ import android.animation.ValueAnimator;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
-
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-/**
- * A class that includes convenience methods for view classes.
- */
+/** A class that includes convenience methods for view classes. */
public class ViewUtils {
private static final String TAG = "ViewUtils";
@@ -40,49 +37,49 @@ public class ViewUtils {
try {
method = View.class.getDeclaredMethod("setTransitionAlpha", Float.TYPE);
method.invoke(v, alpha);
- } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException
- |InvocationTargetException e) {
+ } catch (NoSuchMethodException
+ | IllegalAccessException
+ | IllegalArgumentException
+ | InvocationTargetException e) {
Log.e(TAG, "Fail to call View.setTransitionAlpha", e);
}
}
/**
* Creates an animator in view's height
+ *
* @param target the {@link view} animator performs on.
*/
public static Animator createHeightAnimator(
final View target, int initialHeight, int targetHeight) {
ValueAnimator animator = ValueAnimator.ofInt(initialHeight, targetHeight);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int value = (Integer) animation.getAnimatedValue();
- if (value == 0) {
- if (target.getVisibility() != View.GONE) {
- target.setVisibility(View.GONE);
+ animator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int value = (Integer) animation.getAnimatedValue();
+ if (value == 0) {
+ if (target.getVisibility() != View.GONE) {
+ target.setVisibility(View.GONE);
+ }
+ } else {
+ if (target.getVisibility() != View.VISIBLE) {
+ target.setVisibility(View.VISIBLE);
+ }
+ setLayoutHeight(target, value);
+ }
}
- } else {
- if (target.getVisibility() != View.VISIBLE) {
- target.setVisibility(View.VISIBLE);
- }
- setLayoutHeight(target, value);
- }
- }
- });
+ });
return animator;
}
- /**
- * Gets view's layout height.
- */
+ /** Gets view's layout height. */
public static int getLayoutHeight(View view) {
LayoutParams layoutParams = view.getLayoutParams();
return layoutParams.height;
}
- /**
- * Sets view's layout height.
- */
+ /** Sets view's layout height. */
public static void setLayoutHeight(View view, int height) {
LayoutParams layoutParams = view.getLayoutParams();
if (height != layoutParams.height) {
@@ -90,4 +87,4 @@ public class ViewUtils {
view.setLayoutParams(layoutParams);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/hideable/AutoHideScheduler.java b/src/com/android/tv/ui/hideable/AutoHideScheduler.java
new file mode 100644
index 00000000..75859792
--- /dev/null
+++ b/src/com/android/tv/ui/hideable/AutoHideScheduler.java
@@ -0,0 +1,98 @@
+package com.android.tv.ui.hideable;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+import android.support.annotation.VisibleForTesting;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import com.android.tv.common.WeakHandler;
+
+/**
+ * Schedules a view element to be hidden after a delay.
+ *
+ * <p>When accessibility is turned on elements are not automatically hidden.
+ *
+ * <p>Users of this class must pass it to {@link
+ * AccessibilityManager#addAccessibilityStateChangeListener(AccessibilityStateChangeListener)} and
+ * {@link
+ * AccessibilityManager#removeAccessibilityStateChangeListener(AccessibilityStateChangeListener)}
+ * during the appropriate live cycle event, or handle calling {@link
+ * #onAccessibilityStateChanged(boolean)}.
+ */
+@UiThread
+public final class AutoHideScheduler implements AccessibilityStateChangeListener {
+ private static final int MSG_HIDE = 1;
+
+ private final HideHandler mHandler;
+ private final Runnable mRunnable;
+
+ public AutoHideScheduler(Context context, Runnable runnable) {
+ this(
+ runnable,
+ context.getSystemService(AccessibilityManager.class),
+ Looper.getMainLooper());
+ }
+
+ @VisibleForTesting
+ AutoHideScheduler(Runnable runnable, AccessibilityManager accessibilityManager, Looper looper) {
+ // Keep a reference here because HideHandler only has a weak reference to it.
+ mRunnable = runnable;
+ mHandler = new HideHandler(looper, mRunnable);
+ mHandler.setAllowAutoHide(!accessibilityManager.isEnabled());
+ }
+
+ public void cancel() {
+ mHandler.removeMessages(MSG_HIDE);
+ }
+
+ public void schedule(long delayMs) {
+ cancel();
+ if (mHandler.mAllowAutoHide) {
+ mHandler.sendEmptyMessageDelayed(MSG_HIDE, delayMs);
+ }
+ }
+
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ mHandler.onAccessibilityStateChanged(enabled);
+ }
+
+ public boolean isScheduled() {
+ return mHandler.hasMessages(MSG_HIDE);
+ }
+
+ private static class HideHandler extends WeakHandler<Runnable>
+ implements AccessibilityStateChangeListener {
+
+ private boolean mAllowAutoHide;
+
+ public HideHandler(Looper looper, Runnable hideRunner) {
+ super(looper, hideRunner);
+ }
+
+ @Override
+ protected void handleMessage(Message msg, @NonNull Runnable runnable) {
+ switch (msg.what) {
+ case MSG_HIDE:
+ if (mAllowAutoHide) {
+ runnable.run();
+ }
+ break;
+ default:
+ // do nothing
+ }
+ }
+
+ public void setAllowAutoHide(boolean mAllowAutoHide) {
+ this.mAllowAutoHide = mAllowAutoHide;
+ }
+
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ mAllowAutoHide = !enabled;
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/ActionItem.java b/src/com/android/tv/ui/sidepanel/ActionItem.java
index cd70a886..73c12080 100644
--- a/src/com/android/tv/ui/sidepanel/ActionItem.java
+++ b/src/com/android/tv/ui/sidepanel/ActionItem.java
@@ -18,7 +18,6 @@ package com.android.tv.ui.sidepanel;
import android.view.View;
import android.widget.TextView;
-
import com.android.tv.R;
public abstract class ActionItem extends Item {
@@ -52,4 +51,4 @@ public abstract class ActionItem extends Item {
descriptionView.setVisibility(View.GONE);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
index 8389675e..2726839c 100644
--- a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
+++ b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
@@ -19,14 +19,13 @@ package com.android.tv.ui.sidepanel;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
-
import com.android.tv.R;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.ChannelListener;
import com.android.tv.data.OnCurrentProgramUpdatedListener;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.api.Channel;
public abstract class ChannelCheckItem extends CompoundButtonItem {
private final ChannelDataManager mChannelDataManager;
@@ -34,25 +33,27 @@ public abstract class ChannelCheckItem extends CompoundButtonItem {
private Channel mChannel;
private TextView mProgramTitleView;
private TextView mChannelNumberView;
- private final ChannelListener mChannelListener = new ChannelListener() {
- @Override
- public void onChannelRemoved(Channel channel) { }
-
- @Override
- public void onChannelUpdated(Channel channel) {
- mChannel = channel;
- }
- };
-
- private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener
- = new OnCurrentProgramUpdatedListener() {
- @Override
- public void onCurrentProgramUpdated(long channelId, Program program) {
- updateProgramTitle(program);
- }
- };
-
- public ChannelCheckItem(Channel channel,
+ private final ChannelListener mChannelListener =
+ new ChannelListener() {
+ @Override
+ public void onChannelRemoved(Channel channel) {}
+
+ @Override
+ public void onChannelUpdated(Channel channel) {
+ mChannel = channel;
+ }
+ };
+
+ private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener =
+ new OnCurrentProgramUpdatedListener() {
+ @Override
+ public void onCurrentProgramUpdated(long channelId, Program program) {
+ updateProgramTitle(program);
+ }
+ };
+
+ public ChannelCheckItem(
+ Channel channel,
ChannelDataManager channelDataManager,
ProgramDataManager programDataManager) {
super(channel.getDisplayName(), "");
@@ -91,8 +92,8 @@ public abstract class ChannelCheckItem extends CompoundButtonItem {
mChannelNumberView = (TextView) view.findViewById(R.id.channel_number);
mProgramTitleView = (TextView) view.findViewById(R.id.program_title);
mChannelDataManager.addChannelListener(mChannel.getId(), mChannelListener);
- mProgramDataManager.addOnCurrentProgramUpdatedListener(mChannel.getId(),
- mOnCurrentProgramUpdatedListener);
+ mProgramDataManager.addOnCurrentProgramUpdatedListener(
+ mChannel.getId(), mOnCurrentProgramUpdatedListener);
}
@Override
@@ -105,8 +106,8 @@ public abstract class ChannelCheckItem extends CompoundButtonItem {
@Override
protected void onUnbind() {
mChannelDataManager.removeChannelListener(mChannel.getId(), mChannelListener);
- mProgramDataManager.removeOnCurrentProgramUpdatedListener(mChannel.getId(),
- mOnCurrentProgramUpdatedListener);
+ mProgramDataManager.removeOnCurrentProgramUpdatedListener(
+ mChannel.getId(), mOnCurrentProgramUpdatedListener);
mProgramTitleView = null;
mChannelNumberView = null;
super.onUnbind();
diff --git a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
index 79c2b0a7..ef828945 100644
--- a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
+++ b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
@@ -22,7 +22,6 @@ import android.view.View;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.tv.R;
public class CheckBoxItem extends CompoundButtonItem {
@@ -46,16 +45,17 @@ public class CheckBoxItem extends CompoundButtonItem {
super.onBind(view);
if (mLayoutForLargeDescription) {
CompoundButton checkBox = (CompoundButton) view.findViewById(getCompoundButtonId());
- LinearLayout.LayoutParams lp =
- (LinearLayout.LayoutParams) checkBox.getLayoutParams();
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) checkBox.getLayoutParams();
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.topMargin = view.getResources().getDimensionPixelOffset(
- R.dimen.option_item_check_box_margin_top);
+ lp.topMargin =
+ view.getResources()
+ .getDimensionPixelOffset(R.dimen.option_item_check_box_margin_top);
checkBox.setLayoutParams(lp);
TypedValue outValue = new TypedValue();
- view.getResources().getValue(R.dimen.option_item_check_box_line_spacing_multiplier,
- outValue, true);
+ view.getResources()
+ .getValue(
+ R.dimen.option_item_check_box_line_spacing_multiplier, outValue, true);
TextView descriptionTextView = (TextView) view.findViewById(getDescriptionViewId());
descriptionTextView.setMaxLines(Integer.MAX_VALUE);
@@ -77,4 +77,4 @@ public class CheckBoxItem extends CompoundButtonItem {
protected void onSelected() {
setChecked(!isChecked());
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
index 341e4350..c1e1d18a 100644
--- a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
@@ -23,16 +23,14 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.tv.R;
import com.android.tv.util.CaptionSettings;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class ClosedCaptionFragment extends SideFragment {
- private static final String TRACKER_LABEL ="closed caption" ;
+ private static final String TRACKER_LABEL = "closed caption";
private boolean mResetClosedCaption;
private int mClosedCaptionOption;
private String mClosedCaptionLanguage;
@@ -66,8 +64,10 @@ public class ClosedCaptionFragment extends SideFragment {
List<TvTrackInfo> tracks = getMainActivity().getTracks(TvTrackInfo.TYPE_SUBTITLE);
if (tracks != null && !tracks.isEmpty()) {
- String selectedTrackId = captionSettings.isEnabled() ?
- getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE) : null;
+ String selectedTrackId =
+ captionSettings.isEnabled()
+ ? getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE)
+ : null;
ClosedCaptionOptionItem item = new ClosedCaptionOptionItem(null, null);
items.add(item);
if (selectedTrackId == null) {
@@ -86,37 +86,41 @@ public class ClosedCaptionFragment extends SideFragment {
}
}
if (getMainActivity().hasCaptioningSettingsActivity()) {
- items.add(new ActionItem(getString(R.string.closed_caption_system_settings),
- getString(R.string.closed_caption_system_settings_description)) {
- @Override
- protected void onSelected() {
- getMainActivity().startSystemCaptioningSettingsActivity();
- }
-
- @Override
- protected void onFocused() {
- super.onFocused();
- if (mSelectedItem != null) {
- getMainActivity().selectSubtitleTrack(
- mSelectedItem.mOption, mSelectedItem.mTrackId);
- }
- }
- });
+ items.add(
+ new ActionItem(
+ getString(R.string.closed_caption_system_settings),
+ getString(R.string.closed_caption_system_settings_description)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity().startSystemCaptioningSettingsActivity();
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ if (mSelectedItem != null) {
+ getMainActivity()
+ .selectSubtitleTrack(
+ mSelectedItem.mOption, mSelectedItem.mTrackId);
+ }
+ }
+ });
}
return items;
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onDestroyView() {
if (mResetClosedCaption) {
- getMainActivity().selectSubtitleLanguage(mClosedCaptionOption, mClosedCaptionLanguage,
- mClosedCaptionTrackId);
+ getMainActivity()
+ .selectSubtitleLanguage(
+ mClosedCaptionOption, mClosedCaptionLanguage, mClosedCaptionTrackId);
}
super.onDestroyView();
}
diff --git a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
index c2746937..cc09affb 100644
--- a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
+++ b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
@@ -19,7 +19,6 @@ package com.android.tv.ui.sidepanel;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.TextView;
-
import com.android.tv.R;
public abstract class CompoundButtonItem extends Item {
@@ -44,8 +43,8 @@ public abstract class CompoundButtonItem extends Item {
mMaxLine = 0;
}
- public CompoundButtonItem(String checkedTitle, String uncheckedTitle, String description,
- int maxLine) {
+ public CompoundButtonItem(
+ String checkedTitle, String uncheckedTitle, String description, int maxLine) {
mCheckedTitle = checkedTitle;
mUncheckedTitle = uncheckedTitle;
mDescription = description;
@@ -73,8 +72,10 @@ public abstract class CompoundButtonItem extends Item {
descriptionView.setMaxLines(mMaxLine);
} else {
if (sDefaultMaxLine == 0) {
- sDefaultMaxLine = view.getContext().getResources()
- .getInteger(R.integer.option_item_description_max_lines);
+ sDefaultMaxLine =
+ view.getContext()
+ .getResources()
+ .getInteger(R.integer.option_item_description_max_lines);
}
descriptionView.setMaxLines(sDefaultMaxLine);
}
diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
index 297e69d9..48b80723 100644
--- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
+++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
@@ -26,16 +26,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.common.SharedPreferencesUtils;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.SharedPreferencesUtils;
+import com.android.tv.data.ChannelImpl;
import com.android.tv.data.ChannelNumber;
+import com.android.tv.data.api.Channel;
import com.android.tv.ui.OnRepeatedKeyInterceptListener;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -55,7 +54,7 @@ public class CustomizeChannelListFragment extends SideFragment {
private static Integer sGroupingType;
private TvInputManagerHelper mInputManager;
- private Channel.DefaultComparator mChannelComparator;
+ private ChannelImpl.DefaultComparator mChannelComparator;
private boolean mGroupByFragmentRunning;
private final List<Item> mItems = new ArrayList<>();
@@ -65,38 +64,46 @@ public class CustomizeChannelListFragment extends SideFragment {
super.onCreate(savedInstanceState);
mInputManager = getMainActivity().getTvInputManagerHelper();
mInitialChannelId = getMainActivity().getCurrentChannelId();
- mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager);
+ mChannelComparator = new ChannelImpl.DefaultComparator(getActivity(), mInputManager);
if (sGroupingType == null) {
- SharedPreferences sharedPreferences = getContext().getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE);
+ SharedPreferences sharedPreferences =
+ getContext()
+ .getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS,
+ Context.MODE_PRIVATE);
sGroupingType = sharedPreferences.getInt(PREF_KEY_GROUP_SETTINGS, GROUP_BY_SOURCE);
}
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list);
- listView.setOnKeyInterceptListener(new OnRepeatedKeyInterceptListener(listView) {
- @Override
- public boolean onInterceptKeyEvent(KeyEvent event) {
- // In order to send tune operation once for continuous channel up/down events,
- // we only call the moveToChannel method on ACTION_UP event of channel switch keys.
- if (event.getAction() == KeyEvent.ACTION_UP) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (mLastFocusedChannelId != Channel.INVALID_ID) {
- getMainActivity().tuneToChannel(
- getChannelDataManager().getChannel(mLastFocusedChannelId));
+ listView.setOnKeyInterceptListener(
+ new OnRepeatedKeyInterceptListener(listView) {
+ @Override
+ public boolean onInterceptKeyEvent(KeyEvent event) {
+ // In order to send tune operation once for continuous channel up/down
+ // events,
+ // we only call the moveToChannel method on ACTION_UP event of channel
+ // switch keys.
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (mLastFocusedChannelId != Channel.INVALID_ID) {
+ getMainActivity()
+ .tuneToChannel(
+ getChannelDataManager()
+ .getChannel(mLastFocusedChannelId));
+ }
+ break;
}
- break;
+ }
+ return super.onInterceptKeyEvent(event);
}
- }
- return super.onInterceptKeyEvent(event);
- }
- });
+ });
if (!mGroupByFragmentRunning) {
getMainActivity().startShrunkenTvView(false, true);
@@ -118,8 +125,8 @@ public class CustomizeChannelListFragment extends SideFragment {
}
mLastFocusedChannelId = mInitialChannelId;
MainActivity tvActivity = getMainActivity();
- if (mLastFocusedChannelId != Channel.INVALID_ID &&
- mLastFocusedChannelId != tvActivity.getCurrentChannelId()) {
+ if (mLastFocusedChannelId != Channel.INVALID_ID
+ && mLastFocusedChannelId != tvActivity.getCurrentChannelId()) {
tvActivity.tuneToChannel(getChannelDataManager().getChannel(mLastFocusedChannelId));
}
}
@@ -184,11 +191,11 @@ public class CustomizeChannelListFragment extends SideFragment {
Collections.sort(channels, mChannelComparator);
String inputId = null;
- for (Channel channel: channels) {
+ for (Channel channel : channels) {
if (!channel.getInputId().equals(inputId)) {
inputId = channel.getInputId();
- String inputLabel = Utils.loadLabel(getActivity(),
- mInputManager.getTvInputInfo(inputId));
+ String inputLabel =
+ Utils.loadLabel(getActivity(), mInputManager.getTvInputInfo(inputId));
items.add(new DividerItem(inputLabel));
selectGroupItem = new SelectGroupItem();
items.add(selectGroupItem);
@@ -204,27 +211,32 @@ public class CustomizeChannelListFragment extends SideFragment {
items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_hd_sd)));
SelectGroupItem selectGroupItem = null;
ArrayList<Channel> channels = new ArrayList<>(mChannels);
- Collections.sort(channels, new Comparator<Channel>() {
- @Override
- public int compare(Channel lhs, Channel rhs) {
- boolean lhsHd = isHdChannel(lhs);
- boolean rhsHd = isHdChannel(rhs);
- if (lhsHd == rhsHd) {
- return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
- } else {
- return lhsHd ? -1 : 1;
- }
- }
- });
+ Collections.sort(
+ channels,
+ new Comparator<Channel>() {
+ @Override
+ public int compare(Channel lhs, Channel rhs) {
+ boolean lhsHd = isHdChannel(lhs);
+ boolean rhsHd = isHdChannel(rhs);
+ if (lhsHd == rhsHd) {
+ return ChannelNumber.compare(
+ lhs.getDisplayNumber(), rhs.getDisplayNumber());
+ } else {
+ return lhsHd ? -1 : 1;
+ }
+ }
+ });
Boolean isHdGroup = null;
- for (Channel channel: channels) {
+ for (Channel channel : channels) {
boolean isHd = isHdChannel(channel);
if (isHdGroup == null || isHd != isHdGroup) {
isHdGroup = isHd;
- items.add(new DividerItem(isHd
- ? getString(R.string.edit_channels_group_divider_for_hd)
- : getString(R.string.edit_channels_group_divider_for_sd)));
+ items.add(
+ new DividerItem(
+ isHd
+ ? getString(R.string.edit_channels_group_divider_for_hd)
+ : getString(R.string.edit_channels_group_divider_for_sd)));
selectGroupItem = new SelectGroupItem();
items.add(selectGroupItem);
}
@@ -237,8 +249,8 @@ public class CustomizeChannelListFragment extends SideFragment {
private static boolean isHdChannel(Channel channel) {
String videoFormat = channel.getVideoFormat();
- return videoFormat != null &&
- (Channels.VIDEO_FORMAT_720P.equals(videoFormat)
+ return videoFormat != null
+ && (Channels.VIDEO_FORMAT_720P.equals(videoFormat)
|| Channels.VIDEO_FORMAT_1080I.equals(videoFormat)
|| Channels.VIDEO_FORMAT_1080P.equals(videoFormat)
|| Channels.VIDEO_FORMAT_2160P.equals(videoFormat)
@@ -274,9 +286,11 @@ public class CustomizeChannelListFragment extends SideFragment {
break;
}
}
- mTextView.setText(getString(mAllChecked
- ? R.string.edit_channels_item_deselect_group
- : R.string.edit_channels_item_select_group));
+ mTextView.setText(
+ getString(
+ mAllChecked
+ ? R.string.edit_channels_item_deselect_group
+ : R.string.edit_channels_item_select_group));
}
@Override
@@ -290,9 +304,11 @@ public class CustomizeChannelListFragment extends SideFragment {
}
getChannelDataManager().notifyChannelBrowsableChanged();
mAllChecked = !mAllChecked;
- mTextView.setText(getString(mAllChecked
- ? R.string.edit_channels_item_deselect_group
- : R.string.edit_channels_item_select_group));
+ mTextView.setText(
+ getString(
+ mAllChecked
+ ? R.string.edit_channels_item_deselect_group
+ : R.string.edit_channels_item_select_group));
}
}
@@ -331,6 +347,7 @@ public class CustomizeChannelListFragment extends SideFragment {
protected String getTitle() {
return getString(R.string.side_panel_title_group_by);
}
+
@Override
public String getTrackerLabel() {
return GroupBySubMenu.TRACKER_LABEL;
@@ -339,40 +356,46 @@ public class CustomizeChannelListFragment extends SideFragment {
@Override
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
- items.add(new RadioButtonItem(
- getString(R.string.edit_channels_group_by_sources)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- setGroupingType(GROUP_BY_SOURCE);
- closeFragment();
- }
- });
- items.add(new RadioButtonItem(
- getString(R.string.edit_channels_group_by_hd_sd)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- setGroupingType(GROUP_BY_HD_SD);
- closeFragment();
- }
- });
+ items.add(
+ new RadioButtonItem(getString(R.string.edit_channels_group_by_sources)) {
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ setGroupingType(GROUP_BY_SOURCE);
+ closeFragment();
+ }
+ });
+ items.add(
+ new RadioButtonItem(getString(R.string.edit_channels_group_by_hd_sd)) {
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ setGroupingType(GROUP_BY_HD_SD);
+ closeFragment();
+ }
+ });
((RadioButtonItem) items.get(sGroupingType)).setChecked(true);
return items;
}
private void setGroupingType(int groupingType) {
sGroupingType = groupingType;
- SharedPreferences sharedPreferences = getContext().getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE);
+ SharedPreferences sharedPreferences =
+ getContext()
+ .getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS,
+ Context.MODE_PRIVATE);
sharedPreferences.edit().putInt(PREF_KEY_GROUP_SETTINGS, groupingType).apply();
}
}
private class GroupBySubMenu extends SubMenuItem {
private static final String TRACKER_LABEL = "Group by";
+
public GroupBySubMenu(String description) {
- super(getString(R.string.edit_channels_item_group_by), description,
+ super(
+ getString(R.string.edit_channels_item_group_by),
+ description,
getMainActivity().getOverlayManager().getSideFragmentManager());
}
diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
index f633fa5a..36ee5a2d 100644
--- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
@@ -21,22 +21,20 @@ import android.app.Activity;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
-
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.BuildConfig;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.CommonPreferences;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.data.epg.EpgFetcher;
-import com.android.tv.experiments.Experiments;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.util.Utils;
+import com.android.tv.common.util.CommonUtils;
+
+
+
+
import java.util.ArrayList;
import java.util.List;
-/**
- * Options for developers only
- */
+/** Options for developers only */
public class DeveloperOptionFragment extends SideFragment {
private static final String TAG = "DeveloperOptionFragment";
private static final String TRACKER_LABEL = "debug options";
@@ -55,42 +53,46 @@ public class DeveloperOptionFragment extends SideFragment {
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
if (CommonFeatures.DVR.isEnabled(getContext())) {
- items.add(new ActionItem(getString(R.string.dev_item_dvr_history)) {
- @Override
- protected void onSelected() {
- getMainActivity().getOverlayManager().showDvrHistoryDialog();
- }
- });
+ items.add(
+ new ActionItem(getString(R.string.dev_item_dvr_history)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity().getOverlayManager().showDvrHistoryDialog();
+ }
+ });
}
- if (Utils.isDeveloper()) {
- items.add(new ActionItem(getString(R.string.dev_item_watch_history)) {
- @Override
- protected void onSelected() {
- getMainActivity().getOverlayManager().showRecentlyWatchedDialog();
- }
- });
+ if (CommonUtils.isDeveloper()) {
+ items.add(
+ new ActionItem(getString(R.string.dev_item_watch_history)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity().getOverlayManager().showRecentlyWatchedDialog();
+ }
+ });
}
- items.add(new SwitchItem(getString(R.string.dev_item_store_ts_on),
- getString(R.string.dev_item_store_ts_off),
- getString(R.string.dev_item_store_ts_description)) {
- @Override
- protected void onUpdate() {
- super.onUpdate();
- setChecked(TunerPreferences.getStoreTsStream(getContext()));
- }
+ items.add(
+ new SwitchItem(
+ getString(R.string.dev_item_store_ts_on),
+ getString(R.string.dev_item_store_ts_off),
+ getString(R.string.dev_item_store_ts_description)) {
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(CommonPreferences.getStoreTsStream(getContext()));
+ }
- @Override
- protected void onSelected() {
- super.onSelected();
- TunerPreferences.setStoreTsStream(getContext(), isChecked());
- }
- });
- if (Utils.isDeveloper()) {
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ CommonPreferences.setStoreTsStream(getContext(), isChecked());
+ }
+ });
+ if (CommonUtils.isDeveloper()) {
items.add(
new ActionItem(getString(R.string.dev_item_show_performance_monitor_log)) {
@Override
protected void onSelected() {
- TvApplication.getSingletons(getContext())
+ TvSingletons.getSingletons(getContext())
.getPerformanceMonitor()
.startPerformanceMonitorEventDebugActivity(getContext());
}
@@ -98,5 +100,4 @@ public class DeveloperOptionFragment extends SideFragment {
}
return items;
}
-
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java b/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java
index 29792757..c51fb38e 100644
--- a/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java
+++ b/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java
@@ -17,11 +17,9 @@
package com.android.tv.ui.sidepanel;
import android.content.Context;
-
import com.android.tv.R;
import com.android.tv.data.DisplayMode;
import com.android.tv.ui.TvViewUiManager;
-
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/ui/sidepanel/DividerItem.java b/src/com/android/tv/ui/sidepanel/DividerItem.java
index 0edf8488..3c237ffc 100644
--- a/src/com/android/tv/ui/sidepanel/DividerItem.java
+++ b/src/com/android/tv/ui/sidepanel/DividerItem.java
@@ -18,14 +18,13 @@ package com.android.tv.ui.sidepanel;
import android.view.View;
import android.widget.TextView;
-
import com.android.tv.R;
public class DividerItem extends Item {
private TextView mTitleView;
private String mTitle;
- public DividerItem() { }
+ public DividerItem() {}
public DividerItem(String title) {
mTitle = title;
@@ -46,8 +45,10 @@ public class DividerItem extends Item {
} else {
mTitleView.setVisibility(View.VISIBLE);
mTitleView.setText(mTitle);
- view.setMinimumHeight(view.getContext().getResources().getDimensionPixelOffset(
- R.dimen.option_item_height));
+ view.setMinimumHeight(
+ view.getContext()
+ .getResources()
+ .getDimensionPixelOffset(R.dimen.option_item_height));
}
}
@@ -57,5 +58,5 @@ public class DividerItem extends Item {
}
@Override
- protected void onSelected() { }
+ protected void onSelected() {}
}
diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java
index 4e47e75b..693b7536 100644
--- a/src/com/android/tv/ui/sidepanel/Item.java
+++ b/src/com/android/tv/ui/sidepanel/Item.java
@@ -35,9 +35,7 @@ public abstract class Item {
}
}
- /**
- * Sets the item to be clickable or not.
- */
+ /** Sets the item to be clickable or not. */
public void setClickable(boolean clickable) {
mClickable = clickable;
if (mItemView != null) {
@@ -45,9 +43,7 @@ public abstract class Item {
}
}
- /**
- * Returns whether this item is enabled.
- */
+ /** Returns whether this item is enabled. */
public boolean isEnabled() {
return mEnabled;
}
@@ -69,9 +65,9 @@ public abstract class Item {
}
/**
- * Called after onBind is called and when {@link #notifyUpdated} is called.
- * {@link #notifyUpdated} is usually called by {@link SideFragment#notifyItemChanged} and
- * {@link SideFragment#notifyItemsChanged}.
+ * Called after onBind is called and when {@link #notifyUpdated} is called. {@link
+ * #notifyUpdated} is usually called by {@link SideFragment#notifyItemChanged} and {@link
+ * SideFragment#notifyItemsChanged}.
*/
protected void onUpdate() {
setEnabledInternal(mItemView, mEnabled);
@@ -80,12 +76,9 @@ public abstract class Item {
protected abstract void onSelected();
- protected void onFocused() {
- }
+ protected void onFocused() {}
- /**
- * Returns true if the item is bound, i.e., onBind is called.
- */
+ /** Returns true if the item is bound, i.e., onBind is called. */
protected boolean isBound() {
return mItemView != null;
}
@@ -100,4 +93,4 @@ public abstract class Item {
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java b/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java
index 10c8c3e2..03b71c8c 100644
--- a/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java
+++ b/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java
@@ -19,10 +19,8 @@ package com.android.tv.ui.sidepanel;
import android.media.tv.TvTrackInfo;
import android.text.TextUtils;
import android.view.KeyEvent;
-
import com.android.tv.R;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
@@ -56,9 +54,11 @@ public class MultiAudioFragment extends SideFragment {
boolean needToShowSampleRate = Utils.needToShowSampleRate(getActivity(), tracks);
int pos = 0;
for (final TvTrackInfo track : tracks) {
- RadioButtonItem item = new MultiAudioOptionItem(
- Utils.getMultiAudioString(getActivity(), track, needToShowSampleRate),
- track.getId());
+ RadioButtonItem item =
+ new MultiAudioOptionItem(
+ Utils.getMultiAudioString(
+ getActivity(), track, needToShowSampleRate),
+ track.getId());
if (track.getId().equals(mSelectedTrackId)) {
item.setChecked(true);
mInitialSelectedPosition = pos;
diff --git a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
index e0477493..b6c36795 100644
--- a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
+++ b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
@@ -41,4 +41,4 @@ public class RadioButtonItem extends CompoundButtonItem {
protected void onSelected() {
setChecked(true);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
index 6a5b510c..31d00fa6 100644
--- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
@@ -16,32 +16,28 @@
package com.android.tv.ui.sidepanel;
-import static com.android.tv.Features.TUNER;
+import static com.android.tv.TvFeatures.TUNER;
import android.app.ApplicationErrorReport;
import android.content.Intent;
import android.media.tv.TvInputInfo;
import android.view.View;
import android.widget.Toast;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.customization.TvCustomizationManager;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.CommonPreferences;
+import com.android.tv.common.customization.CustomizationManager;
+import com.android.tv.common.util.PermissionUtils;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.license.LicenseSideFragment;
import com.android.tv.license.Licenses;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.util.PermissionUtils;
-import com.android.tv.util.SetupUtils;
import com.android.tv.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
-/**
- * Shows Live TV settings.
- */
+/** Shows Live TV settings. */
public class SettingsFragment extends SideFragment {
private static final String TRACKER_LABEL = "settings";
@@ -58,58 +54,73 @@ public class SettingsFragment extends SideFragment {
@Override
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
- final Item customizeChannelListItem = new SubMenuItem(
- getString(R.string.settings_channel_source_item_customize_channels),
- getString(R.string.settings_channel_source_item_customize_channels_description),
- getMainActivity().getOverlayManager().getSideFragmentManager()) {
- @Override
- protected SideFragment getFragment() {
- return new CustomizeChannelListFragment();
- }
+ final Item customizeChannelListItem =
+ new SubMenuItem(
+ getString(R.string.settings_channel_source_item_customize_channels),
+ getString(
+ R.string
+ .settings_channel_source_item_customize_channels_description),
+ getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ @Override
+ protected SideFragment getFragment() {
+ return new CustomizeChannelListFragment();
+ }
- @Override
- protected void onBind(View view) {
- super.onBind(view);
- setEnabled(false);
- }
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ setEnabled(false);
+ }
- @Override
- protected void onUpdate() {
- super.onUpdate();
- setEnabled(getChannelDataManager().getChannelCount() != 0);
- }
- };
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setEnabled(getChannelDataManager().getChannelCount() != 0);
+ }
+ };
customizeChannelListItem.setEnabled(false);
items.add(customizeChannelListItem);
final MainActivity activity = getMainActivity();
- boolean hasNewInput = SetupUtils.getInstance(activity)
- .hasNewInput(activity.getTvInputManagerHelper());
- items.add(new ActionItem(
- getString(R.string.settings_channel_source_item_setup),
- hasNewInput ? getString(R.string.settings_channel_source_item_setup_new_inputs)
- : null) {
- @Override
- protected void onSelected() {
- closeFragment();
- activity.getOverlayManager().showSetupFragment();
- }
- });
+ boolean hasNewInput =
+ TvSingletons.getSingletons(getContext())
+ .getSetupUtils()
+ .hasNewInput(activity.getTvInputManagerHelper());
+ items.add(
+ new ActionItem(
+ getString(R.string.settings_channel_source_item_setup),
+ hasNewInput
+ ? getString(R.string.settings_channel_source_item_setup_new_inputs)
+ : null) {
+ @Override
+ protected void onSelected() {
+ closeFragment();
+ activity.getOverlayManager().showSetupFragment();
+ }
+ });
if (PermissionUtils.hasModifyParentalControls(getMainActivity())) {
- items.add(new ActionItem(
- getString(R.string.settings_parental_controls), getString(
- activity.getParentalControlSettings().isParentalControlsEnabled()
- ? R.string.option_toggle_parental_controls_on
- : R.string.option_toggle_parental_controls_off)) {
- @Override
- protected void onSelected() {
- getMainActivity().getOverlayManager()
- .getSideFragmentManager().hideSidePanel(true);
- PinDialogFragment fragment = PinDialogFragment
- .create(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN);
- getMainActivity().getOverlayManager()
- .showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true);
- }
- });
+ items.add(
+ new ActionItem(
+ getString(R.string.settings_parental_controls),
+ getString(
+ activity.getParentalControlSettings()
+ .isParentalControlsEnabled()
+ ? R.string.option_toggle_parental_controls_on
+ : R.string.option_toggle_parental_controls_off)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
+ .hideSidePanel(true);
+ PinDialogFragment fragment =
+ PinDialogFragment.create(
+ PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN);
+ getMainActivity()
+ .getOverlayManager()
+ .showDialogFragment(
+ PinDialogFragment.DIALOG_TAG, fragment, true);
+ }
+ });
} else {
// Note: parental control is turned off, when MODIFY_PARENTAL_CONTROLS is not granted.
// But, we may be able to turn on channel lock feature regardless of the permission.
@@ -117,8 +128,10 @@ public class SettingsFragment extends SideFragment {
}
boolean showTrickplaySetting = false;
if (TUNER.isEnabled(getContext())) {
- for (TvInputInfo inputInfo : TvApplication.getSingletons(getContext())
- .getTvInputManagerHelper().getTvInputInfos(true, true)) {
+ for (TvInputInfo inputInfo :
+ TvSingletons.getSingletons(getContext())
+ .getTvInputManagerHelper()
+ .getTvInputInfos(true, true)) {
if (Utils.isInternalTvInput(getContext(), inputInfo.getId())) {
showTrickplaySetting = true;
break;
@@ -126,46 +139,51 @@ public class SettingsFragment extends SideFragment {
}
if (showTrickplaySetting) {
showTrickplaySetting =
- TvCustomizationManager.getTrickplayMode(getContext())
- == TvCustomizationManager.TRICKPLAY_MODE_ENABLED;
+ CustomizationManager.getTrickplayMode(getContext())
+ == CustomizationManager.TRICKPLAY_MODE_ENABLED;
}
}
if (showTrickplaySetting) {
items.add(
- new SwitchItem(getString(R.string.settings_trickplay),
+ new SwitchItem(
+ getString(R.string.settings_trickplay),
getString(R.string.settings_trickplay),
getString(R.string.settings_trickplay_description),
getResources().getInteger(R.integer.trickplay_description_max_lines)) {
@Override
protected void onUpdate() {
super.onUpdate();
- boolean enabled = TunerPreferences.getTrickplaySetting(getContext())
- != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
+ boolean enabled =
+ CommonPreferences.getTrickplaySetting(getContext())
+ != CommonPreferences.TRICKPLAY_SETTING_DISABLED;
setChecked(enabled);
}
@Override
protected void onSelected() {
super.onSelected();
- @TunerPreferences.TrickplaySetting int setting =
- isChecked() ? TunerPreferences.TRICKPLAY_SETTING_ENABLED
- : TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- TunerPreferences.setTrickplaySetting(getContext(), setting);
+ @CommonPreferences.TrickplaySetting
+ int setting =
+ isChecked()
+ ? CommonPreferences.TRICKPLAY_SETTING_ENABLED
+ : CommonPreferences.TRICKPLAY_SETTING_DISABLED;
+ CommonPreferences.setTrickplaySetting(getContext(), setting);
}
});
}
- items.add(new ActionItem(getString(R.string.settings_send_feedback)) {
- @Override
- protected void onSelected() {
- Intent intent = new Intent(Intent.ACTION_APP_ERROR);
- ApplicationErrorReport report = new ApplicationErrorReport();
- report.packageName = report.processName = getContext().getPackageName();
- report.time = System.currentTimeMillis();
- report.type = ApplicationErrorReport.TYPE_NONE;
- intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
- startActivityForResult(intent, 0);
- }
- });
+ items.add(
+ new ActionItem(getString(R.string.settings_send_feedback)) {
+ @Override
+ protected void onSelected() {
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ ApplicationErrorReport report = new ApplicationErrorReport();
+ report.packageName = report.processName = getContext().getPackageName();
+ report.time = System.currentTimeMillis();
+ report.type = ApplicationErrorReport.TYPE_NONE;
+ intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
+ startActivityForResult(intent, 0);
+ }
+ });
if (Licenses.hasLicenses(getContext())) {
items.add(
new SubMenuItem(
@@ -178,8 +196,10 @@ public class SettingsFragment extends SideFragment {
});
}
// Show version.
- SimpleActionItem version = new SimpleActionItem(getString(R.string.settings_menu_version),
- ((TvApplication) activity.getApplicationContext()).getVersionName());
+ SimpleActionItem version =
+ new SimpleActionItem(
+ getString(R.string.settings_menu_version),
+ ((TvApplication) activity.getApplicationContext()).getVersionName());
version.setClickable(false);
items.add(version);
return items;
diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java
index 6bd921a2..2902ea7f 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragment.java
@@ -28,18 +28,16 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.util.DurationTimer;
+import com.android.tv.TvSingletons;
import com.android.tv.analytics.HasTrackerLabel;
import com.android.tv.analytics.Tracker;
+import com.android.tv.common.util.DurationTimer;
+import com.android.tv.common.util.SystemProperties;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ProgramDataManager;
-import com.android.tv.util.SystemProperties;
import com.android.tv.util.ViewCache;
-
import java.util.List;
public abstract class SideFragment<T extends Item> extends Fragment implements HasTrackerLabel {
@@ -74,8 +72,8 @@ public abstract class SideFragment<T extends Item> extends Fragment implements H
/**
* @param hideKey the KeyCode used to hide the fragment
- * @param debugHideKey the KeyCode used to hide the fragment if
- * {@link SystemProperties#USE_DEBUG_KEYS}.
+ * @param debugHideKey the KeyCode used to hide the fragment if {@link
+ * SystemProperties#USE_DEBUG_KEYS}.
*/
public SideFragment(int hideKey, int debugHideKey) {
mHideKey = hideKey;
@@ -87,14 +85,15 @@ public abstract class SideFragment<T extends Item> extends Fragment implements H
super.onAttach(context);
mChannelDataManager = getMainActivity().getChannelDataManager();
mProgramDataManager = getMainActivity().getProgramDataManager();
- mTracker = TvApplication.getSingletons(context).getTracker();
+ mTracker = TvSingletons.getSingletons(context).getTracker();
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = ViewCache.getInstance().getOrCreateView(
- inflater, getFragmentLayoutResourceId(), container);
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view =
+ ViewCache.getInstance()
+ .getOrCreateView(inflater, getFragmentLayoutResourceId(), container);
TextView textView = (TextView) view.findViewById(R.id.side_panel_title);
textView.setText(getTitle());
@@ -131,8 +130,8 @@ public abstract class SideFragment<T extends Item> extends Fragment implements H
public final boolean isHideKeyForThisPanel(int keyCode) {
boolean debugKeysEnabled = SystemProperties.USE_DEBUG_KEYS.getValue();
- return mHideKey != KeyEvent.KEYCODE_UNKNOWN &&
- (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode));
+ return mHideKey != KeyEvent.KEYCODE_UNKNOWN
+ && (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode));
}
@Override
@@ -195,16 +194,14 @@ public abstract class SideFragment<T extends Item> extends Fragment implements H
item.notifyUpdated();
}
- /**
- * Notifies all items of ItemAdapter has changed without structural changes.
- */
+ /** Notifies all items of ItemAdapter has changed without structural changes. */
protected void notifyItemsChanged() {
notifyItemsChanged(0, mAdapter.getItemCount());
}
/**
- * Notifies some items of ItemAdapter has changed starting from position
- * <code>positionStart</code> to the end without structural changes.
+ * Notifies some items of ItemAdapter has changed starting from position <code>positionStart
+ * </code> to the end without structural changes.
*/
protected void notifyItemsChanged(int positionStart) {
notifyItemsChanged(positionStart, mAdapter.getItemCount() - positionStart);
@@ -225,20 +222,20 @@ public abstract class SideFragment<T extends Item> extends Fragment implements H
}
protected abstract String getTitle();
+
@Override
public abstract String getTrackerLabel();
+
protected abstract List<T> getItemList();
public interface SideFragmentListener {
void onSideFragmentViewDestroyed();
}
- /**
- * Preloads the item views.
- */
+ /** Preloads the item views. */
public static void preloadItemViews(Context context) {
- ViewCache.getInstance().putView(
- context, R.layout.option_fragment, new FrameLayout(context), 1);
+ ViewCache.getInstance()
+ .putView(context, R.layout.option_fragment, new FrameLayout(context), 1);
VerticalGridView fakeParent = new VerticalGridView(context);
for (int id : PRELOAD_VIEW_IDS) {
sRecycledViewPool.setMaxRecycledViews(id, PRELOAD_VIEW_SIZE);
@@ -246,9 +243,7 @@ public abstract class SideFragment<T extends Item> extends Fragment implements H
}
}
- /**
- * Releases the recycled view pool.
- */
+ /** Releases the recycled view pool. */
public static void releaseRecycledViewPool() {
sRecycledViewPool.clear();
}
diff --git a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
index d02d3fb7..5bba4097 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
@@ -22,13 +22,14 @@ import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
-import android.os.Handler;
import android.view.View;
import android.view.ViewTreeObserver;
-
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import com.android.tv.R;
+import com.android.tv.ui.hideable.AutoHideScheduler;
-public class SideFragmentManager {
+/** Manages {@link SideFragment}s. */
+public class SideFragmentManager implements AccessibilityStateChangeListener {
private static final String FIRST_BACKSTACK_RECORD_NAME = "0";
private final Activity mActivity;
@@ -45,17 +46,11 @@ public class SideFragmentManager {
private final Animator mShowAnimator;
private final Animator mHideAnimator;
- private final Handler mHandler = new Handler();
- private final Runnable mHideAllRunnable = new Runnable() {
- @Override
- public void run() {
- hideAll(true);
- }
- };
+ private final AutoHideScheduler mAutoHideScheduler;
private final long mShowDurationMillis;
- public SideFragmentManager(Activity activity, Runnable preShowRunnable,
- Runnable postHideRunnable) {
+ public SideFragmentManager(
+ Activity activity, Runnable preShowRunnable, Runnable postHideRunnable) {
mActivity = activity;
mFragmentManager = mActivity.getFragmentManager();
mPreShowRunnable = preShowRunnable;
@@ -66,16 +61,18 @@ public class SideFragmentManager {
mShowAnimator.setTarget(mPanel);
mHideAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit);
mHideAnimator.setTarget(mPanel);
- mHideAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Animation is still in running state at this point.
- hideAllInternal();
- }
- });
+ mHideAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Animation is still in running state at this point.
+ hideAllInternal();
+ }
+ });
- mShowDurationMillis = mActivity.getResources().getInteger(
- R.integer.side_panel_show_duration);
+ mShowDurationMillis =
+ mActivity.getResources().getInteger(R.integer.side_panel_show_duration);
+ mAutoHideScheduler = new AutoHideScheduler(activity, () -> hideAll(true));
}
public int getCount() {
@@ -90,16 +87,12 @@ public class SideFragmentManager {
return mHideAnimator.isStarted();
}
- /**
- * Shows the given {@link SideFragment}.
- */
+ /** Shows the given {@link SideFragment}. */
public void show(SideFragment sideFragment) {
show(sideFragment, true);
}
- /**
- * Shows the given {@link SideFragment}.
- */
+ /** Shows the given {@link SideFragment}. */
public void show(SideFragment sideFragment, boolean showEnterAnimation) {
if (isHiding()) {
mHideAnimator.end();
@@ -114,7 +107,8 @@ public class SideFragmentManager {
R.animator.side_panel_fragment_pop_exit);
}
ft.replace(R.id.side_fragment_container, sideFragment)
- .addToBackStack(Integer.toString(mFragmentCount)).commit();
+ .addToBackStack(Integer.toString(mFragmentCount))
+ .commit();
mFragmentCount++;
if (isFirst) {
@@ -122,17 +116,18 @@ public class SideFragmentManager {
// slide-in animation to prevent jankiness resulted by performing transition and
// layouting at the same time with animation.
mPanel.setVisibility(View.VISIBLE);
- mShowOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- mShowOnGlobalLayoutListener = null;
- if (mPreShowRunnable != null) {
- mPreShowRunnable.run();
- }
- mShowAnimator.start();
- }
- };
+ mShowOnGlobalLayoutListener =
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mShowOnGlobalLayoutListener = null;
+ if (mPreShowRunnable != null) {
+ mPreShowRunnable.run();
+ }
+ mShowAnimator.start();
+ }
+ };
mPanel.getViewTreeObserver().addOnGlobalLayoutListener(mShowOnGlobalLayoutListener);
}
scheduleHideAll();
@@ -177,14 +172,14 @@ public class SideFragmentManager {
}
private void hideAllInternal() {
- mHandler.removeCallbacksAndMessages(null);
+ mAutoHideScheduler.cancel();
if (mFragmentCount == 0) {
return;
}
mPanel.setVisibility(View.GONE);
- mFragmentManager.popBackStack(FIRST_BACKSTACK_RECORD_NAME,
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ mFragmentManager.popBackStack(
+ FIRST_BACKSTACK_RECORD_NAME, FragmentManager.POP_BACK_STACK_INCLUSIVE);
mFragmentCount = 0;
if (mPostHideRunnable != null) {
@@ -193,8 +188,8 @@ public class SideFragmentManager {
}
/**
- * Show the side panel with animation. If there are many entries in the fragment stack,
- * the animation look like that there's only one fragment.
+ * Show the side panel with animation. If there are many entries in the fragment stack, the
+ * animation look like that there's only one fragment.
*
* @param withAnimation specifies if animation should be shown.
*/
@@ -211,22 +206,23 @@ public class SideFragmentManager {
}
/**
- * Hide the side panel. This method just hide the panel and preserves the back
- * stack. If you want to empty the back stack, call {@link #hideAll}.
+ * Hide the side panel. This method just hide the panel and preserves the back stack. If you
+ * want to empty the back stack, call {@link #hideAll}.
*/
public void hideSidePanel(boolean withAnimation) {
- mHandler.removeCallbacks(mHideAllRunnable);
+ mAutoHideScheduler.cancel();
if (withAnimation) {
Animator hideAnimator =
AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_exit);
hideAnimator.setTarget(mPanel);
hideAnimator.start();
- hideAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPanel.setVisibility(View.GONE);
- }
- });
+ hideAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPanel.setVisibility(View.GONE);
+ }
+ });
} else {
mPanel.setVisibility(View.GONE);
}
@@ -236,23 +232,23 @@ public class SideFragmentManager {
return mPanel.getVisibility() == View.VISIBLE;
}
- /**
- * Resets the timer for hiding side fragment.
- */
+ /** Resets the timer for hiding side fragment. */
public void scheduleHideAll() {
- mHandler.removeCallbacks(mHideAllRunnable);
- mHandler.postDelayed(mHideAllRunnable, mShowDurationMillis);
+ mAutoHideScheduler.schedule(mShowDurationMillis);
}
- /**
- * Should {@code keyCode} hide the current panel.
- */
+ /** Should {@code keyCode} hide the current panel. */
public boolean isHideKeyForCurrentPanel(int keyCode) {
if (isActive()) {
- SideFragment current = (SideFragment) mFragmentManager.findFragmentById(
- R.id.side_fragment_container);
+ SideFragment current =
+ (SideFragment) mFragmentManager.findFragmentById(R.id.side_fragment_container);
return current != null && current.isHideKeyForThisPanel(keyCode);
}
return false;
}
+
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ mAutoHideScheduler.onAccessibilityStateChanged(enabled);
+ }
}
diff --git a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java
index 42553b66..4457086d 100644
--- a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java
+++ b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java
@@ -16,9 +16,7 @@
package com.android.tv.ui.sidepanel;
-/**
- * A simple item which shows title and description.
- */
+/** A simple item which shows title and description. */
public class SimpleActionItem extends ActionItem {
public SimpleActionItem(String title) {
super(title);
@@ -29,6 +27,5 @@ public class SimpleActionItem extends ActionItem {
}
@Override
- protected void onSelected() {
- }
+ protected void onSelected() {}
}
diff --git a/src/com/android/tv/ui/sidepanel/SubMenuItem.java b/src/com/android/tv/ui/sidepanel/SubMenuItem.java
index 4b0e8e2c..5e4c77ca 100644
--- a/src/com/android/tv/ui/sidepanel/SubMenuItem.java
+++ b/src/com/android/tv/ui/sidepanel/SubMenuItem.java
@@ -16,7 +16,6 @@
package com.android.tv.ui.sidepanel;
-
public abstract class SubMenuItem extends ActionItem {
private final SideFragmentManager mSideFragmentManager;
diff --git a/src/com/android/tv/ui/sidepanel/SwitchItem.java b/src/com/android/tv/ui/sidepanel/SwitchItem.java
index 06591b62..8b5e1b64 100644
--- a/src/com/android/tv/ui/sidepanel/SwitchItem.java
+++ b/src/com/android/tv/ui/sidepanel/SwitchItem.java
@@ -31,8 +31,8 @@ public class SwitchItem extends CompoundButtonItem {
super(checkedTitle, uncheckedTitle, description);
}
- public SwitchItem(String checkedTitle, String uncheckedTitle, String description,
- int maxLines) {
+ public SwitchItem(
+ String checkedTitle, String uncheckedTitle, String description, int maxLines) {
super(checkedTitle, uncheckedTitle, description, maxLines);
}
@@ -50,4 +50,4 @@ public class SwitchItem extends CompoundButtonItem {
protected void onSelected() {
setChecked(!isChecked());
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
index ede5c268..4e3cf7fb 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
@@ -19,6 +19,8 @@ package com.android.tv.ui.sidepanel.parentalcontrols;
import android.database.ContentObserver;
import android.media.tv.TvContract;
import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.widget.VerticalGridView;
@@ -27,17 +29,16 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
import com.android.tv.R;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelNumber;
+import com.android.tv.data.api.Channel;
+import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.ui.OnRepeatedKeyInterceptListener;
import com.android.tv.ui.sidepanel.ActionItem;
import com.android.tv.ui.sidepanel.ChannelCheckItem;
import com.android.tv.ui.sidepanel.DividerItem;
import com.android.tv.ui.sidepanel.Item;
import com.android.tv.ui.sidepanel.SideFragment;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -49,45 +50,55 @@ public class ChannelsBlockedFragment extends SideFragment {
private final List<Channel> mChannels = new ArrayList<>();
private long mLastFocusedChannelId = Channel.INVALID_ID;
private int mSelectedPosition = INVALID_POSITION;
- private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- notifyItemsChanged();
- }
- };
+ private boolean mUpdated;
+ private final ContentObserver mProgramUpdateObserver =
+ new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ notifyItemsChanged();
+ }
+ };
private final Item mLockAllItem = new BlockAllItem();
private final List<Item> mItems = new ArrayList<>();
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (mSelectedPosition != INVALID_POSITION) {
setSelectedPosition(mSelectedPosition);
}
VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list);
- listView.setOnKeyInterceptListener(new OnRepeatedKeyInterceptListener(listView) {
- @Override
- public boolean onInterceptKeyEvent(KeyEvent event) {
- // In order to send tune operation once for continuous channel up/down events,
- // we only call the moveToChannel method on ACTION_UP event of channel switch keys.
- if (event.getAction() == KeyEvent.ACTION_UP) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (mLastFocusedChannelId != Channel.INVALID_ID) {
- getMainActivity().tuneToChannel(
- getChannelDataManager().getChannel(mLastFocusedChannelId));
+ listView.setOnKeyInterceptListener(
+ new OnRepeatedKeyInterceptListener(listView) {
+ @Override
+ public boolean onInterceptKeyEvent(KeyEvent event) {
+ // In order to send tune operation once for continuous channel up/down
+ // events,
+ // we only call the moveToChannel method on ACTION_UP event of channel
+ // switch keys.
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (mLastFocusedChannelId != Channel.INVALID_ID) {
+ getMainActivity()
+ .tuneToChannel(
+ getChannelDataManager()
+ .getChannel(mLastFocusedChannelId));
+ }
+ break;
}
- break;
+ }
+ return super.onInterceptKeyEvent(event);
}
- }
- return super.onInterceptKeyEvent(event);
- }
- });
- getActivity().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI,
- true, mProgramUpdateObserver);
+ });
+ getActivity()
+ .getContentResolver()
+ .registerContentObserver(
+ TvContract.Programs.CONTENT_URI, true, mProgramUpdateObserver);
getMainActivity().startShrunkenTvView(true, true);
+ mUpdated = false;
return view;
}
@@ -96,6 +107,10 @@ public class ChannelsBlockedFragment extends SideFragment {
getActivity().getContentResolver().unregisterContentObserver(mProgramUpdateObserver);
getChannelDataManager().applyUpdatedValuesToDb();
getMainActivity().endShrunkenTvView();
+ if (VERSION.SDK_INT >= VERSION_CODES.O && mUpdated) {
+ ChannelPreviewUpdater.getInstance(getMainActivity())
+ .updatePreviewDataForChannelsImmediately();
+ }
super.onDestroyView();
}
@@ -115,22 +130,24 @@ public class ChannelsBlockedFragment extends SideFragment {
mItems.add(mLockAllItem);
mChannels.clear();
mChannels.addAll(getChannelDataManager().getChannelList());
- Collections.sort(mChannels, new Comparator<Channel>() {
- @Override
- public int compare(Channel lhs, Channel rhs) {
- if (lhs.isBrowsable() != rhs.isBrowsable()) {
- return lhs.isBrowsable() ? -1 : 1;
- }
- return ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
- }
- });
+ Collections.sort(
+ mChannels,
+ new Comparator<Channel>() {
+ @Override
+ public int compare(Channel lhs, Channel rhs) {
+ if (lhs.isBrowsable() != rhs.isBrowsable()) {
+ return lhs.isBrowsable() ? -1 : 1;
+ }
+ return ChannelNumber.compare(
+ lhs.getDisplayNumber(), rhs.getDisplayNumber());
+ }
+ });
final long currentChannelId = getMainActivity().getCurrentChannelId();
boolean hasHiddenChannels = false;
for (Channel channel : mChannels) {
if (!channel.isBrowsable() && !hasHiddenChannels) {
- mItems.add(new DividerItem(
- getString(R.string.option_channels_subheader_hidden)));
+ mItems.add(new DividerItem(getString(R.string.option_channels_subheader_hidden)));
hasHiddenChannels = true;
}
mItems.add(new ChannelBlockedItem(channel));
@@ -177,6 +194,7 @@ public class ChannelsBlockedFragment extends SideFragment {
}
mBlockedChannelCount = lock ? mChannels.size() : 0;
notifyItemsChanged();
+ mUpdated = true;
}
@Override
@@ -186,8 +204,11 @@ public class ChannelsBlockedFragment extends SideFragment {
}
private void updateText() {
- mTextView.setText(getString(areAllChannelsBlocked() ?
- R.string.option_channels_unlock_all : R.string.option_channels_lock_all));
+ mTextView.setText(
+ getString(
+ areAllChannelsBlocked()
+ ? R.string.option_channels_unlock_all
+ : R.string.option_channels_lock_all));
}
private boolean areAllChannelsBlocked() {
@@ -217,6 +238,7 @@ public class ChannelsBlockedFragment extends SideFragment {
getChannelDataManager().updateLocked(getChannel().getId(), isChecked());
mBlockedChannelCount += isChecked() ? 1 : -1;
notifyItemChanged(mLockAllItem);
+ mUpdated = true;
}
@Override
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
index 9a4879fc..1c91ba9b 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
@@ -18,17 +18,15 @@ package com.android.tv.ui.sidepanel.parentalcontrols;
import android.view.View;
import android.widget.TextView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.ui.sidepanel.ActionItem;
import com.android.tv.ui.sidepanel.Item;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.ui.sidepanel.SubMenuItem;
import com.android.tv.ui.sidepanel.SwitchItem;
-
import java.util.ArrayList;
import java.util.List;
@@ -36,12 +34,13 @@ public class ParentalControlsFragment extends SideFragment {
private static final String TRACKER_LABEL = "Parental controls";
private List<ActionItem> mActionItems;
- private final SideFragmentListener mSideFragmentListener = new SideFragmentListener() {
- @Override
- public void onSideFragmentViewDestroyed() {
- notifyDataSetChanged();
- }
- };
+ private final SideFragmentListener mSideFragmentListener =
+ new SideFragmentListener() {
+ @Override
+ public void onSideFragmentViewDestroyed() {
+ notifyDataSetChanged();
+ }
+ };
@Override
protected String getTitle() {
@@ -56,89 +55,103 @@ public class ParentalControlsFragment extends SideFragment {
@Override
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
- items.add(new SwitchItem(getString(R.string.option_toggle_parental_controls_on),
- getString(R.string.option_toggle_parental_controls_off)) {
- @Override
- protected void onUpdate() {
- super.onUpdate();
- setChecked(getMainActivity().getParentalControlSettings()
- .isParentalControlsEnabled());
- }
+ items.add(
+ new SwitchItem(
+ getString(R.string.option_toggle_parental_controls_on),
+ getString(R.string.option_toggle_parental_controls_off)) {
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(
+ getMainActivity()
+ .getParentalControlSettings()
+ .isParentalControlsEnabled());
+ }
- @Override
- protected void onSelected() {
- super.onSelected();
- boolean checked = isChecked();
- getMainActivity().getParentalControlSettings().setParentalControlsEnabled(checked);
- enableActionItems(checked);
- }
- });
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ boolean checked = isChecked();
+ getMainActivity()
+ .getParentalControlSettings()
+ .setParentalControlsEnabled(checked);
+ enableActionItems(checked);
+ }
+ });
mActionItems = new ArrayList<>();
- mActionItems.add(new SubMenuItem(getString(R.string.option_channels_locked), "",
- getMainActivity().getOverlayManager().getSideFragmentManager()) {
- TextView mDescriptionView;
-
- @Override
- protected SideFragment getFragment() {
- SideFragment fragment = new ChannelsBlockedFragment();
- fragment.setListener(mSideFragmentListener);
- return fragment;
- }
+ mActionItems.add(
+ new SubMenuItem(
+ getString(R.string.option_channels_locked),
+ "",
+ getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ TextView mDescriptionView;
+
+ @Override
+ protected SideFragment getFragment() {
+ SideFragment fragment = new ChannelsBlockedFragment();
+ fragment.setListener(mSideFragmentListener);
+ return fragment;
+ }
- @Override
- protected void onBind(View view) {
- super.onBind(view);
- mDescriptionView = (TextView) view.findViewById(R.id.description);
- }
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ mDescriptionView = (TextView) view.findViewById(R.id.description);
+ }
- @Override
- protected void onUpdate() {
- super.onUpdate();
- int lockedAndBrowsableChannelCount = 0;
- for (Channel channel : getChannelDataManager().getChannelList()) {
- if (channel.isLocked() && channel.isBrowsable()) {
- ++lockedAndBrowsableChannelCount;
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ int lockedAndBrowsableChannelCount = 0;
+ for (Channel channel : getChannelDataManager().getChannelList()) {
+ if (channel.isLocked() && channel.isBrowsable()) {
+ ++lockedAndBrowsableChannelCount;
+ }
+ }
+ if (lockedAndBrowsableChannelCount > 0) {
+ mDescriptionView.setText(
+ Integer.toString(lockedAndBrowsableChannelCount));
+ } else {
+ mDescriptionView.setText(
+ getMainActivity().getString(R.string.option_no_locked_channel));
+ }
}
- }
- if (lockedAndBrowsableChannelCount > 0) {
- mDescriptionView.setText(Integer.toString(lockedAndBrowsableChannelCount));
- } else {
- mDescriptionView.setText(
- getMainActivity().getString(R.string.option_no_locked_channel));
- }
- }
- @Override
- protected void onUnbind() {
- super.onUnbind();
- mDescriptionView = null;
- }
- });
- mActionItems.add(new SubMenuItem(getString(R.string.option_program_restrictions),
- ProgramRestrictionsFragment.getDescription(getMainActivity()),
- getMainActivity().getOverlayManager().getSideFragmentManager()) {
- @Override
- protected SideFragment getFragment() {
- SideFragment fragment = new ProgramRestrictionsFragment();
- fragment.setListener(mSideFragmentListener);
- return fragment;
- }
- });
- mActionItems.add(new ActionItem(getString(R.string.option_change_pin)) {
- @Override
- protected void onSelected() {
- final MainActivity tvActivity = getMainActivity();
- tvActivity.getOverlayManager().getSideFragmentManager().hideSidePanel(true);
- PinDialogFragment fragment = PinDialogFragment.create(
- PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN);
- tvActivity.getOverlayManager().showDialogFragment(PinDialogFragment.DIALOG_TAG,
- fragment, true);
- }
- });
+ @Override
+ protected void onUnbind() {
+ super.onUnbind();
+ mDescriptionView = null;
+ }
+ });
+ mActionItems.add(
+ new SubMenuItem(
+ getString(R.string.option_program_restrictions),
+ ProgramRestrictionsFragment.getDescription(getMainActivity()),
+ getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ @Override
+ protected SideFragment getFragment() {
+ SideFragment fragment = new ProgramRestrictionsFragment();
+ fragment.setListener(mSideFragmentListener);
+ return fragment;
+ }
+ });
+ mActionItems.add(
+ new ActionItem(getString(R.string.option_change_pin)) {
+ @Override
+ protected void onSelected() {
+ final MainActivity tvActivity = getMainActivity();
+ tvActivity.getOverlayManager().getSideFragmentManager().hideSidePanel(true);
+ PinDialogFragment fragment =
+ PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN);
+ tvActivity
+ .getOverlayManager()
+ .showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true);
+ }
+ });
items.addAll(mActionItems);
- enableActionItems(getMainActivity().getParentalControlSettings()
- .isParentalControlsEnabled());
+ enableActionItems(
+ getMainActivity().getParentalControlSettings().isParentalControlsEnabled());
return items;
}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
index 1df7fe59..ead3c105 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
@@ -21,19 +21,19 @@ import com.android.tv.R;
import com.android.tv.ui.sidepanel.Item;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.ui.sidepanel.SubMenuItem;
-
import java.util.ArrayList;
import java.util.List;
public class ProgramRestrictionsFragment extends SideFragment {
private static final String TRACKER_LABEL = "Program restrictions";
- private final SideFragmentListener mSideFragmentListener = new SideFragmentListener() {
- @Override
- public void onSideFragmentViewDestroyed() {
- notifyDataSetChanged();
- }
- };
+ private final SideFragmentListener mSideFragmentListener =
+ new SideFragmentListener() {
+ @Override
+ public void onSideFragmentViewDestroyed() {
+ notifyDataSetChanged();
+ }
+ };
public static String getDescription(MainActivity tvActivity) {
return RatingsFragment.getDescription(tvActivity);
@@ -53,33 +53,37 @@ public class ProgramRestrictionsFragment extends SideFragment {
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
- items.add(new SubMenuItem(getString(R.string.option_country_rating_systems),
- RatingSystemsFragment.getDescription(getMainActivity()),
- getMainActivity().getOverlayManager().getSideFragmentManager()) {
- @Override
- protected SideFragment getFragment() {
- SideFragment fragment = new RatingSystemsFragment();
- fragment.setListener(mSideFragmentListener);
- return fragment;
- }
- });
+ items.add(
+ new SubMenuItem(
+ getString(R.string.option_country_rating_systems),
+ RatingSystemsFragment.getDescription(getMainActivity()),
+ getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ @Override
+ protected SideFragment getFragment() {
+ SideFragment fragment = new RatingSystemsFragment();
+ fragment.setListener(mSideFragmentListener);
+ return fragment;
+ }
+ });
String ratingsDescription = RatingsFragment.getDescription(getMainActivity());
- SubMenuItem ratingsItem = new SubMenuItem(getString(R.string.option_ratings),
- ratingsDescription,
- getMainActivity().getOverlayManager().getSideFragmentManager()) {
- @Override
- protected SideFragment getFragment() {
- SideFragment fragment = new RatingsFragment();
- fragment.setListener(mSideFragmentListener);
- return fragment;
- }
- };
+ SubMenuItem ratingsItem =
+ new SubMenuItem(
+ getString(R.string.option_ratings),
+ ratingsDescription,
+ getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ @Override
+ protected SideFragment getFragment() {
+ SideFragment fragment = new RatingsFragment();
+ fragment.setListener(mSideFragmentListener);
+ return fragment;
+ }
+ };
// When "None" is selected for rating systems, disable the Ratings option.
- if (RatingSystemsFragment.getDescription(getMainActivity()).equals(
- getString(R.string.option_no_enabled_rating_system))) {
+ if (RatingSystemsFragment.getDescription(getMainActivity())
+ .equals(getString(R.string.option_no_enabled_rating_system))) {
ratingsItem.setEnabled(false);
}
items.add(ratingsItem);
return items;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java
index ba9adc50..e0f546f9 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java
@@ -17,7 +17,6 @@
package com.android.tv.ui.sidepanel.parentalcontrols;
import android.os.Bundle;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.parental.ContentRatingSystem;
@@ -28,7 +27,6 @@ import com.android.tv.ui.sidepanel.CheckBoxItem;
import com.android.tv.ui.sidepanel.Item;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.util.TvSettings;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -57,7 +55,8 @@ public class RatingSystemsFragment extends SideFragment {
builder.append(s.getDisplayName());
builder.append(", ");
}
- return builder.length() > 0 ? builder.substring(0, builder.length() - 2)
+ return builder.length() > 0
+ ? builder.substring(0, builder.length() - 2)
: tvActivity.getString(R.string.option_no_enabled_rating_system);
}
@@ -85,7 +84,8 @@ public class RatingSystemsFragment extends SideFragment {
// Add default, custom and preselected content rating systems to the "short" list.
for (ContentRatingSystem s : contentRatingSystems) {
- if (!s.isCustom() && s.getCountries() != null
+ if (!s.isCustom()
+ && s.getCountries() != null
&& s.getCountries().contains(Locale.getDefault().getCountry())) {
items.add(new RatingSystemItem(s));
} else if (s.isCustom() || parentalControlSettings.isContentRatingSystemEnabled(s)) {
@@ -117,12 +117,13 @@ public class RatingSystemsFragment extends SideFragment {
allItems.addAll(itemsHiddenMultipleCountries);
// Add "See All" to the "short" list.
- items.add(new ActionItem(getString(R.string.option_see_all_rating_systems)) {
- @Override
- protected void onSelected() {
- setItems(allItems);
- }
- });
+ items.add(
+ new ActionItem(getString(R.string.option_see_all_rating_systems)) {
+ @Override
+ protected void onSelected() {
+ setItems(allItems);
+ }
+ });
return items;
}
@@ -136,7 +137,8 @@ public class RatingSystemsFragment extends SideFragment {
ContentRatingsManager manager = tvActivity.getContentRatingsManager();
ParentalControlSettings settings = tvActivity.getParentalControlSettings();
for (ContentRatingSystem s : contentRatingSystems) {
- if (!s.isCustom() && s.getCountries() != null
+ if (!s.isCustom()
+ && s.getCountries() != null
&& s.getCountries().contains(Locale.getDefault().getCountry())) {
settings.setContentRatingSystemEnabled(manager, s, true);
}
@@ -158,16 +160,21 @@ public class RatingSystemsFragment extends SideFragment {
@Override
protected void onUpdate() {
super.onUpdate();
- setChecked(getMainActivity().getParentalControlSettings()
- .isContentRatingSystemEnabled(mContentRatingSystem));
+ setChecked(
+ getMainActivity()
+ .getParentalControlSettings()
+ .isContentRatingSystemEnabled(mContentRatingSystem));
}
@Override
protected void onSelected() {
super.onSelected();
- getMainActivity().getParentalControlSettings().setContentRatingSystemEnabled(
- getMainActivity().getContentRatingsManager(), mContentRatingSystem,
- isChecked());
+ getMainActivity()
+ .getParentalControlSettings()
+ .setContentRatingSystemEnabled(
+ getMainActivity().getContentRatingsManager(),
+ mContentRatingSystem,
+ isChecked());
}
}
}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
index 7c8cecbe..128fcd1a 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
@@ -26,8 +26,8 @@ import android.widget.CompoundButton;
import android.widget.ImageView;
import com.android.tv.MainActivity;
import com.android.tv.R;
+import com.android.tv.common.experiments.Experiments;
import com.android.tv.dialog.WebDialogFragment;
-import com.android.tv.experiments.Experiments;
import com.android.tv.license.LicenseUtils;
import com.android.tv.parental.ContentRatingSystem;
import com.android.tv.parental.ContentRatingSystem.Rating;
@@ -52,26 +52,23 @@ public class RatingsFragment extends SideFragment {
static {
sLevelResourceIdMap = new SparseIntArray(5);
- sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE,
- R.string.option_rating_none);
- sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH,
- R.string.option_rating_high);
- sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_MEDIUM,
- R.string.option_rating_medium);
- sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW,
- R.string.option_rating_low);
- sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_CUSTOM,
- R.string.option_rating_custom);
+ sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_NONE, R.string.option_rating_none);
+ sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high);
+ sLevelResourceIdMap.put(
+ TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium);
+ sLevelResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low);
+ sLevelResourceIdMap.put(
+ TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom);
sDescriptionResourceIdMap = new SparseIntArray(sLevelResourceIdMap.size());
- sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_HIGH,
- R.string.option_rating_high_description);
- sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_MEDIUM,
- R.string.option_rating_medium_description);
- sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_LOW,
- R.string.option_rating_low_description);
- sDescriptionResourceIdMap.put(TvSettings.CONTENT_RATING_LEVEL_CUSTOM,
- R.string.option_rating_custom_description);
+ sDescriptionResourceIdMap.put(
+ TvSettings.CONTENT_RATING_LEVEL_HIGH, R.string.option_rating_high_description);
+ sDescriptionResourceIdMap.put(
+ TvSettings.CONTENT_RATING_LEVEL_MEDIUM, R.string.option_rating_medium_description);
+ sDescriptionResourceIdMap.put(
+ TvSettings.CONTENT_RATING_LEVEL_LOW, R.string.option_rating_low_description);
+ sDescriptionResourceIdMap.put(
+ TvSettings.CONTENT_RATING_LEVEL_CUSTOM, R.string.option_rating_custom_description);
}
private final List<RatingLevelItem> mRatingLevelItems = new ArrayList<>();
@@ -81,8 +78,8 @@ public class RatingsFragment extends SideFragment {
private ParentalControlSettings mParentalControlSettings;
public static String getDescription(MainActivity tvActivity) {
- @ContentRatingLevel int currentLevel =
- tvActivity.getParentalControlSettings().getContentRatingLevel();
+ @ContentRatingLevel
+ int currentLevel = tvActivity.getParentalControlSettings().getContentRatingLevel();
if (sLevelResourceIdMap.indexOfKey(currentLevel) >= 0) {
return tvActivity.getString(sLevelResourceIdMap.get(currentLevel));
}
@@ -128,9 +125,10 @@ public class RatingsFragment extends SideFragment {
boolean hasSubRating = false;
items.add(new DividerItem(s.getDisplayName()));
for (Rating rating : s.getRatings()) {
- RatingItem item = rating.getSubRatings().isEmpty() ?
- new RatingItem(s, rating) :
- new RatingWithSubItem(s, rating);
+ RatingItem item =
+ rating.getSubRatings().isEmpty()
+ ? new RatingItem(s, rating)
+ : new RatingWithSubItem(s, rating);
items.add(item);
if (rating.getSubRatings().isEmpty()) {
ratingItems.add(item);
@@ -201,15 +199,18 @@ public class RatingsFragment extends SideFragment {
}
}
- private void updateDependentRatingItems(ContentRatingSystem.Order order,
- int selectedRatingOrderIndex, String contentRatingSystemId, boolean isChecked) {
+ private void updateDependentRatingItems(
+ ContentRatingSystem.Order order,
+ int selectedRatingOrderIndex,
+ String contentRatingSystemId,
+ boolean isChecked) {
List<RatingItem> ratingItems = mContentRatingSystemItemMap.get(contentRatingSystemId);
if (ratingItems != null) {
for (RatingItem item : ratingItems) {
int ratingOrderIndex = item.getRatingOrderIndex(order);
if (ratingOrderIndex != -1
&& ((ratingOrderIndex > selectedRatingOrderIndex && isChecked)
- || (ratingOrderIndex < selectedRatingOrderIndex && !isChecked))) {
+ || (ratingOrderIndex < selectedRatingOrderIndex && !isChecked))) {
item.setRatingBlocked(isChecked);
}
}
@@ -220,9 +221,11 @@ public class RatingsFragment extends SideFragment {
private final int mRatingLevel;
private RatingLevelItem(int ratingLevel) {
- super(getString(sLevelResourceIdMap.get(ratingLevel)),
- (sDescriptionResourceIdMap.indexOfKey(ratingLevel) >= 0) ?
- getString(sDescriptionResourceIdMap.get(ratingLevel)) : null);
+ super(
+ getString(sLevelResourceIdMap.get(ratingLevel)),
+ (sDescriptionResourceIdMap.indexOfKey(ratingLevel) >= 0)
+ ? getString(sDescriptionResourceIdMap.get(ratingLevel))
+ : null);
mRatingLevel = ratingLevel;
}
@@ -302,8 +305,11 @@ public class RatingsFragment extends SideFragment {
}
// Automatically check/uncheck dependent ratings.
for (int i = 0; i < mOrders.size(); i++) {
- updateDependentRatingItems(mOrders.get(i), mOrderIndexes.get(i),
- mContentRatingSystem.getId(), isChecked());
+ updateDependentRatingItems(
+ mOrders.get(i),
+ mOrderIndexes.get(i),
+ mContentRatingSystem.getId(),
+ isChecked());
}
}
@@ -337,14 +343,16 @@ public class RatingsFragment extends SideFragment {
@Override
protected void onSelected() {
- getMainActivity().getOverlayManager().getSideFragmentManager()
+ getMainActivity()
+ .getOverlayManager()
+ .getSideFragmentManager()
.show(SubRatingsFragment.create(mContentRatingSystem, mRating.getName()));
}
@Override
protected int getButtonDrawable() {
- int blockedStatus = mParentalControlSettings.getBlockedStatus(
- mContentRatingSystem, mRating);
+ int blockedStatus =
+ mParentalControlSettings.getBlockedStatus(mContentRatingSystem, mRating);
if (blockedStatus == ParentalControlSettings.RATING_BLOCKED) {
return R.drawable.btn_lock_material;
} else if (blockedStatus == ParentalControlSettings.RATING_BLOCKED_PARTIAL) {
@@ -354,11 +362,9 @@ public class RatingsFragment extends SideFragment {
}
}
- /**
- * Opens a dialog showing the sources of the rating descriptions.
- */
+ /** Opens a dialog showing the sources of the rating descriptions. */
public static class AttributionItem extends Item {
- public final static String DIALOG_TAG = AttributionItem.class.getSimpleName();
+ public static final String DIALOG_TAG = AttributionItem.class.getSimpleName();
public static final String TRACKER_LABEL = "Sources for content rating systems";
private final MainActivity mMainActivity;
@@ -373,9 +379,11 @@ public class RatingsFragment extends SideFragment {
@Override
protected void onSelected() {
- WebDialogFragment dialog = WebDialogFragment.newInstance(
- LicenseUtils.RATING_SOURCE_FILE,
- mMainActivity.getString(R.string.option_attribution), TRACKER_LABEL);
+ WebDialogFragment dialog =
+ WebDialogFragment.newInstance(
+ LicenseUtils.RATING_SOURCE_FILE,
+ mMainActivity.getString(R.string.option_attribution),
+ TRACKER_LABEL);
mMainActivity.getOverlayManager().showDialogFragment(DIALOG_TAG, dialog, false);
}
}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
index 4634b74c..b9b03ed1 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
@@ -21,7 +21,6 @@ import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
-
import com.android.tv.R;
import com.android.tv.parental.ContentRatingSystem;
import com.android.tv.parental.ContentRatingSystem.Rating;
@@ -30,7 +29,6 @@ import com.android.tv.ui.sidepanel.CheckBoxItem;
import com.android.tv.ui.sidepanel.DividerItem;
import com.android.tv.ui.sidepanel.Item;
import com.android.tv.ui.sidepanel.SideFragment;
-
import java.util.ArrayList;
import java.util.List;
@@ -44,8 +42,8 @@ public class SubRatingsFragment extends SideFragment {
private Rating mRating;
private final List<SubRatingItem> mSubRatingItems = new ArrayList<>();
- public static SubRatingsFragment create(ContentRatingSystem contentRatingSystem,
- String ratingName) {
+ public static SubRatingsFragment create(
+ ContentRatingSystem contentRatingSystem, String ratingName) {
SubRatingsFragment fragment = new SubRatingsFragment();
Bundle args = new Bundle();
args.putString(ARGS_CONTENT_RATING_SYSTEM_ID, contentRatingSystem.getId());
@@ -57,8 +55,11 @@ public class SubRatingsFragment extends SideFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mContentRatingSystem = getMainActivity().getContentRatingsManager()
- .getContentRatingSystem(getArguments().getString(ARGS_CONTENT_RATING_SYSTEM_ID));
+ mContentRatingSystem =
+ getMainActivity()
+ .getContentRatingsManager()
+ .getContentRatingSystem(
+ getArguments().getString(ARGS_CONTENT_RATING_SYSTEM_ID));
if (mContentRatingSystem != null) {
mRating = mContentRatingSystem.getRating(getArguments().getString(ARGS_RATING_NAME));
}
@@ -194,22 +195,26 @@ public class SubRatingsFragment extends SideFragment {
}
private boolean isRatingEnabled() {
- return getMainActivity().getParentalControlSettings()
+ return getMainActivity()
+ .getParentalControlSettings()
.isRatingBlocked(mContentRatingSystem, mRating);
}
private boolean isSubRatingEnabled(SubRating subRating) {
- return getMainActivity().getParentalControlSettings()
+ return getMainActivity()
+ .getParentalControlSettings()
.isSubRatingEnabled(mContentRatingSystem, mRating, subRating);
}
private void setRatingEnabled(boolean enabled) {
- getMainActivity().getParentalControlSettings()
+ getMainActivity()
+ .getParentalControlSettings()
.setRatingBlocked(mContentRatingSystem, mRating, enabled);
}
private void setSubRatingEnabled(SubRating subRating, boolean enabled) {
- getMainActivity().getParentalControlSettings()
+ getMainActivity()
+ .getParentalControlSettings()
.setSubRatingBlocked(mContentRatingSystem, mRating, subRating, enabled);
}
}
diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java
index 477412e4..60fa3018 100644
--- a/src/com/android/tv/util/AsyncDbTask.java
+++ b/src/com/android/tv/util/AsyncDbTask.java
@@ -27,24 +27,20 @@ import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Range;
-
+import com.android.tv.TvSingletons;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelImpl;
import com.android.tv.data.Program;
+import com.android.tv.data.api.Channel;
import com.android.tv.dvr.data.RecordedProgram;
-
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.Executor;
/**
* {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
*
- * <p>Instances of this class should only be executed this using {@link
- * #executeOnDbThread(Object[])}.
- *
* @param <Params> the type of the parameters sent to the task upon execution.
* @param <Progress> the type of the progress units published during the background computation.
* @param <Result> the type of the result of the background computation.
@@ -54,38 +50,19 @@ public abstract class AsyncDbTask<Params, Progress, Result>
private static final String TAG = "AsyncDbTask";
private static final boolean DEBUG = false;
- private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory(
- AsyncDbTask.class.getSimpleName());
- private static final ExecutorService DB_EXECUTOR = Executors
- .newSingleThreadExecutor(THREAD_FACTORY);
+ private final Executor mExecutor;
+ boolean mCalledExecuteOnDbThread;
- /**
- * Returns the single tread executor used for DbTasks.
- */
- public static ExecutorService getExecutor() {
- return DB_EXECUTOR;
- }
-
- /**
- * Executes the given command at some time in the future.
- *
- * <p>The command will be executed by {@link #getExecutor()}.
- *
- * @param command the runnable task
- * @throws RejectedExecutionException if this task cannot be
- * accepted for execution
- * @throws NullPointerException if command is null
- */
- public static void executeOnDbThread(Runnable command) {
- DB_EXECUTOR.execute(command);
+ protected AsyncDbTask(Executor mExecutor) {
+ this.mExecutor = mExecutor;
}
/**
* Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
* String)}.
*
- * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)}
- * which is implemented by subclasses.
+ * <p>{@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} which
+ * is implemented by subclasses.
*
* @param <Result> the type of result returned by {@link #onQuery(Cursor)}
*/
@@ -97,9 +74,15 @@ public abstract class AsyncDbTask<Params, Progress, Result>
private final String[] mSelectionArgs;
private final String mOrderBy;
-
- public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection,
- String selection, String[] selectionArgs, String orderBy) {
+ public AsyncQueryTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String orderBy) {
+ super(executor);
mContentResolver = contentResolver;
mUri = uri;
mProjection = projection;
@@ -110,13 +93,15 @@ public abstract class AsyncDbTask<Params, Progress, Result>
@Override
protected final Result doInBackground(Void... params) {
- if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) {
- IllegalStateException e = new IllegalStateException(this
- + " should only be executed using executeOnDbThread, "
- + "but it was called on thread "
- + Thread.currentThread());
+ if (!mCalledExecuteOnDbThread) {
+ IllegalStateException e =
+ new IllegalStateException(
+ this
+ + " should only be executed using executeOnDbThread, "
+ + "but it was called on thread "
+ + Thread.currentThread());
Log.w(TAG, e);
- if (DEBUG) {
+ if (BuildConfig.ENG) {
throw e;
}
}
@@ -128,8 +113,9 @@ public abstract class AsyncDbTask<Params, Progress, Result>
if (DEBUG) {
Log.v(TAG, "Starting query for " + this);
}
- try (Cursor c = mContentResolver
- .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
+ try (Cursor c =
+ mContentResolver.query(
+ mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
if (c != null && !isCancelled()) {
Result result = onQuery(c);
if (DEBUG) {
@@ -147,7 +133,7 @@ public abstract class AsyncDbTask<Params, Progress, Result>
return null;
}
} catch (Exception e) {
- SoftPreconditions.warn(TAG, null, "Error querying " + this, e);
+ SoftPreconditions.warn(TAG, null, e, "Error querying " + this);
return null;
}
}
@@ -176,14 +162,35 @@ public abstract class AsyncDbTask<Params, Progress, Result>
public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
private final CursorFilter mFilter;
- public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
- String selection, String[] selectionArgs, String orderBy) {
- this(contentResolver, uri, projection, selection, selectionArgs, orderBy, null);
+ public AsyncQueryListTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String orderBy) {
+ this(
+ executor,
+ contentResolver,
+ uri,
+ projection,
+ selection,
+ selectionArgs,
+ orderBy,
+ null);
}
- public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
- String selection, String[] selectionArgs, String orderBy, CursorFilter filter) {
- super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
+ public AsyncQueryListTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String orderBy,
+ CursorFilter filter) {
+ super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy);
mFilter = filter;
}
@@ -228,9 +235,15 @@ public abstract class AsyncDbTask<Params, Progress, Result>
*/
public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> {
- public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection,
- String selection, String[] selectionArgs, String orderBy) {
- super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
+ public AsyncQueryItemTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String orderBy) {
+ super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy);
}
@Override
@@ -251,7 +264,6 @@ public abstract class AsyncDbTask<Params, Progress, Result>
}
return null;
}
-
}
/**
@@ -268,33 +280,55 @@ public abstract class AsyncDbTask<Params, Progress, Result>
protected abstract T fromCursor(Cursor c);
}
- /**
- * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}.
- */
+ /** Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. */
public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
- public AsyncChannelQueryTask(ContentResolver contentResolver) {
- super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
- null, null, null);
+ public AsyncChannelQueryTask(Executor executor, ContentResolver contentResolver) {
+ super(
+ executor,
+ contentResolver,
+ TvContract.Channels.CONTENT_URI,
+ ChannelImpl.PROJECTION,
+ null,
+ null,
+ null);
}
@Override
protected final Channel fromCursor(Cursor c) {
- return Channel.fromCursor(c);
+ return ChannelImpl.fromCursor(c);
}
}
- /**
- * Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}.
- */
+ /** Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}. */
public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> {
- public AsyncProgramQueryTask(ContentResolver contentResolver) {
- super(contentResolver, Programs.CONTENT_URI, Program.PROJECTION, null, null, null);
+ public AsyncProgramQueryTask(Executor executor, ContentResolver contentResolver) {
+ super(
+ executor,
+ contentResolver,
+ Programs.CONTENT_URI,
+ Program.PROJECTION,
+ null,
+ null,
+ null);
}
- public AsyncProgramQueryTask(ContentResolver contentResolver, Uri uri, String selection,
- String[] selectionArgs, String sortOrder, CursorFilter filter) {
- super(contentResolver, uri, Program.PROJECTION, selection, selectionArgs, sortOrder,
+ public AsyncProgramQueryTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ Uri uri,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder,
+ CursorFilter filter) {
+ super(
+ executor,
+ contentResolver,
+ uri,
+ Program.PROJECTION,
+ selection,
+ selectionArgs,
+ sortOrder,
filter);
}
@@ -304,13 +338,12 @@ public abstract class AsyncDbTask<Params, Progress, Result>
}
}
- /**
- * Gets an {@link List} of {@link TvContract.RecordedPrograms}s.
- */
+ /** Gets an {@link List} of {@link TvContract.RecordedPrograms}s. */
public abstract static class AsyncRecordedProgramQueryTask
extends AsyncQueryListTask<RecordedProgram> {
- public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) {
- super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
+ public AsyncRecordedProgramQueryTask(
+ Executor executor, ContentResolver contentResolver, Uri uri) {
+ super(executor, contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
}
@Override
@@ -319,31 +352,39 @@ public abstract class AsyncDbTask<Params, Progress, Result>
}
}
- /**
- * Execute the task on the {@link #DB_EXECUTOR} thread.
- */
+ /** Execute the task on {@link TvSingletons#getDbExecutor()}. */
@SafeVarargs
@MainThread
public final void executeOnDbThread(Params... params) {
- executeOnExecutor(DB_EXECUTOR, params);
+ mCalledExecuteOnDbThread = true;
+ executeOnExecutor(mExecutor, params);
}
/**
* Gets an {@link List} of {@link Program}s for a given channel and period {@link
- * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is
- * {@code null}, then all the programs is queried.
+ * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is {@code
+ * null}, then all the programs is queried.
*/
public static class LoadProgramsForChannelTask extends AsyncProgramQueryTask {
protected final Range<Long> mPeriod;
protected final long mChannelId;
- public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId,
+ public LoadProgramsForChannelTask(
+ Executor executor,
+ ContentResolver contentResolver,
+ long channelId,
@Nullable Range<Long> period) {
- super(contentResolver, period == null
- ? TvContract.buildProgramsUriForChannel(channelId)
- : TvContract.buildProgramsUriForChannel(channelId, period.getLower(),
- period.getUpper()),
- null, null, null, null);
+ super(
+ executor,
+ contentResolver,
+ period == null
+ ? TvContract.buildProgramsUriForChannel(channelId)
+ : TvContract.buildProgramsUriForChannel(
+ channelId, period.getLower(), period.getUpper()),
+ null,
+ null,
+ null,
+ null);
mPeriod = period;
mChannelId = channelId;
}
@@ -357,14 +398,19 @@ public abstract class AsyncDbTask<Params, Progress, Result>
}
}
- /**
- * Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}.
- */
+ /** Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. */
public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> {
- public AsyncQueryProgramTask(ContentResolver contentResolver, long programId) {
- super(contentResolver, TvContract.buildProgramUri(programId), Program.PROJECTION, null,
- null, null);
+ public AsyncQueryProgramTask(
+ Executor executor, ContentResolver contentResolver, long programId) {
+ super(
+ executor,
+ contentResolver,
+ TvContract.buildProgramUri(programId),
+ Program.PROJECTION,
+ null,
+ null,
+ null);
}
@Override
@@ -373,8 +419,6 @@ public abstract class AsyncDbTask<Params, Progress, Result>
}
}
- /**
- * An interface which filters the row.
- */
- public interface CursorFilter extends Filter<Cursor> { }
+ /** An interface which filters the row. */
+ public interface CursorFilter extends Filter<Cursor> {}
}
diff --git a/src/com/android/tv/util/CaptionSettings.java b/src/com/android/tv/util/CaptionSettings.java
index 3b38905b..6d7e9901 100644
--- a/src/com/android/tv/util/CaptionSettings.java
+++ b/src/com/android/tv/util/CaptionSettings.java
@@ -18,7 +18,6 @@ package com.android.tv.util;
import android.content.Context;
import android.view.accessibility.CaptioningManager;
-
import java.util.Locale;
public class CaptionSettings {
@@ -32,8 +31,8 @@ public class CaptionSettings {
private String mTrackId;
public CaptionSettings(Context context) {
- mCaptioningManager = (CaptioningManager) context.getSystemService(
- Context.CAPTIONING_SERVICE);
+ mCaptioningManager =
+ (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
}
public final String getSystemLanguage() {
@@ -84,16 +83,12 @@ public class CaptionSettings {
mLanguage = language;
}
- /**
- * Returns the track ID to be used as an alternative key.
- */
+ /** Returns the track ID to be used as an alternative key. */
public String getTrackId() {
return mTrackId;
}
- /**
- * Sets the track ID to be used as an alternative key.
- */
+ /** Sets the track ID to be used as an alternative key. */
public void setTrackId(String trackId) {
mTrackId = trackId;
}
diff --git a/src/com/android/tv/util/Clock.java b/src/com/android/tv/util/Clock.java
deleted file mode 100644
index c5e96431..00000000
--- a/src/com/android/tv/util/Clock.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tv.util;
-
-import android.os.SystemClock;
-
-/**
- * An interface through which system clocks can be read. The {@link #SYSTEM} implementation
- * must be used for all non-test cases.
- */
-public interface Clock {
- /**
- * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
- * See {@link System#currentTimeMillis()}.
- */
- long currentTimeMillis();
-
- /**
- * Returns milliseconds since boot, including time spent in sleep.
- *
- * @see SystemClock#elapsedRealtime()
- */
- long elapsedRealtime();
-
- /**
- * Waits a given number of milliseconds (of uptimeMillis) before returning.
- *
- * @param ms to sleep before returning, in milliseconds of uptime.
- * @see SystemClock#sleep(long)
- */
- void sleep(long ms);
-
- /**
- * The default implementation of Clock.
- */
- Clock SYSTEM = new Clock() {
- @Override
- public long currentTimeMillis() {
- return System.currentTimeMillis();
- }
-
- @Override
- public long elapsedRealtime() {
- return SystemClock.elapsedRealtime();
- }
-
- @Override
- public void sleep(long ms) {
- SystemClock.sleep(ms);
- }
- };
-}
diff --git a/src/com/android/tv/util/CompositeComparator.java b/src/com/android/tv/util/CompositeComparator.java
index 47cf50fe..ccf4e944 100644
--- a/src/com/android/tv/util/CompositeComparator.java
+++ b/src/com/android/tv/util/CompositeComparator.java
@@ -18,9 +18,7 @@ package com.android.tv.util;
import java.util.Comparator;
-/**
- * A comparator which runs multiple comparators sequentially.
- */
+/** A comparator which runs multiple comparators sequentially. */
public class CompositeComparator<T> implements Comparator<T> {
private final Comparator<T>[] mComparators;
diff --git a/src/com/android/tv/util/Debug.java b/src/com/android/tv/util/Debug.java
deleted file mode 100644
index 67a2683d..00000000
--- a/src/com/android/tv/util/Debug.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A class only for help developers.
- */
-public class Debug {
- /**
- * A threshold of start up time, when the start up time of Live TV is more than it,
- * a warning will show to the developer.
- */
- public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6);
- /**
- * Tag for measuring start up time of Live TV.
- */
- public static final String TAG_START_UP_TIMER = "start_up_timer";
-
- /**
- * A global map for duration timers.
- */
- private final static Map<String, DurationTimer> sTimerMap = new HashMap<>();
-
- /**
- * Returns the global duration timer by tag.
- */
- public static DurationTimer getTimer(String tag) {
- if (sTimerMap.get(tag) != null) {
- return sTimerMap.get(tag);
- }
- DurationTimer timer = new DurationTimer(tag, true);
- sTimerMap.put(tag, timer);
- return timer;
- }
-
- /**
- * Removes the global duration timer by tag.
- */
- public static DurationTimer removeTimer(String tag) {
- return sTimerMap.remove(tag);
- }
-}
diff --git a/src/com/android/tv/util/DurationTimer.java b/src/com/android/tv/util/DurationTimer.java
deleted file mode 100644
index 1f057bf6..00000000
--- a/src/com/android/tv/util/DurationTimer.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.tv.common.BuildConfig;
-
-/**
- * Times a duration.
- */
-public final class DurationTimer {
- private static final String TAG = "DurationTimer";
- public static final long TIME_NOT_SET = -1;
-
- private long mStartTimeMs = TIME_NOT_SET;
- private String mTag = TAG;
- private boolean mLogEngOnly;
-
- public DurationTimer() { }
-
- public DurationTimer(String tag, boolean logEngOnly) {
- mTag = tag;
- mLogEngOnly = logEngOnly;
- }
-
- /**
- * Returns true if the timer is running.
- */
- public boolean isRunning() {
- return mStartTimeMs != TIME_NOT_SET;
- }
-
- /**
- * Start the timer.
- */
- public void start() {
- mStartTimeMs = SystemClock.elapsedRealtime();
- }
-
- /**
- * Returns true if timer is started.
- */
- public boolean isStarted() {
- return mStartTimeMs != TIME_NOT_SET;
- }
-
- /**
- * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not
- * running.
- */
- public long getDuration() {
- return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMs : TIME_NOT_SET;
- }
-
- /**
- * Stops the timer and resets its value to {@link #TIME_NOT_SET}.
- *
- * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not
- * running.
- */
- public long reset() {
- long duration = getDuration();
- mStartTimeMs = TIME_NOT_SET;
- return duration;
- }
-
- /**
- * Adds information and duration time to the log.
- */
- public void log(String message) {
- if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) {
- Log.i(mTag, message + " : " + getDuration() + "ms");
- }
- }
-}
diff --git a/src/com/android/tv/util/Filter.java b/src/com/android/tv/util/Filter.java
index d5b356e4..3e24a496 100644
--- a/src/com/android/tv/util/Filter.java
+++ b/src/com/android/tv/util/Filter.java
@@ -16,12 +16,8 @@
package com.android.tv.util;
-/**
- * Interface to decide whether an input is filtered out or not.
- */
+/** Interface to decide whether an input is filtered out or not. */
public interface Filter<T> {
- /**
- * Returns true, if {@code input} is acceptable.
- */
+ /** Returns true, if {@code input} is acceptable. */
boolean filter(T input);
}
diff --git a/src/com/android/tv/util/LocationUtils.java b/src/com/android/tv/util/LocationUtils.java
deleted file mode 100644
index d5d7bee3..00000000
--- a/src/com/android/tv/util/LocationUtils.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.location.Address;
-import android.location.Geocoder;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.tv.tuner.util.PostalCodeUtils;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * A utility class to get the current location.
- */
-public class LocationUtils {
- private static final String TAG = "LocationUtils";
- private static final boolean DEBUG = false;
-
- private static Context sApplicationContext;
- private static Address sAddress;
- private static String sCountry;
- private static IOException sError;
-
- /**
- * Checks the current location.
- */
- public static synchronized Address getCurrentAddress(Context context) throws IOException,
- SecurityException {
- if (sAddress != null) {
- return sAddress;
- }
- if (sError != null) {
- throw sError;
- }
- if (sApplicationContext == null) {
- sApplicationContext = context.getApplicationContext();
- }
- LocationUtilsHelper.startLocationUpdates();
- return null;
- }
-
- /** Returns the current country. */
- @NonNull
- public static synchronized String getCurrentCountry(Context context) {
- if (sCountry != null) {
- return sCountry;
- }
- if (TextUtils.isEmpty(sCountry)) {
- sCountry = context.getResources().getConfiguration().locale.getCountry();
- }
- return sCountry;
- }
-
- private static void updateAddress(Location location) {
- if (DEBUG) Log.d(TAG, "Updating address with " + location);
- if (location == null) {
- return;
- }
- Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault());
- try {
- List<Address> addresses = geocoder.getFromLocation(
- location.getLatitude(), location.getLongitude(), 1);
- if (addresses != null && !addresses.isEmpty()) {
- sAddress = addresses.get(0);
- if (DEBUG) Log.d(TAG, "Got " + sAddress);
- try {
- PostalCodeUtils.updatePostalCode(sApplicationContext);
- } catch (Exception e) {
- // Do nothing
- }
- } else {
- if (DEBUG) Log.d(TAG, "No address returned");
- }
- sError = null;
- } catch (IOException e) {
- Log.w(TAG, "Error in updating address", e);
- sError = e;
- }
- }
-
- private LocationUtils() { }
-
- private static class LocationUtilsHelper {
- private static final LocationListener LOCATION_LISTENER = new LocationListener() {
- @Override
- public void onLocationChanged(Location location) {
- updateAddress(location);
- }
-
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) { }
-
- @Override
- public void onProviderEnabled(String provider) { }
-
- @Override
- public void onProviderDisabled(String provider) { }
- };
-
- private static LocationManager sLocationManager;
-
- public static void startLocationUpdates() {
- if (sLocationManager == null) {
- sLocationManager = (LocationManager) sApplicationContext.getSystemService(
- Context.LOCATION_SERVICE);
- try {
- sLocationManager.requestLocationUpdates(
- LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null);
- } catch (SecurityException e) {
- // Enables requesting the location updates again.
- sLocationManager = null;
- throw e;
- }
- }
- }
- }
-}
diff --git a/src/com/android/tv/util/MainThreadExecutor.java b/src/com/android/tv/util/MainThreadExecutor.java
index ce8f8ff3..5102ddbd 100644
--- a/src/com/android/tv/util/MainThreadExecutor.java
+++ b/src/com/android/tv/util/MainThreadExecutor.java
@@ -18,7 +18,6 @@ package com.android.tv.util;
import android.os.Handler;
import android.os.Looper;
-
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
@@ -26,11 +25,11 @@ import java.util.concurrent.TimeUnit;
/**
* An executor service that executes its tasks on the main thread.
*
- * Shutting down this executor is not supported.
+ * <p>Shutting down this executor is not supported.
*/
public class MainThreadExecutor extends AbstractExecutorService {
- private final static MainThreadExecutor INSTANCE = new MainThreadExecutor();
+ private static final MainThreadExecutor INSTANCE = new MainThreadExecutor();
public static MainThreadExecutor getInstance() {
return INSTANCE;
@@ -47,18 +46,14 @@ public class MainThreadExecutor extends AbstractExecutorService {
}
}
- /**
- * Not supported and throws an exception when used.
- */
+ /** Not supported and throws an exception when used. */
@Override
@Deprecated
public void shutdown() {
throw new UnsupportedOperationException();
}
- /**
- * Not supported and throws an exception when used.
- */
+ /** Not supported and throws an exception when used. */
@Override
@Deprecated
public List<Runnable> shutdownNow() {
@@ -75,12 +70,10 @@ public class MainThreadExecutor extends AbstractExecutorService {
return false;
}
- /**
- * Not supported and throws an exception when used.
- */
+ /** Not supported and throws an exception when used. */
@Override
@Deprecated
public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
throw new UnsupportedOperationException();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/MultiLongSparseArray.java b/src/com/android/tv/util/MultiLongSparseArray.java
index 1d5fa80b..a456df91 100644
--- a/src/com/android/tv/util/MultiLongSparseArray.java
+++ b/src/com/android/tv/util/MultiLongSparseArray.java
@@ -19,7 +19,6 @@ package com.android.tv.util;
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.LongSparseArray;
-
import java.util.Collections;
import java.util.Set;
@@ -29,8 +28,7 @@ import java.util.Set;
* <p>This has the same memory and performance trade offs listed in {@link LongSparseArray}.
*/
public class MultiLongSparseArray<T> {
- @VisibleForTesting
- static final int DEFAULT_MAX_EMPTIES_KEPT = 4;
+ @VisibleForTesting static final int DEFAULT_MAX_EMPTIES_KEPT = 4;
private final LongSparseArray<Set<T>> mSparseArray;
private final Set<T>[] mEmptySets;
private int mEmptyIndex = -1;
@@ -46,9 +44,8 @@ public class MultiLongSparseArray<T> {
}
/**
- * Adds a mapping from the specified key to the specified value,
- * replacing the previous mapping from the specified key if there
- * was one.
+ * Adds a mapping from the specified key to the specified value, replacing the previous mapping
+ * from the specified key if there was one.
*/
public void put(long key, T value) {
Set<T> values = mSparseArray.get(key);
@@ -59,9 +56,7 @@ public class MultiLongSparseArray<T> {
values.add(value);
}
- /**
- * Removes the value at the specified index.
- */
+ /** Removes the value at the specified index. */
public void remove(long key, T value) {
Set<T> values = mSparseArray.get(key);
if (values != null) {
@@ -74,17 +69,15 @@ public class MultiLongSparseArray<T> {
}
/**
- * Gets the set of Objects mapped from the specified key, or an empty set
- * if no such mapping has been made.
+ * Gets the set of Objects mapped from the specified key, or an empty set if no such mapping has
+ * been made.
*/
public Iterable<T> get(long key) {
Set<T> values = mSparseArray.get(key);
return values == null ? Collections.EMPTY_SET : values;
}
- /**
- * Clears cached empty sets.
- */
+ /** Clears cached empty sets. */
public void clearEmptyCache() {
while (mEmptyIndex >= 0) {
mEmptySets[mEmptyIndex--] = null;
diff --git a/src/com/android/tv/util/NamedThreadFactory.java b/src/com/android/tv/util/NamedThreadFactory.java
deleted file mode 100644
index fcdde952..00000000
--- a/src/com/android/tv/util/NamedThreadFactory.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.util;
-
-import android.support.annotation.NonNull;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * A thread factory that creates threads with a suffix.
- */
-public class NamedThreadFactory implements ThreadFactory {
- private final AtomicInteger mCount = new AtomicInteger(0);
- private final ThreadFactory mDefaultThreadFactory;
- private final String mPrefix;
-
- public NamedThreadFactory(final String baseName) {
- mDefaultThreadFactory = Executors.defaultThreadFactory();
- mPrefix = baseName + "-";
- }
-
- @Override
- public Thread newThread(@NonNull final Runnable runnable) {
- final Thread thread = mDefaultThreadFactory.newThread(runnable);
- thread.setName(mPrefix + mCount.getAndIncrement());
- return thread;
- }
-
- public boolean namedWithPrefix(Thread thread) {
- return thread.getName().startsWith(mPrefix);
- }
-}
diff --git a/src/com/android/tv/util/NetworkTrafficTags.java b/src/com/android/tv/util/NetworkTrafficTags.java
deleted file mode 100644
index 2dca613c..00000000
--- a/src/com/android/tv/util/NetworkTrafficTags.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.util;
-
-import android.net.TrafficStats;
-import android.support.annotation.NonNull;
-
-import java.util.concurrent.Executor;
-
-/** Constants for tagging network traffic in the Live channels app. */
-public final class NetworkTrafficTags {
-
- public static final int DEFAULT_LIVE_CHANNELS = 1;
- public static final int LOGO_FETCHER = 2;
- public static final int HDHOMERUN = 3;
- public static final int EPG_FETCH = 4;
-
- /**
- * An executor which simply wraps a provided delegate executor, but calls {@link
- * TrafficStats#setThreadStatsTag(int)} before executing any task.
- */
- public static class TrafficStatsTaggingExecutor implements Executor {
- private final Executor delegateExecutor;
- private final int tag;
-
- public TrafficStatsTaggingExecutor(Executor delegateExecutor, int tag) {
- this.delegateExecutor = delegateExecutor;
- this.tag = tag;
- }
-
- @Override
- public void execute(final @NonNull Runnable command) {
- // TODO(b/62038127): robolectric does not support lamdas in unbundled apps
- delegateExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- TrafficStats.setThreadStatsTag(tag);
- try {
- command.run();
- } finally {
- TrafficStats.clearThreadStatsTag();
- }
- }
- });
- }
- }
-
- private NetworkTrafficTags() {}
-}
diff --git a/src/com/android/tv/util/NetworkUtils.java b/src/com/android/tv/util/NetworkUtils.java
index ed3ce383..94581d5a 100644
--- a/src/com/android/tv/util/NetworkUtils.java
+++ b/src/com/android/tv/util/NetworkUtils.java
@@ -20,21 +20,16 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
-/**
- * A utility class to check the connectivity.
- */
+/** A utility class to check the connectivity. */
@WorkerThread
public class NetworkUtils {
private static final String GENERATE_204 = "http://clients3.google.com/generate_204";
- /**
- * Checks if the internet connection is available.
- */
+ /** Checks if the internet connection is available. */
public static boolean isNetworkAvailable(@Nullable ConnectivityManager connectivityManager) {
if (connectivityManager == null) {
return false;
@@ -62,5 +57,5 @@ public class NetworkUtils {
return false;
}
- private NetworkUtils() { }
+ private NetworkUtils() {}
}
diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java
index 49b02b82..3b72e091 100644
--- a/src/com/android/tv/util/OnboardingUtils.java
+++ b/src/com/android/tv/util/OnboardingUtils.java
@@ -21,9 +21,7 @@ import android.content.Intent;
import android.net.Uri;
import android.preference.PreferenceManager;
-/**
- * A utility class related to onboarding experience.
- */
+/** A utility class related to onboarding experience. */
public final class OnboardingUtils {
private static final String PREF_KEY_IS_FIRST_BOOT = "pref_onbaording_is_first_boot";
private static final String PREF_KEY_ONBOARDING_VERSION_CODE = "pref_onbaording_versionCode";
@@ -31,23 +29,17 @@ public final class OnboardingUtils {
private static final String MERCHANT_COLLECTION_URL_STRING = getMerchantCollectionUrl();
- /**
- * Intent to show merchant collection in online store.
- */
- public static final Intent ONLINE_STORE_INTENT = new Intent(Intent.ACTION_VIEW,
- Uri.parse(MERCHANT_COLLECTION_URL_STRING));
+ /** Intent to show merchant collection in online store. */
+ public static final Intent ONLINE_STORE_INTENT =
+ new Intent(Intent.ACTION_VIEW, Uri.parse(MERCHANT_COLLECTION_URL_STRING));
- /**
- * Checks if this is the first boot after the onboarding experience has been applied.
- */
+ /** Checks if this is the first boot after the onboarding experience has been applied. */
public static boolean isFirstBoot(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(PREF_KEY_IS_FIRST_BOOT, true);
}
- /**
- * Marks that the first boot has been completed.
- */
+ /** Marks that the first boot has been completed. */
public static void setFirstBootCompleted(Context context) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
@@ -56,27 +48,28 @@ public final class OnboardingUtils {
}
/**
- * Checks if this is the first run of {@link com.android.tv.MainActivity} with the
- * current onboarding version.
+ * Checks if this is the first run of {@link com.android.tv.MainActivity} with the current
+ * onboarding version.
*/
public static boolean isFirstRunWithCurrentVersion(Context context) {
- int versionCode = PreferenceManager.getDefaultSharedPreferences(context)
- .getInt(PREF_KEY_ONBOARDING_VERSION_CODE, 0);
+ int versionCode =
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .getInt(PREF_KEY_ONBOARDING_VERSION_CODE, 0);
return versionCode != ONBOARDING_VERSION;
}
/**
- * Marks that the first run of {@link com.android.tv.MainActivity} with the current
- * onboarding version has been completed.
+ * Marks that the first run of {@link com.android.tv.MainActivity} with the current onboarding
+ * version has been completed.
*/
public static void setFirstRunWithCurrentVersionCompleted(Context context) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putInt(PREF_KEY_ONBOARDING_VERSION_CODE, ONBOARDING_VERSION).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putInt(PREF_KEY_ONBOARDING_VERSION_CODE, ONBOARDING_VERSION)
+ .apply();
}
- /**
- * Returns merchant collection URL.
- */
+ /** Returns merchant collection URL. */
private static String getMerchantCollectionUrl() {
return "TODO: add a merchant collection url";
}
diff --git a/src/com/android/tv/util/Partner.java b/src/com/android/tv/util/Partner.java
index e3688392..c5e9aad2 100644
--- a/src/com/android/tv/util/Partner.java
+++ b/src/com/android/tv/util/Partner.java
@@ -26,7 +26,6 @@ import android.content.res.Resources;
import android.media.tv.TvInputInfo;
import android.text.TextUtils;
import android.util.Log;
-
import java.util.HashMap;
import java.util.Map;
@@ -42,6 +41,7 @@ public class Partner {
/** ID tags for device input types */
public static final String INPUT_TYPE_BUNDLED_TUNER = "input_type_combined_tuners";
+
public static final String INPUT_TYPE_TUNER = "input_type_tuner";
public static final String INPUT_TYPE_CEC_LOGICAL = "input_type_cec_logical";
public static final String INPUT_TYPE_CEC_RECORDER = "input_type_cec_recorder";
@@ -68,6 +68,7 @@ public class Partner {
private final Resources mResources;
private static final Map<String, Integer> INPUT_TYPE_MAP = new HashMap<>();
+
static {
INPUT_TYPE_MAP.put(INPUT_TYPE_BUNDLED_TUNER, TvInputManagerHelper.TYPE_BUNDLED_TUNER);
INPUT_TYPE_MAP.put(INPUT_TYPE_TUNER, TvInputInfo.TYPE_TUNER);
diff --git a/src/com/android/tv/util/PermissionUtils.java b/src/com/android/tv/util/PermissionUtils.java
deleted file mode 100644
index a355be99..00000000
--- a/src/com/android/tv/util/PermissionUtils.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.android.tv.util;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-/**
- * Util class to handle permissions.
- */
-public class PermissionUtils {
- /**
- * Permission to read the TV listings.
- */
- public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
-
- private static Boolean sHasAccessAllEpgPermission;
- private static Boolean sHasAccessWatchedHistoryPermission;
- private static Boolean sHasModifyParentalControlsPermission;
-
- public static boolean hasAccessAllEpg(Context context) {
- if (sHasAccessAllEpgPermission == null) {
- sHasAccessAllEpgPermission = context.checkSelfPermission(
- "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA")
- == PackageManager.PERMISSION_GRANTED;
- }
- return sHasAccessAllEpgPermission;
- }
-
- public static boolean hasAccessWatchedHistory(Context context) {
- if (sHasAccessWatchedHistoryPermission == null) {
- sHasAccessWatchedHistoryPermission = context.checkSelfPermission(
- "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS")
- == PackageManager.PERMISSION_GRANTED;
- }
- return sHasAccessWatchedHistoryPermission;
- }
-
- public static boolean hasModifyParentalControls(Context context) {
- if (sHasModifyParentalControlsPermission == null) {
- sHasModifyParentalControlsPermission = context.checkSelfPermission(
- "android.permission.MODIFY_PARENTAL_CONTROLS")
- == PackageManager.PERMISSION_GRANTED;
- }
- return sHasModifyParentalControlsPermission;
- }
-
- public static boolean hasReadTvListings(Context context) {
- return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- public static boolean hasInternet(Context context) {
- return context.checkSelfPermission("android.permission.INTERNET")
- == PackageManager.PERMISSION_GRANTED;
- }
-}
diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java
index 8b45131b..764689c2 100644
--- a/src/com/android/tv/util/RecurringRunner.java
+++ b/src/com/android/tv/util/RecurringRunner.java
@@ -22,10 +22,8 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.support.annotation.WorkerThread;
import android.util.Log;
-
-import com.android.tv.common.SharedPreferencesUtils;
import com.android.tv.common.SoftPreconditions;
-
+import com.android.tv.common.util.SharedPreferencesUtils;
import java.util.Date;
/**
@@ -46,8 +44,8 @@ public final class RecurringRunner {
private final String mName;
private boolean mRunning;
- public RecurringRunner(Context context, long intervalMs, Runnable runnable,
- Runnable onStopRunnable) {
+ public RecurringRunner(
+ Context context, long intervalMs, Runnable runnable, Runnable onStopRunnable) {
mContext = context.getApplicationContext();
mRunnable = runnable;
mOnStopRunnable = onStopRunnable;
@@ -99,18 +97,21 @@ public final class RecurringRunner {
// Run it anyways even if it is in the past
if (DEBUG) Log.i(TAG, "Next run of " + mName + " at " + new Date(next));
long delay = Math.max(next - now, 0);
- boolean posted = mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- try {
- if (DEBUG) Log.i(TAG, "Starting " + mName);
- mRunnable.run();
- } catch (Exception e) {
- Log.w(TAG, "Error running " + mName, e);
- }
- postAt(resetNextRunTime());
- }
- }, delay);
+ boolean posted =
+ mHandler.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.i(TAG, "Starting " + mName);
+ mRunnable.run();
+ } catch (Exception e) {
+ Log.w(TAG, "Error running " + mName, e);
+ }
+ postAt(resetNextRunTime());
+ }
+ },
+ delay);
if (!posted) {
Log.w(TAG, "Scheduling a future run of " + mName + " at " + new Date(next) + "failed");
}
@@ -118,8 +119,8 @@ public final class RecurringRunner {
}
private SharedPreferences getSharedPreferences() {
- return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER,
- Context.MODE_PRIVATE);
+ return mContext.getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE);
}
@WorkerThread
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
index 32e3a81f..0d536320 100644
--- a/src/com/android/tv/util/SetupUtils.java
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -28,24 +28,20 @@ import android.media.tv.TvInputManager;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.TvApplication;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.BaseApplication;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-
+import com.android.tv.data.api.Channel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-/**
- * A utility class related to input setup.
- */
+/** A utility class related to input setup. */
public class SetupUtils {
private static final String TAG = "SetupUtils";
private static final boolean DEBUG = false;
@@ -58,9 +54,8 @@ public class SetupUtils {
// Recognized inputs means that the user already knows the inputs are installed.
private static final String PREF_KEY_RECOGNIZED_INPUTS = "recognized_inputs";
private static final String PREF_KEY_IS_FIRST_TUNE = "is_first_tune";
- private static SetupUtils sSetupUtils;
- private final TvApplication mTvApplication;
+ private final Context mContext;
private final SharedPreferences mSharedPreferences;
private final Set<String> mKnownInputs;
private final Set<String> mSetUpInputs;
@@ -68,106 +63,104 @@ public class SetupUtils {
private boolean mIsFirstTune;
private final String mTunerInputId;
- private SetupUtils(TvApplication tvApplication) {
- mTvApplication = tvApplication;
- mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication);
+ @VisibleForTesting
+ protected SetupUtils(Context context) {
+ mContext = context;
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
mSetUpInputs = new ArraySet<>();
- mSetUpInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS,
- Collections.emptySet()));
+ mSetUpInputs.addAll(
+ mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.emptySet()));
mKnownInputs = new ArraySet<>();
- mKnownInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS,
- Collections.emptySet()));
+ mKnownInputs.addAll(
+ mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS, Collections.emptySet()));
mRecognizedInputs = new ArraySet<>();
- mRecognizedInputs.addAll(mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS,
- mKnownInputs));
+ mRecognizedInputs.addAll(
+ mSharedPreferences.getStringSet(PREF_KEY_RECOGNIZED_INPUTS, mKnownInputs));
mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true);
- mTunerInputId = TvContract.buildInputId(new ComponentName(tvApplication,
- TunerTvInputService.class));
+ mTunerInputId = BaseApplication.getSingletons(context).getEmbeddedTunerInputId();
}
/**
- * Gets an instance of {@link SetupUtils}.
+ * Creates an instance of {@link SetupUtils}.
+ *
+ * <p><b>WARNING</b> this should only be called by the top level application.
*/
- public static SetupUtils getInstance(Context context) {
- if (sSetupUtils != null) {
- return sSetupUtils;
- }
- sSetupUtils = new SetupUtils((TvApplication) context.getApplicationContext());
- return sSetupUtils;
+ public static SetupUtils createForTvSingletons(Context context) {
+ return new SetupUtils(context.getApplicationContext());
}
- /**
- * Additional work after the setup of TV input.
- */
- public void onTvInputSetupFinished(final String inputId,
- @Nullable final Runnable postRunnable) {
+ /** Additional work after the setup of TV input. */
+ public void onTvInputSetupFinished(
+ final String inputId, @Nullable final Runnable postRunnable) {
// When TIS adds several channels, ChannelDataManager.Listener.onChannelList
// Updated() can be called several times. In this case, it is hard to detect
// which one is the last callback. To reduce error prune, we update channel
// list again and make all channels of {@code inputId} browsable.
onSetupDone(inputId);
- final ChannelDataManager manager = mTvApplication.getChannelDataManager();
+ final ChannelDataManager manager =
+ TvSingletons.getSingletons(mContext).getChannelDataManager();
if (!manager.isDbLoadFinished()) {
- manager.addListener(new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- manager.removeListener(this);
- updateChannelsAfterSetup(mTvApplication, inputId, postRunnable);
- }
+ manager.addListener(
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ manager.removeListener(this);
+ updateChannelsAfterSetup(mContext, inputId, postRunnable);
+ }
- @Override
- public void onChannelListUpdated() { }
+ @Override
+ public void onChannelListUpdated() {}
- @Override
- public void onChannelBrowsableChanged() { }
- });
+ @Override
+ public void onChannelBrowsableChanged() {}
+ });
} else {
- updateChannelsAfterSetup(mTvApplication, inputId, postRunnable);
+ updateChannelsAfterSetup(mContext, inputId, postRunnable);
}
}
- private static void updateChannelsAfterSetup(Context context, final String inputId,
- final Runnable postRunnable) {
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- final ChannelDataManager manager = appSingletons.getChannelDataManager();
- manager.updateChannels(new Runnable() {
- @Override
- public void run() {
- Channel firstChannelForInput = null;
- boolean browsableChanged = false;
- for (Channel channel : manager.getChannelList()) {
- if (channel.getInputId().equals(inputId)) {
- if (!channel.isBrowsable()) {
- manager.updateBrowsable(channel.getId(), true, true);
- browsableChanged = true;
+ private static void updateChannelsAfterSetup(
+ Context context, final String inputId, final Runnable postRunnable) {
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ final ChannelDataManager manager = tvSingletons.getChannelDataManager();
+ manager.updateChannels(
+ new Runnable() {
+ @Override
+ public void run() {
+ Channel firstChannelForInput = null;
+ boolean browsableChanged = false;
+ for (Channel channel : manager.getChannelList()) {
+ if (channel.getInputId().equals(inputId)) {
+ if (!channel.isBrowsable()) {
+ manager.updateBrowsable(channel.getId(), true, true);
+ browsableChanged = true;
+ }
+ if (firstChannelForInput == null) {
+ firstChannelForInput = channel;
+ }
+ }
+ }
+ if (firstChannelForInput != null) {
+ Utils.setLastWatchedChannel(context, firstChannelForInput);
}
- if (firstChannelForInput == null) {
- firstChannelForInput = channel;
+ if (browsableChanged) {
+ manager.notifyChannelBrowsableChanged();
+ manager.applyUpdatedValuesToDb();
+ }
+ if (postRunnable != null) {
+ postRunnable.run();
}
}
- }
- if (firstChannelForInput != null) {
- Utils.setLastWatchedChannel(context, firstChannelForInput);
- }
- if (browsableChanged) {
- manager.notifyChannelBrowsableChanged();
- manager.applyUpdatedValuesToDb();
- }
- if (postRunnable != null) {
- postRunnable.run();
- }
- }
- });
+ });
}
- /**
- * Marks the channels in newly installed inputs browsable.
- */
+ /** Marks the channels in newly installed inputs browsable. */
@UiThread
public void markNewChannelsBrowsable() {
Set<String> newInputsWithChannels = new HashSet<>();
- TvInputManagerHelper tvInputManagerHelper = mTvApplication.getTvInputManagerHelper();
- ChannelDataManager channelDataManager = mTvApplication.getChannelDataManager();
+ TvSingletons singletons = TvSingletons.getSingletons(mContext);
+ TvInputManagerHelper tvInputManagerHelper = singletons.getTvInputManagerHelper();
+ ChannelDataManager channelDataManager = singletons.getChannelDataManager();
SoftPreconditions.checkState(channelDataManager.isDbLoadFinished());
for (TvInputInfo input : tvInputManagerHelper.getTvInputInfos(true, true)) {
String inputId = input.getId();
@@ -175,9 +168,13 @@ public class SetupUtils {
onSetupDone(inputId);
newInputsWithChannels.add(inputId);
if (DEBUG) {
- Log.d(TAG, "New input " + inputId + " has "
- + channelDataManager.getChannelCountForInput(inputId)
- + " channels");
+ Log.d(
+ TAG,
+ "New input "
+ + inputId
+ + " has "
+ + channelDataManager.getChannelCountForInput(inputId)
+ + " channels");
}
}
}
@@ -195,9 +192,7 @@ public class SetupUtils {
return mIsFirstTune;
}
- /**
- * Returns true, if the input with {@code inputId} is newly installed.
- */
+ /** Returns true, if the input with {@code inputId} is newly installed. */
public boolean isNewInput(String inputId) {
return !mKnownInputs.contains(inputId);
}
@@ -209,13 +204,14 @@ public class SetupUtils {
public void markAsKnownInput(String inputId) {
mKnownInputs.add(inputId);
mRecognizedInputs.add(inputId);
- mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs)
- .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply();
+ mSharedPreferences
+ .edit()
+ .putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs)
+ .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs)
+ .apply();
}
- /**
- * Returns {@code true}, if {@code inputId}'s setup has been done before.
- */
+ /** Returns {@code true}, if {@code inputId}'s setup has been done before. */
public boolean isSetupDone(String inputId) {
boolean done = mSetUpInputs.contains(inputId);
if (DEBUG) {
@@ -224,9 +220,7 @@ public class SetupUtils {
return done;
}
- /**
- * Returns true, if there is any newly installed input.
- */
+ /** Returns true, if there is any newly installed input. */
public boolean hasNewInput(TvInputManagerHelper inputManager) {
for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) {
if (isNewInput(input.getId())) {
@@ -236,9 +230,7 @@ public class SetupUtils {
return false;
}
- /**
- * Checks whether the given input is already recognized by the user or not.
- */
+ /** Checks whether the given input is already recognized by the user or not. */
private boolean isRecognizedInput(String inputId) {
return mRecognizedInputs.contains(inputId);
}
@@ -251,13 +243,13 @@ public class SetupUtils {
for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) {
mRecognizedInputs.add(input.getId());
}
- mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs)
+ mSharedPreferences
+ .edit()
+ .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs)
.apply();
}
- /**
- * Checks whether there are any unrecognized inputs.
- */
+ /** Checks whether there are any unrecognized inputs. */
public boolean hasUnrecognizedInput(TvInputManagerHelper inputManager) {
for (TvInputInfo input : inputManager.getTvInputInfos(true, true)) {
if (!isRecognizedInput(input.getId())) {
@@ -276,8 +268,8 @@ public class SetupUtils {
// Find all already-verified packages.
Set<String> setUpPackages = new HashSet<>();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
- for (String input : sp.getStringSet(PREF_KEY_SET_UP_INPUTS,
- Collections.<String>emptySet())) {
+ for (String input :
+ sp.getStringSet(PREF_KEY_SET_UP_INPUTS, Collections.<String>emptySet())) {
if (!TextUtils.isEmpty(input)) {
ComponentName componentName = ComponentName.unflattenFromString(input);
if (componentName != null) {
@@ -299,23 +291,28 @@ public class SetupUtils {
*/
public static void grantEpgPermission(Context context, String packageName) {
if (DEBUG) {
- Log.d(TAG, "grantEpgPermission(context=" + context + ", packageName=" + packageName
- + ")");
+ Log.d(
+ TAG,
+ "grantEpgPermission(context=" + context + ", packageName=" + packageName + ")");
}
try {
- int modeFlags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ int modeFlags =
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
context.grantUriPermission(packageName, TvContract.Channels.CONTENT_URI, modeFlags);
context.grantUriPermission(packageName, TvContract.Programs.CONTENT_URI, modeFlags);
} catch (SecurityException e) {
- Log.e(TAG, "Either TvProvider does not allow granting of Uri permissions or the app"
- + " does not have permission.", e);
+ Log.e(
+ TAG,
+ "Either TvProvider does not allow granting of Uri permissions or the app"
+ + " does not have permission.",
+ e);
}
}
/**
- * Called when Live channels app is launched. Once it is called, {@link
- * #isFirstTune} will return false.
+ * Called when Live channels app is launched. Once it is called, {@link #isFirstTune} will
+ * return false.
*/
public void onTuned() {
if (!mIsFirstTune) {
@@ -325,9 +322,7 @@ public class SetupUtils {
mSharedPreferences.edit().putBoolean(PREF_KEY_IS_FIRST_TUNE, false).apply();
}
- /**
- * Called when input list is changed. It mainly handles input removals.
- */
+ /** Called when input list is changed. It mainly handles input removals. */
public void onInputListUpdated(TvInputManager manager) {
// mRecognizedInputs > mKnownInputs > mSetUpInputs.
Set<String> removedInputList = new HashSet<>(mRecognizedInputs);
@@ -345,9 +340,10 @@ public class SetupUtils {
try {
// Just after booting, input list from TvInputManager are not reliable.
// So we need to double-check package existence. b/29034900
- mTvApplication.getPackageManager().getPackageInfo(
- ComponentName.unflattenFromString(input)
- .getPackageName(), PackageManager.GET_ACTIVITIES);
+ mContext.getPackageManager()
+ .getPackageInfo(
+ ComponentName.unflattenFromString(input).getPackageName(),
+ PackageManager.GET_ACTIVITIES);
Log.i(TAG, "TV input (" + input + ") is removed but package is not deleted");
} catch (NameNotFoundException e) {
Log.i(TAG, "TV input (" + input + ") and its package are removed");
@@ -358,9 +354,12 @@ public class SetupUtils {
}
}
if (inputPackageDeleted) {
- mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs)
+ mSharedPreferences
+ .edit()
+ .putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs)
.putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs)
- .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs).apply();
+ .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs)
+ .apply();
}
}
}
@@ -375,7 +374,9 @@ public class SetupUtils {
if (!mRecognizedInputs.contains(inputId)) {
Log.i(TAG, "An unrecognized input's setup has been done. inputId=" + inputId);
mRecognizedInputs.add(inputId);
- mSharedPreferences.edit().putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs)
+ mSharedPreferences
+ .edit()
+ .putStringSet(PREF_KEY_RECOGNIZED_INPUTS, mRecognizedInputs)
.apply();
}
if (!mKnownInputs.contains(inputId)) {
@@ -388,4 +389,4 @@ public class SetupUtils {
mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply();
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/SqlParams.java b/src/com/android/tv/util/SqlParams.java
new file mode 100644
index 00000000..c4b803b6
--- /dev/null
+++ b/src/com/android/tv/util/SqlParams.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.util;
+
+import android.database.DatabaseUtils;
+import java.util.Arrays;
+
+/** Convenience class for SQL operations. */
+public class SqlParams {
+ private String mTables;
+ private String mSelection;
+ private String[] mSelectionArgs;
+
+ public SqlParams(String tables, String selection, String... selectionArgs) {
+ setTables(tables);
+ setWhere(selection, selectionArgs);
+ }
+
+ public String getTables() {
+ return mTables;
+ }
+
+ public String getSelection() {
+ return mSelection;
+ }
+
+ public String[] getSelectionArgs() {
+ return mSelectionArgs;
+ }
+
+ public void setTables(String tables) {
+ mTables = tables;
+ }
+
+ public void setWhere(String selection, String... selectionArgs) {
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ }
+
+ public void appendWhere(String selection, String... selectionArgs) {
+ mSelection = DatabaseUtils.concatenateWhere(mSelection, selection);
+ if (selectionArgs != null) {
+ mSelectionArgs = DatabaseUtils.appendSelectionArgs(mSelectionArgs, selectionArgs);
+ }
+ }
+
+ public void appendWhereEquals(String name, String value) {
+ appendWhere(name + "=?", value);
+ }
+
+ @Override
+ public String toString() {
+ return "tables "
+ + getTables()
+ + " where "
+ + getSelection()
+ + " with "
+ + Arrays.toString(getSelectionArgs());
+ }
+}
diff --git a/src/com/android/tv/util/StringUtils.java b/src/com/android/tv/util/StringUtils.java
deleted file mode 100644
index 659807e2..00000000
--- a/src/com/android/tv/util/StringUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-/**
- * Utility class for handling {@link String}.
- */
-public final class StringUtils {
-
- private StringUtils() { }
-
- /**
- * Returns compares two strings lexicographically and handles null values quietly.
- */
- public static int compare(String a, String b) {
- if (a == null) {
- return b == null ? 0 : -1;
- }
- if (b == null) {
- return 1;
- }
- return a.compareTo(b);
- }
-}
diff --git a/src/com/android/tv/util/SystemProperties.java b/src/com/android/tv/util/SystemProperties.java
deleted file mode 100644
index e737f233..00000000
--- a/src/com/android/tv/util/SystemProperties.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-import com.android.tv.common.BooleanSystemProperty;
-
-/**
- * A convenience class for getting TV related system properties.
- */
-public final class SystemProperties {
-
- /**
- * Allow Google Analytics for eng builds.
- */
- public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG = new BooleanSystemProperty(
- "tv_allow_analytics_in_eng", false);
-
- /**
- * Allow Strict mode for debug builds.
- */
- public static final BooleanSystemProperty ALLOW_STRICT_MODE = new BooleanSystemProperty(
- "tv_allow_strict_mode", true);
-
- /**
- * When true {@link android.view.KeyEvent}s are logged. Defaults to false.
- */
- public static final BooleanSystemProperty LOG_KEYEVENT = new BooleanSystemProperty(
- "tv_log_keyevent", false);
- /**
- * When true debug keys are used. Defaults to false.
- */
- public static final BooleanSystemProperty USE_DEBUG_KEYS = new BooleanSystemProperty(
- "tv_use_debug_keys", false);
-
- /**
- * Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}.
- */
- public static final BooleanSystemProperty USE_TRACKER = new BooleanSystemProperty(
- "tv_use_tracker", true);
-
- static {
- updateSystemProperties();
- }
-
- private SystemProperties() {
- }
-
- /**
- * Update the TV related system properties.
- */
- public static void updateSystemProperties() {
- BooleanSystemProperty.resetAll();
- }
-}
diff --git a/src/com/android/tv/util/TimeShiftUtils.java b/src/com/android/tv/util/TimeShiftUtils.java
index 8038a78f..977f3333 100644
--- a/src/com/android/tv/util/TimeShiftUtils.java
+++ b/src/com/android/tv/util/TimeShiftUtils.java
@@ -18,9 +18,7 @@ package com.android.tv.util;
import java.util.concurrent.TimeUnit;
-/**
- * A class that includes convenience methods for time shift plays.
- */
+/** A class that includes convenience methods for time shift plays. */
public class TimeShiftUtils {
private static final String TAG = "TimeShiftUtils";
private static final boolean DEBUG = false;
@@ -30,8 +28,8 @@ public class TimeShiftUtils {
private static final int[] LONG_PROGRAM_SPEED_FACTORS = new int[] {2, 8, 32, 128};
/**
- * The maximum play speed level support by time shift play. In other words, the valid
- * speed levels are ranged from 0 to MAX_SPEED_LEVEL (included).
+ * The maximum play speed level support by time shift play. In other words, the valid speed
+ * levels are ranged from 0 to MAX_SPEED_LEVEL (included).
*/
public static final int MAX_SPEED_LEVEL = SHORT_PROGRAM_SPEED_FACTORS.length - 1;
@@ -45,17 +43,19 @@ public class TimeShiftUtils {
*/
public static int getPlaybackSpeed(int speedLevel, long programDurationMillis)
throws IndexOutOfBoundsException {
- return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS) ?
- LONG_PROGRAM_SPEED_FACTORS[speedLevel] : SHORT_PROGRAM_SPEED_FACTORS[speedLevel];
+ return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS)
+ ? LONG_PROGRAM_SPEED_FACTORS[speedLevel]
+ : SHORT_PROGRAM_SPEED_FACTORS[speedLevel];
}
/**
* Returns the maxium possible play speed according to the program's length.
+ *
* @param programDurationMillis the length of program under playing.
*/
public static int getMaxPlaybackSpeed(long programDurationMillis) {
- return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS) ?
- LONG_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL]
+ return (programDurationMillis > SHORT_PROGRAM_THRESHOLD_MILLIS)
+ ? LONG_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL]
: SHORT_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL];
}
}
diff --git a/src/com/android/tv/util/ToastUtils.java b/src/com/android/tv/util/ToastUtils.java
index 34346b2a..a25653f6 100644
--- a/src/com/android/tv/util/ToastUtils.java
+++ b/src/com/android/tv/util/ToastUtils.java
@@ -19,18 +19,13 @@ package com.android.tv.util;
import android.content.Context;
import android.support.annotation.MainThread;
import android.widget.Toast;
-
import java.lang.ref.WeakReference;
-/**
- * A utility class for the toast message.
- */
+/** A utility class for the toast message. */
public class ToastUtils {
private static WeakReference<Toast> sToast;
- /**
- * Shows the toast message after canceling the previous one.
- */
+ /** Shows the toast message after canceling the previous one. */
@MainThread
public static void show(Context context, CharSequence text, int duration) {
if (sToast != null && sToast.get() != null) {
diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java
index 730a985b..625fb7b2 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -21,21 +21,23 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.tv.TvContentRatingSystemInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Handler;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
-
-import com.android.tv.Features;
+import com.android.tv.TvFeatures;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.common.util.CommonUtils;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
-
+import com.android.tv.util.images.ImageCache;
+import com.android.tv.util.images.ImageLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -49,10 +51,61 @@ public class TvInputManagerHelper {
private static final String TAG = "TvInputManagerHelper";
private static final boolean DEBUG = false;
- /**
- * Types of HDMI device and bundled tuner.
- */
+ public interface TvInputManagerInterface {
+ TvInputInfo getTvInputInfo(String inputId);
+
+ Integer getInputState(String inputId);
+
+ void registerCallback(TvInputCallback internalCallback, Handler handler);
+
+ void unregisterCallback(TvInputCallback internalCallback);
+
+ List<TvInputInfo> getTvInputList();
+
+ List<TvContentRatingSystemInfo> getTvContentRatingSystemList();
+ }
+
+ private static final class TvInputManagerImpl implements TvInputManagerInterface {
+ private final TvInputManager delegate;
+
+ private TvInputManagerImpl(TvInputManager delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public TvInputInfo getTvInputInfo(String inputId) {
+ return delegate.getTvInputInfo(inputId);
+ }
+
+ @Override
+ public Integer getInputState(String inputId) {
+ return delegate.getInputState(inputId);
+ }
+
+ @Override
+ public void registerCallback(TvInputCallback internalCallback, Handler handler) {
+ delegate.registerCallback(internalCallback, handler);
+ }
+
+ @Override
+ public void unregisterCallback(TvInputCallback internalCallback) {
+ delegate.unregisterCallback(internalCallback);
+ }
+
+ @Override
+ public List<TvInputInfo> getTvInputList() {
+ return delegate.getTvInputList();
+ }
+
+ @Override
+ public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() {
+ return delegate.getTvContentRatingSystemList();
+ }
+ }
+
+ /** Types of HDMI device and bundled tuner. */
public static final int TYPE_CEC_DEVICE = -2;
+
public static final int TYPE_BUNDLED_TUNER = -3;
public static final int TYPE_CEC_DEVICE_RECORDER = -4;
public static final int TYPE_CEC_DEVICE_PLAYBACK = -5;
@@ -60,14 +113,13 @@ public class TvInputManagerHelper {
private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
"com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
- private static final String [] mPhysicalTunerBlackList = {
+ private static final String[] mPhysicalTunerBlackList = {
};
private static final String META_LABEL_SORT_KEY = "input_sort_key";
- /**
- * The default tv input priority to show.
- */
+ /** The default tv input priority to show. */
private static final ArrayList<Integer> DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>();
+
static {
DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER);
DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER);
@@ -90,12 +142,12 @@ public class TvInputManagerHelper {
};
private static final String[] TESTABLE_INPUTS = {
- "com.android.tv.testinput/.TestTvInputService"
+ "com.android.tv.testinput/.TestTvInputService"
};
private final Context mContext;
private final PackageManager mPackageManager;
- private final TvInputManager mTvInputManager;
+ protected final TvInputManagerInterface mTvInputManager;
private final Map<String, Integer> mInputStateMap = new HashMap<>();
private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
private final Map<String, String> mTvInputLabels = new ArrayMap<>();
@@ -106,100 +158,105 @@ public class TvInputManagerHelper {
private final Map<String, Drawable> mTvInputApplicationIcons = new ArrayMap<>();
private final Map<String, Drawable> mTvInputAppliactionBanners = new ArrayMap<>();
- private final TvInputCallback mInternalCallback = new TvInputCallback() {
- @Override
- public void onInputStateChanged(String inputId, int state) {
- if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state);
- if (isInBlackList(inputId)) {
- return;
- }
- mInputStateMap.put(inputId, state);
- for (TvInputCallback callback : mCallbacks) {
- callback.onInputStateChanged(inputId, state);
- }
- }
+ private final TvInputCallback mInternalCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputStateChanged(String inputId, int state) {
+ if (DEBUG) Log.d(TAG, "onInputStateChanged " + inputId + " state=" + state);
+ if (isInBlackList(inputId)) {
+ return;
+ }
+ mInputStateMap.put(inputId, state);
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputStateChanged(inputId, state);
+ }
+ }
- @Override
- public void onInputAdded(String inputId) {
- if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
- if (isInBlackList(inputId)) {
- return;
- }
- TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
- if (info != null) {
- mInputMap.put(inputId, info);
- mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
- CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
- if (inputCustomLabel != null) {
- mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
+ @Override
+ public void onInputAdded(String inputId) {
+ if (DEBUG) Log.d(TAG, "onInputAdded " + inputId);
+ if (isInBlackList(inputId)) {
+ return;
+ }
+ TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
+ if (info != null) {
+ mInputMap.put(inputId, info);
+ CharSequence label = info.loadLabel(mContext);
+ // in tests the label may be missing just use the input id
+ mTvInputLabels.put(inputId, label != null ? label.toString() : inputId);
+ CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
+ if (inputCustomLabel != null) {
+ mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
+ }
+ mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
+ mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
+ }
+ mContentRatingsManager.update();
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputAdded(inputId);
+ }
}
- mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
- mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
- }
- mContentRatingsManager.update();
- for (TvInputCallback callback : mCallbacks) {
- callback.onInputAdded(inputId);
- }
- }
- @Override
- public void onInputRemoved(String inputId) {
- if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
- mInputMap.remove(inputId);
- mTvInputLabels.remove(inputId);
- mTvInputCustomLabels.remove(inputId);
- mTvInputApplicationLabels.remove(inputId);
- mTvInputApplicationIcons.remove(inputId);
- mTvInputAppliactionBanners.remove(inputId);
- mInputStateMap.remove(inputId);
- mInputIdToPartnerInputMap.remove(inputId);
- mContentRatingsManager.update();
- for (TvInputCallback callback : mCallbacks) {
- callback.onInputRemoved(inputId);
- }
- ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
- inputId));
- }
+ @Override
+ public void onInputRemoved(String inputId) {
+ if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
+ mInputMap.remove(inputId);
+ mTvInputLabels.remove(inputId);
+ mTvInputCustomLabels.remove(inputId);
+ mTvInputApplicationLabels.remove(inputId);
+ mTvInputApplicationIcons.remove(inputId);
+ mTvInputAppliactionBanners.remove(inputId);
+ mInputStateMap.remove(inputId);
+ mInputIdToPartnerInputMap.remove(inputId);
+ mContentRatingsManager.update();
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputRemoved(inputId);
+ }
+ ImageCache.getInstance()
+ .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId));
+ }
- @Override
- public void onInputUpdated(String inputId) {
- if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId);
- if (isInBlackList(inputId)) {
- return;
- }
- TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
- mInputMap.put(inputId, info);
- mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
- CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
- if (inputCustomLabel != null) {
- mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
- }
- mTvInputApplicationLabels.remove(inputId);
- mTvInputApplicationIcons.remove(inputId);
- mTvInputAppliactionBanners.remove(inputId);
- for (TvInputCallback callback : mCallbacks) {
- callback.onInputUpdated(inputId);
- }
- ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
- inputId));
- }
+ @Override
+ public void onInputUpdated(String inputId) {
+ if (DEBUG) Log.d(TAG, "onInputUpdated " + inputId);
+ if (isInBlackList(inputId)) {
+ return;
+ }
+ TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
+ mInputMap.put(inputId, info);
+ mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
+ CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
+ if (inputCustomLabel != null) {
+ mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
+ }
+ mTvInputApplicationLabels.remove(inputId);
+ mTvInputApplicationIcons.remove(inputId);
+ mTvInputAppliactionBanners.remove(inputId);
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputUpdated(inputId);
+ }
+ ImageCache.getInstance()
+ .remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(inputId));
+ }
- @Override
- public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
- if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo);
- mInputMap.put(inputInfo.getId(), inputInfo);
- mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString());
- CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext);
- if (inputCustomLabel != null) {
- mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString());
- }
- for (TvInputCallback callback : mCallbacks) {
- callback.onTvInputInfoUpdated(inputInfo);
- }
- ImageCache.getInstance().remove(ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
- inputInfo.getId()));
- }
- };
+ @Override
+ public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
+ if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo);
+ mInputMap.put(inputInfo.getId(), inputInfo);
+ mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString());
+ CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext);
+ if (inputCustomLabel != null) {
+ mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString());
+ }
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onTvInputInfoUpdated(inputInfo);
+ }
+ ImageCache.getInstance()
+ .remove(
+ ImageLoader.LoadTvInputLogoTask.getTvInputLogoKey(
+ inputInfo.getId()));
+ }
+ };
private final Handler mHandler = new Handler();
private boolean mStarted;
@@ -209,10 +266,23 @@ public class TvInputManagerHelper {
private final Comparator<TvInputInfo> mTvInputInfoComparator;
public TvInputManagerHelper(Context context) {
+ this(context, createTvInputManagerWrapper(context));
+ }
+
+ @Nullable
+ protected static TvInputManagerImpl createTvInputManagerWrapper(Context context) {
+ TvInputManager tvInputManager =
+ (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+ return tvInputManager == null ? null : new TvInputManagerImpl(tvInputManager);
+ }
+
+ @VisibleForTesting
+ protected TvInputManagerHelper(
+ Context context, @Nullable TvInputManagerInterface tvInputManager) {
mContext = context.getApplicationContext();
mPackageManager = context.getPackageManager();
- mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- mContentRatingsManager = new ContentRatingsManager(context);
+ mTvInputManager = tvInputManager;
+ mContentRatingsManager = new ContentRatingsManager(context, tvInputManager);
mParentalControlSettings = new ParentalControlSettings(context);
mTvInputInfoComparator = new InputComparatorInternal(this);
}
@@ -247,7 +317,9 @@ public class TvInputManagerHelper {
mInputStateMap.put(inputId, state);
mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
}
- SoftPreconditions.checkState(mInputStateMap.size() == mInputMap.size(), TAG,
+ SoftPreconditions.checkState(
+ mInputStateMap.size() == mInputMap.size(),
+ TAG,
"mInputStateMap not the same size as mInputMap");
mContentRatingsManager.update();
}
@@ -264,13 +336,12 @@ public class TvInputManagerHelper {
mTvInputCustomLabels.clear();
mTvInputApplicationLabels.clear();
mTvInputApplicationIcons.clear();
- mTvInputAppliactionBanners.clear();;
+ mTvInputAppliactionBanners.clear();
+ ;
mInputIdToPartnerInputMap.clear();
}
- /**
- * Clears the TvInput labels map.
- */
+ /** Clears the TvInput labels map. */
public void clearTvInputLabels() {
mTvInputLabels.clear();
mTvInputCustomLabels.clear();
@@ -294,8 +365,8 @@ public class TvInputManagerHelper {
}
/**
- * Returns the default comparator for {@link TvInputInfo}.
- * See {@link InputComparatorInternal} for detail.
+ * Returns the default comparator for {@link TvInputInfo}. See {@link InputComparatorInternal}
+ * for detail.
*/
public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
return mTvInputInfoComparator;
@@ -304,35 +375,31 @@ public class TvInputManagerHelper {
/**
* Checks if the input is from a partner.
*
- * It's visible for comparator test.
- * Package private is enough for this method, but public is necessary to workaround mockito
- * bug.
+ * <p>It's visible for comparator test. Package private is enough for this method, but public is
+ * necessary to workaround mockito bug.
*/
@VisibleForTesting
public boolean isPartnerInput(TvInputInfo inputInfo) {
return isSystemInput(inputInfo) && !isBundledInput(inputInfo);
}
- /**
- * Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set.
- */
+ /** Does the input have {@link ApplicationInfo#FLAG_SYSTEM} set. */
public boolean isSystemInput(TvInputInfo inputInfo) {
return inputInfo != null
- && (inputInfo.getServiceInfo().applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) != 0;
+ && (inputInfo.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ != 0;
}
- /**
- * Is the input one known bundled inputs not written by OEM/SOCs.
- */
+ /** Is the input one known bundled inputs not written by OEM/SOCs. */
public boolean isBundledInput(TvInputInfo inputInfo) {
- return inputInfo != null && Utils.isInBundledPackageSet(inputInfo.getServiceInfo()
- .applicationInfo.packageName);
+ return inputInfo != null
+ && CommonUtils.isInBundledPackageSet(
+ inputInfo.getServiceInfo().applicationInfo.packageName);
}
/**
- * Returns if the given input is bundled and written by OEM/SOCs.
- * This returns the cached result.
+ * Returns if the given input is bundled and written by OEM/SOCs. This returns the cached
+ * result.
*/
public boolean isPartnerInput(String inputId) {
Boolean isPartnerInput = mInputIdToPartnerInputMap.get(inputId);
@@ -348,9 +415,7 @@ public class TvInputManagerHelper {
return mTvInputManager != null;
}
- /**
- * Loads label of {@code info}.
- */
+ /** Loads label of {@code info}. */
public String loadLabel(TvInputInfo info) {
String label = mTvInputLabels.get(info.getId());
if (label == null) {
@@ -360,9 +425,7 @@ public class TvInputManagerHelper {
return label;
}
- /**
- * Loads custom label of {@code info}
- */
+ /** Loads custom label of {@code info} */
public String loadCustomLabel(TvInputInfo info) {
String customLabel = mTvInputCustomLabels.get(info.getId());
if (customLabel == null) {
@@ -375,60 +438,46 @@ public class TvInputManagerHelper {
return customLabel;
}
- /**
- * Gets the tv input application's label.
- */
+ /** Gets the tv input application's label. */
public CharSequence getTvInputApplicationLabel(CharSequence inputId) {
return mTvInputApplicationLabels.get(inputId);
}
- /**
- * Stores the tv input application's label.
- */
+ /** Stores the tv input application's label. */
public void setTvInputApplicationLabel(String inputId, CharSequence label) {
mTvInputApplicationLabels.put(inputId, label);
}
- /**
- * Gets the tv input application's icon.
- */
+ /** Gets the tv input application's icon. */
public Drawable getTvInputApplicationIcon(String inputId) {
return mTvInputApplicationIcons.get(inputId);
}
- /**
- * Stores the tv input application's icon.
- */
+ /** Stores the tv input application's icon. */
public void setTvInputApplicationIcon(String inputId, Drawable icon) {
mTvInputApplicationIcons.put(inputId, icon);
}
- /**
- * Gets the tv input application's banner.
- */
+ /** Gets the tv input application's banner. */
public Drawable getTvInputApplicationBanner(String inputId) {
return mTvInputAppliactionBanners.get(inputId);
}
- /**
- * Stores the tv input application's banner.
- */
+ /** Stores the tv input application's banner. */
public void setTvInputApplicationBanner(String inputId, Drawable banner) {
mTvInputAppliactionBanners.put(inputId, banner);
}
- /**
- * Returns if TV input exists with the input id.
- */
+ /** Returns if TV input exists with the input id. */
public boolean hasTvInputInfo(String inputId) {
- SoftPreconditions.checkState(mStarted, TAG,
- "hasTvInputInfo() called before TvInputManagerHelper was started.");
+ SoftPreconditions.checkState(
+ mStarted, TAG, "hasTvInputInfo() called before TvInputManagerHelper was started.");
return mStarted && !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
}
public TvInputInfo getTvInputInfo(String inputId) {
- SoftPreconditions.checkState(mStarted, TAG,
- "getTvInputInfo() called before TvInputManagerHelper was started.");
+ SoftPreconditions.checkState(
+ mStarted, TAG, "getTvInputInfo() called before TvInputManagerHelper was started.");
if (!mStarted) {
return null;
}
@@ -452,16 +501,23 @@ public class TvInputManagerHelper {
}
return size;
}
-
- public int getInputState(TvInputInfo inputInfo) {
- return getInputState(inputInfo.getId());
+ /**
+ * Returns TvInputInfo's input state.
+ *
+ * @param inputInfo
+ * @return An Integer which stands for the input state {@link
+ * TvInputManager.INPUT_STATE_DISCONNECTED} if inputInfo is null
+ */
+ public int getInputState(@Nullable TvInputInfo inputInfo) {
+ return inputInfo == null
+ ? TvInputManager.INPUT_STATE_DISCONNECTED
+ : getInputState(inputInfo.getId());
}
public int getInputState(String inputId) {
SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started");
if (!mStarted) {
return TvInputManager.INPUT_STATE_DISCONNECTED;
-
}
Integer state = mInputStateMap.get(inputId);
if (state == null) {
@@ -483,16 +539,13 @@ public class TvInputManagerHelper {
return mParentalControlSettings;
}
- /**
- * Returns a ContentRatingsManager instance for a given application context.
- */
+ /** Returns a ContentRatingsManager instance for a given application context. */
public ContentRatingsManager getContentRatingsManager() {
return mContentRatingsManager;
}
private int getInputSortKey(TvInputInfo input) {
- return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY,
- Integer.MAX_VALUE);
+ return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY, Integer.MAX_VALUE);
}
private boolean isInputPhysicalTuner(TvInputInfo input) {
@@ -504,15 +557,20 @@ public class TvInputManagerHelper {
if (input.createSetupIntent() == null) {
return false;
} else {
- boolean mayBeTunerInput = mPackageManager.checkPermission(
- PERMISSION_ACCESS_ALL_EPG_DATA, input.getServiceInfo().packageName)
- == PackageManager.PERMISSION_GRANTED;
+ boolean mayBeTunerInput =
+ mPackageManager.checkPermission(
+ PERMISSION_ACCESS_ALL_EPG_DATA,
+ input.getServiceInfo().packageName)
+ == PackageManager.PERMISSION_GRANTED;
if (!mayBeTunerInput) {
try {
- ApplicationInfo ai = mPackageManager.getApplicationInfo(
- input.getServiceInfo().packageName, 0);
- if ((ai.flags & (ApplicationInfo.FLAG_SYSTEM
- | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) {
+ ApplicationInfo ai =
+ mPackageManager.getApplicationInfo(
+ input.getServiceInfo().packageName, 0);
+ if ((ai.flags
+ & (ApplicationInfo.FLAG_SYSTEM
+ | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP))
+ == 0) {
return false;
}
} catch (PackageManager.NameNotFoundException e) {
@@ -524,14 +582,15 @@ public class TvInputManagerHelper {
}
private boolean isInBlackList(String inputId) {
- if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
+ if (TvFeatures.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
if (inputId.contains(disabledTunerInputPrefix)) {
return true;
}
}
}
- if (TvCommonUtils.isRunningInTest()) {
+ if (CommonUtils.isRoboTest()) return false;
+ if (CommonUtils.isRunningInTest()) {
for (String testableInput : TESTABLE_INPUTS) {
if (testableInput.equals(inputId)) {
return false;
@@ -545,10 +604,9 @@ public class TvInputManagerHelper {
/**
* Default comparator for TvInputInfo.
*
- * It's static class that accepts {@link TvInputManagerHelper} as parameter to test.
- * To test comparator, we need to mock API in parent class such as {@link #isPartnerInput},
- * but it's impossible for an inner class to use mocked methods.
- * (i.e. Mockito's spy doesn't work)
+ * <p>It's static class that accepts {@link TvInputManagerHelper} as parameter to test. To test
+ * comparator, we need to mock API in parent class such as {@link #isPartnerInput}, but it's
+ * impossible for an inner class to use mocked methods. (i.e. Mockito's spy doesn't work)
*/
@VisibleForTesting
static class InputComparatorInternal implements Comparator<TvInputInfo> {
@@ -568,8 +626,8 @@ public class TvInputManagerHelper {
}
/**
- * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of
- * TV inputs.
+ * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of TV
+ * inputs.
*/
public static class HardwareInputComparator implements Comparator<TvInputInfo> {
private Map<Integer, Integer> mTypePriorities = new HashMap<>();
@@ -591,10 +649,12 @@ public class TvInputManagerHelper {
return -1;
}
- boolean enabledL = (mTvInputManagerHelper.getInputState(lhs)
- != TvInputManager.INPUT_STATE_DISCONNECTED);
- boolean enabledR = (mTvInputManagerHelper.getInputState(rhs)
- != TvInputManager.INPUT_STATE_DISCONNECTED);
+ boolean enabledL =
+ (mTvInputManagerHelper.getInputState(lhs)
+ != TvInputManager.INPUT_STATE_DISCONNECTED);
+ boolean enabledR =
+ (mTvInputManagerHelper.getInputState(rhs)
+ != TvInputManager.INPUT_STATE_DISCONNECTED);
if (enabledL != enabledR) {
return enabledL ? -1 : 1;
}
@@ -620,11 +680,13 @@ public class TvInputManagerHelper {
return sortKeyR - sortKeyL;
}
- String parentLabelL = lhs.getParentId() != null
- ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId()))
+ String parentLabelL =
+ lhs.getParentId() != null
+ ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId()))
: getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId()));
- String parentLabelR = rhs.getParentId() != null
- ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId()))
+ String parentLabelR =
+ rhs.getParentId() != null
+ ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId()))
: getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId()));
if (!TextUtils.equals(parentLabelL, parentLabelR)) {
diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java
index c5fde317..ae79e7e5 100644
--- a/src/com/android/tv/util/TvSettings.java
+++ b/src/com/android/tv/util/TvSettings.java
@@ -21,7 +21,6 @@ import android.content.SharedPreferences;
import android.media.tv.TvTrackInfo;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
@@ -29,11 +28,11 @@ import java.util.HashSet;
import java.util.Set;
/**
- * A class about the constants for TV settings.
- * Objects that are returned from the various {@code get} methods must be treated as immutable.
+ * A class about the constants for TV settings. Objects that are returned from the various {@code
+ * get} methods must be treated as immutable.
*/
public final class TvSettings {
- public static final String PREF_DISPLAY_MODE = "display_mode"; // int value
+ public static final String PREF_DISPLAY_MODE = "display_mode"; // int value
public static final String PREF_PIN = "pin"; // 4-digit string value. Otherwise, it's not set.
// Multi-track audio settings
@@ -56,9 +55,14 @@ public final class TvSettings {
@Retention(RetentionPolicy.SOURCE)
@IntDef({
- CONTENT_RATING_LEVEL_NONE, CONTENT_RATING_LEVEL_HIGH, CONTENT_RATING_LEVEL_MEDIUM,
- CONTENT_RATING_LEVEL_LOW, CONTENT_RATING_LEVEL_CUSTOM })
+ CONTENT_RATING_LEVEL_NONE,
+ CONTENT_RATING_LEVEL_HIGH,
+ CONTENT_RATING_LEVEL_MEDIUM,
+ CONTENT_RATING_LEVEL_LOW,
+ CONTENT_RATING_LEVEL_CUSTOM
+ })
public @interface ContentRatingLevel {}
+
public static final int CONTENT_RATING_LEVEL_NONE = 0;
public static final int CONTENT_RATING_LEVEL_HIGH = 1;
public static final int CONTENT_RATING_LEVEL_MEDIUM = 2;
@@ -69,61 +73,74 @@ public final class TvSettings {
// Multi-track audio settings
public static String getMultiAudioId(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getString(
- PREF_MULTI_AUDIO_ID, null);
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getString(PREF_MULTI_AUDIO_ID, null);
}
public static void setMultiAudioId(Context context, String language) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putString(
- PREF_MULTI_AUDIO_ID, language).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putString(PREF_MULTI_AUDIO_ID, language)
+ .apply();
}
public static String getMultiAudioLanguage(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getString(
- PREF_MULTI_AUDIO_LANGUAGE, null);
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getString(PREF_MULTI_AUDIO_LANGUAGE, null);
}
public static void setMultiAudioLanguage(Context context, String language) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putString(
- PREF_MULTI_AUDIO_LANGUAGE, language).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putString(PREF_MULTI_AUDIO_LANGUAGE, language)
+ .apply();
}
public static int getMultiAudioChannelCount(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getInt(
- PREF_MULTI_AUDIO_CHANNEL_COUNT, 0);
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getInt(PREF_MULTI_AUDIO_CHANNEL_COUNT, 0);
}
public static void setMultiAudioChannelCount(Context context, int channelCount) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(
- PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putInt(PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount)
+ .apply();
}
- public static void setDvrPlaybackTrackSettings(Context context, int trackType,
- TvTrackInfo info) {
+ public static void setDvrPlaybackTrackSettings(
+ Context context, int trackType, TvTrackInfo info) {
if (trackType == TvTrackInfo.TYPE_AUDIO) {
if (info == null) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .remove(PREF_DVR_MULTI_AUDIO_ID).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .remove(PREF_DVR_MULTI_AUDIO_ID)
+ .apply();
} else {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
.putString(PREF_DVR_MULTI_AUDIO_LANGUAGE, info.getLanguage())
.putInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, info.getAudioChannelCount())
- .putString(PREF_DVR_MULTI_AUDIO_ID, info.getId()).apply();
+ .putString(PREF_DVR_MULTI_AUDIO_ID, info.getId())
+ .apply();
}
} else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
if (info == null) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .remove(PREF_DVR_SUBTITLE_ID).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .remove(PREF_DVR_SUBTITLE_ID)
+ .apply();
} else {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
.putString(PREF_DVR_SUBTITLE_LANGUAGE, info.getLanguage())
- .putString(PREF_DVR_SUBTITLE_ID, info.getId()).apply();
+ .putString(PREF_DVR_SUBTITLE_ID, info.getId())
+ .apply();
}
}
}
- public static TvTrackInfo getDvrPlaybackTrackSettings(Context context,
- int trackType) {
+ public static TvTrackInfo getDvrPlaybackTrackSettings(Context context, int trackType) {
String language;
String trackId;
int channelCount;
@@ -136,7 +153,9 @@ public final class TvSettings {
language = pref.getString(PREF_DVR_MULTI_AUDIO_LANGUAGE, null);
channelCount = pref.getInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, 0);
return new TvTrackInfo.Builder(trackType, trackId)
- .setLanguage(language).setAudioChannelCount(channelCount).build();
+ .setLanguage(language)
+ .setAudioChannelCount(channelCount)
+ .build();
} else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
trackId = pref.getString(PREF_DVR_SUBTITLE_ID, null);
if (trackId == null) {
@@ -153,16 +172,20 @@ public final class TvSettings {
public static void addContentRatingSystem(Context context, String id) {
Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
if (contentRatingSystemSet.add(id)) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet)
+ .apply();
}
}
public static void removeContentRatingSystem(Context context, String id) {
Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
if (contentRatingSystemSet.remove(id)) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet)
+ .apply();
}
}
@@ -176,25 +199,28 @@ public final class TvSettings {
*/
public static boolean isContentRatingSystemSet(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
- .getStringSet(PREF_CONTENT_RATING_SYSTEMS, null) != null;
+ .getStringSet(PREF_CONTENT_RATING_SYSTEMS, null)
+ != null;
}
private static Set<String> getContentRatingSystemSet(Context context) {
- return new HashSet<>(PreferenceManager.getDefaultSharedPreferences(context)
- .getStringSet(PREF_CONTENT_RATING_SYSTEMS, Collections.emptySet()));
+ return new HashSet<>(
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .getStringSet(PREF_CONTENT_RATING_SYSTEMS, Collections.emptySet()));
}
@ContentRatingLevel
@SuppressWarnings("ResourceType")
public static int getContentRatingLevel(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getInt(
- PREF_CONTENT_RATING_LEVEL, CONTENT_RATING_LEVEL_NONE);
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getInt(PREF_CONTENT_RATING_LEVEL, CONTENT_RATING_LEVEL_NONE);
}
- public static void setContentRatingLevel(Context context,
- @ContentRatingLevel int level) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(
- PREF_CONTENT_RATING_LEVEL, level).apply();
+ public static void setContentRatingLevel(Context context, @ContentRatingLevel int level) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putInt(PREF_CONTENT_RATING_LEVEL, level)
+ .apply();
}
/**
@@ -202,8 +228,8 @@ public final class TvSettings {
* repeatedly).
*/
public static long getDisablePinUntil(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getLong(
- PREF_DISABLE_PIN_UNTIL, 0);
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(PREF_DISABLE_PIN_UNTIL, 0);
}
/**
@@ -211,7 +237,9 @@ public final class TvSettings {
* repeatedly).
*/
public static void setDisablePinUntil(Context context, long timeMillis) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(
- PREF_DISABLE_PIN_UNTIL, timeMillis).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putLong(PREF_DISABLE_PIN_UNTIL, timeMillis)
+ .apply();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/TvTrackInfoUtils.java b/src/com/android/tv/util/TvTrackInfoUtils.java
index 667cc9bf..09874502 100644
--- a/src/com/android/tv/util/TvTrackInfoUtils.java
+++ b/src/com/android/tv/util/TvTrackInfoUtils.java
@@ -16,27 +16,24 @@
package com.android.tv.util;
import android.media.tv.TvTrackInfo;
-
import java.util.Comparator;
import java.util.List;
-/**
- * Static utilities for {@link TvTrackInfo}.
- */
+/** Static utilities for {@link TvTrackInfo}. */
public class TvTrackInfoUtils {
/**
* Compares how closely two {@link android.media.tv.TvTrackInfo}s match {@code language}, {@code
* channelCount} and {@code id} in that precedence.
*
- * @param id The track id to match.
- * @param language The language to match.
+ * @param id The track id to match.
+ * @param language The language to match.
* @param channelCount The channel count to match.
* @return -1 if lhs is a worse match, 0 if lhs and rhs match equally and 1 if lhs is a better
- * match.
+ * match.
*/
- public static Comparator<TvTrackInfo> createComparator(final String id, final String language,
- final int channelCount) {
+ public static Comparator<TvTrackInfo> createComparator(
+ final String id, final String language, final int channelCount) {
return new Comparator<TvTrackInfo>() {
@Override
@@ -52,15 +49,17 @@ public class TvTrackInfoUtils {
}
// Assumes {@code null} language matches to any language since it means user hasn't
// selected any track before or selected a track without language information.
- boolean lhsLangMatch = language == null || Utils.isEqualLanguage(lhs.getLanguage(),
- language);
- boolean rhsLangMatch = language == null || Utils.isEqualLanguage(rhs.getLanguage(),
- language);
+ boolean lhsLangMatch =
+ language == null || Utils.isEqualLanguage(lhs.getLanguage(), language);
+ boolean rhsLangMatch =
+ language == null || Utils.isEqualLanguage(rhs.getLanguage(), language);
if (lhsLangMatch && rhsLangMatch) {
- boolean lhsCountMatch = lhs.getType() != TvTrackInfo.TYPE_AUDIO
- || lhs.getAudioChannelCount() == channelCount;
- boolean rhsCountMatch = rhs.getType() != TvTrackInfo.TYPE_AUDIO
- || rhs.getAudioChannelCount() == channelCount;
+ boolean lhsCountMatch =
+ lhs.getType() != TvTrackInfo.TYPE_AUDIO
+ || lhs.getAudioChannelCount() == channelCount;
+ boolean rhsCountMatch =
+ rhs.getType() != TvTrackInfo.TYPE_AUDIO
+ || rhs.getAudioChannelCount() == channelCount;
if (lhsCountMatch && rhsCountMatch) {
return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id));
} else {
@@ -74,16 +73,16 @@ public class TvTrackInfoUtils {
}
/**
- * Selects the best TvTrackInfo available or the first if none matches.
+ * Selects the best TvTrackInfo available or the first if none matches.
*
- * @param tracks The tracks to choose from
- * @param id The track id to match.
- * @param language The language to match.
+ * @param tracks The tracks to choose from
+ * @param id The track id to match.
+ * @param language The language to match.
* @param channelCount The channel count to match.
* @return the best matching track or the first one if none matches.
*/
- public static TvTrackInfo getBestTrackInfo(List<TvTrackInfo> tracks, String id, String language,
- int channelCount) {
+ public static TvTrackInfo getBestTrackInfo(
+ List<TvTrackInfo> tracks, String id, String language, int channelCount) {
if (tracks == null) {
return null;
}
@@ -97,6 +96,5 @@ public class TvTrackInfoUtils {
return best;
}
- private TvTrackInfoUtils() {
- }
-} \ No newline at end of file
+ private TvTrackInfoUtils() {}
+}
diff --git a/src/com/android/tv/util/TvUriMatcher.java b/src/com/android/tv/util/TvUriMatcher.java
index 3d91cdad..9e74117e 100644
--- a/src/com/android/tv/util/TvUriMatcher.java
+++ b/src/com/android/tv/util/TvUriMatcher.java
@@ -21,22 +21,25 @@ import android.content.UriMatcher;
import android.media.tv.TvContract;
import android.net.Uri;
import android.support.annotation.IntDef;
-
import com.android.tv.search.LocalSearchProvider;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/**
- * Utility class to aid in matching URIs in TvProvider.
- */
+/** Utility class to aid in matching URIs in TvProvider. */
public class TvUriMatcher {
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@Retention(RetentionPolicy.SOURCE)
- @IntDef({MATCH_CHANNEL, MATCH_CHANNEL_ID, MATCH_PROGRAM, MATCH_PROGRAM_ID,
- MATCH_RECORDED_PROGRAM, MATCH_RECORDED_PROGRAM_ID, MATCH_WATCHED_PROGRAM_ID,
- MATCH_ON_DEVICE_SEARCH})
+ @IntDef({
+ MATCH_CHANNEL,
+ MATCH_CHANNEL_ID,
+ MATCH_PROGRAM,
+ MATCH_PROGRAM_ID,
+ MATCH_RECORDED_PROGRAM,
+ MATCH_RECORDED_PROGRAM_ID,
+ MATCH_WATCHED_PROGRAM_ID,
+ MATCH_ON_DEVICE_SEARCH
+ })
private @interface TvProviderUriMatchCode {}
/** The code for the channels URI. */
public static final int MATCH_CHANNEL = 1;
@@ -54,6 +57,7 @@ public class TvUriMatcher {
public static final int MATCH_WATCHED_PROGRAM_ID = 7;
/** The code for the on-device search URI. */
public static final int MATCH_ON_DEVICE_SEARCH = 8;
+
static {
URI_MATCHER.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL);
URI_MATCHER.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
@@ -62,11 +66,13 @@ public class TvUriMatcher {
URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM);
URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID);
URI_MATCHER.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
- URI_MATCHER.addURI(LocalSearchProvider.AUTHORITY,
- SearchManager.SUGGEST_URI_PATH_QUERY + "/*", MATCH_ON_DEVICE_SEARCH);
+ URI_MATCHER.addURI(
+ LocalSearchProvider.AUTHORITY,
+ SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
+ MATCH_ON_DEVICE_SEARCH);
}
- private TvUriMatcher() { }
+ private TvUriMatcher() {}
/**
* Try to match against the path in a url.
@@ -74,7 +80,8 @@ public class TvUriMatcher {
* @see UriMatcher#match
*/
@SuppressWarnings("WrongConstant")
- @TvProviderUriMatchCode public static int match(Uri uri) {
+ @TvProviderUriMatchCode
+ public static int match(Uri uri) {
return URI_MATCHER.match(uri);
}
}
diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java
index d11bab3c..a75bd446 100644
--- a/src/com/android/tv/util/Utils.java
+++ b/src/com/android/tv/util/Utils.java
@@ -38,22 +38,16 @@ import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.text.format.DateUtils;
-import android.util.ArraySet;
import android.util.Log;
import android.view.View;
-
-import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.BuildConfig;
+import com.android.tv.TvSingletons;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.Clock;
import com.android.tv.data.GenreItems;
import com.android.tv.data.Program;
import com.android.tv.data.StreamInfo;
-import com.android.tv.experiments.Experiments;
-
-import java.io.File;
+import com.android.tv.data.api.Channel;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -69,18 +63,13 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-/**
- * A class that includes convenience methods for accessing TvProvider database.
- */
+/** A class that includes convenience methods for accessing TvProvider database. */
public class Utils {
private static final String TAG = "Utils";
private static final boolean DEBUG = false;
- private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",
- Locale.US);
-
public static final String EXTRA_KEY_ACTION = "action";
- public static final String EXTRA_ACTION_SHOW_TV_INPUT ="show_tv_input";
+ public static final String EXTRA_ACTION_SHOW_TV_INPUT = "show_tv_input";
public static final String EXTRA_KEY_FROM_LAUNCHER = "from_launcher";
public static final String EXTRA_KEY_RECORDED_PROGRAM_ID = "recorded_program_id";
public static final String EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME = "recorded_program_seek_time";
@@ -97,8 +86,7 @@ public class Utils {
private static final String PREF_KEY_LAST_WATCHED_CHANNEL_URI = "last_watched_channel_uri";
private static final String PREF_KEY_LAST_WATCHED_TUNER_INPUT_ID =
"last_watched_tuner_input_id";
- private static final String PREF_KEY_RECORDING_FAILED_REASONS =
- "recording_failed_reasons";
+ private static final String PREF_KEY_RECORDING_FAILED_REASONS = "recording_failed_reasons";
private static final String PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET =
"failed_scheduled_recording_info_set";
@@ -121,15 +109,6 @@ public class Utils {
private static final long HALF_MINUTE_MS = TimeUnit.SECONDS.toMillis(30);
private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
- // Hardcoded list for known bundled inputs not written by OEM/SOCs.
- // Bundled (system) inputs not in the list will get the high priority
- // so they and their channels come first in the UI.
- private static final Set<String> BUNDLED_PACKAGE_SET = new ArraySet<>();
-
- static {
- BUNDLED_PACKAGE_SET.add("com.android.tv");
- }
-
private enum AspectRatio {
ASPECT_RATIO_4_3(4, 3),
ASPECT_RATIO_16_9(16, 9),
@@ -150,13 +129,11 @@ public class Utils {
}
}
- private Utils() {
- }
+ private Utils() {}
public static String buildSelectionForIds(String idName, List<Long> ids) {
StringBuilder sb = new StringBuilder();
- sb.append(idName).append(" in (")
- .append(ids.get(0));
+ sb.append(idName).append(" in (").append(ids.get(0));
for (int i = 1; i < ids.size(); ++i) {
sb.append(",").append(ids.get(i));
}
@@ -171,8 +148,8 @@ public class Utils {
}
Uri channelUri = TvContract.buildChannelUri(channelId);
String[] projection = {TvContract.Channels.COLUMN_INPUT_ID};
- try (Cursor cursor = context.getContentResolver()
- .query(channelUri, projection, null, null, null)) {
+ try (Cursor cursor =
+ context.getContentResolver().query(channelUri, projection, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
return Utils.intern(cursor.getString(0));
}
@@ -185,60 +162,61 @@ public class Utils {
Log.e(TAG, "setLastWatchedChannel: channel cannot be null");
return;
}
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, channel.getUri().toString()).apply();
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, channel.getUri().toString())
+ .apply();
if (!channel.isPassthrough()) {
long channelId = channel.getId();
if (channel.getId() < 0) {
throw new IllegalArgumentException("channelId should be equal to or larger than 0");
}
- PreferenceManager.getDefaultSharedPreferences(context).edit()
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
.putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId)
- .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + channel.getInputId(),
+ .putLong(
+ PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + channel.getInputId(),
channelId)
.putString(PREF_KEY_LAST_WATCHED_TUNER_INPUT_ID, channel.getInputId())
.apply();
}
}
- /**
- * Sets recording failed reason.
- */
+ /** Sets recording failed reason. */
public static void setRecordingFailedReason(Context context, int reason) {
long reasons = getRecordingFailedReasons(context) | 0x1 << reason;
- PreferenceManager.getDefaultSharedPreferences(context).edit()
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
.putLong(PREF_KEY_RECORDING_FAILED_REASONS, reasons)
.apply();
}
- /**
- * Adds the info of failed scheduled recording.
- */
- public static void addFailedScheduledRecordingInfo(Context context,
- String scheduledRecordingInfo) {
+ /** Adds the info of failed scheduled recording. */
+ public static void addFailedScheduledRecordingInfo(
+ Context context, String scheduledRecordingInfo) {
Set<String> failedScheduledRecordingInfoSet = getFailedScheduledRecordingInfoSet(context);
failedScheduledRecordingInfoSet.add(scheduledRecordingInfo);
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET,
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putStringSet(
+ PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET,
failedScheduledRecordingInfoSet)
.apply();
}
- /**
- * Clears the failed scheduled recording info set.
- */
+ /** Clears the failed scheduled recording info set. */
public static void clearFailedScheduledRecordingInfoSet(Context context) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
.remove(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET)
.apply();
}
- /**
- * Clears recording failed reason.
- */
+ /** Clears recording failed reason. */
public static void clearRecordingFailedReason(Context context, int reason) {
long reasons = getRecordingFailedReasons(context) & ~(0x1 << reason);
- PreferenceManager.getDefaultSharedPreferences(context).edit()
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
.putLong(PREF_KEY_RECORDING_FAILED_REASONS, reasons)
.apply();
}
@@ -258,9 +236,7 @@ public class Utils {
.getString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, null);
}
- /**
- * Returns the last watched tuner input id.
- */
+ /** Returns the last watched tuner input id. */
public static String getLastWatchedTunerInputId(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getString(PREF_KEY_LAST_WATCHED_TUNER_INPUT_ID, null);
@@ -268,32 +244,28 @@ public class Utils {
private static long getRecordingFailedReasons(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(PREF_KEY_RECORDING_FAILED_REASONS,
- RECORDING_FAILED_REASON_NONE);
+ .getLong(PREF_KEY_RECORDING_FAILED_REASONS, RECORDING_FAILED_REASON_NONE);
}
- /**
- * Returns the failed scheduled recordings info set.
- */
+ /** Returns the failed scheduled recordings info set. */
public static Set<String> getFailedScheduledRecordingInfoSet(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, new HashSet<>());
}
- /**
- * Checks do recording failed reason exist.
- */
+ /** Checks do recording failed reason exist. */
public static boolean hasRecordingFailedReason(Context context, int reason) {
long reasons = getRecordingFailedReasons(context);
return (reasons & 0x1 << reason) != 0;
}
/**
- * Returns {@code true}, if {@code uri} specifies an input, which is usually generated
- * from {@link TvContract#buildChannelsUriForInput}.
+ * Returns {@code true}, if {@code uri} specifies an input, which is usually generated from
+ * {@link TvContract#buildChannelsUriForInput}.
*/
public static boolean isChannelUriForInput(Uri uri) {
- return isTvUri(uri) && PATH_CHANNEL.equals(uri.getPathSegments().get(0))
+ return isTvUri(uri)
+ && PATH_CHANNEL.equals(uri.getPathSegments().get(0))
&& !TextUtils.isEmpty(uri.getQueryParameter("input"));
}
@@ -314,7 +286,8 @@ public class Utils {
}
private static boolean isTvUri(Uri uri) {
- return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ return uri != null
+ && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
&& TvContract.AUTHORITY.equals(uri.getAuthority());
}
@@ -323,23 +296,17 @@ public class Utils {
return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0));
}
- /**
- * Returns {@code true}, if {@code uri} is a programs URI.
- */
+ /** Returns {@code true}, if {@code uri} is a programs URI. */
public static boolean isProgramsUri(Uri uri) {
return isTvUri(uri) && PATH_PROGRAM.equals(uri.getPathSegments().get(0));
}
- /**
- * Returns {@code true}, if {@code uri} is a programs URI.
- */
+ /** Returns {@code true}, if {@code uri} is a programs URI. */
public static boolean isRecordedProgramsUri(Uri uri) {
return isTvUri(uri) && PATH_RECORDED_PROGRAM.equals(uri.getPathSegments().get(0));
}
- /**
- * Gets the info of the program on particular time.
- */
+ /** Gets the info of the program on particular time. */
@WorkerThread
public static Program getProgramAt(Context context, long channelId, long timeMs) {
if (channelId == Channel.INVALID_ID) {
@@ -355,10 +322,11 @@ public class Utils {
Log.w(TAG, message);
}
}
- Uri uri = TvContract.buildProgramsUriForChannel(TvContract.buildChannelUri(channelId),
- timeMs, timeMs);
- try (Cursor cursor = context.getContentResolver().query(uri, Program.PROJECTION,
- null, null, null)) {
+ Uri uri =
+ TvContract.buildProgramsUriForChannel(
+ TvContract.buildChannelUri(channelId), timeMs, timeMs);
+ try (Cursor cursor =
+ context.getContentResolver().query(uri, Program.PROJECTION, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
return Program.fromCursor(cursor);
}
@@ -366,55 +334,98 @@ public class Utils {
return null;
}
- /**
- * Gets the info of the current program.
- */
+ /** Gets the info of the current program. */
@WorkerThread
public static Program getCurrentProgram(Context context, long channelId) {
return getProgramAt(context, channelId, System.currentTimeMillis());
}
- /**
- * Returns the round off minutes when convert milliseconds to minutes.
- */
+ /** Returns the round off minutes when convert milliseconds to minutes. */
public static int getRoundOffMinsFromMs(long millis) {
// Round off the result by adding half minute to the original ms.
return (int) TimeUnit.MILLISECONDS.toMinutes(millis + HALF_MINUTE_MS);
}
/**
- * Returns duration string according to the date & time format.
- * If {@code startUtcMillis} and {@code endUtcMills} are equal,
- * formatted time will be returned instead.
+ * Returns duration string according to the date & time format. If {@code startUtcMillis} and
+ * {@code endUtcMills} are equal, formatted time will be returned instead.
*
* @param startUtcMillis start of duration in millis. Should be less than {code endUtcMillis}.
* @param endUtcMillis end of duration in millis. Should be larger than {@code startUtcMillis}.
- * @param useShortFormat {@code true} if abbreviation is needed to save space.
- * In that case, date will be omitted if duration starts from today
- * and is less than a day. If it's necessary,
- * {@link DateUtils#FORMAT_NUMERIC_DATE} is used otherwise.
+ * @param useShortFormat {@code true} if abbreviation is needed to save space. In that case,
+ * date will be omitted if duration starts from today and is less than a day. If it's
+ * necessary, {@link DateUtils#FORMAT_NUMERIC_DATE} is used otherwise.
*/
public static String getDurationString(
Context context, long startUtcMillis, long endUtcMillis, boolean useShortFormat) {
- return getDurationString(context, System.currentTimeMillis(), startUtcMillis, endUtcMillis,
- useShortFormat, 0);
+ return getDurationString(
+ context,
+ System.currentTimeMillis(),
+ startUtcMillis,
+ endUtcMillis,
+ useShortFormat,
+ 0);
}
- @VisibleForTesting
- static String getDurationString(Context context, long baseMillis, long startUtcMillis,
- long endUtcMillis, boolean useShortFormat, int flag) {
- return getDurationString(context, startUtcMillis, endUtcMillis,
- useShortFormat, !isInGivenDay(baseMillis, startUtcMillis), true, flag);
+ /**
+ * Returns duration string according to the date & time format. If {@code startUtcMillis} and
+ * {@code endUtcMills} are equal, formatted time will be returned instead.
+ *
+ * @param clock the clock used to get the current time.
+ * @param startUtcMillis start of duration in millis. Should be less than {code endUtcMillis}.
+ * @param endUtcMillis end of duration in millis. Should be larger than {@code startUtcMillis}.
+ * @param useShortFormat {@code true} if abbreviation is needed to save space. In that case,
+ * date will be omitted if duration starts from today and is less than a day. If it's
+ * necessary, {@link DateUtils#FORMAT_NUMERIC_DATE} is used otherwise.
+ */
+ public static String getDurationString(
+ Context context,
+ Clock clock,
+ long startUtcMillis,
+ long endUtcMillis,
+ boolean useShortFormat) {
+ return getDurationString(
+ context,
+ clock.currentTimeMillis(),
+ startUtcMillis,
+ endUtcMillis,
+ useShortFormat,
+ 0);
}
- /**
- * Returns duration string according to the time format, may not contain date information.
- * Note: At least one of showDate and showTime should be true.
+ @VisibleForTesting
+ static String getDurationString(
+ Context context,
+ long baseMillis,
+ long startUtcMillis,
+ long endUtcMillis,
+ boolean useShortFormat,
+ int flag) {
+ return getDurationString(
+ context,
+ startUtcMillis,
+ endUtcMillis,
+ useShortFormat,
+ !isInGivenDay(baseMillis, startUtcMillis),
+ true,
+ flag);
+ }
+
+ /**
+ * Returns duration string according to the time format, may not contain date information. Note:
+ * At least one of showDate and showTime should be true.
*/
- public static String getDurationString(Context context, long startUtcMillis, long endUtcMillis,
- boolean useShortFormat, boolean showDate, boolean showTime, int flag) {
- flag |= DateUtils.FORMAT_ABBREV_MONTH
- | ((useShortFormat) ? DateUtils.FORMAT_NUMERIC_DATE : 0);
+ public static String getDurationString(
+ Context context,
+ long startUtcMillis,
+ long endUtcMillis,
+ boolean useShortFormat,
+ boolean showDate,
+ boolean showTime,
+ int flag) {
+ flag |=
+ DateUtils.FORMAT_ABBREV_MONTH
+ | ((useShortFormat) ? DateUtils.FORMAT_NUMERIC_DATE : 0);
SoftPreconditions.checkArgument(showTime || showDate);
if (showTime) {
flag |= DateUtils.FORMAT_SHOW_TIME;
@@ -431,20 +442,21 @@ public class Utils {
// Do not show date for short format.
// Subtracting one day is needed because {@link DateUtils@formatDateRange}
// automatically shows date if the duration covers multiple days.
- return DateUtils.formatDateRange(context,
- startUtcMillis, endUtcMillis - TimeUnit.DAYS.toMillis(1), flag);
+ return DateUtils.formatDateRange(
+ context, startUtcMillis, endUtcMillis - TimeUnit.DAYS.toMillis(1), flag);
}
}
// Workaround of b/28740989.
// Add 1 msec to endUtcMillis to avoid DateUtils' bug with a duration of 12:00AM~12:00AM.
String dateRange = DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis, flag);
- return startUtcMillis == endUtcMillis || dateRange.contains("–") ? dateRange
+ return startUtcMillis == endUtcMillis || dateRange.contains("–")
+ ? dateRange
: DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis + 1, flag);
}
/**
- * Checks if two given time (in milliseconds) are in the same day with regard to the
- * locale timezone.
+ * Checks if two given time (in milliseconds) are in the same day with regard to the locale
+ * timezone.
*/
public static boolean isInGivenDay(long dayToMatchInMillis, long subjectTimeInMillis) {
TimeZone timeZone = Calendar.getInstance().getTimeZone();
@@ -456,9 +468,7 @@ public class Utils {
== Utils.floorTime(subjectTimeInMillis + offset, ONE_DAY_MS);
}
- /**
- * Calculate how many days between two milliseconds.
- */
+ /** Calculate how many days between two milliseconds. */
public static int computeDateDifference(long startTimeMs, long endTimeMs) {
Calendar calFrom = Calendar.getInstance();
Calendar calTo = Calendar.getInstance();
@@ -476,17 +486,23 @@ public class Utils {
cal.set(Calendar.MILLISECOND, 0);
}
- /**
- * Returns the last millisecond of a day which the millis belongs to.
- */
+ /** Returns the last millisecond of a day which the millis belongs to. */
public static long getLastMillisecondOfDay(long millis) {
- Calendar calender = Calendar.getInstance();
- calender.setTime(new Date(millis));
- calender.set(Calendar.HOUR_OF_DAY, 23);
- calender.set(Calendar.MINUTE, 59);
- calender.set(Calendar.SECOND, 59);
- calender.set(Calendar.MILLISECOND, 999);
- return calender.getTimeInMillis();
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(new Date(millis));
+ calendar.set(Calendar.HOUR_OF_DAY, 23);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ calendar.set(Calendar.MILLISECOND, 999);
+ return calendar.getTimeInMillis();
+ }
+
+ /** Returns the last millisecond of a day which the millis belongs to. */
+ public static long getFirstMillisecondOfDay(long millis) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(new Date(millis));
+ resetCalendar(calendar);
+ return calendar.getTimeInMillis();
}
public static String getAspectRatioString(int width, int height) {
@@ -494,7 +510,7 @@ public class Utils {
return "";
}
- for (AspectRatio ratio: AspectRatio.values()) {
+ for (AspectRatio ratio : AspectRatio.values()) {
if (Math.abs((float) ratio.height / ratio.width - (float) height / width) < 0.05f) {
return ratio.toString();
}
@@ -531,11 +547,9 @@ public class Utils {
public static String getVideoDefinitionLevelString(Context context, int videoFormat) {
switch (videoFormat) {
case StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD:
- return context.getResources().getString(
- R.string.video_definition_level_ultra_hd);
+ return context.getResources().getString(R.string.video_definition_level_ultra_hd);
case StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD:
- return context.getResources().getString(
- R.string.video_definition_level_full_hd);
+ return context.getResources().getString(R.string.video_definition_level_full_hd);
case StreamInfo.VIDEO_DEFINITION_LEVEL_HD:
return context.getResources().getString(R.string.video_definition_level_hd);
case StreamInfo.VIDEO_DEFINITION_LEVEL_SD:
@@ -570,8 +584,8 @@ public class Utils {
return false;
}
- public static String getMultiAudioString(Context context, TvTrackInfo track,
- boolean showSampleRate) {
+ public static String getMultiAudioString(
+ Context context, TvTrackInfo track, boolean showSampleRate) {
if (track.getType() != TvTrackInfo.TYPE_AUDIO) {
throw new IllegalArgumentException("Not an audio track: " + track);
}
@@ -600,11 +614,17 @@ public class Utils {
break;
default:
if (track.getAudioChannelCount() > 0) {
- metadata.append(context.getString(R.string.multi_audio_channel_suffix,
- track.getAudioChannelCount()));
+ metadata.append(
+ context.getString(
+ R.string.multi_audio_channel_suffix,
+ track.getAudioChannelCount()));
} else {
- Log.d(TAG, "Invalid audio channel count (" + track.getAudioChannelCount()
- + ") found for the audio track: " + track);
+ Log.d(
+ TAG,
+ "Invalid audio channel count ("
+ + track.getAudioChannelCount()
+ + ") found for the audio track: "
+ + track);
}
break;
}
@@ -628,8 +648,8 @@ public class Utils {
if (metadata.length() == 0) {
return language;
}
- return context.getString(R.string.multi_audio_display_string_with_channel, language,
- metadata.toString());
+ return context.getString(
+ R.string.multi_audio_display_string_with_channel, language, metadata.toString());
}
public static boolean isEqualLanguage(String lang1, String lang2) {
@@ -647,19 +667,19 @@ public class Utils {
}
public static boolean isIntentAvailable(Context context, Intent intent) {
- return context.getPackageManager().queryIntentActivities(
- intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ return context.getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ .size()
+ > 0;
}
- /**
- * Returns the label for a given input. Returns the custom label, if any.
- */
+ /** Returns the label for a given input. Returns the custom label, if any. */
public static String loadLabel(Context context, TvInputInfo input) {
if (input == null) {
return null;
}
TvInputManagerHelper inputManager =
- TvApplication.getSingletons(context).getTvInputManagerHelper();
+ TvSingletons.getSingletons(context).getTvInputManagerHelper();
CharSequence customLabel = inputManager.loadCustomLabel(input);
String label = (customLabel == null) ? null : customLabel.toString();
if (TextUtils.isEmpty(label)) {
@@ -668,9 +688,7 @@ public class Utils {
return label;
}
- /**
- * Enable all channels synchronously.
- */
+ /** Enable all channels synchronously. */
@WorkerThread
public static void enableAllChannels(Context context) {
ContentValues values = new ContentValues();
@@ -681,46 +699,48 @@ public class Utils {
/**
* Converts time in milliseconds to a String.
*
- * @param fullFormat {@code true} for returning date string with a full format
- * (e.g., Mon Aug 15 20:08:35 GMT 2016). {@code false} for a short format,
- * {e.g., [8/15/16] 8:08 AM}, in which date information would only appears
- * when the target time is not today.
+ * @param fullFormat {@code true} for returning date string with a full format (e.g., Mon Aug 15
+ * 20:08:35 GMT 2016). {@code false} for a short format, {e.g., 8/15/16 or 8:08 AM}, in
+ * which only the time is shown if the time is on the same day as now, and only the date is
+ * shown if it's a different day.
*/
public static String toTimeString(long timeMillis, boolean fullFormat) {
if (fullFormat) {
return new Date(timeMillis).toString();
} else {
long currentTime = System.currentTimeMillis();
- return (String) DateUtils.formatSameDayTime(timeMillis, System.currentTimeMillis(),
- SimpleDateFormat.SHORT, SimpleDateFormat.SHORT);
+ return (String)
+ DateUtils.formatSameDayTime(
+ timeMillis,
+ System.currentTimeMillis(),
+ SimpleDateFormat.SHORT,
+ SimpleDateFormat.SHORT);
}
}
- /**
- * Converts time in milliseconds to a String.
- */
+ /** Converts time in milliseconds to a String. */
public static String toTimeString(long timeMillis) {
return toTimeString(timeMillis, true);
}
/**
- * Converts time in milliseconds to a ISO 8061 string.
- */
- public static String toIsoDateTimeString(long timeMillis) {
- return ISO_8601.format(new Date(timeMillis));
- }
-
- /**
* Returns a {@link String} object which contains the layout information of the {@code view}.
*/
public static String toRectString(View view) {
return "{"
- + "l=" + view.getLeft()
- + ",r=" + view.getRight()
- + ",t=" + view.getTop()
- + ",b=" + view.getBottom()
- + ",w=" + view.getWidth()
- + ",h=" + view.getHeight() + "}";
+ + "l="
+ + view.getLeft()
+ + ",r="
+ + view.getRight()
+ + ",t="
+ + view.getTop()
+ + ",b="
+ + view.getBottom()
+ + ",w="
+ + view.getWidth()
+ + ",h="
+ + view.getHeight()
+ + "}";
}
/**
@@ -732,16 +752,14 @@ public class Utils {
}
/**
- * Ceils time to the given {@code timeUnit}. For example, if time is 5:32:11 and timeUnit is
- * one hour (60 * 60 * 1000), then the output will be 6:00:00.
+ * Ceils time to the given {@code timeUnit}. For example, if time is 5:32:11 and timeUnit is one
+ * hour (60 * 60 * 1000), then the output will be 6:00:00.
*/
public static long ceilTime(long timeMs, long timeUnit) {
return timeMs + timeUnit - (timeMs % timeUnit);
}
- /**
- * Returns an {@link String#intern() interned} string or null if the input is null.
- */
+ /** Returns an {@link String#intern() interned} string or null if the input is null. */
@Nullable
public static String intern(@Nullable String string) {
return string == null ? null : string.intern();
@@ -749,6 +767,7 @@ public class Utils {
/**
* Check if the index is valid for the collection,
+ *
* @param collection the collection
* @param index the index position to test
* @return index >= 0 && index < collection.size().
@@ -757,9 +776,7 @@ public class Utils {
return collection != null && (index >= 0 && index < collection.size());
}
- /**
- * Returns a localized version of the text resource specified by resourceId.
- */
+ /** Returns a localized version of the text resource specified by resourceId. */
public static CharSequence getTextForLocale(Context context, Locale locale, int resourceId) {
if (locale.equals(context.getResources().getConfiguration().locale)) {
return context.getText(resourceId);
@@ -769,12 +786,12 @@ public class Utils {
return context.createConfigurationContext(config).getText(resourceId);
}
- /**
- * Checks where there is any internal TV input.
- */
+ /** Checks where there is any internal TV input. */
public static boolean hasInternalTvInputs(Context context, boolean tunerInputOnly) {
- for (TvInputInfo input : TvApplication.getSingletons(context).getTvInputManagerHelper()
- .getTvInputInfos(true, tunerInputOnly)) {
+ for (TvInputInfo input :
+ TvSingletons.getSingletons(context)
+ .getTvInputManagerHelper()
+ .getTvInputInfos(true, tunerInputOnly)) {
if (isInternalTvInput(context, input.getId())) {
return true;
}
@@ -782,13 +799,13 @@ public class Utils {
return false;
}
- /**
- * Returns the internal TV inputs.
- */
+ /** Returns the internal TV inputs. */
public static List<TvInputInfo> getInternalTvInputs(Context context, boolean tunerInputOnly) {
List<TvInputInfo> inputs = new ArrayList<>();
- for (TvInputInfo input : TvApplication.getSingletons(context).getTvInputManagerHelper()
- .getTvInputInfos(true, tunerInputOnly)) {
+ for (TvInputInfo input :
+ TvSingletons.getSingletons(context)
+ .getTvInputManagerHelper()
+ .getTvInputInfos(true, tunerInputOnly)) {
if (isInternalTvInput(context, input.getId())) {
inputs.add(input);
}
@@ -796,81 +813,41 @@ public class Utils {
return inputs;
}
- /**
- * Checks whether the input is internal or not.
- */
+ /** Checks whether the input is internal or not. */
public static boolean isInternalTvInput(Context context, String inputId) {
- return context.getPackageName().equals(ComponentName.unflattenFromString(inputId)
- .getPackageName());
+ return context.getPackageName()
+ .equals(ComponentName.unflattenFromString(inputId).getPackageName());
}
- /**
- * Returns the TV input for the given {@code program}.
- */
+ /** Returns the TV input for the given {@code program}. */
@Nullable
public static TvInputInfo getTvInputInfoForProgram(Context context, Program program) {
- if (!Program.isValid(program)) {
+ if (!Program.isProgramValid(program)) {
return null;
}
return getTvInputInfoForChannelId(context, program.getChannelId());
}
- /**
- * Returns the TV input for the given channel ID.
- */
+ /** Returns the TV input for the given channel ID. */
@Nullable
public static TvInputInfo getTvInputInfoForChannelId(Context context, long channelId) {
- ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
- Channel channel = appSingletons.getChannelDataManager().getChannel(channelId);
+ TvSingletons tvSingletons = TvSingletons.getSingletons(context);
+ Channel channel = tvSingletons.getChannelDataManager().getChannel(channelId);
if (channel == null) {
return null;
}
- return appSingletons.getTvInputManagerHelper().getTvInputInfo(channel.getInputId());
+ return tvSingletons.getTvInputManagerHelper().getTvInputInfo(channel.getInputId());
}
- /**
- * Returns the {@link TvInputInfo} for the given input ID.
- */
+ /** Returns the {@link TvInputInfo} for the given input ID. */
@Nullable
public static TvInputInfo getTvInputInfoForInputId(Context context, String inputId) {
- return TvApplication.getSingletons(context).getTvInputManagerHelper()
+ return TvSingletons.getSingletons(context)
+ .getTvInputManagerHelper()
.getTvInputInfo(inputId);
}
- /**
- * Deletes a file or a directory.
- */
- public static void deleteDirOrFile(File fileOrDirectory) {
- if (fileOrDirectory.isDirectory()) {
- for (File child : fileOrDirectory.listFiles()) {
- deleteDirOrFile(child);
- }
- }
- fileOrDirectory.delete();
- }
-
- /**
- * Checks whether a given package is in our bundled package set.
- */
- public static boolean isInBundledPackageSet(String packageName) {
- return BUNDLED_PACKAGE_SET.contains(packageName);
- }
-
- /**
- * Checks whether a given input is a bundled input.
- */
- public static boolean isBundledInput(String inputId) {
- for (String prefix : BUNDLED_PACKAGE_SET) {
- if (inputId.startsWith(prefix + "/")) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the canonical genre ID's from the {@code genres}.
- */
+ /** Returns the canonical genre ID's from the {@code genres}. */
public static int[] getCanonicalGenreIds(String genres) {
if (TextUtils.isEmpty(genres)) {
return null;
@@ -878,9 +855,7 @@ public class Utils {
return getCanonicalGenreIds(Genres.decode(genres));
}
- /**
- * Returns the canonical genre ID's from the {@code genres}.
- */
+ /** Returns the canonical genre ID's from the {@code genres}. */
public static int[] getCanonicalGenreIds(String[] canonicalGenres) {
if (canonicalGenres != null && canonicalGenres.length > 0) {
int[] results = new int[canonicalGenres.length];
@@ -901,9 +876,7 @@ public class Utils {
return null;
}
- /**
- * Returns the canonical genres for database.
- */
+ /** Returns the canonical genres for database. */
public static String getCanonicalGenre(int[] canonicalGenreIds) {
if (canonicalGenreIds == null || canonicalGenreIds.length == 0) {
return null;
@@ -916,15 +889,8 @@ public class Utils {
}
/**
- * Returns true if the current user is a developer.
- */
- public static boolean isDeveloper() {
- return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get();
- }
-
- /**
- * Runs the method in main thread. If the current thread is not main thread, block it util
- * the method is finished.
+ * Runs the method in main thread. If the current thread is not main thread, block it util the
+ * method is finished.
*/
public static void runInMainThreadAndWait(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
diff --git a/src/com/android/tv/util/ViewCache.java b/src/com/android/tv/util/ViewCache.java
index ed9a8ff6..b8bdb6b8 100644
--- a/src/com/android/tv/util/ViewCache.java
+++ b/src/com/android/tv/util/ViewCache.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.tv.util;
import android.content.Context;
@@ -5,22 +20,17 @@ import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-
import java.util.ArrayList;
-/**
- * A cache for the views.
- */
+/** A cache for the views. */
public class ViewCache {
- private final static SparseArray<ArrayList<View>> mViews = new SparseArray();
+ private static final SparseArray<ArrayList<View>> mViews = new SparseArray();
private static ViewCache sViewCache;
- private ViewCache() { }
+ private ViewCache() {}
- /**
- * Returns an instance of the view cache.
- */
+ /** Returns an instance of the view cache. */
public static ViewCache getInstance() {
if (sViewCache == null) {
sViewCache = new ViewCache();
@@ -28,16 +38,12 @@ public class ViewCache {
return sViewCache;
}
- /**
- * Returns if the view cache is empty.
- */
+ /** Returns if the view cache is empty. */
public boolean isEmpty() {
return mViews.size() == 0;
}
- /**
- * Stores a view into this view cache.
- */
+ /** Stores a view into this view cache. */
public void putView(int resId, View view) {
ArrayList<View> views = mViews.get(resId);
if (views == null) {
@@ -47,9 +53,7 @@ public class ViewCache {
views.add(view);
}
- /**
- * Stores multi specific views into the view cache.
- */
+ /** Stores multi specific views into the view cache. */
public void putView(Context context, int resId, ViewGroup fakeParent, int num) {
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -64,9 +68,7 @@ public class ViewCache {
}
}
- /**
- * Returns the view for specific resource id.
- */
+ /** Returns the view for specific resource id. */
public View getView(int resId) {
ArrayList<View> views = mViews.get(resId);
if (views != null && !views.isEmpty()) {
@@ -80,9 +82,7 @@ public class ViewCache {
}
}
- /**
- * Returns the view if exists, or create a new view for the specific resource id.
- */
+ /** Returns the view if exists, or create a new view for the specific resource id. */
public View getOrCreateView(LayoutInflater inflater, int resId, ViewGroup container) {
View view = getView(resId);
if (view == null) {
@@ -91,9 +91,7 @@ public class ViewCache {
return view;
}
- /**
- * Clears the view cache.
- */
+ /** Clears the view cache. */
public void clear() {
mViews.clear();
}
diff --git a/src/com/android/tv/util/account/AccountHelper.java b/src/com/android/tv/util/account/AccountHelper.java
new file mode 100644
index 00000000..e98b42ec
--- /dev/null
+++ b/src/com/android/tv/util/account/AccountHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util.account;
+
+import android.accounts.Account;
+import android.support.annotation.Nullable;
+
+/** Helper methods for getting and selecting a user account. */
+public interface AccountHelper {
+ /** Returns the currently selected account or {@code null} if none is selected. */
+ @Nullable
+ Account getSelectedAccount();
+ /**
+ * Selects the first account available.
+ *
+ * @return selected account or {@code null} if none is selected.
+ */
+ @Nullable
+ Account selectFirstAccount();
+
+ /** Returns all eligible accounts . */
+ @Nullable
+ Account getFirstEligibleAccount();
+}
diff --git a/src/com/android/tv/util/AccountHelper.java b/src/com/android/tv/util/account/AccountHelperImpl.java
index ece13de1..58fbd27e 100644
--- a/src/com/android/tv/util/AccountHelper.java
+++ b/src/com/android/tv/util/account/AccountHelperImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,43 +14,32 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.util.account;
import android.accounts.Account;
import android.content.Context;
import android.content.SharedPreferences;
-import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
-import android.util.Log;
-
-import java.util.Arrays;
-
-/**
- * Helper methods for getting and selecting a user account.
- */
-public class AccountHelper {
- private static final String TAG = "AccountHelper";
- private static final boolean DEBUG = false;
+/** Helper methods for getting and selecting a user account. */
+public class AccountHelperImpl implements com.android.tv.util.account.AccountHelper {
private static final String SELECTED_ACCOUNT = "android.tv.livechannels.selected_account";
- private final Context mContext;
+ protected final Context mContext;
private final SharedPreferences mDefaultPreferences;
- @Nullable
- private Account mSelectedAccount;
+ @Nullable private Account mSelectedAccount;
- public AccountHelper(Context context) {
+ public AccountHelperImpl(Context context) {
mContext = context.getApplicationContext();
mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
}
- /**
- * Returns the currently selected account or {@code null} if none is selected.
- */
+ /** Returns the currently selected account or {@code null} if none is selected. */
+ @Override
@Nullable
- public Account getSelectedAccount() {
+ public final Account getSelectedAccount() {
String accountId = mDefaultPreferences.getString(SELECTED_ACCOUNT, null);
if (accountId == null) {
return null;
@@ -68,9 +57,11 @@ public class AccountHelper {
}
/**
- * Returns all eligible accounts .
+ * Returns all eligible accounts.
+ *
+ * <p>Override this method to return the accounts needed.
*/
- private Account[] getEligibleAccounts() {
+ protected Account[] getEligibleAccounts() {
return new Account[0];
}
@@ -79,8 +70,9 @@ public class AccountHelper {
*
* @return selected account or {@code null} if none is selected.
*/
+ @Override
@Nullable
- public Account selectFirstAccount() {
+ public final Account selectFirstAccount() {
Account account = getFirstEligibleAccount();
if (account != null) {
selectAccount(account);
@@ -93,19 +85,17 @@ public class AccountHelper {
*
* @return first account or {@code null} if none is eligible.
*/
+ @Override
@Nullable
- public Account getFirstEligibleAccount() {
+ public final Account getFirstEligibleAccount() {
Account[] accounts = getEligibleAccounts();
return accounts.length == 0 ? null : accounts[0];
}
- /**
- * Sets the given account as the selected account.
- */
+ /** Sets the given account as the selected account. */
private void selectAccount(Account account) {
- SharedPreferences defaultPreferences = PreferenceManager
- .getDefaultSharedPreferences(mContext);
+ SharedPreferences defaultPreferences =
+ PreferenceManager.getDefaultSharedPreferences(mContext);
defaultPreferences.edit().putString(SELECTED_ACCOUNT, account.name).commit();
}
}
-
diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/images/BitmapUtils.java
index fbaab023..d6bd5a31 100644
--- a/src/com/android/tv/util/BitmapUtils.java
+++ b/src/com/android/tv/util/images/BitmapUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.util.images;
import android.content.ContentResolver;
import android.content.Context;
@@ -29,7 +29,7 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
-
+import com.android.tv.common.util.NetworkTrafficTags;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.IOException;
@@ -45,12 +45,14 @@ public final class BitmapUtils {
// The value of 64K, for MARK_READ_LIMIT, is chosen to be eight times the default buffer size
// of BufferedInputStream (8K) allowing it to double its buffers three times. Also it is a
// fairly reasonable value, not using too much memory and being large enough for most cases.
- private static final int MARK_READ_LIMIT = 64 * 1024; // 64K
+ private static final int MARK_READ_LIMIT = 64 * 1024; // 64K
- private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec
- private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec
+ private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec
+ private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec
- private BitmapUtils() { /* cannot be instantiated */ }
+ private BitmapUtils() {
+ /* cannot be instantiated */
+ }
public static Bitmap scaleBitmap(Bitmap bm, int maxWidth, int maxHeight) {
Rect rect = calculateNewSize(bm, maxWidth, maxHeight);
@@ -59,7 +61,8 @@ public final class BitmapUtils {
public static Bitmap getScaledMutableBitmap(Bitmap bm, int maxWidth, int maxHeight) {
Bitmap scaledBitmap = scaleBitmap(bm, maxWidth, maxHeight);
- return scaledBitmap.isMutable() ? scaledBitmap
+ return scaledBitmap.isMutable()
+ ? scaledBitmap
: scaledBitmap.copy(Bitmap.Config.ARGB_8888, true);
}
@@ -77,17 +80,17 @@ public final class BitmapUtils {
return rect;
}
- public static ScaledBitmapInfo createScaledBitmapInfo(String id, Bitmap bm, int maxWidth,
- int maxHeight) {
- return new ScaledBitmapInfo(id, scaleBitmap(bm, maxWidth, maxHeight),
+ public static ScaledBitmapInfo createScaledBitmapInfo(
+ String id, Bitmap bm, int maxWidth, int maxHeight) {
+ return new ScaledBitmapInfo(
+ id,
+ scaleBitmap(bm, maxWidth, maxHeight),
calculateInSampleSize(bm.getWidth(), bm.getHeight(), maxWidth, maxHeight));
}
- /**
- * Decode large sized bitmap into requested size.
- */
- public static ScaledBitmapInfo decodeSampledBitmapFromUriString(Context context,
- String uriString, int reqWidth, int reqHeight) {
+ /** Decode large sized bitmap into requested size. */
+ public static ScaledBitmapInfo decodeSampledBitmapFromUriString(
+ Context context, String uriString, int reqWidth, int reqHeight) {
if (TextUtils.isEmpty(uriString)) {
return null;
}
@@ -162,8 +165,8 @@ public final class BitmapUtils {
return urlConnection;
}
- private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
- int reqHeight) {
+ private static int calculateInSampleSize(
+ BitmapFactory.Options options, int reqWidth, int reqHeight) {
return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
}
@@ -187,7 +190,7 @@ public final class BitmapUtils {
closeable.close();
} catch (IOException e) {
// Log and continue.
- Log.w(TAG,"Error closing " + closeable, e);
+ Log.w(TAG, "Error closing " + closeable, e);
}
}
if (urlConnection instanceof HttpURLConnection) {
@@ -195,21 +198,13 @@ public final class BitmapUtils {
}
}
- /**
- * A wrapper class which contains the loaded bitmap and the scaling information.
- */
+ /** A wrapper class which contains the loaded bitmap and the scaling information. */
public static class ScaledBitmapInfo {
- /**
- * The id of bitmap, usually this is the URI of the original.
- */
- @NonNull
- public final String id;
+ /** The id of bitmap, usually this is the URI of the original. */
+ @NonNull public final String id;
- /**
- * The loaded bitmap object.
- */
- @NonNull
- public final Bitmap bitmap;
+ /** The loaded bitmap object. */
+ @NonNull public final Bitmap bitmap;
/**
* The scaling factor to the original bitmap. It should be an positive integer.
@@ -222,8 +217,8 @@ public final class BitmapUtils {
* A constructor.
*
* @param bitmap The loaded bitmap object.
- * @param inSampleSize The sampling size.
- * See {@link android.graphics.BitmapFactory.Options#inSampleSize}
+ * @param inSampleSize The sampling size. See {@link
+ * android.graphics.BitmapFactory.Options#inSampleSize}
*/
public ScaledBitmapInfo(@NonNull String id, @NonNull Bitmap bitmap, int inSampleSize) {
this.id = id;
@@ -232,10 +227,9 @@ public final class BitmapUtils {
}
/**
- * Checks if the bitmap needs to be reloaded. The scaling is performed by power 2.
- * The bitmap can be reloaded only if the required width or height is greater then or equal
- * to the existing bitmap.
- * If the full sized bitmap is already loaded, returns {@code false}.
+ * Checks if the bitmap needs to be reloaded. The scaling is performed by power 2. The
+ * bitmap can be reloaded only if the required width or height is greater then or equal to
+ * the existing bitmap. If the full sized bitmap is already loaded, returns {@code false}.
*
* @see android.graphics.BitmapFactory.Options#inSampleSize
*/
@@ -245,26 +239,41 @@ public final class BitmapUtils {
return false;
}
Rect size = calculateNewSize(this.bitmap, reqWidth, reqHeight);
- boolean reload = (size.right >= bitmap.getWidth() * 2
- || size.bottom >= bitmap.getHeight() * 2);
+ boolean reload =
+ (size.right >= bitmap.getWidth() * 2 || size.bottom >= bitmap.getHeight() * 2);
if (DEBUG) {
- Log.d(TAG, "needToReload(" + reqWidth + ", " + reqHeight + ")=" + reload
- + " because the new size would be " + size + " for " + this);
+ Log.d(
+ TAG,
+ "needToReload("
+ + reqWidth
+ + ", "
+ + reqHeight
+ + ")="
+ + reload
+ + " because the new size would be "
+ + size
+ + " for "
+ + this);
}
return reload;
}
- /**
- * Returns {@code true} if a request the size of {@code other} would need a reload.
- */
- public boolean needToReload(ScaledBitmapInfo other){
+ /** Returns {@code true} if a request the size of {@code other} would need a reload. */
+ public boolean needToReload(ScaledBitmapInfo other) {
return needToReload(other.bitmap.getWidth(), other.bitmap.getHeight());
}
@Override
public String toString() {
- return "ScaledBitmapInfo[" + id + "](in=" + inSampleSize + ", w=" + bitmap.getWidth()
- + ", h=" + bitmap.getHeight() + ")";
+ return "ScaledBitmapInfo["
+ + id
+ + "](in="
+ + inSampleSize
+ + ", w="
+ + bitmap.getWidth()
+ + ", h="
+ + bitmap.getHeight()
+ + ")";
}
}
diff --git a/src/com/android/tv/util/ImageCache.java b/src/com/android/tv/util/images/ImageCache.java
index b413c364..e260d67a 100644
--- a/src/com/android/tv/util/ImageCache.java
+++ b/src/com/android/tv/util/images/ImageCache.java
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.util.images;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.LruCache;
+import com.android.tv.common.memory.MemoryManageable;
+import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo;
-import com.android.tv.common.MemoryManageable;
-import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
-
-/**
- * A convenience class for caching bitmap.
- */
+/** A convenience class for caching bitmap. */
public class ImageCache implements MemoryManageable {
private static final float MAX_CACHE_SIZE_PERCENT = 0.8f;
private static final float MIN_CACHE_SIZE_PERCENT = 0.05f;
@@ -48,16 +45,17 @@ public class ImageCache implements MemoryManageable {
if (DEBUG) {
Log.d(TAG, "Memory cache created (size = " + memCacheSize + " Kbytes)");
}
- mMemoryCache = new LruCache<String, ScaledBitmapInfo>(memCacheSize) {
- /**
- * Measure item size in kilobytes rather than units which is more practical for a bitmap
- * cache
- */
- @Override
- protected int sizeOf(String key, ScaledBitmapInfo bitmapInfo) {
- return (bitmapInfo.bitmap.getByteCount() + 1023) / 1024;
- }
- };
+ mMemoryCache =
+ new LruCache<String, ScaledBitmapInfo>(memCacheSize) {
+ /**
+ * Measure item size in kilobytes rather than units which is more practical for
+ * a bitmap cache
+ */
+ @Override
+ protected int sizeOf(String key, ScaledBitmapInfo bitmapInfo) {
+ return (bitmapInfo.bitmap.getByteCount() + 1023) / 1024;
+ }
+ };
}
private static ImageCache sImageCache;
@@ -67,7 +65,7 @@ public class ImageCache implements MemoryManageable {
* param.
*
* @param memCacheSizePercent The cache size as a percent of available app memory. Should be in
- * range of MIN_CACHE_SIZE_PERCENT(0.05) ~ MAX_CACHE_SIZE_PERCENT(0.8).
+ * range of MIN_CACHE_SIZE_PERCENT(0.05) ~ MAX_CACHE_SIZE_PERCENT(0.8).
* @return An existing retained ImageCache object or a new one if one did not exist
*/
public static synchronized ImageCache getInstance(float memCacheSizePercent) {
@@ -82,7 +80,6 @@ public class ImageCache implements MemoryManageable {
return new ImageCache(memCacheSizePercent);
}
-
/**
* Returns an existing ImageCache, if it doesn't exist, a new one is created using
* DEFAULT_CACHE_SIZE_PERCENT (0.1).
@@ -96,8 +93,8 @@ public class ImageCache implements MemoryManageable {
/**
* Adds a bitmap to memory cache.
*
- * <p>If there is an existing bitmap only replace it if
- * {@link ScaledBitmapInfo#needToReload(ScaledBitmapInfo)} is true.
+ * <p>If there is an existing bitmap only replace it if {@link
+ * ScaledBitmapInfo#needToReload(ScaledBitmapInfo)} is true.
*
* @param bitmapInfo The {@link ScaledBitmapInfo} object to store
*/
@@ -112,14 +109,25 @@ public class ImageCache implements MemoryManageable {
if (old != null && !old.needToReload(bitmapInfo)) {
mMemoryCache.put(key, old);
if (DEBUG) {
- Log.d(TAG,
- "Kept original " + old + " in memory cache because it was larger than "
- + bitmapInfo + ".");
+ Log.d(
+ TAG,
+ "Kept original "
+ + old
+ + " in memory cache because it was larger than "
+ + bitmapInfo
+ + ".");
}
} else {
if (DEBUG) {
- Log.d(TAG, "Add " + bitmapInfo + " to memory cache. Current size is " +
- mMemoryCache.size() + " / " + mMemoryCache.maxSize() + " Kbytes");
+ Log.d(
+ TAG,
+ "Add "
+ + bitmapInfo
+ + " to memory cache. Current size is "
+ + mMemoryCache.size()
+ + " / "
+ + mMemoryCache.maxSize()
+ + " Kbytes");
}
}
}
@@ -158,19 +166,21 @@ public class ImageCache implements MemoryManageable {
* Calculates the memory cache size based on a percentage of the max available VM memory. Eg.
* setting percent to 0.2 would set the memory cache to one fifth of the available memory.
* Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8. memCacheSize is stored
- * in kilobytes instead of bytes as this will eventually be passed to construct a LruCache
- * which takes an int in its constructor. This value should be chosen carefully based on a
- * number of factors Refer to the corresponding Android Training class for more discussion:
+ * in kilobytes instead of bytes as this will eventually be passed to construct a LruCache which
+ * takes an int in its constructor. This value should be chosen carefully based on a number of
+ * factors Refer to the corresponding Android Training class for more discussion:
* http://developer.android.com/training/displaying-bitmaps/
*
* @param percent Percent of available app memory to use to size memory cache.
*/
public static int calculateMemCacheSize(float percent) {
if (percent < MIN_CACHE_SIZE_PERCENT || percent > MAX_CACHE_SIZE_PERCENT) {
- throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
- + "between 0.05 and 0.8 (inclusive)");
+ throw new IllegalArgumentException(
+ "setMemCacheSizePercent - percent must be "
+ + "between 0.05 and 0.8 (inclusive)");
}
- return Math.max(MIN_CACHE_SIZE_KBYTES,
+ return Math.max(
+ MIN_CACHE_SIZE_KBYTES,
Math.round(percent * Runtime.getRuntime().maxMemory() / 1024));
}
diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/images/ImageLoader.java
index 86bb94c1..e844e2ca 100644
--- a/src/com/android/tv/util/ImageLoader.java
+++ b/src/com/android/tv/util/images/ImageLoader.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.util.images;
import android.content.Context;
import android.graphics.Bitmap;
@@ -30,10 +30,9 @@ import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.ArraySet;
import android.util.Log;
-
import com.android.tv.R;
-import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
-
+import com.android.tv.common.concurrent.NamedThreadFactory;
+import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
@@ -47,8 +46,8 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
- * This class wraps up completing some arbitrary long running work when loading a bitmap. It
- * handles things like using a memory cache, running the work in a background thread.
+ * This class wraps up completing some arbitrary long running work when loading a bitmap. It handles
+ * things like using a memory cache, running the work in a background thread.
*/
public final class ImageLoader {
private static final String TAG = "ImageLoader";
@@ -69,19 +68,23 @@ public final class ImageLoader {
/**
* An private {@link Executor} that can be used to execute tasks in parallel.
*
- * <p>{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask}
- * Since we do a lot of concurrent image loading we can exhaust a thread pool.
- * ImageLoader catches the error, and just leaves the image blank.
- * However other tasks will fail and crash the application.
+ * <p>{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask} Since we do a
+ * lot of concurrent image loading we can exhaust a thread pool. ImageLoader catches the error,
+ * and just leaves the image blank. However other tasks will fail and crash the application.
*
* <p>Using a separate thread pool prevents image loading from causing other tasks to fail.
*/
private static final Executor IMAGE_THREAD_POOL_EXECUTOR;
static {
- ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
- MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue,
- sThreadFactory);
+ ThreadPoolExecutor threadPoolExecutor =
+ new ThreadPoolExecutor(
+ CORE_POOL_SIZE,
+ MAXIMUM_POOL_SIZE,
+ KEEP_ALIVE_SECONDS,
+ TimeUnit.SECONDS,
+ sPoolWorkQueue,
+ sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
IMAGE_THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
@@ -91,28 +94,26 @@ public final class ImageLoader {
/**
* Handles when image loading is finished.
*
- * <p>Use this to prevent leaking an Activity or other Context while image loading is
- * still pending. When you extend this class you <strong>MUST NOT</strong> use a non static
- * inner class, or the containing object will still be leaked.
+ * <p>Use this to prevent leaking an Activity or other Context while image loading is still
+ * pending. When you extend this class you <strong>MUST NOT</strong> use a non static inner
+ * class, or the containing object will still be leaked.
*/
@UiThread
- public static abstract class ImageLoaderCallback<T> {
+ public abstract static class ImageLoaderCallback<T> {
private final WeakReference<T> mWeakReference;
/**
* Creates an callback keeping a weak reference to {@code referent}.
*
- * <p> If the "referent" is no longer valid, it no longer makes sense to run the
- * callback. The referent is the View, or Activity or whatever that actually needs to
- * receive the Bitmap. If the referent has been GC, then no need to run the callback.
+ * <p>If the "referent" is no longer valid, it no longer makes sense to run the callback.
+ * The referent is the View, or Activity or whatever that actually needs to receive the
+ * Bitmap. If the referent has been GC, then no need to run the callback.
*/
public ImageLoaderCallback(T referent) {
mWeakReference = new WeakReference<>(referent);
}
- /**
- * Called when bitmap is loaded.
- */
+ /** Called when bitmap is loaded. */
private void onBitmapLoaded(@Nullable Bitmap bitmap) {
T referent = mWeakReference.get();
if (referent != null) {
@@ -122,9 +123,7 @@ public final class ImageLoader {
}
}
- /**
- * Called when bitmap is loaded if the weak reference is still valid.
- */
+ /** Called when bitmap is loaded if the weak reference is still valid. */
public abstract void onBitmapLoaded(T referent, @Nullable Bitmap bitmap);
}
@@ -134,62 +133,80 @@ public final class ImageLoader {
* Preload a bitmap image into the cache.
*
* <p>Not to make heavy CPU load, AsyncTask.SERIAL_EXECUTOR is used for the image loading.
+ *
* <p>This method is thread safe.
*/
- public static void prefetchBitmap(Context context, final String uriString, final int maxWidth,
- final int maxHeight) {
+ public static void prefetchBitmap(
+ Context context, final String uriString, final int maxWidth, final int maxHeight) {
if (DEBUG) Log.d(TAG, "prefetchBitmap() " + uriString);
if (Looper.getMainLooper() == Looper.myLooper()) {
doLoadBitmap(context, uriString, maxWidth, maxHeight, null, AsyncTask.SERIAL_EXECUTOR);
} else {
final Context appContext = context.getApplicationContext();
- getMainHandler().post(new Runnable() {
- @Override
- @MainThread
- public void run() {
- // Calling from the main thread prevents a ConcurrentModificationException
- // in LoadBitmapTask.onPostExecute
- doLoadBitmap(appContext, uriString, maxWidth, maxHeight, null,
- AsyncTask.SERIAL_EXECUTOR);
- }
- });
+ getMainHandler()
+ .post(
+ new Runnable() {
+ @Override
+ @MainThread
+ public void run() {
+ // Calling from the main thread prevents a
+ // ConcurrentModificationException
+ // in LoadBitmapTask.onPostExecute
+ doLoadBitmap(
+ appContext,
+ uriString,
+ maxWidth,
+ maxHeight,
+ null,
+ AsyncTask.SERIAL_EXECUTOR);
+ }
+ });
}
}
/**
* Load a bitmap image with the cache using a ContentResolver.
*
- * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in
- * the cache.
+ * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in the
+ * cache.
*
* @return {@code true} if the load is complete and the callback is executed.
*/
@UiThread
- public static boolean loadBitmap(Context context, String uriString,
- ImageLoaderCallback callback) {
+ public static boolean loadBitmap(
+ Context context, String uriString, ImageLoaderCallback callback) {
return loadBitmap(context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE, callback);
}
/**
* Load a bitmap image with the cache and resize it with given params.
*
- * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in
- * the cache.
+ * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in the
+ * cache.
*
* @return {@code true} if the load is complete and the callback is executed.
*/
@UiThread
- public static boolean loadBitmap(Context context, String uriString, int maxWidth, int maxHeight,
+ public static boolean loadBitmap(
+ Context context,
+ String uriString,
+ int maxWidth,
+ int maxHeight,
ImageLoaderCallback callback) {
if (DEBUG) {
Log.d(TAG, "loadBitmap() " + uriString);
}
- return doLoadBitmap(context, uriString, maxWidth, maxHeight, callback,
- IMAGE_THREAD_POOL_EXECUTOR);
+ return doLoadBitmap(
+ context, uriString, maxWidth, maxHeight, callback, IMAGE_THREAD_POOL_EXECUTOR);
}
- private static boolean doLoadBitmap(Context context, String uriString,
- int maxWidth, int maxHeight, ImageLoaderCallback callback, Executor executor) {
+ private static boolean doLoadBitmap(
+ Context context,
+ String uriString,
+ int maxWidth,
+ int maxHeight,
+ ImageLoaderCallback callback,
+ Executor executor) {
// Check the cache before creating a Task. The cache will be checked again in doLoadBitmap
// but checking a cache is much cheaper than creating an new task.
ImageCache imageCache = ImageCache.getInstance();
@@ -200,7 +217,9 @@ public final class ImageLoader {
}
return true;
}
- return doLoadBitmap(callback, executor,
+ return doLoadBitmap(
+ callback,
+ executor,
new LoadBitmapFromUriTask(context, imageCache, uriString, maxWidth, maxHeight));
}
@@ -219,12 +238,10 @@ public final class ImageLoader {
return doLoadBitmap(callback, IMAGE_THREAD_POOL_EXECUTOR, loadBitmapTask);
}
- /**
- * @return {@code true} if the load is complete and the callback is executed.
- */
+ /** @return {@code true} if the load is complete and the callback is executed. */
@UiThread
- private static boolean doLoadBitmap(ImageLoaderCallback callback, Executor executor,
- LoadBitmapTask loadBitmapTask) {
+ private static boolean doLoadBitmap(
+ ImageLoaderCallback callback, Executor executor, LoadBitmapTask loadBitmapTask) {
ScaledBitmapInfo bitmapInfo = loadBitmapTask.getFromCache();
boolean needToReload = loadBitmapTask.isReloadNeeded();
if (bitmapInfo != null && !needToReload) {
@@ -259,7 +276,7 @@ public final class ImageLoader {
*
* <p>Implement {@link #doGetBitmapInBackground} to do the actual loading.
*/
- public static abstract class LoadBitmapTask extends AsyncTask<Void, Void, ScaledBitmapInfo> {
+ public abstract static class LoadBitmapTask extends AsyncTask<Void, Void, ScaledBitmapInfo> {
protected final Context mAppContext;
protected final int mMaxWidth;
protected final int mMaxHeight;
@@ -273,24 +290,28 @@ public final class ImageLoader {
*/
private boolean isReloadNeeded() {
ScaledBitmapInfo bitmapInfo = getFromCache();
- boolean needToReload = bitmapInfo != null && bitmapInfo
- .needToReload(mMaxWidth, mMaxHeight);
+ boolean needToReload =
+ bitmapInfo != null && bitmapInfo.needToReload(mMaxWidth, mMaxHeight);
if (DEBUG) {
if (needToReload) {
- Log.d(TAG, "Bitmap needs to be reloaded. {"
- + "originalWidth=" + bitmapInfo.bitmap.getWidth()
- + ", originalHeight=" + bitmapInfo.bitmap.getHeight()
- + ", reqWidth=" + mMaxWidth
- + ", reqHeight=" + mMaxHeight
- + "}");
+ Log.d(
+ TAG,
+ "Bitmap needs to be reloaded. {"
+ + "originalWidth="
+ + bitmapInfo.bitmap.getWidth()
+ + ", originalHeight="
+ + bitmapInfo.bitmap.getHeight()
+ + ", reqWidth="
+ + mMaxWidth
+ + ", reqHeight="
+ + mMaxHeight
+ + "}");
}
}
return needToReload;
}
- /**
- * Checks if a reload would be needed if the results of other was available.
- */
+ /** Checks if a reload would be needed if the results of other was available. */
private boolean isReloadNeeded(LoadBitmapTask other) {
return (other.mMaxHeight != Integer.MAX_VALUE && mMaxHeight >= other.mMaxHeight * 2)
|| (other.mMaxWidth != Integer.MAX_VALUE && mMaxWidth >= other.mMaxWidth * 2);
@@ -301,11 +322,14 @@ public final class ImageLoader {
return mImageCache.get(mKey);
}
- public LoadBitmapTask(Context context, ImageCache imageCache, String key, int maxHeight,
- int maxWidth) {
+ public LoadBitmapTask(
+ Context context, ImageCache imageCache, String key, int maxHeight, int maxWidth) {
if (maxWidth == 0 || maxHeight == 0) {
throw new IllegalArgumentException(
- "Image size should not be 0. {width=" + maxWidth + ", height=" + maxHeight
+ "Image size should not be 0. {width="
+ + maxWidth
+ + ", height="
+ + maxHeight
+ "}");
}
mAppContext = context.getApplicationContext();
@@ -315,9 +339,7 @@ public final class ImageLoader {
mMaxWidth = maxWidth;
}
- /**
- * Loads the bitmap returning a possibly scaled down version.
- */
+ /** Loads the bitmap returning a possibly scaled down version. */
@Nullable
@WorkerThread
public abstract ScaledBitmapInfo doGetBitmapInBackground();
@@ -352,40 +374,48 @@ public final class ImageLoader {
@Override
public String toString() {
- return this.getClass().getSimpleName() + "(" + mKey + " " + mMaxWidth + "x" + mMaxHeight
+ return this.getClass().getSimpleName()
+ + "("
+ + mKey
+ + " "
+ + mMaxWidth
+ + "x"
+ + mMaxHeight
+ ")";
}
}
private static final class LoadBitmapFromUriTask extends LoadBitmapTask {
- private LoadBitmapFromUriTask(Context context, ImageCache imageCache, String uriString,
- int maxWidth, int maxHeight) {
+ private LoadBitmapFromUriTask(
+ Context context,
+ ImageCache imageCache,
+ String uriString,
+ int maxWidth,
+ int maxHeight) {
super(context, imageCache, uriString, maxHeight, maxWidth);
}
@Override
@Nullable
public final ScaledBitmapInfo doGetBitmapInBackground() {
- return BitmapUtils
- .decodeSampledBitmapFromUriString(mAppContext, getKey(), mMaxWidth, mMaxHeight);
+ return BitmapUtils.decodeSampledBitmapFromUriString(
+ mAppContext, getKey(), mMaxWidth, mMaxHeight);
}
}
- /**
- * Loads and caches the logo for a given {@link TvInputInfo}
- */
+ /** Loads and caches the logo for a given {@link TvInputInfo} */
public static final class LoadTvInputLogoTask extends LoadBitmapTask {
private final TvInputInfo mInfo;
public LoadTvInputLogoTask(Context context, ImageCache cache, TvInputInfo info) {
- super(context,
+ super(
+ context,
cache,
getTvInputLogoKey(info.getId()),
context.getResources()
.getDimensionPixelSize(R.dimen.channel_banner_input_logo_size),
context.getResources()
- .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size)
- );
+ .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size));
mInfo = info;
}
@@ -403,9 +433,7 @@ public final class ImageLoader {
return BitmapUtils.createScaledBitmapInfo(getKey(), original, mMaxWidth, mMaxHeight);
}
- /**
- * Returns key of TV input logo.
- */
+ /** Returns key of TV input logo. */
public static String getTvInputLogoKey(String inputId) {
return inputId + "-logo";
}
@@ -418,6 +446,5 @@ public final class ImageLoader {
return sMainHandler;
}
- private ImageLoader() {
- }
+ private ImageLoader() {}
}