aboutsummaryrefslogtreecommitdiff
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/tv/Features.java64
-rw-r--r--src/com/android/tv/InputSessionManager.java28
-rw-r--r--src/com/android/tv/MainActivity.java764
-rw-r--r--src/com/android/tv/SetupPassthroughActivity.java90
-rw-r--r--src/com/android/tv/TimeShiftManager.java9
-rw-r--r--src/com/android/tv/TvApplication.java19
-rw-r--r--src/com/android/tv/TvOptionsManager.java133
-rw-r--r--src/com/android/tv/analytics/DurationTimer.java (renamed from src/com/android/tv/util/DurationTimer.java)24
-rw-r--r--src/com/android/tv/data/Channel.java97
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java42
-rw-r--r--src/com/android/tv/data/ChannelLogoFetcher.java307
-rw-r--r--src/com/android/tv/data/ChannelNumber.java71
-rw-r--r--src/com/android/tv/data/InternalDataUtils.java2
-rw-r--r--src/com/android/tv/data/StreamInfo.java4
-rw-r--r--src/com/android/tv/data/epg/EpgFetcher.java327
-rw-r--r--src/com/android/tv/data/epg/EpgReader.java29
-rw-r--r--src/com/android/tv/data/epg/StubEpgReader.java17
-rw-r--r--src/com/android/tv/dialog/DvrHistoryDialogFragment.java129
-rw-r--r--src/com/android/tv/dialog/FullscreenDialogFragment.java2
-rw-r--r--src/com/android/tv/dialog/SafeDismissDialogFragment.java26
-rw-r--r--src/com/android/tv/dialog/WebDialogFragment.java17
-rw-r--r--src/com/android/tv/dvr/BaseDvrDataManager.java41
-rw-r--r--src/com/android/tv/dvr/ConflictChecker.java (renamed from src/com/android/tv/dvr/recorder/ConflictChecker.java)5
-rw-r--r--src/com/android/tv/dvr/DvrDataManager.java12
-rw-r--r--src/com/android/tv/dvr/DvrDataManagerImpl.java143
-rw-r--r--src/com/android/tv/dvr/DvrDbSync.java (renamed from src/com/android/tv/dvr/provider/DvrDbSync.java)34
-rw-r--r--src/com/android/tv/dvr/DvrManager.java81
-rw-r--r--src/com/android/tv/dvr/DvrPlaybackActivity.java (renamed from src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java)4
-rw-r--r--src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java (renamed from src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java)104
-rw-r--r--src/com/android/tv/dvr/DvrPlayer.java (renamed from src/com/android/tv/dvr/ui/playback/DvrPlayer.java)220
-rw-r--r--src/com/android/tv/dvr/DvrRecordingService.java (renamed from src/com/android/tv/dvr/recorder/DvrRecordingService.java)36
-rw-r--r--src/com/android/tv/dvr/DvrScheduleManager.java139
-rw-r--r--src/com/android/tv/dvr/DvrStartRecordingReceiver.java (renamed from src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java)2
-rw-r--r--src/com/android/tv/dvr/DvrStorageStatusManager.java35
-rw-r--r--src/com/android/tv/dvr/DvrUiHelper.java (renamed from src/com/android/tv/dvr/ui/DvrUiHelper.java)311
-rw-r--r--src/com/android/tv/dvr/DvrWatchedPositionManager.java1
-rw-r--r--src/com/android/tv/dvr/EpisodicProgramLoadTask.java (renamed from src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java)73
-rw-r--r--src/com/android/tv/dvr/IdGenerator.java (renamed from src/com/android/tv/dvr/data/IdGenerator.java)2
-rw-r--r--src/com/android/tv/dvr/InputTaskScheduler.java (renamed from src/com/android/tv/dvr/recorder/InputTaskScheduler.java)6
-rw-r--r--src/com/android/tv/dvr/RecordedProgram.java (renamed from src/com/android/tv/dvr/data/RecordedProgram.java)2
-rw-r--r--src/com/android/tv/dvr/RecordingTask.java (renamed from src/com/android/tv/dvr/recorder/RecordingTask.java)23
-rw-r--r--src/com/android/tv/dvr/ScheduledProgramReaper.java (renamed from src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java)5
-rw-r--r--src/com/android/tv/dvr/ScheduledRecording.java (renamed from src/com/android/tv/dvr/data/ScheduledRecording.java)17
-rw-r--r--src/com/android/tv/dvr/Scheduler.java (renamed from src/com/android/tv/dvr/recorder/Scheduler.java)6
-rw-r--r--src/com/android/tv/dvr/SeriesInfo.java (renamed from src/com/android/tv/dvr/data/SeriesInfo.java)2
-rw-r--r--src/com/android/tv/dvr/SeriesRecording.java (renamed from src/com/android/tv/dvr/data/SeriesRecording.java)3
-rw-r--r--src/com/android/tv/dvr/SeriesRecordingScheduler.java (renamed from src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java)81
-rw-r--r--src/com/android/tv/dvr/WritableDvrDataManager.java6
-rw-r--r--src/com/android/tv/dvr/data/SeasonEpisodeNumber.java72
-rw-r--r--src/com/android/tv/dvr/provider/AsyncDvrDbTask.java4
-rw-r--r--src/com/android/tv/dvr/provider/DvrDatabaseHelper.java4
-rw-r--r--src/com/android/tv/dvr/ui/ActionPresenterSelector.java (renamed from src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java)10
-rw-r--r--src/com/android/tv/dvr/ui/BigArguments.java54
-rw-r--r--src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java79
-rw-r--r--src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java59
-rw-r--r--src/com/android/tv/dvr/ui/DetailsContent.java (renamed from src/com/android/tv/dvr/ui/browse/DetailsContent.java)4
-rw-r--r--src/com/android/tv/dvr/ui/DetailsContentPresenter.java (renamed from src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java)37
-rw-r--r--src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java (renamed from src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java)4
-rw-r--r--src/com/android/tv/dvr/ui/DvrActivity.java (renamed from src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java)6
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java4
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/DvrBrowseFragment.java (renamed from src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java)159
-rw-r--r--src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java2
-rw-r--r--src/com/android/tv/dvr/ui/DvrConflictFragment.java7
-rw-r--r--src/com/android/tv/dvr/ui/DvrDetailsActivity.java (renamed from src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java)2
-rw-r--r--src/com/android/tv/dvr/ui/DvrDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java12
-rw-r--r--src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java84
-rw-r--r--src/com/android/tv/dvr/ui/DvrItemPresenter.java (renamed from src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java)13
-rw-r--r--src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java (renamed from src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java)31
-rw-r--r--src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java (renamed from src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java)106
-rw-r--r--src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java (renamed from src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java)181
-rw-r--r--src/com/android/tv/dvr/ui/DvrScheduleFragment.java17
-rw-r--r--src/com/android/tv/dvr/ui/DvrSchedulesActivity.java (renamed from src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java)76
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java5
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java45
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java20
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java11
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java4
-rw-r--r--src/com/android/tv/dvr/ui/FadeBackground.java70
-rw-r--r--src/com/android/tv/dvr/ui/FullScheduleCardHolder.java (renamed from src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java)2
-rw-r--r--src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java (renamed from src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java)66
-rw-r--r--src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java (renamed from src/com/android/tv/dialog/HalfSizedDialogFragment.java)24
-rw-r--r--src/com/android/tv/dvr/ui/PrioritySettingsFragment.java (renamed from src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java)7
-rw-r--r--src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/RecordedProgramPresenter.java (renamed from src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java)31
-rw-r--r--src/com/android/tv/dvr/ui/RecordingCardView.java (renamed from src/com/android/tv/dvr/ui/browse/RecordingCardView.java)113
-rw-r--r--src/com/android/tv/dvr/ui/RecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java)4
-rw-r--r--src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java)4
-rw-r--r--src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java)21
-rw-r--r--src/com/android/tv/dvr/ui/SeriesDeletionFragment.java (renamed from src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java)11
-rw-r--r--src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java)24
-rw-r--r--src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java)11
-rw-r--r--src/com/android/tv/dvr/ui/SeriesSettingsFragment.java (renamed from src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java)209
-rw-r--r--src/com/android/tv/dvr/ui/SortedArrayAdapter.java90
-rw-r--r--src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java120
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java34
-rw-r--r--src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java2
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java72
-rw-r--r--src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java6
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRow.java4
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java4
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java50
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java20
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java26
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java24
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java17
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java154
-rw-r--r--src/com/android/tv/experiments/ExperimentFlag.java32
-rw-r--r--src/com/android/tv/experiments/Experiments.java9
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java2
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java21
-rw-r--r--src/com/android/tv/guide/ProgramManager.java20
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java68
-rw-r--r--src/com/android/tv/guide/TimeListAdapter.java27
-rw-r--r--src/com/android/tv/menu/ActionCardView.java6
-rw-r--r--src/com/android/tv/menu/AppLinkCardView.java198
-rw-r--r--src/com/android/tv/menu/BaseCardView.java50
-rw-r--r--src/com/android/tv/menu/ChannelCardView.java8
-rw-r--r--src/com/android/tv/menu/ChannelsRow.java10
-rw-r--r--src/com/android/tv/menu/ChannelsRowAdapter.java4
-rw-r--r--src/com/android/tv/menu/ItemListRowView.java14
-rw-r--r--src/com/android/tv/menu/Menu.java49
-rw-r--r--src/com/android/tv/menu/MenuAction.java69
-rw-r--r--src/com/android/tv/menu/MenuLayoutManager.java20
-rw-r--r--src/com/android/tv/menu/MenuRowFactory.java24
-rw-r--r--src/com/android/tv/menu/MenuUpdater.java41
-rw-r--r--src/com/android/tv/menu/OptionsRowAdapter.java50
-rw-r--r--src/com/android/tv/menu/PartnerOptionsRowAdapter.java3
-rw-r--r--src/com/android/tv/menu/PipOptionsRowAdapter.java137
-rw-r--r--src/com/android/tv/menu/PlayControlsButton.java24
-rw-r--r--src/com/android/tv/menu/PlayControlsRowView.java194
-rw-r--r--src/com/android/tv/menu/PlaybackProgressBar.java168
-rw-r--r--src/com/android/tv/menu/TvOptionsRowAdapter.java128
-rw-r--r--src/com/android/tv/onboarding/SetupSourcesFragment.java2
-rw-r--r--src/com/android/tv/receiver/BootCompletedReceiver.java2
-rw-r--r--src/com/android/tv/receiver/GlobalKeyReceiver.java40
-rw-r--r--src/com/android/tv/receiver/PackageIntentsReceiver.java6
-rw-r--r--src/com/android/tv/recommendation/NotificationService.java1
-rw-r--r--src/com/android/tv/search/DataManagerSearch.java4
-rw-r--r--src/com/android/tv/search/SearchInterface.java2
-rw-r--r--src/com/android/tv/search/TvProviderSearch.java14
-rw-r--r--src/com/android/tv/tuner/ChannelScanFileParser.java2
-rw-r--r--src/com/android/tv/tuner/TunerHal.java43
-rw-r--r--src/com/android/tv/tuner/TunerInputController.java195
-rw-r--r--src/com/android/tv/tuner/TunerPreferences.java97
-rw-r--r--src/com/android/tv/tuner/UsbTunerHal.java6
-rw-r--r--src/com/android/tv/tuner/cc/CaptionLayout.java2
-rw-r--r--src/com/android/tv/tuner/cc/CaptionTrackRenderer.java2
-rw-r--r--src/com/android/tv/tuner/cc/Cea708Parser.java5
-rw-r--r--src/com/android/tv/tuner/data/PsiData.java4
-rw-r--r--src/com/android/tv/tuner/data/PsipData.java6
-rw-r--r--src/com/android/tv/tuner/data/TunerChannel.java130
-rw-r--r--src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java378
-rw-r--r--src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java14
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java62
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java13
-rw-r--r--src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java (renamed from src/com/android/tv/tuner/exoplayer/ac3/Ac3DefaultTrackRenderer.java)114
-rw-r--r--src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java (renamed from src/com/android/tv/tuner/exoplayer/ac3/Ac3MediaCodecTrackRenderer.java)21
-rw-r--r--src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java4
-rw-r--r--src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java20
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java280
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java209
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java23
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java23
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java115
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java1
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java1
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java85
-rw-r--r--src/com/android/tv/tuner/setup/ConnectionTypeFragment.java18
-rw-r--r--src/com/android/tv/tuner/setup/PostalCodeFragment.java184
-rw-r--r--src/com/android/tv/tuner/setup/ScanFragment.java58
-rw-r--r--src/com/android/tv/tuner/setup/ScanResultFragment.java17
-rw-r--r--src/com/android/tv/tuner/setup/TunerSetupActivity.java238
-rw-r--r--src/com/android/tv/tuner/setup/WelcomeFragment.java38
-rw-r--r--src/com/android/tv/tuner/source/FileTsStreamer.java2
-rw-r--r--src/com/android/tv/tuner/source/TsDataSourceManager.java12
-rw-r--r--src/com/android/tv/tuner/source/TunerTsStreamer.java58
-rw-r--r--src/com/android/tv/tuner/source/TunerTsStreamerManager.java26
-rw-r--r--src/com/android/tv/tuner/ts/SectionParser.java20
-rw-r--r--src/com/android/tv/tuner/ts/TsParser.java18
-rw-r--r--src/com/android/tv/tuner/tvinput/ChannelDataManager.java54
-rw-r--r--src/com/android/tv/tuner/tvinput/EventDetector.java52
-rw-r--r--src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java4
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java116
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSession.java17
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSessionWorker.java300
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerTvInputService.java30
-rw-r--r--src/com/android/tv/tuner/util/PostalCodeUtils.java89
-rw-r--r--src/com/android/tv/tuner/util/StringUtils.java (renamed from src/com/android/tv/util/StringUtils.java)2
-rw-r--r--src/com/android/tv/tuner/util/SystemPropertiesProxy.java16
-rw-r--r--src/com/android/tv/tuner/util/TunerInputInfoUtils.java46
-rw-r--r--src/com/android/tv/ui/AppLayerTvView.java10
-rw-r--r--src/com/android/tv/ui/ChannelBannerView.java137
-rw-r--r--src/com/android/tv/ui/KeypadChannelSwitchView.java2
-rw-r--r--src/com/android/tv/ui/SelectInputView.java73
-rw-r--r--src/com/android/tv/ui/TunableTvView.java336
-rw-r--r--src/com/android/tv/ui/TuningBlockView.java113
-rw-r--r--src/com/android/tv/ui/TvOverlayManager.java30
-rw-r--r--src/com/android/tv/ui/TvViewUiManager.java265
-rw-r--r--src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java9
-rw-r--r--src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java23
-rw-r--r--src/com/android/tv/ui/sidepanel/Item.java12
-rw-r--r--src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java170
-rw-r--r--src/com/android/tv/ui/sidepanel/SettingsFragment.java20
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragment.java47
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragmentManager.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/SimpleItem.java (renamed from src/com/android/tv/ui/sidepanel/SimpleActionItem.java)6
-rw-r--r--src/com/android/tv/util/AsyncDbTask.java2
-rw-r--r--src/com/android/tv/util/Debug.java60
-rw-r--r--src/com/android/tv/util/LocationUtils.java24
-rw-r--r--src/com/android/tv/util/Partner.java181
-rw-r--r--src/com/android/tv/util/PipInputManager.java432
-rw-r--r--src/com/android/tv/util/RecurringRunner.java9
-rw-r--r--src/com/android/tv/util/SearchManagerHelper.java61
-rw-r--r--src/com/android/tv/util/SetupUtils.java20
-rw-r--r--src/com/android/tv/util/TvInputManagerHelper.java332
-rw-r--r--src/com/android/tv/util/TvSettings.java148
-rw-r--r--src/com/android/tv/util/TvTrackInfoUtils.java37
-rw-r--r--src/com/android/tv/util/Utils.java65
-rw-r--r--src/com/android/tv/util/ViewCache.java70
225 files changed, 5202 insertions, 8347 deletions
diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java
index d8b7ae26..7e8e3689 100644
--- a/src/com/android/tv/Features.java
+++ b/src/com/android/tv/Features.java
@@ -27,18 +27,11 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.VisibleForTesting;
import android.support.v4.os.BuildCompat;
-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.util.LocationUtils;
import com.android.tv.util.PermissionUtils;
-import com.android.tv.util.Utils;
-
-import java.util.Locale;
/**
* List of {@link Feature} for the Live TV App.
@@ -46,9 +39,6 @@ import java.util.Locale;
* <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.
*
@@ -71,11 +61,6 @@ public final class Features {
@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
// After release, change class to this to just be {@link BuildCompat#isAtLeastN()}.
@@ -84,25 +69,6 @@ public final class Features {
};
- /**
- * 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.
@@ -130,36 +96,6 @@ public final class Features {
};
/**
- * Use AC3 software decode.
- */
- public static final Feature AC3_SOFTWARE_DECODE =
- new Feature() {
- private final String[] SUPPORTED_COUNTRIES = {
- };
-
- 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_COUNTRIES.length; ++i) {
- if (SUPPORTED_COUNTRIES[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;
- }
- };
-
- /**
* Enable a conflict dialog between currently watched channel and upcoming recording.
*/
public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF;
diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java
index faf76555..e4b0f456 100644
--- a/src/com/android/tv/InputSessionManager.java
+++ b/src/com/android/tv/InputSessionManager.java
@@ -37,6 +37,7 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.OnTuneListener;
@@ -72,8 +73,6 @@ public class InputSessionManager {
Collections.synchronizedSet(new ArraySet<>());
private final Set<OnTvViewChannelChangeListener> mOnTvViewChannelChangeListeners =
new ArraySet<>();
- private final Set<OnRecordingSessionChangeListener> mOnRecordingSessionChangeListeners =
- new ArraySet<>();
public InputSessionManager(Context context) {
mContext = context.getApplicationContext();
@@ -114,9 +113,6 @@ public class InputSessionManager {
RecordingSession session = new RecordingSession(inputId, tag, callback, handler, endTimeMs);
mRecordingSessions.add(session);
if (DEBUG) Log.d(TAG, "Recording session created: " + session);
- for (OnRecordingSessionChangeListener listener : mOnRecordingSessionChangeListeners) {
- listener.onRecordingSessionChange(true, mRecordingSessions.size());
- }
return session;
}
@@ -127,9 +123,6 @@ public class InputSessionManager {
mRecordingSessions.remove(session);
session.release();
if (DEBUG) Log.d(TAG, "Recording session released: " + session);
- for (OnRecordingSessionChangeListener listener : mOnRecordingSessionChangeListeners) {
- listener.onRecordingSessionChange(false, mRecordingSessions.size());
- }
}
/**
@@ -155,17 +148,9 @@ public class InputSessionManager {
}
}
- /** Adds the {@link OnRecordingSessionChangeListener}. */
- public void addOnRecordingSessionChangeListener(OnRecordingSessionChangeListener listener) {
- mOnRecordingSessionChangeListeners.add(listener);
- }
-
- /** Removes the {@link OnRecordingSessionChangeListener}. */
- public void removeRecordingSessionChangeListener(OnRecordingSessionChangeListener listener) {
- mOnRecordingSessionChangeListeners.remove(listener);
- }
-
- /** Returns the current {@link TvView} channel. */
+ /**
+ * Returns the current {@link TvView} channel.
+ */
@MainThread
public Uri getCurrentTvViewChannelUri() {
for (TvViewSession session : mTvViewSessions) {
@@ -561,9 +546,4 @@ public class InputSessionManager {
public interface OnTvViewChannelChangeListener {
void onTvViewChannelChange(@Nullable Uri channelUri);
}
-
- /** Called when recording session is created or destroyed. */
- public interface OnRecordingSessionChangeListener {
- void onRecordingSessionChange(boolean create, int count);
- }
}
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 94006b72..58850b5f 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -29,6 +29,7 @@ import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.MediaMetadata;
@@ -70,6 +71,7 @@ import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.Toast;
+import com.android.tv.analytics.DurationTimer;
import com.android.tv.analytics.SendChannelStatusRunnable;
import com.android.tv.analytics.SendConfigInfoRunnable;
import com.android.tv.analytics.Tracker;
@@ -89,14 +91,14 @@ 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.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.SafeDismissDialogFragment;
+import com.android.tv.dvr.ConflictChecker;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.dvr.recorder.ConflictChecker;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.ui.HalfSizedDialogFragment;
import com.android.tv.experiments.Experiments;
import com.android.tv.menu.Menu;
import com.android.tv.onboarding.OnboardingActivity;
@@ -105,11 +107,9 @@ import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.receiver.AudioCapabilitiesReceiver;
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.tuner.util.PostalCodeUtils;
import com.android.tv.ui.AppLayerTvView;
import com.android.tv.ui.ChannelBannerView;
import com.android.tv.ui.InputBannerView;
@@ -119,7 +119,6 @@ import com.android.tv.ui.SelectInputView.OnInputSelectedCallback;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.BlockScreenType;
import com.android.tv.ui.TunableTvView.OnTuneListener;
-import com.android.tv.ui.TuningBlockView;
import com.android.tv.ui.TvOverlayManager;
import com.android.tv.ui.TvViewUiManager;
import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
@@ -131,20 +130,21 @@ import com.android.tv.ui.sidepanel.SettingsFragment;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.util.AccountHelper;
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.ImageLoader;
import com.android.tv.util.OnboardingUtils;
import com.android.tv.util.PermissionUtils;
+import com.android.tv.util.PipInputManager;
+import com.android.tv.util.PipInputManager.PipInput;
import com.android.tv.util.RecurringRunner;
+import com.android.tv.util.SearchManagerHelper;
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.TvSettings.PipSound;
import com.android.tv.util.TvTrackInfoUtils;
import com.android.tv.util.Utils;
-import com.android.tv.util.ViewCache;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -181,12 +181,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private static final float FRAME_RATE_FOR_FILM = 23.976f;
private static final float FRAME_RATE_EPSILON = 0.1f;
- 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 float MEDIA_SESSION_STOPPED_SPEED = 0.0f;
+ private static final float MEDIA_SESSION_PLAYING_SPEED = 1.0f;
private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
@@ -231,12 +227,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private static final int MSG_CHANNEL_DOWN_PRESSED = 1000;
private static final int MSG_CHANNEL_UP_PRESSED = 1001;
+ private static final int MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE = 1002;
@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})
+ UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK})
private @interface ChannelBannerUpdateReason {}
/**
* Updates channel banner because the channel banner is forced to show.
@@ -258,10 +254,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
* Updates channel banner because the current watched channel is locked or unlocked.
*/
private static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5;
- /**
- * Updates channel banner because of stream info updating.
- */
- private static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6;
private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000;
@@ -269,13 +261,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
// Delay 1 second in order not to interrupt the first tune.
private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1);
- private static final int UNDEFINED_TRACK_INDEX = -1;
-
private AccessibilityManager mAccessibilityManager;
private ChannelDataManager mChannelDataManager;
private ProgramDataManager mProgramDataManager;
private TvInputManagerHelper mTvInputManagerHelper;
private ChannelTuner mChannelTuner;
+ private PipInputManager mPipInputManager;
private final TvOptionsManager mTvOptionsManager = new TvOptionsManager(this);
private TvViewUiManager mTvViewUiManager;
private TimeShiftManager mTimeShiftManager;
@@ -287,6 +278,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private View mContentView;
private TunableTvView mTvView;
+ private TunableTvView mPipView;
private Bundle mTuneParams;
private boolean mChannelBannerHiddenBySideFragment;
// TODO: Move the scene views into TvTransitionManager or TvOverlayManager.
@@ -311,6 +303,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private AudioManager mAudioManager;
private int mAudioFocusStatus;
private boolean mTunePending;
+ private boolean mPipEnabled;
+ private Channel mPipChannel;
+ private boolean mPipSwap;
+ @PipSound private int mPipSound = TvSettings.PIP_SOUND_MAIN; // Default
private boolean mDebugNonFullSizeScreen;
private boolean mActivityResumed;
private boolean mActivityStarted;
@@ -335,6 +331,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private boolean mIsCurrentChannelUnblockedByUser;
private boolean mWasChannelUnblockedBeforeShrunkenByUser;
private Channel mChannelBeforeShrunkenTvView;
+ private Channel mPipChannelBeforeShrunkenTvView;
private boolean mIsCompletingShrunkenTvView;
// TODO: Need to consider the case that TIS explicitly request PIN code while TV view is
@@ -353,6 +350,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private RecurringRunner mSendConfigInfoRecurringRunner;
private RecurringRunner mChannelStatusRecurringRunner;
+ // A caller which started this activity. (e.g. TvSearch)
+ private String mSource;
+
private final Handler mHandler = new MainActivityHandler(this);
private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
@@ -372,13 +372,15 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
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.
+ // In this case, we need to resume TvView and PipView explicitly.
resumeTvIfNeeded();
+ resumePipIfNeeded();
}
} else if (intent.getAction().equals(
TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)) {
if (DEBUG) Log.d(TAG, "Received parental control settings change");
- checkChannelLockNeeded(mTvView, null);
+ checkChannelLockNeeded(mTvView);
+ checkChannelLockNeeded(mPipView);
applyParentalControlSettings();
}
}
@@ -405,11 +407,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
new ChannelTuner.Listener() {
@Override
public void onLoadFinished() {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "MainActivity.mChannelTunerListener.onLoadFinished");
SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable();
if (mActivityResumed) {
resumeTvIfNeeded();
+ resumePipIfNeeded();
}
mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList());
}
@@ -429,12 +430,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
};
- 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() {
@@ -454,14 +456,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
.isParentalControlsEnabled();
mTvView.onParentalControlChanged(parentalControlEnabled);
+ mPipView.onParentalControlChanged(parentalControlEnabled);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
- // Restarts the global duration timer to avoid the case that TvApplication starts much
- // earlier than MainActivity.
- Debug.getTimer(Debug.TAG_START_UP_TIMER).start();
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate restarts timer");
if (DEBUG) Log.d(TAG,"onCreate()");
TvApplication.setCurrentRunningProcess(this, true);
super.onCreate(savedInstanceState);
@@ -479,14 +478,32 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return;
}
+ // Check this permission for the EPG fetch.
+ // TODO: check {@link shouldShowRequestPermissionRationale}.
+ // While testing, no way to allow the permission when the dialog shows up. So we'd better
+ // not show the dialog.
+ if (!TvCommonUtils.isRunningInTest() && Utils.hasInternalTvInputs(this, true)
+ && checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
+ PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
+ }
+
+ DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ Point size = new Point();
+ display.getSize(size);
+ int screenWidth = size.x;
+ int screenHeight = size.y;
+ mDefaultRefreshRate = display.getRefreshRate();
+
setContentView(R.layout.activity_tv);
- TvApplication tvApplication = (TvApplication) getApplication();
- mProgramDataManager = tvApplication.getProgramDataManager();
- mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
+ mContentView = findViewById(android.R.id.content);
mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
- mTvView.initialize((AppLayerTvView) findViewById(R.id.main_tv_view),
- (TuningBlockView) findViewById(R.id.tuning_block), mProgramDataManager,
- mTvInputManagerHelper);
+ int shrunkenTvViewHeight = getResources().getDimensionPixelSize(
+ R.dimen.shrunken_tvview_height);
+ mTvView.initialize((AppLayerTvView) findViewById(R.id.main_tv_view), false, screenHeight,
+ shrunkenTvViewHeight);
mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
@Override
public boolean onUnhandledInputEvent(InputEvent event) {
@@ -509,6 +526,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return false;
}
});
+
long channelId = Utils.getLastWatchedChannelId(this);
String inputId = Utils.getLastWatchedTunerInputId(this);
if (!isPassthroughInput && inputId != null
@@ -516,34 +534,27 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId));
}
- // Check this permission for the EPG fetch.
- // TODO: check {@link shouldShowRequestPermissionRationale}.
- // While testing, no way to allow the permission when the dialog shows up. So we'd better
- // not show the dialog.
- if (!TvCommonUtils.isRunningInTest() && Utils.hasInternalTvInputs(this, true)
- && TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(this))
- && checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
- PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
- }
-
+ TvApplication tvApplication = (TvApplication) getApplication();
tvApplication.getMainActivityWrapper().onMainActivityCreated(this);
if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
}
mTracker = tvApplication.getTracker();
+ mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
if (Features.TUNER.isEnabled(this)) {
mTvInputManagerHelper.addCallback(mTvInputCallback);
}
mTunerInputId = TunerTvInputService.getInputId(this);
mChannelDataManager = tvApplication.getChannelDataManager();
+ mProgramDataManager = tvApplication.getProgramDataManager();
mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
mOnCurrentProgramUpdatedListener);
mProgramDataManager.setPrefetchEnabled(true);
mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper);
mChannelTuner.addListener(mChannelTunerListener);
mChannelTuner.start();
+ mPipInputManager = new PipInputManager(this, mTvInputManagerHelper, mChannelTuner);
+ mPipInputManager.start();
mMemoryManageables.add(mProgramDataManager);
mMemoryManageables.add(ImageCache.getInstance());
mMemoryManageables.add(TvContentRatingCache.getInstance());
@@ -573,9 +584,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
});
- DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
- Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- mDefaultRefreshRate = display.getRefreshRate();
+ mPipView = (TunableTvView) findViewById(R.id.pip_tunable_tv_view);
+ mPipView.initialize((AppLayerTvView) findViewById(R.id.pip_tv_view), true, screenHeight,
+ shrunkenTvViewHeight);
if (!PermissionUtils.hasAccessWatchedHistory(this)) {
WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager(
@@ -583,10 +594,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
watchedHistoryManager.start();
mTvView.setWatchedHistoryManager(watchedHistoryManager);
}
- mTvViewUiManager = new TvViewUiManager(this, mTvView,
+ mTvViewUiManager = new TvViewUiManager(this, mTvView, mPipView,
(FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
- mContentView = findViewById(android.R.id.content);
+ mPipView.setFixedSurfaceSize(screenWidth / 2, screenHeight / 2);
+ mPipView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW);
+
ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
mChannelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
R.layout.channel_banner, sceneContainer, false);
@@ -628,7 +641,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
});
mSearchFragment = new ProgramGuideSearchFragment();
- mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager,
+ mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView,
mKeypadChannelSwitchView, mChannelBannerView, inputBannerView,
selectInputView, sceneContainer, mSearchFragment);
@@ -679,7 +692,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mDvrConflictChecker = new ConflictChecker(this);
}
initForTest();
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onCreate end");
}
@Override
@@ -714,7 +726,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
case PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
&& Experiments.CLOUD_EPG.get()) {
- EpgFetcher.getInstance(this).startImmediately(false);
+ EpgFetcher.getInstance(this).startImmediately();
+ } else {
+ EpgFetcher.getInstance(this).stop();
}
break;
}
@@ -784,14 +798,14 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
Intent notificationIntent = new Intent(this, NotificationService.class);
notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
startService(notificationIntent);
- TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this);
}
@Override
protected void onResume() {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start");
if (DEBUG) Log.d(TAG, "onResume()");
super.onResume();
+ // Refresh the remote config, it is throttled automatically.
+ TvApplication.getSingletons(this).getRemoteConfig().fetch(null);
if (!PermissionUtils.hasAccessAllEpg(this)
&& checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
!= PackageManager.PERMISSION_GRANTED) {
@@ -816,16 +830,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
// visible behind.
requestVisibleBehind(true);
}
- Set<String> failedScheduledRecordingInfoSet =
- Utils.getFailedScheduledRecordingInfoSet(getApplicationContext());
- if (Utils.hasRecordingFailedReason(
- getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
- && !failedScheduledRecordingInfoSet.isEmpty()) {
+ if (Utils.hasRecordingFailedReason(getApplicationContext(),
+ TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)) {
runAfterAttachedToWindow(new Runnable() {
@Override
public void run() {
- DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this,
- failedScheduledRecordingInfoSet);
+ DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this);
}
});
}
@@ -833,6 +843,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (mChannelTuner.areAllChannelsLoaded()) {
SetupUtils.getInstance(this).markNewChannelsBrowsable();
resumeTvIfNeeded();
+ resumePipIfNeeded();
}
mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
@@ -870,7 +881,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (mDvrConflictChecker != null) {
mDvrConflictChecker.start();
}
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume end");
}
@Override
@@ -883,6 +893,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mActivityResumed = false;
mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT);
mTvView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_NO_UI);
+ if (mPipEnabled) {
+ mTvViewUiManager.hidePipForPause();
+ }
mBackKeyPressed = false;
mShowLockedChannelsTemporarily = false;
mShouldTuneToTunerChannel = false;
@@ -890,7 +903,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
mAudioManager.abandonAudioFocus(this);
if (mMediaSession.isActive()) {
- setMediaSessionActiveAndPlaybackState(false);
+ mMediaSession.setActive(false);
}
mTracker.sendScreenView("");
} else {
@@ -949,6 +962,21 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mTvView.setBlockScreenType(getDesiredBlockScreenType());
}
+ private void resumePipIfNeeded() {
+ if (mPipEnabled && !(mPipView.isPlaying() && mPipView.isShown())) {
+ if (mPipInputManager.areInSamePipInput(
+ mChannelTuner.getCurrentChannel(), mPipChannel)) {
+ enablePipView(false, false);
+ } else {
+ if (!mPipView.isPlaying()) {
+ startPip(false);
+ } else {
+ mTvViewUiManager.showPipForResume();
+ }
+ }
+ }
+ }
+
private void startTv(Uri channelUri) {
if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri);
if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri))
@@ -999,9 +1027,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
- mTvView.start();
+ mTvView.start(mTvInputManagerHelper);
setVolumeByAudioFocusStatus();
- tune(true);
+ tune();
}
@Override
@@ -1044,6 +1072,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private void stopAll(boolean keepVisibleBehind) {
mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
stopTv("stopAll()", keepVisibleBehind);
+ stopPip();
}
public TvInputManagerHelper getTvInputManagerHelper() {
@@ -1116,6 +1145,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return mProgramDataManager;
}
+ public PipInputManager getPipInputManager() {
+ return mPipInputManager;
+ }
+
public TvOptionsManager getTvOptionsManager() {
return mTvOptionsManager;
}
@@ -1239,11 +1272,19 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel();
mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser;
mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel;
+
+ if (willMainViewBeTunerInput && mChannelTuner.isCurrentChannelPassthrough()
+ && mPipEnabled) {
+ mPipChannelBeforeShrunkenTvView = mPipChannel;
+ enablePipView(false, false);
+ } else {
+ mPipChannelBeforeShrunkenTvView = null;
+ }
mTvViewUiManager.startShrunkenTvView();
if (showLockedChannelsTemporarily) {
mShowLockedChannelsTemporarily = true;
- checkChannelLockNeeded(mTvView, null);
+ checkChannelLockNeeded(mTvView);
}
mTvView.setBlockScreenType(getDesiredBlockScreenType());
@@ -1279,15 +1320,23 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mIsCompletingShrunkenTvView = false;
mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ if (mPipChannelBeforeShrunkenTvView != null) {
+ enablePipView(true, false);
+ mPipChannelBeforeShrunkenTvView = null;
+ }
}
};
mTvViewUiManager.fadeOutTvView(tuneAction);
// Will automatically fade-in when video becomes available.
} else {
- checkChannelLockNeeded(mTvView, null);
+ checkChannelLockNeeded(mTvView);
mIsCompletingShrunkenTvView = false;
mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ if (mPipChannelBeforeShrunkenTvView != null) {
+ enablePipView(true, false);
+ mPipChannelBeforeShrunkenTvView = null;
+ }
}
}
@@ -1314,7 +1363,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mChannelTuner.moveToAdjacentBrowsableChannel(true);
}
if (mTunePending) {
- tune(true);
+ tune();
}
} else {
mInputIdUnderSetup = null;
@@ -1439,6 +1488,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
});
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
+ try {
+ mSource = uri.getQueryParameter(Utils.PARAM_SOURCE);
+ } catch (UnsupportedOperationException e) {
+ // ignore this exception.
+ }
// When the URI points to the programs (directory, not an individual item), go to the
// program guide. The intention here is to respond to
// "content://android.media.tv/program", not "content://android.media.tv/program/XXX".
@@ -1514,20 +1568,38 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
private void setVolumeByAudioFocusStatus() {
- if (mTvView.isPlaying()) {
+ if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
+ setVolumeByAudioFocusStatus(mTvView);
+ } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
+ setVolumeByAudioFocusStatus(mPipView);
+ }
+ }
+
+ private void setVolumeByAudioFocusStatus(TunableTvView tvView) {
+ SoftPreconditions.checkState(tvView == mTvView || tvView == mPipView);
+ if (tvView.isPlaying()) {
switch (mAudioFocusStatus) {
case AudioManager.AUDIOFOCUS_GAIN:
- mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
+ tvView.setStreamVolume(AUDIO_MAX_VOLUME);
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ tvView.setStreamVolume(AUDIO_MIN_VOLUME);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
+ tvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
break;
}
}
+ if (tvView == mTvView) {
+ if (mPipView != null && mPipView.isPlaying()) {
+ mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
+ }
+ } else { // tvView == mPipView
+ if (mTvView != null && mTvView.isPlaying()) {
+ mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ }
+ }
}
private void stopTv() {
@@ -1547,7 +1619,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
mAudioManager.abandonAudioFocus(this);
if (mMediaSession.isActive()) {
- setMediaSessionActiveAndPlaybackState(false);
+ mMediaSession.setActive(false);
}
}
TvApplication.getSingletons(this).getMainActivityWrapper()
@@ -1560,11 +1632,95 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return mTvView.isPlaying() && mTvView.getCurrentChannel() != null;
}
+ private void startPip(final boolean fromUserInteraction) {
+ if (mPipChannel == null) {
+ Log.w(TAG, "PIP channel id is an invalid id.");
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "startPip() " + mPipChannel);
+ mPipView.start(mTvInputManagerHelper);
+ boolean success = mPipView.tuneTo(mPipChannel, null, new OnTuneListener() {
+ @Override
+ public void onUnexpectedStop(Channel channel) {
+ Log.w(TAG, "The PIP is Unexpectedly stopped");
+ enablePipView(false, false);
+ }
+
+ @Override
+ public void onTuneFailed(Channel channel) {
+ Log.w(TAG, "Fail to start the PIP during channel tuning");
+ if (fromUserInteraction) {
+ Toast.makeText(MainActivity.this, R.string.msg_no_pip_support,
+ Toast.LENGTH_SHORT).show();
+ enablePipView(false, false);
+ }
+ }
+
+ @Override
+ public void onStreamInfoChanged(StreamInfo info) {
+ mTvViewUiManager.updatePipView();
+ mHandler.removeCallbacks(mRestoreMainViewRunnable);
+ restoreMainTvView();
+ }
+
+ @Override
+ public void onChannelRetuned(Uri channel) {
+ if (channel == null) {
+ return;
+ }
+ Channel currentChannel =
+ mChannelDataManager.getChannel(ContentUris.parseId(channel));
+ if (currentChannel == null) {
+ Log.e(TAG, "onChannelRetuned is called from PIP input but can't find a channel"
+ + " with the URI " + channel);
+ return;
+ }
+ if (isChannelChangeKeyDownReceived()) {
+ // Ignore this message if the user is changing the channel.
+ return;
+ }
+ mPipChannel = currentChannel;
+ mPipView.setCurrentChannel(mPipChannel);
+ }
+
+ @Override
+ public void onContentBlocked() {
+ updateMediaSession();
+ }
+
+ @Override
+ public void onContentAllowed() {
+ updateMediaSession();
+ }
+ });
+ if (!success) {
+ Log.w(TAG, "Fail to start the PIP");
+ return;
+ }
+ if (fromUserInteraction) {
+ checkChannelLockNeeded(mPipView);
+ }
+ // Explicitly make the PIP view main to make the selected input an HDMI-CEC active source.
+ mPipView.setMain();
+ scheduleRestoreMainTvView();
+ mTvViewUiManager.onPipStart();
+ setVolumeByAudioFocusStatus();
+ }
+
private void scheduleRestoreMainTvView() {
mHandler.removeCallbacks(mRestoreMainViewRunnable);
mHandler.postDelayed(mRestoreMainViewRunnable, TVVIEW_SET_MAIN_TIMEOUT_MS);
}
+ private void stopPip() {
+ if (DEBUG) Log.d(TAG, "stopPip");
+ if (mPipView.isPlaying()) {
+ mPipView.stop();
+ mPipSwap = false;
+ mTvViewUiManager.onPipStop();
+ }
+ }
+
/**
* Says {@code text} when accessibility is turned on.
*/
@@ -1579,7 +1735,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
- private void tune(boolean updateChannelBanner) {
+ private void tune() {
if (DEBUG) Log.d(TAG, "tune()");
mTuneDurationTimer.start();
@@ -1669,6 +1825,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (!isUnderShrunkenTvView()) {
mLastAllowedRatingForCurrentChannel = null;
}
+ mHandler.removeMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE);
// For every tune, we need to inform the tuned channel or input to a user,
// if Talkback is turned on.
sendAccessibilityText(!mChannelTuner.isCurrentChannelPassthrough() ?
@@ -1695,13 +1852,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
TvApplication.getSingletons(this).getMainActivityWrapper()
.notifyCurrentChannelChange(this, channel);
}
- // We have to provide channel here instead of using TvView's channel, because TvView's
- // channel might be null when there's tuner conflict. In that case, TvView will resets
- // its current channel onConnectionFailed().
- checkChannelLockNeeded(mTvView, channel);
- if (updateChannelBanner) {
- updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
- }
+ checkChannelLockNeeded(mTvView);
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
if (mActivityResumed) {
// requestVisibleBehind should be called after onResume() is called. But, when
// launcher is over the TV app and the screen is turned off and on, tune() can
@@ -1745,18 +1897,19 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private void updateMediaSession() {
if (getCurrentChannel() == null) {
- setMediaSessionActiveAndPlaybackState(false);
+ mMediaSession.setActive(false);
return;
}
// If the channel is blocked, display a lock and a short text on the Now Playing Card
if (mTvView.isScreenBlocked() || mTvView.getBlockedContentRating() != null) {
+ setMediaSessionPlaybackState(false);
Bitmap art = BitmapFactory.decodeResource(
getResources(), R.drawable.ic_message_lock_preview);
updateMediaMetadata(
getResources().getString(R.string.channel_banner_locked_channel_title), art);
- setMediaSessionActiveAndPlaybackState(true);
+ mMediaSession.setActive(true);
return;
}
@@ -1771,11 +1924,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
cardTitleText = getCurrentChannelName();
}
updateMediaMetadata(cardTitleText, null);
+ setMediaSessionPlaybackState(true);
+
if (posterArtUri == null) {
posterArtUri = TvContract.buildChannelLogoUri(getCurrentChannelId()).toString();
}
updatePosterArt(getCurrentChannel(), currentProgram, cardTitleText, null, posterArtUri);
- setMediaSessionActiveAndPlaybackState(true);
+ mMediaSession.setActive(true);
}
private void updatePosterArt(Channel currentChannel, Program currentProgram,
@@ -1862,17 +2017,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
- private void setMediaSessionActiveAndPlaybackState(boolean isPlaying) {
- if (isPlaying) {
- mMediaSession.setActive(true);
- // setMediaSessionPlaybackState has to be called after calling mMediaSession.setActive
- // b/31933276
- mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_PLAYING);
- } else {
- mMediaSession.setPlaybackState(MEDIA_SESSION_STATE_STOPPED);
- mMediaSession.setActive(false);
- }
-
+ private void setMediaSessionPlaybackState(boolean isPlaying) {
+ PlaybackState.Builder builder = new PlaybackState.Builder();
+ builder.setState(isPlaying ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_STOPPED,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ isPlaying ? MEDIA_SESSION_PLAYING_SPEED : MEDIA_SESSION_STOPPED_SPEED);
+ mMediaSession.setPlaybackState(builder.build());
}
private void addToRecentChannels(long channelId) {
@@ -1892,27 +2042,33 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return mRecentChannels;
}
- private void checkChannelLockNeeded(TunableTvView tvView, Channel currentChannel) {
- if (currentChannel == null) {
- currentChannel = tvView.getCurrentChannel();
- }
- if (tvView.isPlaying() && currentChannel != null) {
+ private void checkChannelLockNeeded(TunableTvView tvView) {
+ Channel channel = tvView.getCurrentChannel();
+ if (tvView.isPlaying() && channel != null) {
if (getParentalControlSettings().isParentalControlsEnabled()
- && currentChannel.isLocked()
+ && channel.isLocked()
&& !mShowLockedChannelsTemporarily
&& !(isUnderShrunkenTvView()
- && currentChannel.equals(mChannelBeforeShrunkenTvView)
+ && channel.equals(mChannelBeforeShrunkenTvView)
&& mWasChannelUnblockedBeforeShrunkenByUser)) {
- if (DEBUG) Log.d(TAG, "Channel " + currentChannel.getId() + " is locked");
- blockOrUnblockScreen(tvView, true);
+ if (DEBUG) Log.d(TAG, "Channel " + channel.getId() + " is locked");
+ blockScreen(tvView);
} else {
- blockOrUnblockScreen(tvView, false);
+ unblockScreen(tvView);
}
}
}
- private void blockOrUnblockScreen(TunableTvView tvView, boolean blockOrUnblock) {
- tvView.blockOrUnblockScreen(blockOrUnblock);
+ private void blockScreen(TunableTvView tvView) {
+ tvView.blockScreen();
+ if (tvView == mTvView) {
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
+ updateMediaSession();
+ }
+ }
+
+ private void unblockScreen(TunableTvView tvView) {
+ tvView.unblockScreen();
if (tvView == mTvView) {
updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
updateMediaSession();
@@ -1933,59 +2089,36 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) {
if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")");
- if (isChannelChangeKeyDownReceived() && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE
- && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
- // Tuning is still ongoing, no need to update banner for other reasons
- return;
- }
if (!mChannelTuner.isCurrentChannelPassthrough()) {
int lockType = ChannelBannerView.LOCK_NONE;
- if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
- if (getParentalControlSettings().isParentalControlsEnabled()
- && getCurrentChannel().isLocked()) {
- lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
- } else {
- // Do not show detailed program information while fast-tuning.
- lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
- }
- } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE) {
- if (getParentalControlSettings().isParentalControlsEnabled()) {
- if (getCurrentChannel().isLocked()) {
- lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
- } else {
- // If parental control is turned on,
- // assumes that program is locked by default and waits for onContentAllowed.
- lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
- }
- }
- } else if (mTvView.isScreenBlocked()) {
+ if (mTvView.isScreenBlocked()) {
lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
} else if (mTvView.getBlockedContentRating() != null
|| (getParentalControlSettings().isParentalControlsEnabled()
- && !mTvView.isVideoOrAudioAvailable())) {
+ && !mTvView.isVideoAvailable())) {
// If the parental control is enabled, do not show the program detail until the
// video becomes available.
lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
}
+ if (lockType == ChannelBannerView.LOCK_NONE) {
+ if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
+ // Do not show detailed program information while fast-tuning.
+ lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
+ } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
+ && getParentalControlSettings().isParentalControlsEnabled()) {
+ // If parental control is turned on,
+ // assumes that program is locked by default and waits for onContentAllowed.
+ lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
+ }
+ }
// If lock type is not changed, we don't need to update channel banner by parental
// control.
- int previousLockType = mChannelBannerView.setLockType(lockType);
- if (previousLockType == lockType
+ if (!mChannelBannerView.setLockType(lockType)
&& reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) {
return;
- } else if (reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO) {
- mChannelBannerView.updateStreamInfo(mTvView);
- // 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) {
- mChannelBannerView.updateViews(false);
- }
- } else {
- mChannelBannerView.updateViews(reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
- || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
}
+
+ mChannelBannerView.updateViews(mTvView);
}
boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW
|| reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
@@ -2064,7 +2197,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (bestTrack != null) {
String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
if (!bestTrack.getId().equals(selectedTrack)) {
- selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack, UNDEFINED_TRACK_INDEX);
+ selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack);
} else {
mTvOptionsManager.onMultiAudioChanged(
Utils.getMultiAudioString(this, bestTrack, false));
@@ -2077,7 +2210,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private void applyClosedCaption() {
List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
if (tracks == null) {
- mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX);
+ mTvOptionsManager.onClosedCaptionsChanged(null);
return;
}
@@ -2086,19 +2219,17 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE);
TvTrackInfo alternativeTrack = null;
- int alternativeTrackIndex = UNDEFINED_TRACK_INDEX;
if (enabled) {
String language = mCaptionSettings.getLanguage();
String trackId = mCaptionSettings.getTrackId();
- for (int i = 0; i < tracks.size(); i++) {
- TvTrackInfo track = tracks.get(i);
+ for (TvTrackInfo track : tracks) {
if (Utils.isEqualLanguage(track.getLanguage(), language)) {
if (track.getId().equals(trackId)) {
if (!track.getId().equals(selectedTrackId)) {
- selectTrack(TvTrackInfo.TYPE_SUBTITLE, track, i);
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, track);
} else {
// Already selected. Update the option string only.
- mTvOptionsManager.onClosedCaptionsChanged(track, i);
+ mTvOptionsManager.onClosedCaptionsChanged(track);
}
if (DEBUG) {
Log.d(TAG, "Subtitle Track Selected {id=" + track.getId()
@@ -2107,16 +2238,14 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return;
} else if (alternativeTrack == null) {
alternativeTrack = track;
- alternativeTrackIndex = i;
}
}
}
if (alternativeTrack != null) {
if (!alternativeTrack.getId().equals(selectedTrackId)) {
- selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack, alternativeTrackIndex);
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack);
} else {
- mTvOptionsManager
- .onClosedCaptionsChanged(alternativeTrack, alternativeTrackIndex);
+ mTvOptionsManager.onClosedCaptionsChanged(alternativeTrack);
}
if (DEBUG) {
Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId()
@@ -2126,11 +2255,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
if (selectedTrackId != null) {
- selectTrack(TvTrackInfo.TYPE_SUBTITLE, null, UNDEFINED_TRACK_INDEX);
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
if (DEBUG) Log.d(TAG, "Subtitle Track Unselected");
return;
}
- mTvOptionsManager.onClosedCaptionsChanged(null, UNDEFINED_TRACK_INDEX);
+ mTvOptionsManager.onClosedCaptionsChanged(null);
}
/**
@@ -2145,6 +2274,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
+ public void showSearchActivity() {
+ // HACK: Once we moved the window layer to TYPE_APPLICATION_SUB_PANEL,
+ // the voice button doesn't work. So we directly call the voice action.
+ SearchManagerHelper.getInstance(this).launchAssistAction();
+ }
+
public void showProgramGuideSearchFragment() {
getFragmentManager().beginTransaction().replace(R.id.fragment_container, mSearchFragment)
.addToBackStack(null).commit();
@@ -2160,11 +2295,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
@Override
protected void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy()");
- SideFragment.releaseRecycledViewPool();
- ViewCache.getInstance().clear();
+ SideFragment.releasePreloadedRecycledViews();
if (mTvView != null) {
mTvView.release();
}
+ if (mPipView != null) {
+ mPipView.release();
+ }
if (mChannelTuner != null) {
mChannelTuner.removeListener(mChannelTunerListener);
mChannelTuner.stop();
@@ -2177,6 +2314,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mProgramDataManager.setPrefetchEnabled(false);
}
}
+ if (mPipInputManager != null) {
+ mPipInputManager.stop();
+ }
if (mOverlayManager != null) {
mOverlayManager.release();
}
@@ -2200,11 +2340,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mChannelStatusRecurringRunner.stop();
mChannelStatusRecurringRunner = null;
}
- if (mTvInputManagerHelper != null) {
- mTvInputManagerHelper.clearTvInputLabels();
- if (Features.TUNER.isEnabled(this)) {
- mTvInputManagerHelper.removeCallback(mTvInputCallback);
- }
+ if (mTvInputManagerHelper != null && Features.TUNER.isEnabled(this)) {
+ mTvInputManagerHelper.removeCallback(mTvInputCallback);
}
super.onDestroy();
}
@@ -2273,6 +2410,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
* G debug: refresh cloud epg
* I KEYCODE_TV_INPUT
* O debug: show display mode option
+ * P debug: togglePipView
* S KEYCODE_CAPTIONS: select subtitle
* W debug: toggle screen size
* V KEYCODE_MEDIA_RECORD debug: record the current channel for 30 sec
@@ -2284,9 +2422,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
finishChannelChangeIfNeeded();
if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
- // Prevent MainActivity from being closed by onVisibleBehindCanceled()
- mOtherActivityLaunched = true;
- return false;
+ showSearchActivity();
+ return true;
}
switch (mOverlayManager.onKeyUp(keyCode, event)) {
case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
@@ -2335,7 +2472,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (!mTvView.isVideoOrAudioAvailable()
+ if (!mTvView.isVideoAvailable()
&& mTvView.getVideoUnavailableReason()
== TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE) {
DvrUiHelper.startSchedulesActivityForTuneConflict(this,
@@ -2354,7 +2491,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
@Override
public void done(boolean success) {
if (success) {
- blockOrUnblockScreen(mTvView, false);
+ unblockScreen(mTvView);
mIsCurrentChannelUnblockedByUser = true;
}
}
@@ -2441,17 +2578,22 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
Toast.makeText(this, R.string.dvr_msg_cannot_record_program,
Toast.LENGTH_SHORT).show();
} else {
+ if (!DvrUiHelper.checkStorageStatusAndShowErrorMessage(this,
+ currentChannel.getInputId())) {
+ return true;
+ }
Program program = mProgramDataManager
.getCurrentProgram(currentChannel.getId());
- DvrUiHelper.checkStorageStatusAndShowErrorMessage(this,
- currentChannel.getInputId(), new Runnable() {
- @Override
- public void run() {
- DvrUiHelper.requestRecordingCurrentProgram(
- MainActivity.this,
- currentChannel, program, false);
- }
- });
+ if (program == null) {
+ DvrUiHelper
+ .showChannelRecordDurationOptions(this, currentChannel);
+ } else if (DvrUiHelper.handleCreateSchedule(this, program)) {
+ String msg = getString(
+ R.string.dvr_msg_current_program_scheduled,
+ program.getTitle(), Utils.toTimeString(
+ program.getEndTimeUtcMillis(), false));
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+ }
}
} else {
DvrUiHelper.showStopRecordingDialog(this, currentChannel.getId(),
@@ -2501,6 +2643,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
return true;
}
+ case KeyEvent.KEYCODE_P: {
+ togglePipView();
+ return true;
+ }
case KeyEvent.KEYCODE_CTRL_LEFT:
case KeyEvent.KEYCODE_CTRL_RIGHT: {
mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
@@ -2535,6 +2681,22 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
@Override
+ public void onBackPressed() {
+ // The activity should be returned to the caller of this activity
+ // when the mSource is not null.
+ if (!mOverlayManager.getSideFragmentManager().isActive() && isPlaying()
+ && mSource == null) {
+ // If back key would exit TV app,
+ // show McLauncher instead so we can get benefit of McLauncher's shyMode.
+ Intent startMain = new Intent(Intent.ACTION_MAIN);
+ startMain.addCategory(Intent.CATEGORY_HOME);
+ startActivity(startMain);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
public void onUserInteraction() {
super.onUserInteraction();
if (mOverlayManager != null) {
@@ -2563,6 +2725,65 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
+ public void togglePipView() {
+ enablePipView(!mPipEnabled, true);
+ mOverlayManager.getMenu().update();
+ }
+
+ public boolean isPipEnabled() {
+ return mPipEnabled;
+ }
+
+ public void tuneToChannelForPip(Channel channel) {
+ if (!mPipEnabled) {
+ throw new IllegalStateException("tuneToChannelForPip is called when PIP is off");
+ }
+ if (mPipChannel.equals(channel)) {
+ return;
+ }
+ mPipChannel = channel;
+ startPip(true);
+ }
+
+ private void enablePipView(boolean enable, boolean fromUserInteraction) {
+ if (enable == mPipEnabled) {
+ return;
+ }
+ if (enable) {
+ List<PipInput> pipAvailableInputs = mPipInputManager.getPipInputList(true);
+ if (pipAvailableInputs.isEmpty()) {
+ Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ // TODO: choose the last pip input.
+ Channel pipChannel = pipAvailableInputs.get(0).getChannel();
+ if (pipChannel != null) {
+ mPipEnabled = true;
+ mPipChannel = pipChannel;
+ startPip(fromUserInteraction);
+ mTvViewUiManager.restorePipSize();
+ mTvViewUiManager.restorePipLayout();
+ mTvOptionsManager.onPipChanged(mPipEnabled);
+ } else {
+ Toast.makeText(this, R.string.msg_no_available_input_by_pip, Toast.LENGTH_SHORT)
+ .show();
+ }
+ } else {
+ mPipEnabled = false;
+ mPipChannel = null;
+ // Recover the stream volume of the main TV view, if needed.
+ if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) {
+ setVolumeByAudioFocusStatus(mTvView);
+ mPipSound = TvSettings.PIP_SOUND_MAIN;
+ mTvOptionsManager.onPipSoundChanged(mPipSound);
+ }
+ stopPip();
+ mTvViewUiManager.restoreDisplayMode(false);
+ mTvOptionsManager.onPipChanged(mPipEnabled);
+ }
+ }
+
private boolean isChannelChangeKeyDownReceived() {
return mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED)
|| mHandler.hasMessages(MSG_CHANNEL_DOWN_PRESSED);
@@ -2590,6 +2811,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (SystemProperties.LOG_KEYEVENT.getValue()) {
Log.d(TAG, "dispatchKeyEventToSession(" + event + ")");
}
+ if (mPipEnabled && mChannelTuner.isCurrentChannelPassthrough()) {
+ // If PIP is enabled, key events will be used by UI.
+ return false;
+ }
boolean handled = false;
if (mTvView != null) {
handled = mTvView.dispatchKeyEvent(event);
@@ -2607,15 +2832,21 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
private boolean isKeyEventBlocked() {
- // If the current channel is a passthrough channel, we don't handle the key events in TV
- // activity. Instead, the key event will be handled by the passthrough TV input.
- return mChannelTuner.isCurrentChannelPassthrough();
+ // If the current channel is passthrough channel without a PIP view,
+ // we always don't handle the key events in TV activity. Instead, the key event will
+ // be handled by the passthrough TV input.
+ return mChannelTuner.isCurrentChannelPassthrough() && !mPipEnabled;
}
private void tuneToLastWatchedChannelForTunerInput() {
if (!mChannelTuner.isCurrentChannelPassthrough()) {
return;
}
+ if (mPipEnabled) {
+ if (!mPipChannel.isPassthrough()) {
+ enablePipView(false, true);
+ }
+ }
stopTv();
startTv(null);
}
@@ -2626,16 +2857,16 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mTvView.reset();
}
} else {
+ if (mPipEnabled && mPipInputManager.areInSamePipInput(channel, mPipChannel)) {
+ enablePipView(false, true);
+ }
if (!mTvView.isPlaying()) {
startTv(channel.getUri());
} else if (channel.equals(mTvView.getCurrentChannel())) {
updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
- } else if (channel == mChannelTuner.getCurrentChannel()) {
- // Channel banner is already updated in moveToAdjacentChannel
- tune(false);
} else if (mChannelTuner.moveToChannel(channel)) {
// Channel banner would be updated inside of tune.
- tune(true);
+ tune();
} else {
showSettingsFragment();
}
@@ -2657,11 +2888,90 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
+ public Channel getPipChannel() {
+ return mPipChannel;
+ }
+
+ /**
+ * Swap the main and the sub screens while in the PIP mode.
+ */
+ public void swapPip() {
+ if (!mPipEnabled || mTvView == null || mPipView == null) {
+ Log.e(TAG, "swapPip() - not in PIP");
+ mPipSwap = false;
+ return;
+ }
+
+ Channel channel = mTvView.getCurrentChannel();
+ boolean tvViewBlocked = mTvView.isScreenBlocked();
+ boolean pipViewBlocked = mPipView.isScreenBlocked();
+ if (channel == null || !mTvView.isPlaying()) {
+ // If the TV view is not currently playing or its current channel is null, swapping here
+ // basically means disabling the PIP mode and getting back to the full screen since
+ // there's no point of keeping a blank PIP screen at the bottom which is not tune-able.
+ enablePipView(false, true);
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_DEFAULT);
+ mPipSwap = false;
+ return;
+ }
+
+ // Reset the TV view and tune the PIP view to the previous channel of the TV view.
+ mTvView.reset();
+ mPipView.reset();
+ Channel oldPipChannel = mPipChannel;
+ tuneToChannelForPip(channel);
+ if (tvViewBlocked) {
+ mPipView.blockScreen();
+ } else {
+ mPipView.unblockScreen();
+ }
+
+ if (oldPipChannel != null) {
+ // Tune the TV view to the previous PIP channel.
+ tuneToChannel(oldPipChannel);
+ }
+ if (pipViewBlocked) {
+ mTvView.blockScreen();
+ } else {
+ mTvView.unblockScreen();
+ }
+ if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
+ setVolumeByAudioFocusStatus(mTvView);
+ } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
+ setVolumeByAudioFocusStatus(mPipView);
+ }
+ mPipSwap = !mPipSwap;
+ mTvOptionsManager.onPipSwapChanged(mPipSwap);
+ }
+
+ /**
+ * Toggle where the sound is coming from when the user is watching the PIP.
+ */
+ public void togglePipSoundMode() {
+ if (!mPipEnabled || mTvView == null || mPipView == null) {
+ Log.e(TAG, "togglePipSoundMode() - not in PIP");
+ return;
+ }
+ if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
+ setVolumeByAudioFocusStatus(mPipView);
+ mPipSound = TvSettings.PIP_SOUND_PIP_WINDOW;
+ } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
+ setVolumeByAudioFocusStatus(mTvView);
+ mPipSound = TvSettings.PIP_SOUND_MAIN;
+ }
+ restoreMainTvView();
+ mTvOptionsManager.onPipSoundChanged(mPipSound);
+ }
+
/**
* Set the main TV view which holds HDMI-CEC active source based on the sound mode
*/
private void restoreMainTvView() {
- mTvView.setMain();
+ if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
+ mTvView.setMain();
+ } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
+ mPipView.setMain();
+ }
}
@Override
@@ -2671,8 +2981,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
mAudioManager.abandonAudioFocus(this);
if (mMediaSession.isActive()) {
- setMediaSessionActiveAndPlaybackState(false);
+ mMediaSession.setActive(false);
}
+ stopPip();
mVisibleBehind = false;
if (!mOtherActivityLaunched && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
// Workaround: in M, onStop is not called, even though it should be called after
@@ -2701,13 +3012,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return mTvView.getSelectedTrack(type);
}
- private void selectTrack(int type, TvTrackInfo track, int trackIndex) {
+ private void selectTrack(int type, TvTrackInfo track) {
mTvView.selectTrack(type, track == null ? null : track.getId());
if (type == TvTrackInfo.TYPE_AUDIO) {
mTvOptionsManager.onMultiAudioChanged(track == null ? null :
Utils.getMultiAudioString(this, track, false));
} else if (type == TvTrackInfo.TYPE_SUBTITLE) {
- mTvOptionsManager.onClosedCaptionsChanged(track, trackIndex);
+ mTvOptionsManager.onClosedCaptionsChanged(track);
}
}
@@ -2852,7 +3163,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (mActivityStarted) {
initAnimations();
initSideFragments();
- initMenuItemViews();
}
}
}, LAZY_INITIALIZATION_DELAY);
@@ -2864,11 +3174,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
private void initSideFragments() {
- SideFragment.preloadItemViews(this);
- }
-
- private void initMenuItemViews() {
- mOverlayManager.getMenu().preloadItemViews();
+ SideFragment.preloadRecycledViews(this);
}
@Override
@@ -2901,6 +3207,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
mainActivity.moveToAdjacentChannel(true, true);
break;
+ case MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE:
+ mainActivity.updateChannelBannerAndShowIfNeeded(
+ UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ break;
}
}
@@ -2915,12 +3225,14 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private class MyOnTuneListener implements OnTuneListener {
boolean mUnlockAllowedRatingBeforeShrunken = true;
boolean mWasUnderShrunkenTvView;
+ long mStreamInfoUpdateTimeThresholdMs;
Channel mChannel;
public MyOnTuneListener() { }
private void onTune(Channel channel, boolean wasUnderShrukenTvView) {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune");
+ mStreamInfoUpdateTimeThresholdMs =
+ System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS;
mChannel = channel;
mWasUnderShrunkenTvView = wasUnderShrukenTvView;
}
@@ -2946,9 +3258,20 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mTracker.sendChannelTuneTime(info.getCurrentChannel(),
mTuneDurationTimer.reset());
}
- if (info.isVideoOrAudioAvailable() && mChannel == getCurrentChannel()) {
- updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO);
+ // If updateChannelBanner() is called without delay, the stream info seems flickering
+ // when the channel is quickly changed.
+ if (!mHandler.hasMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE)
+ && info.isVideoAvailable()) {
+ if (System.currentTimeMillis() > mStreamInfoUpdateTimeThresholdMs) {
+ updateChannelBannerAndShowIfNeeded(
+ UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE),
+ mStreamInfoUpdateTimeThresholdMs - System.currentTimeMillis());
+ }
}
+
applyDisplayRefreshRate(info.getVideoFrameRate());
mTvViewUiManager.updateTvView();
applyMultiAudio();
@@ -2985,9 +3308,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
@Override
public void onContentBlocked() {
- 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
@@ -2999,10 +3319,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
mTvView.unblockContent(rating);
}
- if (!isChannelChangeKeyDownReceived()) {
- mChannelBannerView.setBlockingContentRating(rating);
- updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
- }
+ mChannelBannerView.setBlockingContentRating(rating);
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
mTvViewUiManager.fadeInTvView();
}
@@ -3011,10 +3329,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (!isUnderShrunkenTvView()) {
mUnlockAllowedRatingBeforeShrunken = false;
}
- if (!isChannelChangeKeyDownReceived()) {
- mChannelBannerView.setBlockingContentRating(null);
- updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
- }
+ mChannelBannerView.setBlockingContentRating(null);
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
index 6459693b..8a263a26 100644
--- a/src/com/android/tv/SetupPassthroughActivity.java
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -44,72 +44,64 @@ public class SetupPassthroughActivity extends Activity {
private TvInputInfo mTvInputInfo;
private Intent mActivityAfterCompletion;
- private boolean mEpgFetcherDuringScan;
@Override
public void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ SoftPreconditions.checkState(
+ intent.getAction().equals(TvCommonConstants.INTENT_ACTION_INPUT_SETUP));
ApplicationSingletons appSingletons = TvApplication.getSingletons(this);
TvInputManagerHelper inputManager = appSingletons.getTvInputManagerHelper();
- Intent intent = getIntent();
String inputId = intent.getStringExtra(TvCommonConstants.EXTRA_INPUT_ID);
mTvInputInfo = inputManager.getTvInputInfo(inputId);
+ 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 (DEBUG) Log.d(TAG, "Setup activity launch intent: " + setupIntent);
+ if (setupIntent == null) {
+ Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup.");
+ finish();
+ return;
+ }
+ SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName);
mActivityAfterCompletion = intent.getParcelableExtra(
TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION);
- boolean needToFetchEpg = 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 (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion);
+ // 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);
+ setupIntent.putExtras(extras);
+ try {
+ startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Can't find activity: " + setupIntent.getComponent());
+ finish();
+ return;
}
- if (savedInstanceState == null) {
- SoftPreconditions.checkState(
- intent.getAction().equals(TvCommonConstants.INTENT_ACTION_INPUT_SETUP));
- 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 (DEBUG) Log.d(TAG, "Setup activity launch intent: " + setupIntent);
- if (setupIntent == null) {
- Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup.");
- finish();
- return;
- }
- SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName);
- if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion);
- // 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);
- setupIntent.putExtras(extras);
- try {
- startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Can't find activity: " + setupIntent.getComponent());
- finish();
- return;
- }
- if (needToFetchEpg) {
- EpgFetcher.getInstance(this).onChannelScanStarted();
- }
+ if (Utils.isInternalTvInput(this, mTvInputInfo.getId()) && Experiments.CLOUD_EPG.get()) {
+ EpgFetcher.getInstance(this).stop();
}
}
@Override
+ protected void onDestroy() {
+ if (mTvInputInfo != null && Utils.isInternalTvInput(this, mTvInputInfo.getId())
+ && Experiments.CLOUD_EPG.get()) {
+ EpgFetcher.getInstance(this).start();
+ }
+ super.onDestroy();
+ }
+
+ @Override
public void onActivityResult(int requestCode, final int resultCode, final Intent data) {
- if (DEBUG) Log.d(TAG, "onActivityResult");
boolean setupComplete = requestCode == REQUEST_START_SETUP_ACTIVITY
&& resultCode == Activity.RESULT_OK;
- // Tells EpgFetcher that channel source setup is finished.
- if (mEpgFetcherDuringScan) {
- EpgFetcher.getInstance(this).onChannelScanFinished();
- mEpgFetcherDuringScan = false;
- }
if (!setupComplete) {
setResult(resultCode, data);
finish();
@@ -130,4 +122,4 @@ public class SetupPassthroughActivity extends Activity {
}
});
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java
index e1024705..2d6d45c4 100644
--- a/src/com/android/tv/TimeShiftManager.java
+++ b/src/com/android/tv/TimeShiftManager.java
@@ -887,11 +887,10 @@ 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);
- boolean needToLoad = addDummyPrograms(fetchStartTimeMs, fetchEndTimeMs);
+ boolean needToLoad = addDummyPrograms(fetchStartTimeMs,
+ endTimeMs + PREFETCH_DURATION_FOR_NEXT);
if (needToLoad) {
- Range<Long> period = Range.create(fetchStartTimeMs, fetchEndTimeMs);
+ Range<Long> period = Range.create(fetchStartTimeMs, endTimeMs);
mProgramLoadQueue.add(period);
startTaskIfNeeded();
}
@@ -1013,7 +1012,7 @@ public class TimeShiftManager {
for (int i = 0, j = 0; i < mPrograms.size() && j < loadedPrograms.size(); ++j) {
Program loadedProgram = loadedPrograms.get(j);
// Skip previous programs.
- while (program.getEndTimeUtcMillis() <= loadedProgram.getStartTimeUtcMillis()) {
+ while (program.getEndTimeUtcMillis() < loadedProgram.getStartTimeUtcMillis()) {
// Reached end of mPrograms.
if (++i == mPrograms.size()) {
return;
diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java
index 159df7b6..0e18a259 100644
--- a/src/com/android/tv/TvApplication.java
+++ b/src/com/android/tv/TvApplication.java
@@ -37,7 +37,6 @@ import android.util.Log;
import android.view.KeyEvent;
import com.android.tv.analytics.Analytics;
-import com.android.tv.util.Debug;
import com.android.tv.analytics.StubAnalytics;
import com.android.tv.analytics.StubAnalytics;
import com.android.tv.analytics.Tracker;
@@ -54,10 +53,10 @@ import com.android.tv.data.ProgramDataManager;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManagerImpl;
import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.DvrRecordingService;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.recorder.DvrRecordingService;
import com.android.tv.tuner.TunerPreferences;
import com.android.tv.tuner.tvinput.TunerTvInputService;
import com.android.tv.tuner.util.TunerInputInfoUtils;
@@ -107,8 +106,6 @@ public class TvApplication extends Application implements ApplicationSingletons
@Override
public void onCreate() {
- Debug.getTimer(Debug.TAG_START_UP_TIMER).start();
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TvApplication.onCreate start");
super.onCreate();
SharedPreferencesUtils.initialize(this, new Runnable() {
@Override
@@ -152,13 +149,13 @@ public class TvApplication extends Application implements ApplicationSingletons
mAnalytics = StubAnalytics.getInstance(this);
}
mTracker = mAnalytics.getDefaultTracker();
- getTvInputManagerHelper();
+ mTvInputManagerHelper = new TvInputManagerHelper(this);
+ mTvInputManagerHelper.start();
// 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.
SetupAnimationHelper.initialize(this);
Log.i(TAG, "Started Live TV " + mVersionName);
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TvApplication.onCreate end");
}
private void setCurrentRunningProcess(boolean isMainProcess) {
@@ -170,10 +167,8 @@ public class TvApplication extends Application implements ApplicationSingletons
if (CommonFeatures.DVR.isEnabled(this)) {
mDvrStorageStatusManager = new DvrStorageStatusManager(this, mRunningInMainProcess);
}
- // Fetch remote config
- getSingletons(this).getRemoteConfig().fetch(null);
if (mRunningInMainProcess) {
- getTvInputManagerHelper().addCallback(new TvInputCallback() {
+ mTvInputManagerHelper.addCallback(new TvInputCallback() {
@Override
public void onInputAdded(String inputId) {
if (Features.TUNER.isEnabled(TvApplication.this) && TextUtils.equals(inputId,
@@ -273,7 +268,7 @@ public class TvApplication extends Application implements ApplicationSingletons
@Override
public ChannelDataManager getChannelDataManager() {
if (mChannelDataManager == null) {
- mChannelDataManager = new ChannelDataManager(this, getTvInputManagerHelper());
+ mChannelDataManager = new ChannelDataManager(this, mTvInputManagerHelper);
mChannelDataManager.start();
}
return mChannelDataManager;
@@ -319,10 +314,6 @@ public class TvApplication extends Application implements ApplicationSingletons
*/
@Override
public TvInputManagerHelper getTvInputManagerHelper() {
- if (mTvInputManagerHelper == null) {
- mTvInputManagerHelper = new TvInputManagerHelper(this);
- mTvInputManagerHelper.start();
- }
return mTvInputManagerHelper;
}
diff --git a/src/com/android/tv/TvOptionsManager.java b/src/com/android/tv/TvOptionsManager.java
index 493e039c..7871cbe7 100644
--- a/src/com/android/tv/TvOptionsManager.java
+++ b/src/com/android/tv/TvOptionsManager.java
@@ -18,13 +18,14 @@ package com.android.tv;
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 com.android.tv.util.TvSettings;
+import com.android.tv.util.TvSettings.PipLayout;
+import com.android.tv.util.TvSettings.PipSize;
+import com.android.tv.util.TvSettings.PipSound;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
/**
@@ -32,34 +33,39 @@ import java.util.Locale;
* captions and display mode. Can be also used to create MenuAction items to control such options.
*/
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})
- 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;
- public static final int OPTION_MULTI_AUDIO = 3;
- public static final int OPTION_MORE_CHANNELS = 4;
- public static final int OPTION_DEVELOPER = 5;
- public static final int OPTION_SETTINGS = 6;
+ public static final int OPTION_IN_APP_PIP = 2;
+ public static final int OPTION_SYSTEMWIDE_PIP = 3;
+ public static final int OPTION_MULTI_AUDIO = 4;
+ public static final int OPTION_MORE_CHANNELS = 5;
+ public static final int OPTION_DEVELOPER = 6;
+ public static final int OPTION_SETTINGS = 7;
+
+ public static final int OPTION_PIP_INPUT = 100;
+ public static final int OPTION_PIP_SWAP = 101;
+ public static final int OPTION_PIP_SOUND = 102;
+ public static final int OPTION_PIP_LAYOUT = 103 ;
+ public static final int OPTION_PIP_SIZE = 104;
private final Context mContext;
private final SparseArray<OptionChangedListener> mOptionChangedListeners = new SparseArray<>();
private String mClosedCaptionsLanguage;
private int mDisplayMode;
+ private boolean mPip;
private String mMultiAudio;
+ private String mPipInput;
+ private boolean mPipSwap;
+ @PipSound private int mPipSound;
+ @PipLayout private int mPipLayout;
+ @PipSize private int mPipSize;
public TvOptionsManager(Context context) {
mContext = context;
}
- /**
- * 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) {
+ public String getOptionString(int option) {
switch (option) {
case OPTION_CLOSED_CAPTIONS:
if (mClosedCaptionsLanguage == null) {
@@ -71,48 +77,101 @@ public class TvOptionsManager {
.isDisplayModeAvailable(mDisplayMode)
? DisplayMode.getLabel(mDisplayMode, mContext)
: DisplayMode.getLabel(DisplayMode.MODE_NORMAL, mContext);
+ case OPTION_IN_APP_PIP:
+ return mContext.getString(
+ mPip ? R.string.options_item_pip_on : R.string.options_item_pip_off);
case OPTION_MULTI_AUDIO:
return mMultiAudio;
+ case OPTION_PIP_INPUT:
+ return mPipInput;
+ case OPTION_PIP_SWAP:
+ return mContext.getString(mPipSwap ? R.string.pip_options_item_swap_on
+ : R.string.pip_options_item_swap_off);
+ case OPTION_PIP_SOUND:
+ if (mPipSound == TvSettings.PIP_SOUND_MAIN) {
+ return mContext.getString(R.string.pip_options_item_sound_main);
+ } else if (mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW) {
+ return mContext.getString(R.string.pip_options_item_sound_pip_window);
+ }
+ break;
+ case OPTION_PIP_LAYOUT:
+ if (mPipLayout == TvSettings.PIP_LAYOUT_BOTTOM_RIGHT) {
+ return mContext.getString(R.string.pip_options_item_layout_bottom_right);
+ } else if (mPipLayout == TvSettings.PIP_LAYOUT_TOP_RIGHT) {
+ return mContext.getString(R.string.pip_options_item_layout_top_right);
+ } else if (mPipLayout == TvSettings.PIP_LAYOUT_TOP_LEFT) {
+ return mContext.getString(R.string.pip_options_item_layout_top_left);
+ } else if (mPipLayout == TvSettings.PIP_LAYOUT_BOTTOM_LEFT) {
+ return mContext.getString(R.string.pip_options_item_layout_bottom_left);
+ } else if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
+ return mContext.getString(R.string.pip_options_item_layout_side_by_side);
+ }
+ break;
+ case OPTION_PIP_SIZE:
+ if (mPipSize == TvSettings.PIP_SIZE_BIG) {
+ return mContext.getString(R.string.pip_options_item_size_big);
+ } else if (mPipSize == TvSettings.PIP_SIZE_SMALL) {
+ return mContext.getString(R.string.pip_options_item_size_small);
+ }
+ break;
}
return "";
}
- /**
- * 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);
+ public void onClosedCaptionsChanged(TvTrackInfo track) {
+ mClosedCaptionsLanguage = (track == null) ? null
+ : (track.getLanguage() != null) ? track.getLanguage()
+ : mContext.getString(R.string.default_language);
notifyOptionChanged(OPTION_CLOSED_CAPTIONS);
}
- /**
- * Handles changing selection of display mode.
- */
public void onDisplayModeChanged(int displayMode) {
mDisplayMode = displayMode;
notifyOptionChanged(OPTION_DISPLAY_MODE);
}
- /**
- * Handles changing selection of multi-audio.
- */
+ public void onPipChanged(boolean pip) {
+ mPip = pip;
+ notifyOptionChanged(OPTION_IN_APP_PIP);
+ }
+
public void onMultiAudioChanged(String multiAudio) {
mMultiAudio = multiAudio;
notifyOptionChanged(OPTION_MULTI_AUDIO);
}
- private void notifyOptionChanged(@OptionType int option) {
+ public void onPipInputChanged(String pipInput) {
+ mPipInput = pipInput;
+ notifyOptionChanged(OPTION_PIP_INPUT);
+ }
+
+ public void onPipSwapChanged(boolean pipSwap) {
+ mPipSwap = pipSwap;
+ notifyOptionChanged(OPTION_PIP_SWAP);
+ }
+
+ public void onPipSoundChanged(@PipSound int pipSound) {
+ mPipSound = pipSound;
+ notifyOptionChanged(OPTION_PIP_SOUND);
+ }
+
+ public void onPipLayoutChanged(@PipLayout int pipLayout) {
+ mPipLayout = pipLayout;
+ notifyOptionChanged(OPTION_PIP_LAYOUT);
+ }
+
+ public void onPipSizeChanged(@PipSize int pipSize) {
+ mPipSize = pipSize;
+ notifyOptionChanged(OPTION_PIP_SIZE);
+ }
+
+ private void notifyOptionChanged(int option) {
OptionChangedListener listener = mOptionChangedListeners.get(option);
if (listener != null) {
- listener.onOptionChanged(option, getOptionString(option));
+ listener.onOptionChanged(getOptionString(option));
}
}
- /**
- * Sets listeners to changes of the given option type.
- */
public void setOptionChangedListener(int option, OptionChangedListener listener) {
mOptionChangedListeners.put(option, listener);
}
@@ -121,6 +180,6 @@ public class TvOptionsManager {
* An interface used to monitor option changes.
*/
public interface OptionChangedListener {
- void onOptionChanged(@OptionType int optionType, String newString);
+ void onOptionChanged(String newOption);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/DurationTimer.java b/src/com/android/tv/analytics/DurationTimer.java
index b6221496..ad2d91f8 100644
--- a/src/com/android/tv/util/DurationTimer.java
+++ b/src/com/android/tv/analytics/DurationTimer.java
@@ -14,30 +14,17 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.analytics;
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 startTimeMs = 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.
@@ -72,13 +59,4 @@ public final class DurationTimer {
startTimeMs = 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/data/Channel.java b/src/com/android/tv/data/Channel.java
index 4da56311..30f84236 100644
--- a/src/com/android/tv/data/Channel.java
+++ b/src/com/android/tv/data/Channel.java
@@ -52,16 +52,6 @@ public final class Channel {
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.
*/
@@ -97,15 +87,9 @@ public final class Channel {
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.
*
* <p>The query that created the cursor MUST use {@link #PROJECTION}
@@ -119,7 +103,7 @@ public final class Channel {
channel.mPackageName = Utils.intern(cursor.getString(index++));
channel.mInputId = Utils.intern(cursor.getString(index++));
channel.mType = Utils.intern(cursor.getString(index++));
- channel.mDisplayNumber = normalizeDisplayNumber(cursor.getString(index++));
+ channel.mDisplayNumber = cursor.getString(index++);
channel.mDisplayName = cursor.getString(index++);
channel.mDescription = cursor.getString(index++);
channel.mVideoFormat = Utils.intern(cursor.getString(index++));
@@ -130,29 +114,17 @@ public final class Channel {
channel.mAppLinkIconUri = cursor.getString(index++);
channel.mAppLinkPosterArtUri = cursor.getString(index++);
channel.mAppLinkIntentUri = cursor.getString(index++);
- if (Utils.isBundledInput(channel.mInputId)) {
- channel.mRecordingProhibited = cursor.getInt(index++) != 0;
- }
return channel;
}
/**
- * Replaces the channel number separator with dash('-').
+ * Creates a {@link Channel} object from the DVR database.
*/
- 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)
- || Character.getType(c) == Character.DASH_PUNCTUATION) {
- StringBuilder sb = new StringBuilder(string);
- sb.setCharAt(i, CHANNEL_NUMBER_DELIMITER);
- return sb.toString();
- }
- }
- }
- return string;
+ public static Channel fromDvrCursor(Cursor c) {
+ Channel channel = new Channel();
+ int index = -1;
+ channel.mDvrId = c.getLong(++index);
+ return channel;
}
/** ID of this channel. Matches to BaseColumns._ID. */
@@ -175,10 +147,8 @@ public final class Channel {
private String mAppLinkIntentUri;
private Intent mAppLinkIntent;
private int mAppLinkType;
- private String mLogoUri;
- private boolean mRecordingProhibited;
- private boolean mChannelLogoExist;
+ private long mDvrId;
private Channel() {
// Do nothing.
@@ -260,14 +230,10 @@ public final class Channel {
}
/**
- * Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher.
+ * Returns an ID in DVR database.
*/
- public String getLogoUri() {
- return mLogoUri;
- }
-
- public boolean isRecordingProhibited() {
- return mRecordingProhibited;
+ public long getDvrId() {
+ return mDvrId;
}
/**
@@ -313,13 +279,6 @@ public final class Channel {
}
/**
- * Sets channel logo uri which is got from cloud.
- */
- public void setLogoUri(String logoUri) {
- mLogoUri = logoUri;
- }
-
- /**
* Check whether {@code other} has same read-only channel info as this. But, it cannot check two
* channels have same logos. It also excludes browsable and locked, because two fields are
* changed by TV app.
@@ -339,8 +298,7 @@ public final class Channel {
&& 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(mAppLinkIntentUri, other.mAppLinkIntentUri);
}
@Override
@@ -357,8 +315,7 @@ public final class Channel {
+ ", isPassthrough=" + mIsPassthrough
+ ", browsable=" + mBrowsable
+ ", locked=" + mLocked
- + ", appLinkText=" + mAppLinkText
- + ", recordingProhibited=" + mRecordingProhibited + "}";
+ + ", appLinkText=" + mAppLinkText + "}";
}
void copyFrom(Channel other) {
@@ -383,8 +340,6 @@ public final class Channel {
mAppLinkIntentUri = other.mAppLinkIntentUri;
mAppLinkIntent = other.mAppLinkIntent;
mAppLinkType = other.mAppLinkType;
- mRecordingProhibited = other.mRecordingProhibited;
- mChannelLogoExist = other.mChannelLogoExist;
}
/**
@@ -434,6 +389,8 @@ public final class Channel {
mChannel.mDisplayName = "name";
mChannel.mDescription = "description";
mChannel.mBrowsable = true;
+ mChannel.mLocked = false;
+ mChannel.mIsPassthrough = false;
}
public Builder(Channel other) {
@@ -465,7 +422,7 @@ public final class Channel {
@VisibleForTesting
public Builder setDisplayNumber(String displayNumber) {
- mChannel.mDisplayNumber = normalizeDisplayNumber(displayNumber);
+ mChannel.mDisplayNumber = displayNumber;
return this;
}
@@ -528,11 +485,6 @@ public final class Channel {
return this;
}
- public Builder setRecordingProhibited(boolean recordingProhibited) {
- mChannel.mRecordingProhibited = recordingProhibited;
- return this;
- }
-
public Channel build() {
Channel channel = new Channel();
channel.copyFrom(mChannel);
@@ -572,21 +524,6 @@ public final class Channel {
}
/**
- * Sets if the channel logo exists. This method should be only called from
- * {@link ChannelDataManager}.
- */
- void setChannelLogoExist(boolean exist) {
- mChannelLogoExist = exist;
- }
-
- /**
- * 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
@@ -718,4 +655,4 @@ public final class Channel {
return label;
}
}
-} \ 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 eb3871fc..6f9ea6d7 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -21,14 +21,10 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
-import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
-import android.database.sqlite.SQLiteException;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
import android.media.tv.TvInputManager.TvInputCallback;
-import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -47,8 +43,6 @@ import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-import java.io.FileNotFoundException;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -198,6 +192,7 @@ public class ChannelDataManager {
mStarted = false;
mDbLoadFinished = false;
+ ChannelLogoFetcher.stopFetchingChannelLogos();
mInputManager.removeCallback(mTvInputCallback);
mContentResolver.unregisterContentObserver(mChannelObserver);
mHandler.removeCallbacksAndMessages(null);
@@ -595,36 +590,6 @@ public class ChannelDataManager {
}
}
- private class checkChannelLogoExistTask extends AsyncTask<Void, Void, Boolean> {
- private final Channel mChannel;
-
- public checkChannelLogoExistTask(Channel channel) {
- mChannel = channel;
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
- boolean result = false;
- try {
- AssetFileDescriptor f = mContext.getContentResolver().openAssetFileDescriptor(
- TvContract.buildChannelLogoUri(mChannel.getId()), "r");
- result = true;
- f.close();
- } catch (SQLiteException | IOException | NullPointerException e) {
- // File not found or asset file not found.
- }
- return result;
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- ChannelWrapper wrapper = mChannelWrapperMap.get(mChannel.getId());
- if (wrapper != null) {
- wrapper.mChannel.setChannelLogoExist(result);
- }
- }
- }
-
private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask {
public QueryAllChannelsTask(ContentResolver contentResolver) {
@@ -660,8 +625,6 @@ public class ChannelDataManager {
boolean newlyAdded = !removedChannelIds.remove(channelId);
ChannelWrapper channelWrapper;
if (newlyAdded) {
- new checkChannelLogoExistTask(channel)
- .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
channelWrapper = new ChannelWrapper(channel);
mChannelWrapperMap.put(channel.getId(), channelWrapper);
if (!channelWrapper.mInputRemoved) {
@@ -677,9 +640,9 @@ public class ChannelDataManager {
// {@link #applyUpdatedValuesToDb} is called. Therefore, the value
// between DB and ChannelDataManager could be different for a while.
// Therefore, we'll keep the values in ChannelDataManager.
+ channelWrapper.mChannel.copyFrom(channel);
channel.setBrowsable(oldChannel.isBrowsable());
channel.setLocked(oldChannel.isLocked());
- channelWrapper.mChannel.copyFrom(channel);
if (!channelWrapper.mInputRemoved) {
channelUpdated = true;
updatedChannelWrappers.add(channelWrapper);
@@ -730,6 +693,7 @@ public class ChannelDataManager {
r.run();
}
mPostRunnablesAfterChannelUpdate.clear();
+ ChannelLogoFetcher.startFetchingChannelLogos(mContext);
}
}
diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java
index 256ecdb2..5a549f83 100644
--- a/src/com/android/tv/data/ChannelLogoFetcher.java
+++ b/src/com/android/tv/data/ChannelLogoFetcher.java
@@ -16,68 +16,155 @@
package com.android.tv.data;
-import android.content.ContentProviderOperation;
import android.content.Context;
-import android.content.OperationApplicationException;
-import android.content.SharedPreferences;
+import android.database.Cursor;
import android.graphics.Bitmap.CompressFormat;
import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.support.annotation.AnyThread;
+import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.BitmapUtils;
import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
import com.android.tv.util.PermissionUtils;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Map;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
/**
- * Fetches channel logos from the cloud into the database. It's for the channels which have no logos
- * or need update logos. This class is thread safe.
+ * Utility class for TMS data.
+ * This class is thread safe.
*/
public class ChannelLogoFetcher {
private static final String TAG = "ChannelLogoFetcher";
private static final boolean DEBUG = false;
- private static final String PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO =
- "is_first_time_fetch_channel_logo";
+ /**
+ * The name of the file which contains the TMS data.
+ * The file has multiple records and each of them is a string separated by '|' like
+ * STATION_NAME|SHORT_NAME|CALL_SIGN|LOGO_URI.
+ */
+ private static final String TMS_US_TABLE_FILE = "tms_us.table";
+ private static final String TMS_KR_TABLE_FILE = "tms_kr.table";
+ private static final String FIELD_SEPARATOR = "\\|";
+ private static final String NAME_SEPARATOR_FOR_TMS = "\\(|\\)|\\{|\\}|\\[|\\]";
+ private static final String NAME_SEPARATOR_FOR_DB = "\\W";
+ private static final int INDEX_NAME = 0;
+ private static final int INDEX_SHORT_NAME = 1;
+ private static final int INDEX_CALL_SIGN = 2;
+ private static final int INDEX_LOGO_URI = 3;
+
+ private static final String COLUMN_CHANNEL_LOGO = "logo";
+ private static final Object sLock = new Object();
+ private static final Set<Long> sChannelIdBlackListSet = new HashSet<>();
+ private static LoadChannelTask sQueryTask;
private static FetchLogoTask sFetchTask;
/**
- * Fetches the channel logos from the cloud data and insert them into TvProvider.
+ * Fetch the channel logos from TMS data and insert them into TvProvider.
* The previous task is canceled and a new task starts.
*/
- @AnyThread
- public static synchronized void startFetchingChannelLogos(
- Context context, List<Channel> channels) {
+ public static void startFetchingChannelLogos(Context context) {
if (!PermissionUtils.hasAccessAllEpg(context)) {
// TODO: support this feature for non-system LC app. b/23939816
return;
}
- if (sFetchTask != null) {
- sFetchTask.cancel(true);
+ synchronized (sLock) {
+ stopFetchingChannelLogos();
+ if (DEBUG) Log.d(TAG, "Request to start fetching logos.");
+ sQueryTask = new LoadChannelTask(context);
+ sQueryTask.executeOnDbThread();
}
- if (DEBUG) Log.d(TAG, "Request to start fetching logos.");
- if (channels == null || channels.isEmpty()) {
- return;
+ }
+
+ /**
+ * Stops the current fetching tasks. This can be called when the Activity pauses.
+ */
+ public static void stopFetchingChannelLogos() {
+ synchronized (sLock) {
+ if (DEBUG) Log.d(TAG, "Request to stop fetching logos.");
+ if (sQueryTask != null) {
+ sQueryTask.cancel(true);
+ sQueryTask = null;
+ }
+ if (sFetchTask != null) {
+ sFetchTask.cancel(true);
+ sFetchTask = null;
+ }
}
- sFetchTask = new FetchLogoTask(context, channels);
- sFetchTask.execute();
}
private ChannelLogoFetcher() {
}
+ private static final class LoadChannelTask extends AsyncDbTask<Void, Void, List<Channel>> {
+ private final Context mContext;
+
+ public LoadChannelTask(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ protected List<Channel> doInBackground(Void... arg) {
+ // Load channels which doesn't have channel logos.
+ if (DEBUG) Log.d(TAG, "Starts loading the channels from DB");
+ String[] projection =
+ new String[] { Channels._ID, Channels.COLUMN_DISPLAY_NAME };
+ String selection = COLUMN_CHANNEL_LOGO + " IS NULL AND "
+ + Channels.COLUMN_PACKAGE_NAME + "=?";
+ String[] selectionArgs = new String[] { mContext.getPackageName() };
+ try (Cursor c = mContext.getContentResolver().query(Channels.CONTENT_URI,
+ projection, selection, selectionArgs, null)) {
+ if (c == null) {
+ Log.e(TAG, "Query returns null cursor", new RuntimeException());
+ return null;
+ }
+ List<Channel> channels = new ArrayList<>();
+ while (!isCancelled() && c.moveToNext()) {
+ long channelId = c.getLong(0);
+ if (sChannelIdBlackListSet.contains(channelId)) {
+ continue;
+ }
+ channels.add(new Channel.Builder().setId(c.getLong(0))
+ .setDisplayName(c.getString(1).toUpperCase(Locale.getDefault()))
+ .build());
+ }
+ return channels;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(List<Channel> channels) {
+ synchronized (sLock) {
+ if (DEBUG) {
+ int count = channels == null ? 0 : channels.size();
+ Log.d(TAG, count + " channels are loaded");
+ }
+ if (sQueryTask == this) {
+ sQueryTask = null;
+ if (channels != null && !channels.isEmpty()) {
+ sFetchTask = new FetchLogoTask(mContext, channels);
+ sFetchTask.execute();
+ }
+ }
+ }
+ }
+ }
+
private static final class FetchLogoTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final List<Channel> mChannels;
@@ -93,53 +180,83 @@ public class ChannelLogoFetcher {
if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled");
return null;
}
- List<Channel> channelsToUpdate = new ArrayList<>();
- List<Channel> channelsToRemove = new ArrayList<>();
- // Updates or removes the logo by comparing the logo uri which is got from the cloud
- // and the stored one. And we assume that the data got form the cloud is 100%
- // correct and completed.
- SharedPreferences sharedPreferences =
- mContext.getSharedPreferences(
- SharedPreferencesUtils.SHARED_PREF_CHANNEL_LOGO_URIS,
- 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);
- // Iterating channels.
- for (Channel channel : mChannels) {
- String channelIdString = Long.toString(channel.getId());
- String storedChannelLogoUri = (String) uncheckedChannels.remove(channelIdString);
- if (!TextUtils.isEmpty(channel.getLogoUri())
- && !TextUtils.equals(storedChannelLogoUri, channel.getLogoUri())) {
- channelsToUpdate.add(channel);
- sharedPreferencesEditor.putString(channelIdString, channel.getLogoUri());
- } else if (TextUtils.isEmpty(channel.getLogoUri())
- && (!TextUtils.isEmpty(storedChannelLogoUri)
- || isFirstTimeFetchChannelLogo)) {
- channelsToRemove.add(channel);
- sharedPreferencesEditor.remove(channelIdString);
+ // Load the TMS table data.
+ if (DEBUG) Log.d(TAG, "Loads TMS data");
+ Map<String, String> channelNameLogoUriMap = new HashMap<>();
+ try {
+ channelNameLogoUriMap.putAll(readTmsFile(mContext, TMS_US_TABLE_FILE));
+ if (isCancelled()) {
+ if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled");
+ return null;
}
+ channelNameLogoUriMap.putAll(readTmsFile(mContext, TMS_KR_TABLE_FILE));
+ } catch (IOException e) {
+ Log.e(TAG, "Loading TMS data failed.", e);
+ return null;
}
-
- // Removes non existing channels from SharedPreferences.
- for (String channelId : uncheckedChannels.keySet()) {
- sharedPreferencesEditor.remove(channelId);
+ if (isCancelled()) {
+ if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled");
+ return null;
}
- // Updates channel logos.
- for (Channel channel : channelsToUpdate) {
+ // Iterating channels.
+ for (Channel channel : mChannels) {
if (isCancelled()) {
if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled");
return null;
}
- // Downloads the channel logo.
- String logoUri = channel.getLogoUri();
+ // Download the channel logo.
+ if (TextUtils.isEmpty(channel.getDisplayName())) {
+ if (DEBUG) {
+ Log.d(TAG, "The channel with ID (" + channel.getId()
+ + ") doesn't have the display name.");
+ }
+ sChannelIdBlackListSet.add(channel.getId());
+ continue;
+ }
+ String channelName = channel.getDisplayName().trim();
+ String logoUri = channelNameLogoUriMap.get(channelName);
+ if (TextUtils.isEmpty(logoUri)) {
+ if (DEBUG) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + channelName + "'");
+ }
+ // Find the candidate names. If the channel name is CNN-HD, then find CNNHD
+ // and CNN. Or if the channel name is KQED+, then find KQED.
+ String[] splitNames = channelName.split(NAME_SEPARATOR_FOR_DB);
+ if (splitNames.length > 1) {
+ StringBuilder sb = new StringBuilder();
+ for (String splitName : splitNames) {
+ sb.append(splitName);
+ }
+ logoUri = channelNameLogoUriMap.get(sb.toString());
+ if (DEBUG) {
+ if (TextUtils.isEmpty(logoUri)) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + sb.toString()
+ + "'");
+ }
+ }
+ }
+ if (TextUtils.isEmpty(logoUri)
+ && splitNames[0].length() != channelName.length()) {
+ logoUri = channelNameLogoUriMap.get(splitNames[0]);
+ if (DEBUG) {
+ if (TextUtils.isEmpty(logoUri)) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + splitNames[0]
+ + "'");
+ }
+ }
+ }
+ }
+ if (TextUtils.isEmpty(logoUri)) {
+ sChannelIdBlackListSet.add(channel.getId());
+ continue;
+ }
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 + "}");
+ sChannelIdBlackListSet.add(channel.getId());
continue;
}
if (isCancelled()) {
@@ -147,15 +264,12 @@ public class ChannelLogoFetcher {
return null;
}
- // Inserts the logo to DB.
+ // Insert the logo to DB.
Uri dstLogoUri = TvContract.buildChannelLogoUri(channel.getId());
try (OutputStream os = mContext.getContentResolver().openOutputStream(dstLogoUri)) {
bitmapInfo.bitmap.compress(CompressFormat.PNG, 100, os);
} catch (IOException e) {
Log.e(TAG, "Failed to write " + logoUri + " to " + dstLogoUri, e);
- // Removes it from the shared preference for the failed channels to make it
- // retry next time.
- sharedPreferencesEditor.remove(Long.toString(channel.getId()));
continue;
}
if (DEBUG) {
@@ -163,30 +277,63 @@ public class ChannelLogoFetcher {
+ dstLogoUri + "}");
}
}
+ if (DEBUG) Log.d(TAG, "Fetching logos has been finished successfully.");
+ return null;
+ }
- // Removes the logos for the channels that have logos before but now
- // their logo uris are null.
- boolean deleteChannelLogoFailed = false;
- if (!channelsToRemove.isEmpty()) {
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- for (Channel channel : channelsToRemove) {
- ops.add(ContentProviderOperation.newDelete(
- TvContract.buildChannelLogoUri(channel.getId())).build());
- }
- try {
- mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
- } catch (RemoteException | OperationApplicationException e) {
- deleteChannelLogoFailed = true;
- Log.e(TAG, "Error deleting obsolete channels", e);
+ @WorkerThread
+ private Map<String, String> readTmsFile(Context context, String fileName)
+ throws IOException {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ context.getAssets().open(fileName)))) {
+ Map<String, String> channelNameLogoUriMap = new HashMap<>();
+ String line;
+ while ((line = reader.readLine()) != null && !isCancelled()) {
+ String[] data = line.split(FIELD_SEPARATOR);
+ if (data.length != INDEX_LOGO_URI + 1) {
+ if (DEBUG) Log.d(TAG, "Invalid or comment row: " + line);
+ continue;
+ }
+ addChannelNames(channelNameLogoUriMap,
+ data[INDEX_NAME].toUpperCase(Locale.getDefault()),
+ data[INDEX_LOGO_URI]);
+ addChannelNames(channelNameLogoUriMap,
+ data[INDEX_SHORT_NAME].toUpperCase(Locale.getDefault()),
+ data[INDEX_LOGO_URI]);
+ addChannelNames(channelNameLogoUriMap,
+ data[INDEX_CALL_SIGN].toUpperCase(Locale.getDefault()),
+ data[INDEX_LOGO_URI]);
}
+ return channelNameLogoUriMap;
}
- if (isFirstTimeFetchChannelLogo && !deleteChannelLogoFailed) {
- sharedPreferencesEditor.putBoolean(
- PREF_KEY_IS_FIRST_TIME_FETCH_CHANNEL_LOGO, false);
+ }
+
+ private void addChannelNames(Map<String, String> channelNameLogoUriMap, String channelName,
+ String logoUri) {
+ if (!TextUtils.isEmpty(channelName)) {
+ channelNameLogoUriMap.put(channelName, logoUri);
+ // Find the candidate names.
+ // If the name is like "W05AAD (W05AA-D)", then split the names into "W05AAD" and
+ // "W05AA-D"
+ String[] splitNames = channelName.split(NAME_SEPARATOR_FOR_TMS);
+ if (splitNames.length > 1) {
+ for (String name : splitNames) {
+ name = name.trim();
+ if (channelNameLogoUriMap.get(name) == null) {
+ channelNameLogoUriMap.put(name, logoUri);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ synchronized (sLock) {
+ if (sFetchTask == this) {
+ sFetchTask = null;
+ }
}
- sharedPreferencesEditor.commit();
- if (DEBUG) Log.d(TAG, "Fetching logos has been finished successfully.");
- return null;
}
}
}
diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java
index 29054aa5..59021609 100644
--- a/src/com/android/tv/data/ChannelNumber.java
+++ b/src/com/android/tv/data/ChannelNumber.java
@@ -17,38 +17,37 @@
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 java.util.Objects;
-
/**
* A convenience class to handle channel number.
*/
public final class ChannelNumber implements Comparable<ChannelNumber> {
+ public static final String PRIMARY_CHANNEL_DELIMITER = "-";
+ public static final String[] CHANNEL_DELIMITERS = {"-", ".", " "};
+
private static final int[] CHANNEL_DELIMITER_KEYCODES = {
KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_PERIOD,
KeyEvent.KEYCODE_NUMPAD_DOT, KeyEvent.KEYCODE_SPACE
};
- /** The major part of the channel number. */
public String majorNumber;
- /** The flag which indicates whether it has a delimiter or not. */
public boolean hasDelimiter;
- /** The major part of the channel number. */
public String minorNumber;
public ChannelNumber() {
reset();
}
+ public ChannelNumber(String major, boolean hasDelimiter, String minor) {
+ setChannelNumber(major, hasDelimiter, minor);
+ }
+
public void reset() {
setChannelNumber("", false, "");
}
- private void setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber) {
+ public void setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber) {
this.majorNumber = majorNumber;
this.hasDelimiter = hasDelimiter;
this.minorNumber = minorNumber;
@@ -57,7 +56,7 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
@Override
public String toString() {
if (hasDelimiter) {
- return majorNumber + Channel.CHANNEL_NUMBER_DELIMITER + minorNumber;
+ return majorNumber + PRIMARY_CHANNEL_DELIMITER + minorNumber;
}
return majorNumber;
}
@@ -76,22 +75,6 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
return major - opponentMajor;
}
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof ChannelNumber) {
- ChannelNumber channelNumber = (ChannelNumber) obj;
- return TextUtils.equals(majorNumber, channelNumber.majorNumber)
- && TextUtils.equals(minorNumber, channelNumber.minorNumber)
- && hasDelimiter == channelNumber.hasDelimiter;
- }
- return super.equals(obj);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(majorNumber, hasDelimiter, minorNumber);
- }
-
public static boolean isChannelNumberDelimiterKey(int keyCode) {
for (int delimiterKeyCode : CHANNEL_DELIMITER_KEYCODES) {
if (delimiterKeyCode == keyCode) {
@@ -101,22 +84,22 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
return false;
}
- /**
- * 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.
- */
public static ChannelNumber parseChannelNumber(String number) {
if (number == null) {
return null;
}
ChannelNumber ret = new ChannelNumber();
- int indexOfDelimiter = number.indexOf(Channel.CHANNEL_NUMBER_DELIMITER);
+ int indexOfDelimiter = -1;
+ for (String delimiter : CHANNEL_DELIMITERS) {
+ indexOfDelimiter = number.indexOf(delimiter);
+ if (indexOfDelimiter >= 0) {
+ break;
+ }
+ }
if (indexOfDelimiter == 0 || indexOfDelimiter == number.length() - 1) {
return null;
- } else if (indexOfDelimiter < 0) {
+ }
+ if (indexOfDelimiter < 0) {
ret.majorNumber = number;
if (!isInteger(ret.majorNumber)) {
return null;
@@ -132,31 +115,25 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
return ret;
}
- /**
- * 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.
- */
public static int compare(String lhs, String rhs) {
ChannelNumber lhsNumber = parseChannelNumber(lhs);
ChannelNumber rhsNumber = parseChannelNumber(rhs);
- // Null first
if (lhsNumber == null && rhsNumber == null) {
- return StringUtils.compare(lhs, rhs);
+ return 0;
} else if (lhsNumber == null /* && rhsNumber != null */) {
return -1;
- } else if (rhsNumber == null) {
+ } else if (lhsNumber != null && rhsNumber == null) {
return 1;
}
return lhsNumber.compareTo(rhsNumber);
}
- private static boolean isInteger(String string) {
+ public static boolean isInteger(String string) {
try {
Integer.parseInt(string);
- } catch(NumberFormatException | NullPointerException e) {
+ } catch(NumberFormatException e) {
+ return false;
+ } catch(NullPointerException e) {
return false;
}
return true;
diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java
index e33ca18f..6054f089 100644
--- a/src/com/android/tv/data/InternalDataUtils.java
+++ b/src/com/android/tv/data/InternalDataUtils.java
@@ -21,7 +21,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.tv.data.Program.CriticScore;
-import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.RecordedProgram;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java
index 709863cf..fe461f14 100644
--- a/src/com/android/tv/data/StreamInfo.java
+++ b/src/com/android/tv/data/StreamInfo.java
@@ -38,9 +38,5 @@ public interface StreamInfo {
int getAudioChannelCount();
boolean hasClosedCaption();
boolean isVideoAvailable();
- /**
- * Returns true, if video or audio is available.
- */
- boolean isVideoOrAudioAvailable();
int getVideoUnavailableReason();
}
diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java
index ddd68ad7..3b093b6a 100644
--- a/src/com/android/tv/data/epg/EpgFetcher.java
+++ b/src/com/android/tv/data/epg/EpgFetcher.java
@@ -16,11 +16,13 @@
package com.android.tv.data.epg;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.location.Address;
import android.media.tv.TvContentRating;
@@ -44,11 +46,9 @@ import com.android.tv.TvApplication;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.ChannelLogoFetcher;
import com.android.tv.data.InternalDataUtils;
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
-import com.android.tv.tuner.util.PostalCodeUtils;
import com.android.tv.util.LocationUtils;
import com.android.tv.util.RecurringRunner;
import com.android.tv.util.Utils;
@@ -56,10 +56,8 @@ import com.android.tv.util.Utils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -71,27 +69,14 @@ public class EpgFetcher {
private static final boolean DEBUG = false;
private static final int MSG_FETCH_EPG = 1;
- private static final int MSG_FAST_FETCH_EPG = 2;
private static final long EPG_PREFETCH_RECURRING_PERIOD_MS = TimeUnit.HOURS.toMillis(4);
private static final long EPG_READER_INIT_WAIT_MS = TimeUnit.MINUTES.toMillis(1);
private static final long LOCATION_INIT_WAIT_MS = TimeUnit.SECONDS.toMillis(10);
private static final long LOCATION_ERROR_WAIT_MS = TimeUnit.HOURS.toMillis(1);
- private static final long NO_INFO_FETCHED_WAIT_MS = TimeUnit.SECONDS.toMillis(10);
private static final long PROGRAM_QUERY_DURATION = TimeUnit.DAYS.toMillis(30);
- private static final long PROGRAM_FETCH_SHORT_DURATION_SEC = TimeUnit.HOURS.toSeconds(3);
- private static final long PROGRAM_FETCH_LONG_DURATION_SEC = TimeUnit.DAYS.toSeconds(2)
- + EPG_PREFETCH_RECURRING_PERIOD_MS / 1000;
-
- // This equals log2(EPG_PREFETCH_RECURRING_PERIOD_MS / NO_INFO_FETCHED_WAIT_MS + 1),
- // since we will double waiting time every other trial, therefore this limit the maximum
- // waiting time less than half of EPG_PREFETCH_RECURRING_PERIOD_MS.
- private static final int NO_INFO_RETRY_LIMIT = 31 - Integer.numberOfLeadingZeros(
- (int) (EPG_PREFETCH_RECURRING_PERIOD_MS / NO_INFO_FETCHED_WAIT_MS + 1));
-
private static final int BATCH_OPERATION_COUNT = 100;
- private static final int QUERY_CHANNEL_COUNT = 50;
private static final String SUPPORTED_COUNTRY_CODE = Locale.US.getCountry();
private static final String CONTENT_RATING_SEPARATOR = ",";
@@ -111,11 +96,8 @@ public class EpgFetcher {
private EpgFetcherHandler mHandler;
private RecurringRunner mRecurringRunner;
private boolean mStarted;
- private boolean mScanningChannels;
- private int mFetchRetryCount;
private long mLastEpgTimestamp = -1;
- // @GuardedBy("this")
private String mLineupId;
public static synchronized EpgFetcher getInstance(Context context) {
@@ -140,33 +122,21 @@ public class EpgFetcher {
@Override
public void onLoadFinished() {
if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()");
- if (!mScanningChannels) {
- handleChannelChanged();
- }
+ handleChannelChanged();
}
@Override
public void onChannelListUpdated() {
if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()");
- if (!mScanningChannels) {
- handleChannelChanged();
- }
+ handleChannelChanged();
}
@Override
public void onChannelBrowsableChanged() {
if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelBrowsableChanged()");
- if (!mScanningChannels) {
- handleChannelChanged();
- }
+ handleChannelChanged();
}
});
- // Warm up to get address, because the first call of getCurrentAddress is usually failed.
- try {
- LocationUtils.getCurrentAddress(mContext);
- } catch (SecurityException | IOException e) {
- // Do nothing
- }
}
private void handleChannelChanged() {
@@ -175,9 +145,7 @@ public class EpgFetcher {
stop();
}
} else {
- if (canStart()) {
- start();
- }
+ start();
}
}
@@ -205,14 +173,17 @@ public class EpgFetcher {
if (!TextUtils.isEmpty(getLastLineupId())) {
return true;
}
- if (!TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- return true;
+ if (mContext.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED) {
+ if (DEBUG) Log.d(TAG, "No permission to check the current location.");
+ return false;
}
+
try {
Address address = LocationUtils.getCurrentAddress(mContext);
if (address != null
&& !TextUtils.equals(address.getCountryCode(), SUPPORTED_COUNTRY_CODE)) {
- Log.i(TAG, "Country not supported: " + address.getCountryCode());
+ if (DEBUG) Log.d(TAG, "Country not supported: " + address.getCountryCode());
return false;
}
} catch (SecurityException e) {
@@ -226,13 +197,9 @@ public class EpgFetcher {
/**
* Starts fetching EPG.
- *
- * @param resetNextRunTime if true, next run time is reset, so EPG will be fetched
- * {@link #EPG_PREFETCH_RECURRING_PERIOD_MS} later.
- * otherwise, EPG is fetched when this method is called.
*/
@MainThread
- private void startInternal(boolean resetNextRunTime) {
+ public void start() {
if (DEBUG) Log.d(TAG, "start()");
if (mStarted) {
if (DEBUG) Log.d(TAG, "EpgFetcher thread already started.");
@@ -248,35 +215,19 @@ public class EpgFetcher {
mHandler = new EpgFetcherHandler(handlerThread.getLooper(), this);
mRecurringRunner = new RecurringRunner(mContext, EPG_PREFETCH_RECURRING_PERIOD_MS,
new EpgRunner(), null);
- mRecurringRunner.start(resetNextRunTime);
+ mRecurringRunner.start();
if (DEBUG) Log.d(TAG, "EpgFetcher thread started successfully.");
}
- @MainThread
- public void start() {
- if (System.currentTimeMillis() - getLastUpdatedEpgTimestamp() >
- EPG_PREFETCH_RECURRING_PERIOD_MS) {
- startImmediately(false);
- } else {
- startInternal(false);
- }
- }
-
/**
* Starts fetching EPG immediately if possible without waiting for the timer.
- *
- * @param clearStoredLineupId if true, stored lineup id will be clear before fetching EPG.
*/
@MainThread
- public void startImmediately(boolean clearStoredLineupId) {
- startInternal(true);
+ public void startImmediately() {
+ start();
if (mStarted) {
- if (clearStoredLineupId) {
- if (DEBUG) Log.d(TAG, "Clear stored lineup id: " + mLineupId);
- setLastLineupId(null);
- }
if (DEBUG) Log.d(TAG, "Starting fetcher immediately");
- postFetchRequest(true, 0);
+ fetchEpg();
}
}
@@ -295,71 +246,48 @@ public class EpgFetcher {
mHandler.getLooper().quit();
}
- /**
- * Notifies EPG fetcher that channel scanning is started.
- */
- @MainThread
- public void onChannelScanStarted() {
- stop();
- mScanningChannels = true;
+ private void fetchEpg() {
+ fetchEpg(0);
}
- /**
- * Notifies EPG fetcher that channel scanning is finished.
- */
- @MainThread
- public void onChannelScanFinished() {
- mScanningChannels = false;
- start();
- }
-
- private void postFetchRequest(boolean fastFetch, long delay) {
- int msg = fastFetch ? MSG_FAST_FETCH_EPG : MSG_FETCH_EPG;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessageDelayed(msg, delay);
+ private void fetchEpg(long delay) {
+ mHandler.removeMessages(MSG_FETCH_EPG);
+ mHandler.sendEmptyMessageDelayed(MSG_FETCH_EPG, delay);
}
private void onFetchEpg() {
- onFetchEpg(false);
- }
-
- private void onFetchEpg(boolean fastFetch) {
if (DEBUG) Log.d(TAG, "Start fetching EPG.");
if (!mEpgReader.isAvailable()) {
- Log.i(TAG, "EPG reader is not temporarily available.");
- postFetchRequest(fastFetch, EPG_READER_INIT_WAIT_MS);
+ if (DEBUG) Log.d(TAG, "EPG reader is not temporarily available.");
+ fetchEpg(EPG_READER_INIT_WAIT_MS);
return;
}
String lineupId = getLastLineupId();
if (lineupId == null) {
+ Address address;
try {
- PostalCodeUtils.updatePostalCode(mContext);
+ address = LocationUtils.getCurrentAddress(mContext);
} catch (IOException e) {
- if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e);
- postFetchRequest(fastFetch, LOCATION_ERROR_WAIT_MS);
- return;
- }
+ if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e);
+ fetchEpg(LOCATION_ERROR_WAIT_MS);
+ return;
} catch (SecurityException e) {
- if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- Log.w(TAG, "No permission to get the current location.");
- return;
- }
- } catch (PostalCodeUtils.NoPostalCodeException e) {
- Log.i(TAG, "Failed to get the current postal code.");
- postFetchRequest(fastFetch, LOCATION_INIT_WAIT_MS);
+ Log.w(TAG, "No permission to get the current location.");
return;
}
- String postalCode = PostalCodeUtils.getLastPostalCode(mContext);
- if (DEBUG) Log.d(TAG, "The current postal code is " + postalCode);
+ if (address == null) {
+ if (DEBUG) Log.d(TAG, "Null address returned.");
+ fetchEpg(LOCATION_INIT_WAIT_MS);
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "Current location is " + address);
- lineupId = pickLineupForPostalCode(postalCode);
+ lineupId = getLineupForAddress(address);
if (lineupId != null) {
- Log.i(TAG, "Selecting the lineup " + lineupId);
+ if (DEBUG) Log.d(TAG, "Saving lineup " + lineupId + "found for " + address);
setLastLineupId(lineupId);
} else {
- Log.i(TAG, "Failed to get lineup id");
- retryFetchEpg(fastFetch);
+ if (DEBUG) Log.d(TAG, "No lineup found for " + address);
return;
}
}
@@ -371,109 +299,48 @@ public class EpgFetcher {
return;
}
+ boolean updated = false;
List<Channel> channels = mEpgReader.getChannels(lineupId);
- if (channels.isEmpty()) {
- Log.i(TAG, "Failed to get EPG channels.");
- retryFetchEpg(fastFetch);
- return;
- }
- mFetchRetryCount = 0;
- if (!fastFetch) {
- for (Channel channel : channels) {
- if (!mStarted) {
- break;
- }
- List<Program> programs = new ArrayList<>(mEpgReader.getPrograms(channel.getId()));
- Collections.sort(programs);
- Log.i(TAG, "Fetched " + programs.size() + " programs for channel " + channel);
- updateEpg(channel.getId(), programs);
+ for (Channel channel : channels) {
+ List<Program> programs = new ArrayList<>(mEpgReader.getPrograms(channel.getId()));
+ Collections.sort(programs);
+ if (DEBUG) {
+ Log.d(TAG, "Fetched " + programs.size() + " programs for channel " + channel);
+ }
+ if (updateEpg(channel.getId(), programs)) {
+ updated = true;
}
- setLastUpdatedEpgTimestamp(epgTimestamp);
- } else {
- handleFastFetch(channels, PROGRAM_FETCH_SHORT_DURATION_SEC);
- if (DEBUG) Log.d(TAG, "First fast fetch Done.");
- handleFastFetch(channels, PROGRAM_FETCH_LONG_DURATION_SEC);
- if (DEBUG) Log.d(TAG, "Second fast fetch Done.");
}
- if (!fastFetch) {
- mHandler.removeMessages(MSG_FETCH_EPG);
- }
+ final boolean epgUpdated = updated;
+ setLastUpdatedEpgTimestamp(epgTimestamp);
+ mHandler.removeMessages(MSG_FETCH_EPG);
if (DEBUG) Log.d(TAG, "Fetching EPG is finished.");
- // Start to fetch channel logos after epg fetching finished.
- ChannelLogoFetcher.startFetchingChannelLogos(mContext, channels);
- }
-
- private void retryFetchEpg(boolean fastFetch) {
- if (mFetchRetryCount < NO_INFO_RETRY_LIMIT) {
- postFetchRequest(fastFetch, NO_INFO_FETCHED_WAIT_MS * 1 << mFetchRetryCount);
- mFetchRetryCount++;
- } else {
- mFetchRetryCount = 0;
- }
}
- private void handleFastFetch(List<Channel> channels, long duration) {
- List<Long> channelIds = new ArrayList<>(channels.size());
- for (Channel channel : channels) {
- channelIds.add(channel.getId());
- }
- Map<Long, List<Program>> allPrograms = new HashMap<>();
- List<Long> queryChannelIds = new ArrayList<>(QUERY_CHANNEL_COUNT);
- for (Long channelId : channelIds) {
- queryChannelIds.add(channelId);
- if (queryChannelIds.size() >= QUERY_CHANNEL_COUNT) {
- allPrograms.putAll(
- new HashMap<>(mEpgReader.getPrograms(queryChannelIds, duration)));
- queryChannelIds.clear();
+ @Nullable
+ private String getLineupForAddress(Address address) {
+ String lineup = null;
+ if (TextUtils.equals(address.getCountryCode(), SUPPORTED_COUNTRY_CODE)) {
+ String postalCode = address.getPostalCode();
+ if (!TextUtils.isEmpty(postalCode)) {
+ lineup = getLineupForPostalCode(postalCode);
}
}
- if (!queryChannelIds.isEmpty()) {
- allPrograms.putAll(
- new HashMap<>(mEpgReader.getPrograms(queryChannelIds, duration)));
- }
- for (Channel channel : channels) {
- List<Program> programs = allPrograms.get(channel.getId());
- if (programs == null) continue;
- Collections.sort(programs);
- Log.i(TAG, "Fast fetched " + programs.size() + " programs for channel " + channel);
- updateEpg(channel.getId(), programs);
- }
+ return lineup;
}
@Nullable
- private String pickLineupForPostalCode(String postalCode) {
+ private String getLineupForPostalCode(String postalCode) {
List<Lineup> lineups = mEpgReader.getLineups(postalCode);
- int maxCount = 0;
- String maxLineupId = null;
for (Lineup lineup : lineups) {
- int count = getMatchedChannelCount(lineup.id);
- Log.i(TAG, lineup.name + " (" + lineup.id + ") - " + count + " matches");
- if (count > maxCount) {
- maxCount = count;
- maxLineupId = lineup.id;
+ // TODO(EPG): handle more than OTA digital
+ if (lineup.type == Lineup.LINEUP_BROADCAST_DIGITAL) {
+ if (DEBUG) Log.d(TAG, "Setting lineup to " + lineup.name + "(" + lineup.id + ")");
+ return lineup.id;
}
}
- return maxLineupId;
- }
-
- private int getMatchedChannelCount(String lineupId) {
- // Construct a list of display numbers for existing channels.
- List<Channel> channels = mChannelDataManager.getChannelList();
- if (channels.isEmpty()) {
- if (DEBUG) Log.d(TAG, "No existing channel to compare");
- return 0;
- }
- List<String> numbers = new ArrayList<>(channels.size());
- for (Channel c : channels) {
- // We only support local channels from physical tuners.
- if (c.isPhysicalTunerChannel()) {
- numbers.add(c.getDisplayNumber());
- }
- }
-
- numbers.retainAll(mEpgReader.getChannelNumbers(lineupId));
- return numbers.size();
+ return null;
}
private long getLastUpdatedEpgTimestamp() {
@@ -490,16 +357,16 @@ public class EpgFetcher {
KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp).commit();
}
- synchronized private String getLastLineupId() {
+ private String getLastLineupId() {
if (mLineupId == null) {
mLineupId = PreferenceManager.getDefaultSharedPreferences(mContext)
.getString(KEY_LAST_LINEUP_ID, null);
}
- if (DEBUG) Log.d(TAG, "Last lineup is " + mLineupId);
+ if (DEBUG) Log.d(TAG, "Last lineup_id " + mLineupId);
return mLineupId;
}
- synchronized private void setLastLineupId(String lineupId) {
+ private void setLastLineupId(String lineupId) {
mLineupId = lineupId;
PreferenceManager.getDefaultSharedPreferences(mContext).edit()
.putString(KEY_LAST_LINEUP_ID, lineupId).commit();
@@ -514,9 +381,19 @@ public class EpgFetcher {
long startTimeMs = System.currentTimeMillis();
long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION;
List<Program> oldPrograms = queryPrograms(channelId, startTimeMs, endTimeMs);
+ Program currentOldProgram = oldPrograms.size() > 0 ? oldPrograms.get(0) : null;
int oldProgramsIndex = 0;
int newProgramsIndex = 0;
-
+ // Skip the past programs. They will be automatically removed by the system.
+ if (currentOldProgram != null) {
+ long oldStartTimeUtcMillis = currentOldProgram.getStartTimeUtcMillis();
+ for (Program program : newPrograms) {
+ if (program.getEndTimeUtcMillis() > oldStartTimeUtcMillis) {
+ break;
+ }
+ newProgramsIndex++;
+ }
+ }
// Compare the new programs with old programs one by one and update/delete the old one
// or insert new program if there is no matching program in the database.
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
@@ -562,7 +439,7 @@ public class EpgFetcher {
}
if (addNewProgram) {
ops.add(ContentProviderOperation
- .newInsert(Programs.CONTENT_URI)
+ .newInsert(TvContract.Programs.CONTENT_URI)
.withValues(toContentValues(newProgram))
.build());
}
@@ -624,25 +501,27 @@ public class EpgFetcher {
@SuppressWarnings("deprecation")
private static ContentValues toContentValues(Program program) {
ContentValues values = new ContentValues();
- values.put(Programs.COLUMN_CHANNEL_ID, program.getChannelId());
- putValue(values, Programs.COLUMN_TITLE, program.getTitle());
- putValue(values, Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle());
+ values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId());
+ putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle());
+ putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle());
if (BuildCompat.isAtLeastN()) {
- putValue(values, Programs.COLUMN_SEASON_DISPLAY_NUMBER, program.getSeasonNumber());
- putValue(values, Programs.COLUMN_EPISODE_DISPLAY_NUMBER, program.getEpisodeNumber());
+ putValue(values, TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+ program.getSeasonNumber());
+ putValue(values, TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+ program.getEpisodeNumber());
} else {
- putValue(values, Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber());
- putValue(values, Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber());
+ putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber());
+ putValue(values, TvContract.Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber());
}
- putValue(values, Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription());
- putValue(values, Programs.COLUMN_LONG_DESCRIPTION, program.getLongDescription());
- putValue(values, Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri());
- putValue(values, Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri());
+ putValue(values, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription());
+ putValue(values, TvContract.Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri());
+ putValue(values, TvContract.Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri());
String[] canonicalGenres = program.getCanonicalGenres();
if (canonicalGenres != null && canonicalGenres.length > 0) {
- putValue(values, Programs.COLUMN_CANONICAL_GENRE, Genres.encode(canonicalGenres));
+ putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE,
+ Genres.encode(canonicalGenres));
} else {
- putValue(values, Programs.COLUMN_CANONICAL_GENRE, "");
+ putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, "");
}
TvContentRating[] ratings = program.getContentRatings();
if (ratings != null && ratings.length > 0) {
@@ -651,13 +530,14 @@ public class EpgFetcher {
sb.append(CONTENT_RATING_SEPARATOR);
sb.append(ratings[i].flattenToString());
}
- putValue(values, Programs.COLUMN_CONTENT_RATING, sb.toString());
+ putValue(values, TvContract.Programs.COLUMN_CONTENT_RATING, sb.toString());
} else {
- putValue(values, Programs.COLUMN_CONTENT_RATING, "");
+ putValue(values, TvContract.Programs.COLUMN_CONTENT_RATING, "");
}
- values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, program.getStartTimeUtcMillis());
- values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis());
- putValue(values, Programs.COLUMN_INTERNAL_PROVIDER_DATA,
+ 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,
InternalDataUtils.serializeInternalProviderData(program));
return values;
}
@@ -689,9 +569,6 @@ public class EpgFetcher {
case MSG_FETCH_EPG:
epgFetcher.onFetchEpg();
break;
- case MSG_FAST_FETCH_EPG:
- epgFetcher.onFetchEpg(true);
- break;
default:
super.handleMessage(msg);
break;
@@ -702,7 +579,7 @@ public class EpgFetcher {
private class EpgRunner implements Runnable {
@Override
public void run() {
- postFetchRequest(false, 0);
+ fetchEpg();
}
}
}
diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java
index 95cd933e..4f3b6f52 100644
--- a/src/com/android/tv/data/epg/EpgReader.java
+++ b/src/com/android/tv/data/epg/EpgReader.java
@@ -22,10 +22,9 @@ 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.dvr.data.SeriesInfo;
+import com.android.tv.dvr.SeriesInfo;
import java.util.List;
-import java.util.Map;
/**
* An interface used to retrieve the EPG data. This class should be used in worker thread.
@@ -44,37 +43,21 @@ public interface EpgReader {
long getEpgTimestamp();
/**
- * Returns the lineups list.
- */
- List<Lineup> getLineups(@NonNull String postalCode);
-
- /**
- * Returns the list of channel numbers (unsorted) for the given lineup. The result is used to
- * choose the most appropriate lineup among others by comparing the channel numbers of the
- * existing channels on the device.
- */
- List<String> getChannelNumbers(@NonNull String lineupId);
-
- /**
- * 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.
+ * Returns the channels list.
*/
List<Channel> getChannels(@NonNull String lineupId);
/**
- * Returns the programs for the given channel. Must call {@link #getChannels(String)}
- * beforehand. Note that the {@code Program} doesn't have valid program ID because it's not
- * retrieved from TvProvider.
+ * Returns the lineups list.
*/
- List<Program> getPrograms(long channelId);
+ List<Lineup> getLineups(@NonNull String postalCode);
/**
- * Returns the programs for the given channels.
+ * Returns the programs for the given channel. The result is sorted by the start time.
* 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(List<Long> channelIds, long duration);
+ List<Program> getPrograms(long channelId);
/**
* Returns the series information for the given series ID.
diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java
index 220daf22..64093f89 100644
--- a/src/com/android/tv/data/epg/StubEpgReader.java
+++ b/src/com/android/tv/data/epg/StubEpgReader.java
@@ -21,11 +21,10 @@ import android.content.Context;
import com.android.tv.data.Channel;
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
-import com.android.tv.dvr.data.SeriesInfo;
+import com.android.tv.dvr.SeriesInfo;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
/**
* A stub class to read EPG.
@@ -45,17 +44,12 @@ public class StubEpgReader implements EpgReader{
}
@Override
- public List<Lineup> getLineups(String postalCode) {
- return Collections.emptyList();
- }
-
- @Override
- public List<String> getChannelNumbers(String lineupId) {
+ public List<Channel> getChannels(String lineupId) {
return Collections.emptyList();
}
@Override
- public List<Channel> getChannels(String lineupId) {
+ public List<Lineup> getLineups(String postalCode) {
return Collections.emptyList();
}
@@ -65,11 +59,6 @@ public class StubEpgReader implements EpgReader{
}
@Override
- public Map<Long, List<Program>> getPrograms(List<Long> channelIds, long duration) {
- return Collections.emptyMap();
- }
-
- @Override
public SeriesInfo getSeriesInfo(String seriesId) {
return null;
}
diff --git a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java
deleted file mode 100644
index 2ed98b87..00000000
--- a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java
+++ /dev/null
@@ -1,129 +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.dialog;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-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.data.ChannelDataManager;
-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.util.Utils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Displays the DVR history.
- */
-@TargetApi(VERSION_CODES.N)
-public class DvrHistoryDialogFragment extends SafeDismissDialogFragment {
- public static final String DIALOG_TAG = DvrHistoryDialogFragment.class.getSimpleName();
-
- private static final String TRACKER_LABEL = "DVR history";
- private final List<ScheduledRecording> mSchedules = new ArrayList<>();
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
- DvrDataManager dataManager = singletons.getDvrDataManager();
- ChannelDataManager channelDataManager = singletons.getChannelDataManager();
- for (ScheduledRecording schedule : dataManager.getAllScheduledRecordings()) {
- if (!schedule.isInProgress() && !schedule.isNotStarted()) {
- mSchedules.add(schedule);
- }
- }
- 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,
- schedule.getProgramTitleWithEpisodeNumber(getContext()));
- setText(view, R.id.channel_name, getChannelNameText(schedule));
- return view;
- }
-
- private void setText(View view, int id, String 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;
- }
-
- 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);
- }
- };
- ListView listView = new ListView(getActivity());
- listView.setAdapter(adapter);
- return new AlertDialog.Builder(getActivity()).setTitle(R.string.dvr_history_dialog_title)
- .setView(listView).create();
- }
-
- @Override
- public String getTrackerLabel() {
- return TRACKER_LABEL;
- }
-}
diff --git a/src/com/android/tv/dialog/FullscreenDialogFragment.java b/src/com/android/tv/dialog/FullscreenDialogFragment.java
index d00422a7..d16202a1 100644
--- a/src/com/android/tv/dialog/FullscreenDialogFragment.java
+++ b/src/com/android/tv/dialog/FullscreenDialogFragment.java
@@ -77,7 +77,7 @@ public class FullscreenDialogFragment extends SafeDismissDialogFragment {
return mTrackerLabel;
}
- private class FullscreenDialog extends Dialog {
+ private class FullscreenDialog extends TvDialog {
public FullscreenDialog(Context context, int theme) {
super(context, theme);
}
diff --git a/src/com/android/tv/dialog/SafeDismissDialogFragment.java b/src/com/android/tv/dialog/SafeDismissDialogFragment.java
index e3390b0a..f671a87d 100644
--- a/src/com/android/tv/dialog/SafeDismissDialogFragment.java
+++ b/src/com/android/tv/dialog/SafeDismissDialogFragment.java
@@ -17,7 +17,11 @@
package com.android.tv.dialog;
import android.app.Activity;
+import android.app.Dialog;
import android.app.DialogFragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.KeyEvent;
import com.android.tv.MainActivity;
import com.android.tv.TvApplication;
@@ -35,6 +39,11 @@ public abstract class SafeDismissDialogFragment extends DialogFragment
private Tracker mTracker;
@Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new TvDialog(getActivity(), getTheme());
+ }
+
+ @Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mAttached = true;
@@ -83,4 +92,21 @@ public abstract class SafeDismissDialogFragment extends DialogFragment
super.dismiss();
}
}
+
+ protected class TvDialog extends Dialog {
+ public TvDialog(Context context, int theme) {
+ super(context, theme);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ // When a dialog is showing, key events are handled by the dialog instead of
+ // MainActivity. Therefore, unless a key is a global key, it should be handled here.
+ if (mAttached && keyCode == KeyEvent.KEYCODE_SEARCH && mActivity != null) {
+ mActivity.showSearchActivity();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+ }
}
diff --git a/src/com/android/tv/dialog/WebDialogFragment.java b/src/com/android/tv/dialog/WebDialogFragment.java
index 171a256b..75f93bb2 100644
--- a/src/com/android/tv/dialog/WebDialogFragment.java
+++ b/src/com/android/tv/dialog/WebDialogFragment.java
@@ -37,7 +37,6 @@ public class WebDialogFragment extends SafeDismissDialogFragment {
private static final String TITLE = "TITLE";
private static final String TRACKER_LABEL = "TRACKER_LABEL";
- private WebView mWebView;
private String mTrackerLabel;
/**
@@ -74,21 +73,13 @@ public class WebDialogFragment extends SafeDismissDialogFragment {
String title = getArguments().getString(TITLE);
getDialog().setTitle(title);
- mWebView = new WebView(getActivity());
- mWebView.setWebViewClient(new WebViewClient());
+ WebView webView = new WebView(getActivity());
+ webView.setWebViewClient(new WebViewClient());
String url = getArguments().getString(URL);
- mWebView.loadUrl(url);
+ webView.loadUrl(url);
Log.d(TAG, "Loading web content from " + url);
- return mWebView;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- if (mWebView != null) {
- mWebView.destroy();
- }
+ return webView;
}
@Override
diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java
index a8637449..89661df3 100644
--- a/src/com/android/tv/dvr/BaseDvrDataManager.java
+++ b/src/com/android/tv/dvr/BaseDvrDataManager.java
@@ -26,10 +26,7 @@ import android.util.Log;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-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.dvr.ScheduledRecording.RecordingState;
import com.android.tv.util.Clock;
import java.util.ArrayList;
@@ -321,41 +318,5 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
@Override
- public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) {
- List<SeriesRecording> toRemove = new ArrayList<>();
- for (long rId : seriesRecordingIds) {
- SeriesRecording seriesRecording = getSeriesRecording(rId);
- if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) {
- toRemove.add(seriesRecording);
- }
- }
- removeSeriesRecording(SeriesRecording.toArray(toRemove));
- }
-
- /**
- * Returns {@code true}, if the series recording is empty and can be removed. If a series
- * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be
- * removed.
- */
- protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) {
- if (!seriesRecording.isStopped()) {
- return false;
- }
- long seriesRecordingId = seriesRecording.getId();
- for (ScheduledRecording r : getAvailableScheduledRecordings()) {
- if (r.getSeriesRecordingId() == seriesRecordingId) {
- return false;
- }
- }
- String seriesId = seriesRecording.getSeriesId();
- for (RecordedProgram r : getRecordedPrograms()) {
- if (seriesId.equals(r.getSeriesId())) {
- return false;
- }
- }
- return true;
- }
-
- @Override
public void forgetStorage(String inputId) { }
}
diff --git a/src/com/android/tv/dvr/recorder/ConflictChecker.java b/src/com/android/tv/dvr/ConflictChecker.java
index 8aa90116..201e379e 100644
--- a/src/com/android/tv/dvr/recorder/ConflictChecker.java
+++ b/src/com/android/tv/dvr/ConflictChecker.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.annotation.TargetApi;
import android.content.ContentUris;
@@ -37,9 +37,6 @@ import com.android.tv.common.WeakHandler;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
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.HashMap;
diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java
index 6d400b82..06613667 100644
--- a/src/com/android/tv/dvr/DvrDataManager.java
+++ b/src/com/android/tv/dvr/DvrDataManager.java
@@ -21,10 +21,7 @@ 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 com.android.tv.dvr.ScheduledRecording.RecordingState;
import java.util.Collection;
import java.util.List;
@@ -214,13 +211,6 @@ public interface DvrDataManager {
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.
- */
- void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds);
-
- /**
* Listens for the DVR schedules loading finished.
*/
interface OnDvrScheduleLoadFinishedListener {
diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java
index 6d0a9959..46682a48 100644
--- a/src/com/android/tv/dvr/DvrDataManagerImpl.java
+++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java
@@ -42,11 +42,7 @@ import android.util.Range;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrStorageStatusManager.OnStorageMountChangedListener;
-import com.android.tv.dvr.data.IdGenerator;
-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.dvr.ScheduledRecording.RecordingState;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddScheduleTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddSeriesRecordingTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDeleteScheduleTask;
@@ -55,8 +51,6 @@ import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQueryScheduleTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQuerySeriesRecordingTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateScheduleTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateSeriesRecordingTask;
-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;
@@ -273,14 +267,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
removeScheduledRecording(ScheduledRecording.toArray(toDelete));
}
IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId);
- if (mRecordedProgramLoadFinished) {
- validateSeriesRecordings();
- }
mDvrLoadFinished = true;
notifyDvrScheduleLoadFinished();
+ mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
+ mDbSync.start();
if (isInitialized()) {
- mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
- mDbSync.start();
SeriesRecordingScheduler.getInstance(mContext).start();
}
}
@@ -315,9 +306,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (uri == null) {
uri = RecordedPrograms.CONTENT_URI;
}
- if (recordedPrograms == null) {
- recordedPrograms = Collections.emptyList();
- }
int match = TvProviderUriMatcher.match(uri);
if (match == TvProviderUriMatcher.MATCH_RECORDED_PROGRAM) {
if (!mRecordedProgramLoadFinished) {
@@ -330,11 +318,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
mRecordedProgramLoadFinished = true;
notifyRecordedProgramLoadFinished();
- if (isInitialized()) {
- mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
- mDbSync.start();
- }
- } else if (recordedPrograms.isEmpty()) {
+ } else if (recordedPrograms == null || recordedPrograms.isEmpty()) {
List<RecordedProgram> oldRecordedPrograms =
new ArrayList<>(mRecordedPrograms.values());
mRecordedPrograms.clear();
@@ -371,7 +355,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (isInitialized()) {
- validateSeriesRecordings();
SeriesRecordingScheduler.getInstance(mContext).start();
}
} else if (match == TvProviderUriMatcher.MATCH_RECORDED_PROGRAM_ID) {
@@ -380,15 +363,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
long id = ContentUris.parseId(uri);
if (DEBUG) Log.d(TAG, "changed recorded program #" + id + " to " + recordedPrograms);
- if (recordedPrograms.isEmpty()) {
+ if (recordedPrograms == null || recordedPrograms.isEmpty()) {
mRecordedProgramsForRemovedInput.remove(id);
RecordedProgram old = mRecordedPrograms.remove(id);
if (old != null) {
notifyRecordedProgramsRemoved(old);
- SeriesRecording r = mSeriesId2SeriesRecordings.get(old.getSeriesId());
- if (r != null && isEmptySeriesRecording(r)) {
- removeSeriesRecording(r);
- }
}
} else {
RecordedProgram recordedProgram = recordedPrograms.get(0);
@@ -613,16 +592,10 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
public void removeScheduledRecording(boolean forceRemove, ScheduledRecording... schedules) {
List<ScheduledRecording> schedulesToDelete = new ArrayList<>();
List<ScheduledRecording> schedulesNotToDelete = new ArrayList<>();
- Set<Long> seriesRecordingIdsToCheck = new HashSet<>();
for (ScheduledRecording r : schedules) {
mScheduledRecordings.remove(r.getId());
- getDeletedScheduleMap().remove(r.getProgramId());
+ getDeletedScheduleMap().remove(r.getId());
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)) {
- 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
@@ -641,19 +614,8 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (mDvrLoadFinished) {
- if (mRecordedProgramLoadFinished) {
- checkAndRemoveEmptySeriesRecording(seriesRecordingIdsToCheck);
- }
notifyScheduledRecordingRemoved(schedules);
}
- Iterator<ScheduledRecording> iterator = schedulesNotToDelete.iterator();
- while (iterator.hasNext()) {
- ScheduledRecording r = iterator.next();
- if (!mSeriesRecordings.containsKey(r.getSeriesRecordingId())) {
- iterator.remove();
- schedulesToDelete.add(r);
- }
- }
if (!schedulesToDelete.isEmpty()) {
new AsyncDeleteScheduleTask(mContext).executeOnDbThread(
ScheduledRecording.toArray(schedulesToDelete));
@@ -707,7 +669,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
private void updateScheduledRecording(boolean updateDb, final ScheduledRecording... schedules) {
List<ScheduledRecording> toUpdate = new ArrayList<>();
- Set<Long> seriesRecordingIdsToCheck = new HashSet<>();
for (ScheduledRecording r : schedules) {
if (!SoftPreconditions.checkState(mScheduledRecordings.containsKey(r.getId()), TAG,
"Recording not found for: " + r)) {
@@ -730,13 +691,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (programId != ScheduledRecording.ID_NOT_SET) {
mProgramId2ScheduledRecordings.put(programId, r);
}
- if (r.getState() == ScheduledRecording.STATE_RECORDING_FAILED
- && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) {
- // If the scheduled recording is failed, it may cause the automatically generated
- // series recording for this schedule becomes invalid (with no future schedules and
- // past recordings.) We should check and remove these series recordings.
- seriesRecordingIdsToCheck.add(r.getSeriesRecordingId());
- }
}
if (toUpdate.isEmpty()) {
return;
@@ -748,17 +702,12 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (updateDb) {
new AsyncUpdateScheduleTask(mContext).executeOnDbThread(scheduleArray);
}
- checkAndRemoveEmptySeriesRecording(seriesRecordingIdsToCheck);
removeDeletedSchedules(schedules);
}
@Override
public void updateSeriesRecording(final SeriesRecording... seriesRecordings) {
for (SeriesRecording r : seriesRecordings) {
- if (!SoftPreconditions.checkArgument(mSeriesRecordings.containsKey(r.getId()), TAG,
- "Non Existing Series ID: " + 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"
@@ -820,6 +769,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
return r.getInputId().equals(inputId);
}
});
+ List<SeriesRecording> movedSeriesRecordings =
+ moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings,
+ new Filter<SeriesRecording>() {
+ @Override
+ public boolean filter(SeriesRecording r) {
+ return r.getInputId().equals(inputId);
+ }
+ });
List<RecordedProgram> movedRecordedPrograms =
moveElements(mRecordedProgramsForRemovedInput, mRecordedPrograms,
new Filter<RecordedProgram>() {
@@ -828,21 +785,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
return r.getInputId().equals(inputId);
}
});
- List<SeriesRecording> removedSeriesRecordings = new ArrayList<>();
- List<SeriesRecording> movedSeriesRecordings =
- moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings,
- new Filter<SeriesRecording>() {
- @Override
- public boolean filter(SeriesRecording r) {
- if (r.getInputId().equals(inputId)) {
- if (!isEmptySeriesRecording(r)) {
- return true;
- }
- removedSeriesRecordings.add(r);
- }
- return false;
- }
- });
if (!movedSchedules.isEmpty()) {
for (ScheduledRecording schedule : movedSchedules) {
mProgramId2ScheduledRecordings.put(schedule.getProgramId(), schedule);
@@ -853,11 +795,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
mSeriesId2SeriesRecordings.put(seriesRecording.getSeriesId(), seriesRecording);
}
}
- for (SeriesRecording r : removedSeriesRecordings) {
- mSeriesRecordingsForRemovedInput.remove(r.getId());
- }
- new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(
- SeriesRecording.toArray(removedSeriesRecordings));
// Notify after all the data are moved.
if (!movedSchedules.isEmpty()) {
notifyScheduledRecordingAdded(ScheduledRecording.toArray(movedSchedules));
@@ -874,20 +811,20 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (DEBUG) Log.d(TAG, "hideInput " + inputId);
List<ScheduledRecording> movedSchedules =
moveElements(mScheduledRecordings, mScheduledRecordingsForRemovedInput,
- new Filter<ScheduledRecording>() {
- @Override
- public boolean filter(ScheduledRecording r) {
- return r.getInputId().equals(inputId);
- }
- });
+ new Filter<ScheduledRecording>() {
+ @Override
+ public boolean filter(ScheduledRecording r) {
+ return r.getInputId().equals(inputId);
+ }
+ });
List<SeriesRecording> movedSeriesRecordings =
moveElements(mSeriesRecordings, mSeriesRecordingsForRemovedInput,
- new Filter<SeriesRecording>() {
- @Override
- public boolean filter(SeriesRecording r) {
- return r.getInputId().equals(inputId);
- }
- });
+ new Filter<SeriesRecording>() {
+ @Override
+ public boolean filter(SeriesRecording r) {
+ return r.getInputId().equals(inputId);
+ }
+ });
List<RecordedProgram> movedRecordedPrograms =
moveElements(mRecordedPrograms, mRecordedProgramsForRemovedInput,
new Filter<RecordedProgram>() {
@@ -918,15 +855,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
- private void checkAndRemoveEmptySeriesRecording(Set<Long> seriesRecordingIds) {
- int i = 0;
- long[] rIds = new long[seriesRecordingIds.size()];
- for (long rId : seriesRecordingIds) {
- rIds[i++] = rId;
- }
- checkAndRemoveEmptySeriesRecording(rIds);
- }
-
@Override
public void forgetStorage(String inputId) {
List<ScheduledRecording> schedulesToDelete = new ArrayList<>();
@@ -973,25 +901,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}.executeOnDbThread();
}
- private void validateSeriesRecordings() {
- Iterator<SeriesRecording> iter = mSeriesRecordings.values().iterator();
- List<SeriesRecording> removedSeriesRecordings = new ArrayList<>();
- while (iter.hasNext()) {
- SeriesRecording r = iter.next();
- if (isEmptySeriesRecording(r)) {
- iter.remove();
- removedSeriesRecordings.add(r);
- }
- }
- if (!removedSeriesRecordings.isEmpty()) {
- SeriesRecording[] removed = SeriesRecording.toArray(removedSeriesRecordings);
- new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(removed);
- if (mDvrLoadFinished) {
- notifySeriesRecordingRemoved(removed);
- }
- }
- }
-
private final class RecordedProgramsQueryTask extends AsyncRecordedProgramQueryTask {
private final Uri mUri;
diff --git a/src/com/android/tv/dvr/provider/DvrDbSync.java b/src/com/android/tv/dvr/DvrDbSync.java
index 8a0c2d19..df181455 100644
--- a/src/com/android/tv/dvr/provider/DvrDbSync.java
+++ b/src/com/android/tv/dvr/DvrDbSync.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.provider;
+package com.android.tv.dvr;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -34,11 +34,6 @@ import com.android.tv.TvApplication;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
-import com.android.tv.dvr.DvrDataManagerImpl;
-import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
-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.TvProviderUriMatcher;
@@ -62,12 +57,11 @@ import java.util.Set;
*/
@MainThread
@TargetApi(Build.VERSION_CODES.N)
-public class DvrDbSync {
+class DvrDbSync {
private static final String TAG = "DvrDbSync";
private static final boolean DEBUG = false;
private final Context mContext;
- private final DvrManager mDvrManager;
private final DvrDataManagerImpl mDataManager;
private final ChannelDataManager mChannelDataManager;
private final Queue<Long> mProgramIdQueue = new LinkedList<>();
@@ -135,21 +129,17 @@ public class DvrDbSync {
}
};
- public DvrDbSync(Context context, DvrDataManagerImpl dataManager) {
- this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager(),
- TvApplication.getSingletons(context).getDvrManager(),
- SeriesRecordingScheduler.getInstance(context));
+ DvrDbSync(Context context, DvrDataManagerImpl dataManager) {
+ this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager());
}
@VisibleForTesting
DvrDbSync(Context context, DvrDataManagerImpl dataManager,
- ChannelDataManager channelDataManager, DvrManager dvrManager,
- SeriesRecordingScheduler seriesRecordingScheduler) {
+ ChannelDataManager channelDataManager) {
mContext = context;
- mDvrManager = dvrManager;
mDataManager = dataManager;
mChannelDataManager = channelDataManager;
- mSeriesRecordingScheduler = seriesRecordingScheduler;
+ mSeriesRecordingScheduler = SeriesRecordingScheduler.getInstance(context);
}
/**
@@ -289,9 +279,10 @@ 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 = TvApplication.getSingletons(mContext)
+ .getDvrManager().addSeriesRecording(program,
+ Collections.singletonList(program),
+ SeriesRecording.STATE_SERIES_STOPPED);
builder.setSeriesRecordingId(newSeriesRecording.getId());
needUpdate = true;
} else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) {
@@ -315,9 +306,8 @@ public class DvrDbSync {
// Old program belongs to a series but the new one doesn't.
seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule);
}
- // Change start time only when the recording is not started yet.
- boolean needToChangeStartTime =
- schedule.getState() != ScheduledRecording.STATE_RECORDING_IN_PROGRESS
+ // Change start time only when the recording start time has not passed.
+ boolean needToChangeStartTime = schedule.getStartTimeMs() > currentTimeMs
&& program.getStartTimeUtcMillis() != schedule.getStartTimeMs();
if (needToChangeStartTime) {
builder.setStartTimeMs(program.getStartTimeUtcMillis());
diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java
index 2effe9cf..5fa6f90f 100644
--- a/src/com/android/tv/dvr/DvrManager.java
+++ b/src/com/android/tv/dvr/DvrManager.java
@@ -46,9 +46,7 @@ import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrScheduleManager.OnInitializeListener;
-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.SeriesRecording.SeriesState;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.Utils;
@@ -236,7 +234,7 @@ public class DvrManager {
* 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) {
+ List<Program> programsToSchedule, @SeriesState int initialState) {
Log.i(TAG, "Adding series recording for program " + selectedProgram + ", and schedules: "
+ programsToSchedule);
if (!SoftPreconditions.checkState(mDataManager.isInitialized())) {
@@ -310,7 +308,8 @@ public class DvrManager {
ScheduledRecording scheduleWithSameProgram =
mDataManager.getScheduledRecordingForProgramId(program.getId());
if (scheduleWithSameProgram != null) {
- if (scheduleWithSameProgram.isNotStarted()) {
+ if (scheduleWithSameProgram.getState()
+ == ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
ScheduledRecording r = ScheduledRecording.buildFrom(scheduleWithSameProgram)
.setSeriesRecordingId(series.getId())
.build();
@@ -338,10 +337,10 @@ public class DvrManager {
*/
public void updateSeriesRecording(SeriesRecording series) {
if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
+ SeriesRecordingScheduler scheduler = SeriesRecordingScheduler.getInstance(mAppContext);
+ scheduler.pauseUpdate();
SeriesRecording previousSeries = mDataManager.getSeriesRecording(series.getId());
if (previousSeries != null) {
- // If the channel option of series changed, remove the existing schedules. The new
- // schedules will be added by SeriesRecordingScheduler or by SeriesSettingsFragment.
if (previousSeries.getChannelOption() != series.getChannelOption()
|| (previousSeries.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE
&& previousSeries.getChannelId() != series.getChannelId())) {
@@ -351,18 +350,6 @@ public class DvrManager {
for (ScheduledRecording schedule : schedules) {
if (schedule.isNotStarted()) {
schedulesToRemove.add(schedule);
- } else if (schedule.isInProgress()
- && series.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE
- && schedule.getChannelId() != series.getChannelId()) {
- stopRecording(schedule);
- }
- }
- List<ScheduledRecording> deletedSchedules =
- new ArrayList<>(mDataManager.getDeletedSchedules());
- for (ScheduledRecording deletedSchedule : deletedSchedules) {
- if (deletedSchedule.getSeriesRecordingId() == series.getId()
- && deletedSchedule.getEndTimeMs() > System.currentTimeMillis()) {
- schedulesToRemove.add(deletedSchedule);
}
}
mDataManager.removeScheduledRecording(true,
@@ -376,7 +363,7 @@ public class DvrManager {
List<ScheduledRecording> schedulesToUpdate = new ArrayList<>();
for (ScheduledRecording schedule
: mDataManager.getScheduledRecordings(series.getId())) {
- if (schedule.isNotStarted() || schedule.isInProgress()) {
+ if (schedule.isNotStarted()) {
schedulesToUpdate.add(ScheduledRecording.buildFrom(schedule)
.setPriority(priority).build());
}
@@ -386,6 +373,7 @@ public class DvrManager {
ScheduledRecording.toArray(schedulesToUpdate));
}
}
+ scheduler.resumeUpdate();
}
}
@@ -412,6 +400,33 @@ public class DvrManager {
}
/**
+ * Returns true, if the series recording can be removed. If a series recording is NORMAL state
+ * or has recordings or schedules, it cannot be removed.
+ */
+ public boolean canRemoveSeriesRecording(long seriesRecordingId) {
+ SeriesRecording seriesRecording = mDataManager.getSeriesRecording(seriesRecordingId);
+ if (seriesRecording == null) {
+ return false;
+ }
+ if (!seriesRecording.isStopped()) {
+ return false;
+ }
+ for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) {
+ if (r.getSeriesRecordingId() == seriesRecordingId) {
+ return false;
+ }
+ }
+ String seriesId = seriesRecording.getSeriesId();
+ SoftPreconditions.checkNotNull(seriesId);
+ for (RecordedProgram r : mDataManager.getRecordedPrograms()) {
+ if (seriesId.equals(r.getSeriesId())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Stops the currently recorded program
*/
public void stopRecording(final ScheduledRecording recording) {
@@ -642,9 +657,6 @@ public class DvrManager {
if (!mDataManager.isDvrScheduleLoadFinished() || channel == null) {
return false;
}
- if (channel.isRecordingProhibited()) {
- return false;
- }
TvInputInfo info = Utils.getTvInputInfoForChannelId(mAppContext, channel.getId());
if (info == null) {
Log.w(TAG, "Could not find TvInputInfo for " + channel);
@@ -668,12 +680,7 @@ public class DvrManager {
if (!mDataManager.isInitialized()) {
return false;
}
- Channel channel = TvApplication.getSingletons(mAppContext).getChannelDataManager()
- .getChannel(program.getChannelId());
- if (channel == null || channel.isRecordingProhibited()) {
- return false;
- }
- TvInputInfo info = Utils.getTvInputInfoForChannelId(mAppContext, channel.getId());
+ TvInputInfo info = Utils.getTvInputInfoForProgram(mAppContext, program);
if (info == null) {
Log.w(TAG, "Could not find TvInputInfo for " + program);
return false;
@@ -726,17 +733,6 @@ public class DvrManager {
return mDataManager.getSeriesRecording(program.getSeriesId());
}
- /**
- * Returns if there are valid items. Valid item contains {@link RecordedProgram},
- * available {@link ScheduledRecording} and {@link SeriesRecording}.
- */
- public boolean hasValidItems() {
- return !(mDataManager.getRecordedPrograms().isEmpty()
- && mDataManager.getStartedRecordings().isEmpty()
- && mDataManager.getNonStartedScheduledRecordings().isEmpty()
- && mDataManager.getSeriesRecordings().isEmpty());
- }
-
@WorkerThread
@VisibleForTesting
// Should be public to use mock DvrManager object.
@@ -844,10 +840,9 @@ public class DvrManager {
}
/**
- * Listener to stop recording request. Should only be internally used inside dvr and its
- * sub-package.
+ * Listener internally used inside dvr package.
*/
- public interface Listener {
+ interface Listener {
void onStopRecordingRequested(ScheduledRecording scheduledRecording);
}
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/DvrPlaybackActivity.java
index 2437d1f5..5deda44a 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
+++ b/src/com/android/tv/dvr/DvrPlaybackActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.playback;
+package com.android.tv.dvr;
import android.app.Activity;
import android.content.Intent;
@@ -24,7 +24,7 @@ import android.util.Log;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.DvrPlaybackOverlayFragment;
/**
* Activity to play a {@link RecordedProgram}.
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java
index 843d2dbe..9759a856 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
+++ b/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.playback;
+package com.android.tv.dvr;
import android.app.Activity;
import android.content.Intent;
@@ -31,16 +31,14 @@ 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.data.ChannelDataManager;
-import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.DvrPlaybackOverlayFragment;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.TimeShiftUtils;
import com.android.tv.util.Utils;
-class DvrPlaybackMediaSessionHelper {
+public class DvrPlaybackMediaSessionHelper {
private static final String TAG = "DvrPlaybackMediaSessionHelper";
private static final boolean DEBUG = false;
@@ -104,7 +102,6 @@ class DvrPlaybackMediaSessionHelper {
}
if (mMediaSession != null) {
mMediaSession.release();
- mMediaSession = null;
}
}
@@ -182,88 +179,83 @@ class DvrPlaybackMediaSessionHelper {
cardTitleText = (channel != null) ? channel.getDisplayName()
: mActivity.getString(R.string.no_program_information);
}
- final MediaMetadata currentMetadata = updateMetadataTextInfo(program.getId(), cardTitleText,
- program.getDescription(), mProgramDurationMs);
+ updateMediaMetadata(program.getId(), cardTitleText, program.getDescription(),
+ mProgramDurationMs, null, 0);
String posterArtUri = program.getPosterArtUri();
if (posterArtUri == null) {
posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString();
}
- updatePosterArt(program, currentMetadata, null, posterArtUri);
+ updatePosterArt(program, cardTitleText, program.getDescription(),
+ mProgramDurationMs, null, posterArtUri);
mMediaSession.setActive(true);
}
- private void updatePosterArt(RecordedProgram program, MediaMetadata currentMetadata,
+ private void updatePosterArt(RecordedProgram program, String cardTitleText,
+ String cardSubtitleText, long duration,
@Nullable Bitmap posterArt, @Nullable String posterArtUri) {
if (posterArt != null) {
- updateMetadataImageInfo(program, currentMetadata, posterArt, 0);
+ updateMediaMetadata(program.getId(), cardTitleText,
+ cardSubtitleText, duration, posterArt, 0);
} else if (posterArtUri != null) {
ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth,
- mNowPlayingCardHeight,
- new ProgramPosterArtCallback(mActivity, program, currentMetadata));
+ mNowPlayingCardHeight, new ProgramPosterArtCallback(
+ mActivity, program, cardTitleText, cardSubtitleText, duration));
} else {
- updateMetadataImageInfo(program, currentMetadata, null, R.drawable.default_now_card);
+ updateMediaMetadata(program.getId(), cardTitleText,
+ cardSubtitleText, duration, null, R.drawable.default_now_card);
}
}
private class ProgramPosterArtCallback extends
ImageLoader.ImageLoaderCallback<Activity> {
- private final RecordedProgram mRecordedProgram;
- private final MediaMetadata mCurrentMetadata;
+ private RecordedProgram mRecordedProgram;
+ private String mCardTitleText;
+ private String mCardSubtitleText;
+ private long mDuration;
public ProgramPosterArtCallback(Activity activity, RecordedProgram program,
- MediaMetadata metadata) {
+ String cardTitleText, String cardSubtitleText, long duration) {
super(activity);
mRecordedProgram = program;
- mCurrentMetadata = metadata;
+ mCardTitleText = cardTitleText;
+ mCardSubtitleText = cardSubtitleText;
+ mDuration = duration;
}
@Override
public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) {
if (isCurrentProgram(mRecordedProgram)) {
- updatePosterArt(mRecordedProgram, mCurrentMetadata, posterArt, null);
+ updatePosterArt(mRecordedProgram, mCardTitleText,
+ mCardSubtitleText, mDuration, posterArt, null);
}
}
}
- 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)
- .putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
- if (subtitle != null) {
- builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
- }
- MediaMetadata metadata = builder.build();
- mMediaSession.setMetadata(metadata);
- return metadata;
- }
-
- 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) {
- builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
+ private void updateMediaMetadata(final long programId, final String title,
+ final String subtitle, final long duration,
+ final Bitmap posterArt, final int imageResId) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ builder.putLong(MediaMetadata.METADATA_KEY_MEDIA_ID, programId)
+ .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+ .putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
+ if (subtitle != null) {
+ builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
+ }
+ Bitmap programPosterArt = posterArt;
+ if (programPosterArt == null && imageResId != 0) {
+ programPosterArt =
+ BitmapFactory.decodeResource(mActivity.getResources(), imageResId);
+ }
+ if (programPosterArt != null) {
+ builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt);
+ }
mMediaSession.setMetadata(builder.build());
- } else {
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... arg0) {
- return BitmapFactory.decodeResource(mActivity.getResources(), imageResId);
- }
-
- @Override
- protected void onPostExecute(Bitmap programPosterArt) {
- if (mMediaSession != null && programPosterArt != null
- && isCurrentProgram(program)) {
- builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt);
- mMediaSession.setMetadata(builder.build());
- }
- }
- }.execute();
+ return null;
}
- }
+ }.execute();
}
// An event was triggered by MediaController.TransportControls and must be handled here.
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java b/src/com/android/tv/dvr/DvrPlayer.java
index 780bfb2f..5656655c 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
+++ b/src/com/android/tv/dvr/DvrPlayer.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.playback;
+package com.android.tv.dvr;
import android.media.PlaybackParams;
import android.media.tv.TvContentRating;
@@ -22,16 +22,12 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.session.PlaybackState;
-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;
-class DvrPlayer {
+public class DvrPlayer {
private static final String TAG = "DvrPlayer";
private static final boolean DEBUG = false;
@@ -51,19 +47,12 @@ class DvrPlayer {
private long mInitialSeekPositionMs;
private final TvView mTvView;
private DvrPlayerCallback mCallback;
- private OnAspectRatioChangedListener mOnAspectRatioChangedListener;
- private OnContentBlockedListener mOnContentBlockedListener;
- private OnTracksAvailabilityChangedListener mOnTracksAvailabilityChangedListener;
- private OnTrackSelectedListener mOnAudioTrackSelectedListener;
- private OnTrackSelectedListener mOnSubtitleTrackSelectedListener;
- private String mSelectedAudioTrackId;
- private String mSelectedSubtitleTrackId;
+ private AspectRatioChangedListener mAspectRatioChangedListener;
+ private ContentBlockedListener mContentBlockedListener;
private float mAspectRatio = Float.NaN;
private int mPlaybackState = PlaybackState.STATE_NONE;
private long mTimeShiftCurrentPositionMs;
private boolean mPauseOnPrepared;
- private boolean mHasClosedCaption;
- private boolean mHasMultiAudio;
private final PlaybackParams mPlaybackParams = new PlaybackParams();
private final DvrPlayerCallback mEmptyCallback = new DvrPlayerCallback();
private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
@@ -86,40 +75,22 @@ class DvrPlayer {
public void onPlaybackEnded() { }
}
- public interface OnAspectRatioChangedListener {
+ public interface AspectRatioChangedListener {
/**
* 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.
*/
void onAspectRatioChanged(float videoAspectRatio);
}
- public interface OnContentBlockedListener {
+ public interface ContentBlockedListener {
/**
* 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.
- */
- void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio);
- }
-
- public interface OnTrackSelectedListener {
- /**
- * Called when certain subtitle or audio track is selected.
- */
- void onTrackSelected(String selectedTrackId);
- }
-
public DvrPlayer(TvView tvView) {
mTvView = tvView;
- mTvView.setCaptionEnabled(true);
mPlaybackParams.setSpeed(1.0f);
setTvViewCallbacks();
setCallback(null);
@@ -265,8 +236,6 @@ class DvrPlayer {
mTimeShiftCurrentPositionMs = 0;
mPlaybackParams.setSpeed(1.0f);
mProgram = null;
- mSelectedAudioTrackId = null;
- mSelectedSubtitleTrackId = null;
}
/**
@@ -281,51 +250,17 @@ class DvrPlayer {
}
/**
- * Sets the listener to aspect ratio changing.
- */
- public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) {
- mOnAspectRatioChangedListener = listener;
- }
-
- /**
- * Sets the listener to content blocking.
- */
- public void setOnContentBlockedListener(OnContentBlockedListener listener) {
- mOnContentBlockedListener = listener;
- }
-
- /**
- * Sets the listener to tracks changing.
- */
- public void setOnTracksAvailabilityChangedListener(
- OnTracksAvailabilityChangedListener listener) {
- mOnTracksAvailabilityChangedListener = listener;
- }
-
- /**
- * 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}.
+ * Sets listener to aspect ratio changing.
*/
- public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) {
- if (trackType == TvTrackInfo.TYPE_AUDIO) {
- mOnAudioTrackSelectedListener = listener;
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- mOnSubtitleTrackSelectedListener = listener;
- }
+ public void setAspectRatioChangedListener(AspectRatioChangedListener listener) {
+ mAspectRatioChangedListener = listener;
}
/**
- * Gets the listener to tracks of the given type being selected.
+ * Sets listener to content blocking.
*/
- public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) {
- if (trackType == TvTrackInfo.TYPE_AUDIO) {
- return mOnAudioTrackSelectedListener;
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- return mOnSubtitleTrackSelectedListener;
- }
- return null;
+ public void setContentBlockedListener(ContentBlockedListener listener) {
+ mContentBlockedListener = listener;
}
/**
@@ -371,32 +306,6 @@ class DvrPlayer {
}
/**
- * 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.
- */
- public ArrayList<TvTrackInfo> getAudioTracks() {
- return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_AUDIO));
- }
-
- /**
- * Returns the ID of the selected track of the given type.
- */
- public String getSelectedTrackId(int trackType) {
- if (trackType == TvTrackInfo.TYPE_AUDIO) {
- return mSelectedAudioTrackId;
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- return mSelectedSubtitleTrackId;
- }
- return null;
- }
-
- /**
* Returns if playback of the recorded program is started.
*/
public boolean isPlaybackPrepared() {
@@ -404,41 +313,6 @@ class DvrPlayer {
&& mPlaybackState != PlaybackState.STATE_CONNECTING;
}
- /**
- * Selects the given track.
- *
- * @return ID of the selected track.
- */
- String selectTrack(int trackType, TvTrackInfo selectedTrack) {
- String oldSelectedTrackId = getSelectedTrackId(trackType);
- String newSelectedTrackId = selectedTrack == null ? null : selectedTrack.getId();
- if (!TextUtils.equals(oldSelectedTrackId, newSelectedTrackId)) {
- if (selectedTrack == null) {
- mTvView.selectTrack(trackType, null);
- return null;
- } else {
- List<TvTrackInfo> tracks = mTvView.getTracks(trackType);
- if (tracks != null && tracks.contains(selectedTrack)) {
- mTvView.selectTrack(trackType, newSelectedTrackId);
- return newSelectedTrackId;
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE && oldSelectedTrackId != null) {
- // Track not found, disabled closed caption.
- mTvView.selectTrack(trackType, null);
- return null;
- }
- }
- }
- return oldSelectedTrackId;
- }
-
- private void setSelectedTrackId(int trackType, String trackId) {
- if (trackType == TvTrackInfo.TYPE_AUDIO) {
- mSelectedAudioTrackId = trackId;
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- mSelectedSubtitleTrackId = trackId;
- }
- }
-
private void setPlaybackSpeed(int speed) {
mPlaybackParams.setSpeed(speed);
mTvView.timeShiftSetPlaybackParams(mPlaybackParams);
@@ -495,60 +369,28 @@ class DvrPlayer {
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);
- }
- } 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;
- }
+ if (trackId == null || type != TvTrackInfo.TYPE_VIDEO
+ || mAspectRatioChangedListener == null) {
+ return;
+ }
+ List<TvTrackInfo> trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
+ if (trackInfos != null) {
+ for (TvTrackInfo trackInfo : trackInfos) {
+ if (trackInfo.getId().equals(trackId)) {
+ float videoAspectRatio = trackInfo.getVideoPixelAspectRatio()
+ * trackInfo.getVideoWidth() / trackInfo.getVideoHeight();
+ if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
+ if (!Float.isNaN(videoAspectRatio)
+ && mAspectRatio != videoAspectRatio) {
+ mAspectRatioChangedListener
+ .onAspectRatioChanged(videoAspectRatio);
+ mAspectRatio = videoAspectRatio;
+ return;
}
}
}
@@ -557,8 +399,8 @@ class DvrPlayer {
@Override
public void onContentBlocked(String inputId, TvContentRating rating) {
- if (mOnContentBlockedListener != null) {
- mOnContentBlockedListener.onContentBlocked(rating);
+ if (mContentBlockedListener != null) {
+ mContentBlockedListener.onContentBlocked(rating);
}
}
});
diff --git a/src/com/android/tv/dvr/recorder/DvrRecordingService.java b/src/com/android/tv/dvr/DvrRecordingService.java
index 08ffaf86..8c40aaa8 100644
--- a/src/com/android/tv/dvr/recorder/DvrRecordingService.java
+++ b/src/com/android/tv/dvr/DvrRecordingService.java
@@ -14,10 +14,9 @@
* limitations under the License
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.app.AlarmManager;
-import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -28,13 +27,9 @@ 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.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.util.Clock;
import com.android.tv.util.RecurringRunner;
@@ -57,8 +52,6 @@ public class DvrRecordingService extends Service {
private static final boolean DEBUG = false;
public static final String HANDLER_THREAD_NAME = "DvrRecordingService-handler";
- private static final int ONGOING_NOTIFICATION_ID = 1;
-
public static void startService(Context context) {
Intent dvrSchedulerIntent = new Intent(context, DvrRecordingService.class);
context.startService(dvrSchedulerIntent);
@@ -69,27 +62,6 @@ public class DvrRecordingService extends Service {
private Scheduler mScheduler;
private HandlerThread mHandlerThread;
- private InputSessionManager mSessionManager;
- private boolean mForeground;
-
- private final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener =
- new OnRecordingSessionChangeListener() {
- @Override
- public void onRecordingSessionChange(final boolean create, final int count) {
- if (create && !mForeground) {
- Notification notification =
- new Notification.Builder(getApplicationContext())
- .setContentTitle(TAG)
- .setSmallIcon(R.drawable.ic_dvr)
- .build();
- startForeground(ONGOING_NOTIFICATION_ID, notification);
- mForeground = true;
- } else if (!create && mForeground && count == 0) {
- stopForeground(STOP_FOREGROUND_REMOVE);
- mForeground = false;
- }
- }
- };
@Override
public void onCreate() {
@@ -98,11 +70,8 @@ public class DvrRecordingService extends Service {
super.onCreate();
SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG);
ApplicationSingletons singletons = TvApplication.getSingletons(this);
- WritableDvrDataManager dataManager =
- (WritableDvrDataManager) singletons.getDvrDataManager();
- mSessionManager = singletons.getInputSessionManager();
+ WritableDvrDataManager dataManager = (WritableDvrDataManager) singletons.getDvrDataManager();
- mSessionManager.addOnRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// mScheduler may have been set for testing.
if (mScheduler == null) {
@@ -136,7 +105,6 @@ public class DvrRecordingService extends Service {
mHandlerThread.quitSafely();
mHandlerThread = null;
}
- mSessionManager.removeRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
super.onDestroy();
}
diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java
index b72117aa..a5851a75 100644
--- a/src/com/android/tv/dvr/DvrScheduleManager.java
+++ b/src/com/android/tv/dvr/DvrScheduleManager.java
@@ -24,6 +24,7 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
+import android.util.LongSparseArray;
import android.util.Range;
import com.android.tv.ApplicationSingletons;
@@ -34,10 +35,7 @@ import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
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.util.Utils;
import java.util.ArrayList;
@@ -90,8 +88,9 @@ public class DvrScheduleManager {
private final Map<String, List<ScheduledRecording>> mInputScheduleMap = new HashMap<>();
// The inner map is a hash map from scheduled recording to its conflicting status, i.e.,
// the boolean value true denotes the schedule is just partially conflicting, which means
- // although there's conflict, it might still be recorded partially.
- private final Map<String, Map<Long, ConflictInfo>> mInputConflictInfoMap = new HashMap<>();
+ // although there's conflictit, it might still be recorded partially.
+ private final Map<String, Map<ScheduledRecording, Boolean>> mInputConflictInfoMap =
+ new HashMap<>();
private boolean mInitialized;
@@ -172,9 +171,10 @@ public class DvrScheduleManager {
mInputScheduleMap.remove(inputId);
}
}
- Map<Long, ConflictInfo> conflictInfo = mInputConflictInfoMap.get(inputId);
+ Map<ScheduledRecording, Boolean> conflictInfo =
+ mInputConflictInfoMap.get(inputId);
if (conflictInfo != null) {
- conflictInfo.remove(schedule.getId());
+ conflictInfo.remove(schedule);
if (conflictInfo.isEmpty()) {
mInputConflictInfoMap.remove(inputId);
}
@@ -221,11 +221,21 @@ public class DvrScheduleManager {
mInputScheduleMap.remove(inputId);
}
// Update conflict list as well
- Map<Long, ConflictInfo> conflictInfo = mInputConflictInfoMap.get(inputId);
+ Map<ScheduledRecording, Boolean> conflictInfo =
+ mInputConflictInfoMap.get(inputId);
if (conflictInfo != null) {
- ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId());
- if (oldConflictInfo != null) {
- oldConflictInfo.schedule = schedule;
+ // Compare ID because ScheduledRecording.equals() doesn't work if the state
+ // is changed.
+ ScheduledRecording oldSchedule = null;
+ for (ScheduledRecording s : conflictInfo.keySet()) {
+ if (s.getId() == schedule.getId()) {
+ oldSchedule = s;
+ break;
+ }
+ }
+ if (oldSchedule != null) {
+ conflictInfo.put(schedule, conflictInfo.get(oldSchedule));
+ conflictInfo.remove(oldSchedule);
}
}
}
@@ -307,25 +317,24 @@ public class DvrScheduleManager {
List<ScheduledRecording> addedConflicts = new ArrayList<>();
List<ScheduledRecording> removedConflicts = new ArrayList<>();
for (String inputId : mInputScheduleMap.keySet()) {
- Map<Long, ConflictInfo> oldConflictInfo = mInputConflictInfoMap.get(inputId);
+ Map<ScheduledRecording, Boolean> oldConflictsInfo = mInputConflictInfoMap.get(inputId);
Map<Long, ScheduledRecording> oldConflictMap = new HashMap<>();
- if (oldConflictInfo != null) {
- for (ConflictInfo conflictInfo : oldConflictInfo.values()) {
- oldConflictMap.put(conflictInfo.schedule.getId(), conflictInfo.schedule);
+ if (oldConflictsInfo != null) {
+ for (ScheduledRecording r : oldConflictsInfo.keySet()) {
+ oldConflictMap.put(r.getId(), r);
}
}
- List<ConflictInfo> conflicts = getConflictingSchedulesInfo(inputId);
- if (conflicts.isEmpty()) {
+ Map<ScheduledRecording, Boolean> conflictInfo = getConflictingSchedulesInfo(inputId);
+ if (conflictInfo.isEmpty()) {
mInputConflictInfoMap.remove(inputId);
} else {
- Map<Long, ConflictInfo> conflictInfos = new HashMap<>();
- for (ConflictInfo conflictInfo : conflicts) {
- conflictInfos.put(conflictInfo.schedule.getId(), conflictInfo);
- if (oldConflictMap.remove(conflictInfo.schedule.getId()) == null) {
- addedConflicts.add(conflictInfo.schedule);
+ mInputConflictInfoMap.put(inputId, conflictInfo);
+ List<ScheduledRecording> conflicts = new ArrayList<>(conflictInfo.keySet());
+ for (ScheduledRecording r : conflicts) {
+ if (oldConflictMap.remove(r.getId()) == null) {
+ addedConflicts.add(r);
}
}
- mInputConflictInfoMap.put(inputId, conflictInfos);
}
removedConflicts.addAll(oldConflictMap.values());
}
@@ -556,7 +565,8 @@ public class DvrScheduleManager {
}
/**
- * Returns list of all conflicting scheduled recordings for the given {@code seriesRecording}
+ * Returns list of all conflicting scheduled recordings with schedules belonging to {@code
+ * seriesRecording}
* recording.
* <p>
* Any empty list means there is no conflicts.
@@ -571,18 +581,9 @@ public class DvrScheduleManager {
if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
return Collections.emptyList();
}
- List<ScheduledRecording> scheduledRecordingForSeries = mDataManager.getScheduledRecordings(
+ List<ScheduledRecording> schedulesForSeries = mDataManager.getScheduledRecordings(
seriesRecording.getId());
- List<ScheduledRecording> availableScheduledRecordingForSeries = new ArrayList<>();
- for (ScheduledRecording scheduledRecording : scheduledRecordingForSeries) {
- if (scheduledRecording.isNotStarted() || scheduledRecording.isInProgress()) {
- availableScheduledRecordingForSeries.add(scheduledRecording);
- }
- }
- if (availableScheduledRecordingForSeries.isEmpty()) {
- return Collections.emptyList();
- }
- return getConflictingSchedules(input, availableScheduledRecordingForSeries);
+ return getConflictingSchedules(input, schedulesForSeries);
}
/**
@@ -616,16 +617,16 @@ public class DvrScheduleManager {
* the given input.
*/
@NonNull
- private List<ConflictInfo> getConflictingSchedulesInfo(String inputId) {
+ private Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(String inputId) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, inputId);
SoftPreconditions.checkState(input != null, TAG, "Can't find input for : " + inputId);
if (!mInitialized || input == null) {
- return Collections.emptyList();
+ return Collections.emptyMap();
}
List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId());
if (schedules == null || schedules.isEmpty()) {
- return Collections.emptyList();
+ return Collections.emptyMap();
}
return getConflictingSchedulesInfo(schedules, input.getTunerCount());
}
@@ -644,8 +645,8 @@ public class DvrScheduleManager {
if (!mInitialized || input == null) {
return false;
}
- Map<Long, ConflictInfo> conflicts = mInputConflictInfoMap.get(input.getId());
- return conflicts != null && conflicts.containsKey(schedule.getId());
+ Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId());
+ return conflicts != null && conflicts.containsKey(schedule);
}
/**
@@ -663,12 +664,8 @@ public class DvrScheduleManager {
if (!mInitialized || input == null) {
return false;
}
- Map<Long, ConflictInfo> conflicts = mInputConflictInfoMap.get(input.getId());
- if (conflicts != null) {
- ConflictInfo conflictInfo = conflicts.get(schedule.getId());
- return conflictInfo != null && conflictInfo.partialConflict;
- }
- return false;
+ Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId());
+ return conflicts != null && conflicts.getOrDefault(schedule, false);
}
/**
@@ -816,17 +813,15 @@ public class DvrScheduleManager {
@VisibleForTesting
static List<ScheduledRecording> getConflictingSchedules(
List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
- List<ScheduledRecording> result = new ArrayList<>();
- for (ConflictInfo conflictInfo :
- getConflictingSchedulesInfo(schedules, tunerCount, periods)) {
- result.add(conflictInfo.schedule);
- }
+ List<ScheduledRecording> result = new ArrayList<>(
+ getConflictingSchedulesInfo(schedules, tunerCount, periods).keySet());
+ Collections.sort(result, RESULT_COMPARATOR);
return result;
}
@VisibleForTesting
- static List<ConflictInfo> getConflictingSchedulesInfo(List<ScheduledRecording> schedules,
- int tunerCount) {
+ static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(
+ List<ScheduledRecording> schedules, int tunerCount) {
return getConflictingSchedulesInfo(schedules, tunerCount, null);
}
@@ -841,13 +836,13 @@ public class DvrScheduleManager {
* to be partially recorded under the given schedules and tuner count {@code true},
* or not {@code false}.
*/
- private static List<ConflictInfo> getConflictingSchedulesInfo(
+ private static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(
List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules);
// Sort by the same order as that in InputTaskScheduler.
Collections.sort(schedulesToCheck, InputTaskScheduler.getRecordingOrderComparator());
List<ScheduledRecording> recordings = new ArrayList<>();
- Map<ScheduledRecording, ConflictInfo> conflicts = new HashMap<>();
+ Map<ScheduledRecording, Boolean> conflicts = new HashMap<>();
Map<ScheduledRecording, ScheduledRecording> modified2OriginalSchedules = new HashMap<>();
// Simulate InputTaskScheduler.
while (!schedulesToCheck.isEmpty()) {
@@ -858,29 +853,26 @@ public class DvrScheduleManager {
if (modified2OriginalSchedules.containsKey(schedule)) {
// Schedule has been modified, which means it's already conflicted.
// Modify its state to partially conflicted.
- ScheduledRecording originalSchedule = modified2OriginalSchedules.get(schedule);
- conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true));
+ conflicts.put(modified2OriginalSchedules.get(schedule), true);
}
} else {
ScheduledRecording candidate = findReplaceableRecording(recordings, schedule);
if (candidate != null) {
if (!modified2OriginalSchedules.containsKey(candidate)) {
- conflicts.put(candidate, new ConflictInfo(candidate, true));
+ conflicts.put(candidate, true);
}
recordings.remove(candidate);
recordings.add(schedule);
if (modified2OriginalSchedules.containsKey(schedule)) {
// Schedule has been modified, which means it's already conflicted.
// Modify its state to partially conflicted.
- ScheduledRecording originalSchedule =
- modified2OriginalSchedules.get(schedule);
- conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true));
+ conflicts.put(modified2OriginalSchedules.get(schedule), true);
}
} else {
if (!modified2OriginalSchedules.containsKey(schedule)) {
// if schedule has been modified, it's already conflicted.
// No need to add it again.
- conflicts.put(schedule, new ConflictInfo(schedule, false));
+ conflicts.put(schedule, false);
}
long earliestEndTime = getEarliestEndTime(recordings);
if (earliestEndTime < schedule.getEndTimeMs()) {
@@ -920,14 +912,7 @@ 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);
- }
- });
- return result;
+ return conflicts;
}
private static void removeFinishedRecordings(List<ScheduledRecording> recordings,
@@ -969,17 +954,6 @@ public class DvrScheduleManager {
return earliest;
}
- @VisibleForTesting
- static class ConflictInfo {
- public ScheduledRecording schedule;
- public boolean partialConflict;
-
- ConflictInfo(ScheduledRecording schedule, boolean partialConflict) {
- this.schedule = schedule;
- this.partialConflict = partialConflict;
- }
- }
-
/**
* A listener which is notified the initialization of schedule manager.
*/
@@ -996,9 +970,6 @@ public class DvrScheduleManager {
public interface OnConflictStateChangeListener {
/**
* Called when the conflicting schedules change.
- * <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}.
diff --git a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/DvrStartRecordingReceiver.java
index 8c6ee145..6d2f0d43 100644
--- a/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java
+++ b/src/com/android/tv/dvr/DvrStartRecordingReceiver.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import com.android.tv.TvApplication;
diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java
index 2d41d732..a653b5f4 100644
--- a/src/com/android/tv/dvr/DvrStorageStatusManager.java
+++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java
@@ -25,7 +25,6 @@ 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;
@@ -37,11 +36,8 @@ import android.support.annotation.IntDef;
import android.support.annotation.WorkerThread;
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.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import java.io.File;
@@ -298,7 +294,7 @@ public class DvrStorageStatusManager {
storageMounted, storageMountedDir, storageMountedCapacity);
}
- private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> {
+ private class CleanUpDbTask extends AsyncTask<Void, Void, Void> {
private final ContentResolver mContentResolver;
private CleanUpDbTask() {
@@ -306,15 +302,13 @@ public class DvrStorageStatusManager {
}
@Override
- protected Boolean doInBackground(Void... params) {
+ protected Void doInBackground(Void... params) {
@DvrStorageStatusManager.StorageStatus int storageStatus = getDvrStorageStatus();
if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
return null;
}
- if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
- return true;
- }
- List<ContentProviderOperation> ops = getDeleteOps();
+ List<ContentProviderOperation> ops = getDeleteOps(storageStatus
+ == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL);
if (ops == null || ops.isEmpty()) {
return null;
}
@@ -335,28 +329,13 @@ public class DvrStorageStatusManager {
}
@Override
- protected void onPostExecute(Boolean forgetStorage) {
- if (forgetStorage != null && forgetStorage == true) {
- DvrManager dvrManager = TvApplication.getSingletons(mContext).getDvrManager();
- TvInputManagerHelper tvInputManagerHelper =
- TvApplication.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())) {
- dvrManager.forgetStorage(info.getId());
- }
- }
- }
+ protected void onPostExecute(Void result) {
if (mCleanUpDbTask == this) {
mCleanUpDbTask = null;
}
}
- private List<ContentProviderOperation> getDeleteOps() {
+ private List<ContentProviderOperation> getDeleteOps(boolean deleteAll) {
List<ContentProviderOperation> ops = new ArrayList<>();
try (Cursor c = mContentResolver.query(
@@ -385,7 +364,7 @@ public class DvrStorageStatusManager {
continue;
}
File recordedProgramDir = new File(dataUri.getPath());
- if (!recordedProgramDir.exists()) {
+ if (deleteAll || !recordedProgramDir.exists()) {
ops.add(ContentProviderOperation.newDelete(
TvContract.buildRecordedProgramUri(Long.parseLong(id))).build());
}
diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/DvrUiHelper.java
index 507db6e7..c0d3b0c5 100644
--- a/src/com/android/tv/dvr/ui/DvrUiHelper.java
+++ b/src/com/android/tv/dvr/DvrUiHelper.java
@@ -14,21 +14,19 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr;
import android.annotation.TargetApi;
import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.media.tv.TvInputManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityOptionsCompat;
+import android.text.TextUtils;
import android.widget.ImageView;
import android.widget.Toast;
@@ -38,36 +36,32 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-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;
-import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.ui.DvrDetailsActivity;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment;
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.DvrInsufficientSpaceErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
-import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment;
import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment;
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.DvrSchedulesActivity;
+import com.android.tv.dvr.ui.DvrSchedulesActivity;
+import com.android.tv.dvr.ui.DvrSeriesDeletionActivity;
+import com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity;
+import com.android.tv.dvr.ui.DvrSeriesSettingsActivity;
+import com.android.tv.dvr.ui.DvrStopRecordingFragment;
+import com.android.tv.dvr.ui.DvrStopSeriesRecordingDialogFragment;
+import com.android.tv.dvr.ui.DvrStopSeriesRecordingFragment;
+import com.android.tv.dvr.ui.HalfSizedDialogFragment;
import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
-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.
@@ -75,54 +69,94 @@ import java.util.Set;
@MainThread
@TargetApi(Build.VERSION_CODES.N)
public class DvrUiHelper {
- private static String TAG = "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}.
+ */
+ public static boolean handleCreateSchedule(MainActivity activity, Program program) {
+ if (program == null) {
+ return false;
+ }
+ DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager();
+ if (!program.isEpisodic()) {
+ // One time recording.
+ dvrManager.addSchedule(program);
+ if (!dvrManager.getConflictingSchedules(program).isEmpty()) {
+ DvrUiHelper.showScheduleConflictDialog(activity, program);
+ return false;
+ }
+ } else {
+ SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program);
+ if (seriesRecording == null || seriesRecording.isStopped()) {
+ DvrUiHelper.showScheduleDialog(activity, program);
+ return false;
+ } else {
+ // Show recorded program rather than the schedule.
+ 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());
+ if (duplicate != null
+ && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
+ || duplicate.getState()
+ == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ DvrUiHelper.showAlreadyScheduleDialog(activity, program);
+ return false;
+ }
+ // Just add the schedule.
+ dvrManager.addSchedule(program);
+ }
+ }
+ return true;
- private static ProgressDialog sProgressDialog = null;
+ }
/**
* 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.
+ * @return true if the storage status is fine to be recorded for {@code inputId}.
*/
- public static void checkStorageStatusAndShowErrorMessage(Activity activity, String inputId,
- Runnable recordingRequestRunnable) {
- if (Utils.isBundledInput(inputId)) {
- switch (TvApplication.getSingletons(activity).getDvrStorageStatusManager()
- .getDvrStorageStatus()) {
- case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL:
- showDvrSmallSizedStorageErrorDialog(activity);
- return;
- case DvrStorageStatusManager.STORAGE_STATUS_MISSING:
- showDvrMissingStorageErrorDialog(activity);
- return;
- case DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT:
- showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable);
- return;
- }
+ public static boolean checkStorageStatusAndShowErrorMessage(Activity activity, String inputId) {
+ if (!Utils.isBundledInput(inputId)) {
+ return true;
+ }
+ DvrStorageStatusManager dvrStorageStatusManager =
+ TvApplication.getSingletons(activity).getDvrStorageStatusManager();
+ int status = dvrStorageStatusManager.getDvrStorageStatus();
+ if (status == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
+ showDvrSmallSizedStorageErrorDialog(activity);
+ return false;
+ } else if (status == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
+ showDvrMissingStorageErrorDialog(activity, inputId);
+ return false;
+ } else if (status == DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT) {
+ // TODO: handle insufficient storage case.
+ return true;
+ } else {
+ return true;
}
- recordingRequestRunnable.run();
}
/**
* Shows the schedule dialog.
*/
- public static void showScheduleDialog(Activity activity, Program program,
- boolean addCurrentProgramToSeries) {
+ public static void showScheduleDialog(MainActivity activity, Program program) {
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);
showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true);
}
/**
* Shows the recording duration options dialog.
*/
- public static void showChannelRecordDurationOptions(Activity activity, Channel channel) {
+ public static void showChannelRecordDurationOptions(MainActivity activity, Channel channel) {
if (SoftPreconditions.checkNotNull(channel) == null) {
return;
}
@@ -134,7 +168,7 @@ public class DvrUiHelper {
/**
* Shows the dialog which says that the new schedule conflicts with others.
*/
- public static void showScheduleConflictDialog(Activity activity, Program program) {
+ public static void showScheduleConflictDialog(MainActivity activity, Program program) {
if (program == null) {
return;
}
@@ -158,47 +192,20 @@ public class DvrUiHelper {
/**
* 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,
- failedScheduledRecordingInfoArray);
- showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args);
+ public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity) {
+ showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), null);
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}.
- */
- 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);
- }
- }
- });
- showDialogFragment(activity, fragment, null);
}
/**
* Shows DVR missing storage error dialog.
*/
- private static void showDvrMissingStorageErrorDialog(Activity activity) {
- showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null);
+ private static void showDvrMissingStorageErrorDialog(Activity activity, String inputId) {
+ SoftPreconditions.checkArgument(!TextUtils.isEmpty(inputId));
+ Bundle args = new Bundle();
+ args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, inputId);
+ showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), args);
}
/**
@@ -224,7 +231,7 @@ public class DvrUiHelper {
/**
* Shows "already scheduled" dialog.
*/
- public static void showAlreadyScheduleDialog(Activity activity, Program program) {
+ public static void showAlreadyScheduleDialog(MainActivity activity, Program program) {
if (program == null) {
return;
}
@@ -236,7 +243,7 @@ public class DvrUiHelper {
/**
* Shows "already recorded" dialog.
*/
- public static void showAlreadyRecordedDialog(Activity activity, Program program) {
+ public static void showAlreadyRecordedDialog(MainActivity activity, Program program) {
if (program == null) {
return;
}
@@ -245,87 +252,6 @@ public class DvrUiHelper {
showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), 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.
- */
- 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));
- 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.
- *
- * @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) {
- if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
- String msg = activity.getString(
- R.string.dvr_msg_program_scheduled, program.getTitle());
- ToastUtils.show(activity, msg, Toast.LENGTH_SHORT);
- }
- }
-
- /**
- * 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) {
- if (program == null) {
- return false;
- }
- DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager();
- if (!program.isEpisodic()) {
- // One time recording.
- dvrManager.addSchedule(program);
- if (!dvrManager.getConflictingSchedules(program).isEmpty()) {
- DvrUiHelper.showScheduleConflictDialog(activity, program);
- return false;
- }
- } else {
- // Show recorded program rather than the schedule.
- 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());
- if (duplicate != null
- && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
- || duplicate.getState()
- == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
- DvrUiHelper.showAlreadyScheduleDialog(activity, program);
- return false;
- }
- SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program);
- if (seriesRecording == null || seriesRecording.isStopped()) {
- DvrUiHelper.showScheduleDialog(activity, program, addProgramToSeries);
- return false;
- } else {
- // Just add the schedule.
- dvrManager.addSchedule(program);
- }
- }
- return true;
- }
-
private static void showDialogFragment(Activity activity,
DvrHalfSizedDialogFragment dialogFragment, Bundle args) {
showDialogFragment(activity, dialogFragment, args, false, false);
@@ -415,66 +341,19 @@ public class DvrUiHelper {
/**
* Shows the series settings activity.
*
- * @param programs list of programs which belong to the series.
+ * @param channelIds Channel ID list which has programs belonging to the series.
*/
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);
- if (series == null) {
- return;
- }
- if (programs != null) {
- 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);
- sProgressDialog = null;
- }
- });
- episodicProgramLoadTask.execute();
- }
- }
-
- 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");
+ @Nullable long[] channelIds, boolean removeEmptySeriesSchedule,
+ boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog) {
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.CHANNEL_ID_LIST, channelIds);
intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING,
removeEmptySeriesSchedule);
intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent);
intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
showViewScheduleOptionInDialog);
- intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram);
context.startActivity(intent);
}
@@ -482,8 +361,7 @@ public class DvrUiHelper {
* Shows "series recording scheduled" dialog activity.
*/
public static void StartSeriesScheduledDialogActivity(Context context,
- SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog,
- List<Program> programs) {
+ SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog) {
if (seriesRecording == null) {
return;
}
@@ -492,9 +370,6 @@ public class DvrUiHelper {
seriesRecording.getId());
intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION,
showViewScheduleOptionInDialog);
- BigArguments.reset();
- BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS,
- programs);
context.startActivity(intent);
}
@@ -572,4 +447,4 @@ public class DvrUiHelper {
Utils.toTimeString(endTimeMs, false));
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
index dc05ed06..4eada742 100644
--- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java
+++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
@@ -22,7 +22,6 @@ import android.media.tv.TvInputManager;
import android.support.annotation.IntDef;
import com.android.tv.common.SharedPreferencesUtils;
-import com.android.tv.dvr.data.RecordedProgram;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/EpisodicProgramLoadTask.java
index ba0aca51..15ca2700 100644
--- a/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
+++ b/src/com/android/tv/dvr/EpisodicProgramLoadTask.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.provider;
+package com.android.tv.dvr;
import android.annotation.TargetApi;
import android.content.Context;
@@ -24,15 +24,13 @@ import android.media.tv.TvContract.Programs;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
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.SeriesRecording;
import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask;
import com.android.tv.util.AsyncDbTask.CursorFilter;
import com.android.tv.util.PermissionUtils;
@@ -42,6 +40,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -254,13 +253,21 @@ abstract public class EpisodicProgramLoadTask {
return sqlParams;
}
+ @VisibleForTesting
+ static boolean isEpisodeScheduled(Collection<ScheduledEpisode> scheduledEpisodes,
+ ScheduledEpisode episode) {
+ // The episode whose season number or episode number is null will always be scheduled.
+ return scheduledEpisodes.contains(episode) && !TextUtils.isEmpty(episode.seasonNumber)
+ && !TextUtils.isEmpty(episode.episodeNumber);
+ }
+
/**
* Filter the programs which match the series recording. The episodes which the schedules are
* already created for are filtered out too.
*/
private class SeriesRecordingCursorFilter implements CursorFilter {
private final Set<Long> mDisallowedProgramIds = new HashSet<>();
- private final Set<SeasonEpisodeNumber> mSeasonEpisodeNumbers = new HashSet<>();
+ private final Set<ScheduledEpisode> mScheduledEpisodes = new HashSet<>();
SeriesRecordingCursorFilter(List<SeriesRecording> seriesRecordings) {
if (!mLoadDisallowedProgram) {
@@ -275,7 +282,7 @@ abstract public class EpisodicProgramLoadTask {
if (seriesRecordingIds.contains(r.getSeriesRecordingId())
&& r.getState() != ScheduledRecording.STATE_RECORDING_FAILED
&& r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) {
- mSeasonEpisodeNumbers.add(new SeasonEpisodeNumber(r));
+ mScheduledEpisodes.add(new ScheduledEpisode(r));
}
}
}
@@ -299,9 +306,9 @@ abstract public class EpisodicProgramLoadTask {
}
if (programMatches) {
return mLoadScheduledEpisode
- || !mSeasonEpisodeNumbers.contains(new SeasonEpisodeNumber(
- seriesRecording.getId(), program.getSeasonNumber(),
- program.getEpisodeNumber()));
+ || !isEpisodeScheduled(mScheduledEpisodes, new ScheduledEpisode(
+ seriesRecording.getId(), program.getSeasonNumber(),
+ program.getEpisodeNumber()));
}
}
return false;
@@ -326,4 +333,50 @@ abstract public class EpisodicProgramLoadTask {
public String[] selectionArgs;
public CursorFilter filter;
}
+
+ /**
+ * A plain java object which includes the season/episode number for the series recording.
+ */
+ public static class ScheduledEpisode {
+ public final long seriesRecordingId;
+ public final String seasonNumber;
+ public final String episodeNumber;
+
+ /**
+ * Create a new Builder with the values set from an existing {@link ScheduledRecording}.
+ */
+ ScheduledEpisode(ScheduledRecording r) {
+ this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber());
+ }
+
+ public ScheduledEpisode(long seriesRecordingId, String seasonNumber, String episodeNumber) {
+ this.seriesRecordingId = seriesRecordingId;
+ this.seasonNumber = seasonNumber;
+ this.episodeNumber = episodeNumber;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ScheduledEpisode)) return false;
+ ScheduledEpisode that = (ScheduledEpisode) o;
+ return seriesRecordingId == that.seriesRecordingId
+ && Objects.equals(seasonNumber, that.seasonNumber)
+ && Objects.equals(episodeNumber, that.episodeNumber);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber);
+ }
+
+ @Override
+ public String toString() {
+ return "ScheduledEpisode{" +
+ "seriesRecordingId=" + seriesRecordingId +
+ ", seasonNumber='" + seasonNumber +
+ ", episodeNumber=" + episodeNumber +
+ '}';
+ }
+ }
}
diff --git a/src/com/android/tv/dvr/data/IdGenerator.java b/src/com/android/tv/dvr/IdGenerator.java
index 2ade1dad..0ed6362c 100644
--- a/src/com/android/tv/dvr/data/IdGenerator.java
+++ b/src/com/android/tv/dvr/IdGenerator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.data;
+package com.android.tv.dvr;
import java.util.concurrent.atomic.AtomicLong;
diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/InputTaskScheduler.java
index 46546a76..53c89ebc 100644
--- a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java
+++ b/src/com/android/tv/dvr/InputTaskScheduler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.content.Context;
import android.media.tv.TvInputInfo;
@@ -30,10 +30,6 @@ import android.util.LongSparseArray;
import com.android.tv.InputSessionManager;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
-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;
diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/RecordedProgram.java
index 18e1c769..dd744f80 100644
--- a/src/com/android/tv/dvr/data/RecordedProgram.java
+++ b/src/com/android/tv/dvr/RecordedProgram.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.data;
+package com.android.tv.dvr;
import static android.media.tv.TvContract.RecordedPrograms;
diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/RecordingTask.java
index c3314dde..c3d236b0 100644
--- a/src/com/android/tv/dvr/recorder/RecordingTask.java
+++ b/src/com/android/tv/dvr/RecordingTask.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.annotation.TargetApi;
import android.content.Context;
@@ -37,10 +37,7 @@ 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.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.dvr.InputTaskScheduler.HandlerWrapper;
import com.android.tv.util.Clock;
import com.android.tv.util.Utils;
@@ -259,21 +256,13 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
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();
- }
+ Toast.makeText(mContext.getApplicationContext(),
+ R.string.dvr_error_insufficient_space_description,
+ Toast.LENGTH_LONG)
+ .show();
} else {
Utils.setRecordingFailedReason(mContext.getApplicationContext(),
TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- Utils.addFailedScheduledRecordingInfo(mContext.getApplicationContext(),
- mScheduledRecording.getProgramDisplayTitle(mContext));
}
}
});
diff --git a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java b/src/com/android/tv/dvr/ScheduledProgramReaper.java
index d958c4a1..cd79a631 100644
--- a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java
+++ b/src/com/android/tv/dvr/ScheduledProgramReaper.java
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
-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;
diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/ScheduledRecording.java
index 88849a2c..2bda10ea 100644
--- a/src/com/android/tv/dvr/data/ScheduledRecording.java
+++ b/src/com/android/tv/dvr/ScheduledRecording.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.data;
+package com.android.tv.dvr;
import android.content.ContentValues;
import android.content.Context;
@@ -27,11 +27,9 @@ import android.text.TextUtils;
import android.util.Range;
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.data.Program;
-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;
@@ -685,19 +683,6 @@ public final class ScheduledRecording implements Parcelable {
}
}
- /**
- * Returns the program's display title, if the program title is not null, returns program title.
- * Otherwise returns the channel name.
- */
- public String getProgramDisplayTitle(Context context) {
- if (!TextUtils.isEmpty(mProgramTitle)) {
- return mProgramTitle;
- }
- Channel channel = TvApplication.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}.
diff --git a/src/com/android/tv/dvr/recorder/Scheduler.java b/src/com/android/tv/dvr/Scheduler.java
index 19e73342..ce78e1be 100644
--- a/src/com/android/tv/dvr/recorder/Scheduler.java
+++ b/src/com/android/tv/dvr/Scheduler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -32,12 +32,8 @@ import android.util.Range;
import com.android.tv.InputSessionManager;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.Listener;
-import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
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;
diff --git a/src/com/android/tv/dvr/data/SeriesInfo.java b/src/com/android/tv/dvr/SeriesInfo.java
index a0dec4a4..30256dc5 100644
--- a/src/com/android/tv/dvr/data/SeriesInfo.java
+++ b/src/com/android/tv/dvr/SeriesInfo.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.data;
+package com.android.tv.dvr;
/**
* Series information.
diff --git a/src/com/android/tv/dvr/data/SeriesRecording.java b/src/com/android/tv/dvr/SeriesRecording.java
index b7cf0f66..f0690f5f 100644
--- a/src/com/android/tv/dvr/data/SeriesRecording.java
+++ b/src/com/android/tv/dvr/SeriesRecording.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.data;
+package com.android.tv.dvr;
import android.content.ContentValues;
import android.database.Cursor;
@@ -26,7 +26,6 @@ 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;
diff --git a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/SeriesRecordingScheduler.java
index 8a211f66..5ed12ce8 100644
--- a/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
+++ b/src/com/android/tv/dvr/SeriesRecordingScheduler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.recorder;
+package com.android.tv.dvr;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -23,6 +23,7 @@ import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.MainThread;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -35,16 +36,9 @@ import com.android.tv.common.SharedPreferencesUtils;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.data.epg.EpgFetcher;
-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.SeriesInfo;
-import com.android.tv.dvr.data.SeriesRecording;
-import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.EpisodicProgramLoadTask.ScheduledEpisode;
import com.android.tv.experiments.Experiments;
import java.util.ArrayList;
@@ -58,11 +52,11 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.Set;
/**
- * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for
- * the {@link com.android.tv.dvr.data.SeriesRecording}.
+ * Creates the {@link ScheduledRecording}s for the {@link SeriesRecording}.
* <p>
* The current implementation assumes that the series recordings are scheduled only for one channel.
*/
@@ -91,13 +85,15 @@ public class SeriesRecordingScheduler {
private final DvrManager mDvrManager;
private final WritableDvrDataManager mDataManager;
private final List<SeriesRecordingUpdateTask> mScheduleTasks = new ArrayList<>();
- private final LongSparseArray<FetchSeriesInfoTask> mFetchSeriesInfoTasks =
- new LongSparseArray<>();
+ private final List<FetchSeriesInfoTask> mFetchSeriesInfoTasks = new ArrayList<>();
private final Set<String> mFetchedSeriesIds = new ArraySet<>();
private final SharedPreferences mSharedPreferences;
private boolean mStarted;
private boolean mPaused;
private final Set<Long> mPendingSeriesRecordings = new ArraySet<>();
+ private final Set<OnSeriesRecordingUpdatedListener> mOnSeriesRecordingUpdatedListeners =
+ new CopyOnWriteArraySet<>();
+
private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() {
@Override
@@ -111,7 +107,7 @@ public class SeriesRecordingScheduler {
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
// Cancel the update.
for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator();
- iter.hasNext(); ) {
+ iter.hasNext(); ) {
SeriesRecordingUpdateTask task = iter.next();
if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings,
SeriesRecording.ID_COMPARATOR).isEmpty()) {
@@ -119,13 +115,6 @@ public class SeriesRecordingScheduler {
iter.remove();
}
}
- for (SeriesRecording seriesRecording : seriesRecordings) {
- FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(seriesRecording.getId());
- if (task != null) {
- task.cancel(true);
- mFetchSeriesInfoTasks.remove(seriesRecording.getId());
- }
- }
}
@Override
@@ -237,8 +226,7 @@ public class SeriesRecordingScheduler {
}
if (DEBUG) Log.d(TAG, "stop");
mStarted = false;
- for (int i = 0; i < mFetchSeriesInfoTasks.size(); i++) {
- FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(mFetchSeriesInfoTasks.keyAt(i));
+ for (FetchSeriesInfoTask task : mFetchSeriesInfoTasks) {
task.cancel(true);
}
mFetchSeriesInfoTasks.clear();
@@ -262,7 +250,7 @@ public class SeriesRecordingScheduler {
if (Experiments.CLOUD_EPG.get()) {
FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording);
task.execute();
- mFetchSeriesInfoTasks.put(seriesRecording.getId(), task);
+ mFetchSeriesInfoTasks.add(task);
}
}
@@ -375,6 +363,20 @@ public class SeriesRecordingScheduler {
}
}
+ /**
+ * Adds {@link OnSeriesRecordingUpdatedListener}.
+ */
+ public void addOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) {
+ mOnSeriesRecordingUpdatedListeners.add(listener);
+ }
+
+ /**
+ * Removes {@link OnSeriesRecordingUpdatedListener}.
+ */
+ public void removeOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) {
+ mOnSeriesRecordingUpdatedListeners.remove(listener);
+ }
+
private boolean needToReadAllChannels(List<SeriesRecording> seriesRecordingsToUpdate) {
for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) {
if (seriesRecording.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ALL) {
@@ -401,7 +403,8 @@ public class SeriesRecordingScheduler {
/**
* @see #pickOneProgramPerEpisode(List, List)
*/
- public static LongSparseArray<List<Program>> pickOneProgramPerEpisode(
+ @VisibleForTesting
+ static LongSparseArray<List<Program>> pickOneProgramPerEpisode(
DvrDataManager dataManager, List<SeriesRecording> seriesRecordings,
List<Program> programs) {
// Initialize.
@@ -412,7 +415,7 @@ public class SeriesRecordingScheduler {
seriesRecordingIds.put(seriesRecording.getSeriesId(), seriesRecording.getId());
}
// Group programs by the episode.
- Map<SeasonEpisodeNumber, List<Program>> programsForEpisodeMap = new HashMap<>();
+ Map<ScheduledEpisode, List<Program>> programsForEpisodeMap = new HashMap<>();
for (Program program : programs) {
long seriesRecordingId = seriesRecordingIds.get(program.getSeriesId());
if (TextUtils.isEmpty(program.getSeasonNumber())
@@ -421,17 +424,17 @@ public class SeriesRecordingScheduler {
result.get(seriesRecordingId).add(program);
continue;
}
- SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber(seriesRecordingId,
+ ScheduledEpisode episode = new ScheduledEpisode(seriesRecordingId,
program.getSeasonNumber(), program.getEpisodeNumber());
- List<Program> programsForEpisode = programsForEpisodeMap.get(seasonEpisodeNumber);
+ List<Program> programsForEpisode = programsForEpisodeMap.get(episode);
if (programsForEpisode == null) {
programsForEpisode = new ArrayList<>();
- programsForEpisodeMap.put(seasonEpisodeNumber, programsForEpisode);
+ programsForEpisodeMap.put(episode, programsForEpisode);
}
programsForEpisode.add(program);
}
// Pick one program.
- for (Entry<SeasonEpisodeNumber, List<Program>> entry : programsForEpisodeMap.entrySet()) {
+ for (Entry<ScheduledEpisode, List<Program>> entry : programsForEpisodeMap.entrySet()) {
List<Program> programsForEpisode = entry.getValue();
Collections.sort(programsForEpisode, new Comparator<Program>() {
@Override
@@ -509,6 +512,13 @@ public class SeriesRecordingScheduler {
mDvrManager.addScheduleToSeriesRecording(seriesRecording, programsToSchedule);
}
}
+ if (!mOnSeriesRecordingUpdatedListeners.isEmpty()) {
+ for (OnSeriesRecordingUpdatedListener listener
+ : mOnSeriesRecordingUpdatedListeners) {
+ listener.onSeriesRecordingUpdated(
+ SeriesRecording.toArray(getSeriesRecordings()));
+ }
+ }
}
@Override
@@ -551,12 +561,19 @@ public class SeriesRecordingScheduler {
mFetchedSeriesIds.add(seriesInfo.getId());
updateFetchedSeries();
}
- mFetchSeriesInfoTasks.remove(mSeriesRecording.getId());
+ mFetchSeriesInfoTasks.remove(this);
}
@Override
protected void onCancelled(SeriesInfo seriesInfo) {
- mFetchSeriesInfoTasks.remove(mSeriesRecording.getId());
+ mFetchSeriesInfoTasks.remove(this);
}
}
+
+ /**
+ * A listener to notify when series recording are updated.
+ */
+ public interface OnSeriesRecordingUpdatedListener {
+ void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings);
+ }
}
diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java
index 129ba153..bf72d912 100644
--- a/src/com/android/tv/dvr/WritableDvrDataManager.java
+++ b/src/com/android/tv/dvr/WritableDvrDataManager.java
@@ -18,9 +18,7 @@ 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;
+import com.android.tv.dvr.ScheduledRecording.RecordingState;
/**
* Full data manager.
@@ -29,7 +27,7 @@ import com.android.tv.dvr.data.SeriesRecording;
* for internal use only. Do not call them from UI directly.
*/
@MainThread
-public interface WritableDvrDataManager extends DvrDataManager {
+interface WritableDvrDataManager extends DvrDataManager {
/**
* Adds new recordings.
*/
diff --git a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
deleted file mode 100644
index 89533dbb..00000000
--- a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
+++ /dev/null
@@ -1,72 +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.dvr.data;
-
-import android.text.TextUtils;
-
-import java.util.Objects;
-
-/**
- * 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}.
- */
- public SeasonEpisodeNumber(ScheduledRecording r) {
- this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber());
- }
-
- public SeasonEpisodeNumber(long seriesRecordingId, String seasonNumber, String episodeNumber) {
- this.seriesRecordingId = seriesRecordingId;
- this.seasonNumber = seasonNumber;
- this.episodeNumber = episodeNumber;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof SeasonEpisodeNumber)
- || TextUtils.isEmpty(seasonNumber) || TextUtils.isEmpty(episodeNumber)) {
- return false;
- }
- SeasonEpisodeNumber that = (SeasonEpisodeNumber) o;
- return seriesRecordingId == that.seriesRecordingId
- && Objects.equals(seasonNumber, that.seasonNumber)
- && Objects.equals(episodeNumber, that.episodeNumber);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber);
- }
-
- @Override
- public String toString() {
- return "SeasonEpisodeNumber{" +
- "seriesRecordingId=" + seriesRecordingId +
- ", seasonNumber='" + seasonNumber +
- ", episodeNumber=" + episodeNumber +
- '}';
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
index c5383d02..1a12fb23 100644
--- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
+++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
@@ -21,8 +21,8 @@ import android.database.Cursor;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
-import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
import com.android.tv.util.NamedThreadFactory;
diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
index 8b9481a9..2f16ba5d 100644
--- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
+++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
@@ -27,8 +27,8 @@ 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.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
diff --git a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/ActionPresenterSelector.java
index 38a78f5d..8b8cd5c5 100644
--- a/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
+++ b/src/com/android/tv/dvr/ui/ActionPresenterSelector.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.R;
@@ -110,7 +110,11 @@ class ActionPresenterSelector extends PresenterSelector {
.getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
vh.view.setPaddingRelative(padding, 0, padding, 0);
}
- vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
+ if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
+ } else {
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ }
CharSequence line1 = action.getLabel1();
CharSequence line2 = action.getLabel2();
@@ -126,7 +130,7 @@ class ActionPresenterSelector extends PresenterSelector {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
ActionViewHolder vh = (ActionViewHolder) viewHolder;
- vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null);
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
vh.view.setPadding(0, 0, 0, 0);
vh.mAction = null;
}
diff --git a/src/com/android/tv/dvr/ui/BigArguments.java b/src/com/android/tv/dvr/ui/BigArguments.java
deleted file mode 100644
index ec3b5065..00000000
--- a/src/com/android/tv/dvr/ui/BigArguments.java
+++ /dev/null
@@ -1,54 +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.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.
- */
-public class BigArguments {
- private final static String TAG = "BigArguments";
- private static Map<String, Object> sBigArgumentMap = new HashMap<>();
-
- /**
- * 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.
- */
- public static Object getArgument(String name) {
- return sBigArgumentMap.get(name);
- }
-
- /**
- * 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
deleted file mode 100644
index cddece73..00000000
--- a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
+++ /dev/null
@@ -1,79 +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.dvr.ui;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Matrix;
-import android.graphics.drawable.BitmapDrawable;
-import android.transition.ChangeImageTransform;
-import android.transition.TransitionValues;
-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}.
- */
-public class ChangeImageTransformWithScaledParent extends ChangeImageTransform {
- private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
-
- public ChangeImageTransformWithScaledParent(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- super.captureStartValues(transitionValues);
- applyParentScale(transitionValues);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- super.captureEndValues(transitionValues);
- applyParentScale(transitionValues);
- }
-
- private void applyParentScale(TransitionValues transitionValues) {
- 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
- && view instanceof ImageView) {
- ImageView imageView = (ImageView) view;
- if (imageView.getScaleType() == ScaleType.CENTER_INSIDE
- && imageView.getDrawable() instanceof BitmapDrawable) {
- 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);
- }
- }
- }
- }
-}
diff --git a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java
new file mode 100644
index 00000000..5d8e20ff
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+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.dvr.DvrManager;
+
+/**
+ * {@link RecordingDetailsFragment} for current recording in DVR.
+ */
+public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
+ private static final int ACTION_STOP_RECORDING = 1;
+
+ @Override
+ protected SparseArrayObjectAdapter onCreateActionsAdapter() {
+ SparseArrayObjectAdapter adapter =
+ new SparseArrayObjectAdapter(new ActionPresenterSelector());
+ Resources res = getResources();
+ adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
+ res.getString(R.string.epg_dvr_dialog_message_stop_recording), null,
+ res.getDrawable(R.drawable.lb_ic_stop)));
+ return adapter;
+ }
+
+ @Override
+ protected OnActionClickedListener onCreateOnActionClickedListener() {
+ return new OnActionClickedListener() {
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == ACTION_STOP_RECORDING) {
+ DvrManager dvrManager = TvApplication.getSingletons(getActivity())
+ .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/DetailsContent.java
index b43d1f12..19521fca 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java
+++ b/src/com/android/tv/dvr/ui/DetailsContent.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.media.tv.TvContract;
import android.support.annotation.Nullable;
@@ -26,7 +26,7 @@ import com.android.tv.data.Channel;
/**
* A class for details content.
*/
-class DetailsContent {
+public class DetailsContent {
/** Constant for invalid time. */
public static final long INVALID_TIME = -1;
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/DetailsContentPresenter.java
index a2e3fe16..175f05bc 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
+++ b/src/com/android/tv/dvr/ui/DetailsContentPresenter.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.app.Activity;
import android.animation.Animator;
@@ -38,14 +38,13 @@ 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}.
+ * Typically this Presenter will be used in a {@link 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 {
+public class DetailsContentPresenter extends Presenter {
/**
* The ViewHolder for the {@link DetailsContentPresenter}.
*/
@@ -114,20 +113,6 @@ class DetailsContentPresenter extends Presenter {
public ViewHolder(final View view) {
super(view);
- view.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- // In case predraw listener was removed in detach, make sure
- // we have the proper layout.
- addPreDrawListener();
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- removePreDrawListener();
- }
- });
mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title);
mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle);
mBody = (TextView) view.findViewById(R.id.dvr_details_description_body);
@@ -291,6 +276,22 @@ class DetailsContentPresenter extends Presenter {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { }
+ @Override
+ public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
+ // In case predraw listener was removed in detach, make sure
+ // we have the proper layout.
+ ViewHolder vh = (ViewHolder) holder;
+ vh.addPreDrawListener();
+ super.onViewAttachedToWindow(holder);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
+ ViewHolder vh = (ViewHolder) holder;
+ vh.removePreDrawListener();
+ super.onViewDetachedFromWindow(holder);
+ }
+
private void setTopMargin(View view, int topMargin) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
lp.topMargin = topMargin;
diff --git a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java
index 82fe9ce3..6714ecd3 100644
--- a/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
+++ b/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.app.Activity;
import android.graphics.drawable.BitmapDrawable;
@@ -26,7 +26,7 @@ import android.support.v17.leanback.app.BackgroundManager;
/**
* The Background Helper.
*/
-class DetailsViewBackgroundHelper {
+public class DetailsViewBackgroundHelper {
// Background delay serves to avoid kicking off expensive bitmap loading
// in case multiple backgrounds are set in quick succession.
private static final int SET_BACKGROUND_DELAY_MS = 100;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java b/src/com/android/tv/dvr/ui/DvrActivity.java
index 2b3dcb25..45fb1cf1 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.app.Activity;
import android.os.Bundle;
@@ -25,11 +25,11 @@ import com.android.tv.TvApplication;
/**
* {@link android.app.Activity} for DVR UI.
*/
-public class DvrBrowseActivity extends Activity {
+public class DvrActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.dvr_main);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
index 936e9c31..9df228d1 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
@@ -28,9 +28,11 @@ import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.dvr.RecordedProgram;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.util.Utils;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
index 3c73cb47..78f21784 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
@@ -25,12 +25,15 @@ 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 android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.util.Utils;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/DvrBrowseFragment.java
index 803d1017..a6dd31d1 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrBrowseFragment.java
@@ -14,9 +14,10 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.content.Context;
+import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BrowseFragment;
@@ -24,11 +25,11 @@ import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
-import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
@@ -41,10 +42,9 @@ import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
import com.android.tv.dvr.DvrScheduleManager;
-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.SortedArrayAdapter;
+import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import java.util.ArrayList;
import java.util.Arrays;
@@ -79,20 +79,6 @@ public class DvrBrowseFragment extends BrowseFragment implements
private ClassPresenterSelector mPresenterSelector;
private final HashMap<String, RecordedProgram> mSeriesId2LatestProgram = new HashMap<>();
private final Handler mHandler = new Handler();
- private final OnGlobalFocusChangeListener mOnGlobalFocusChangeListener =
- new OnGlobalFocusChangeListener() {
- @Override
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- if (oldFocus instanceof RecordingCardView) {
- ((RecordingCardView) oldFocus).expandTitle(false, true);
- }
- if (newFocus instanceof RecordingCardView) {
- // If the header transition is ongoing, expand cards immediately without
- // animation to make a smooth transition.
- ((RecordingCardView) newFocus).expandTitle(true, !isInHeadersTransition());
- }
- }
- };
private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR = new Comparator<Object>() {
@Override
@@ -142,7 +128,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) {
if (mScheduleAdapter != null) {
for (ScheduledRecording schedule : schedules) {
- onScheduledRecordingConflictStatusChanged(schedule);
+ onScheduledRecordingStatusChanged(schedule);
}
}
}
@@ -168,12 +154,16 @@ public class DvrBrowseFragment extends BrowseFragment implements
new ScheduledRecordingPresenter(context))
.addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context))
.addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context))
- .addClassPresenter(FullScheduleCardHolder.class,
- new FullSchedulesCardPresenter(context));
+ .addClassPresenter(FullScheduleCardHolder.class, new FullSchedulesCardPresenter());
mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context)));
mGenreLabels.add(getString(R.string.dvr_main_others));
- prepareUiElements();
- if (!startBrowseIfDvrInitialized()) {
+ setupUiElements();
+ setupAdapters();
+ mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
+ prepareEntranceTransition();
+ if (mDvrDataManager.isInitialized()) {
+ startEntranceTransition();
+ } else {
if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
mDvrDataManager.addDvrScheduleLoadFinishedListener(this);
}
@@ -184,19 +174,6 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- view.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
- }
-
- @Override
- public void onDestroyView() {
- getView().getViewTreeObserver()
- .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
- super.onDestroyView();
- }
-
- @Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy");
mHandler.removeCallbacks(mUpdateRowsRunnable);
@@ -218,13 +195,25 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public void onDvrScheduleLoadFinished() {
- startBrowseIfDvrInitialized();
+ List<ScheduledRecording> scheduledRecordings = mDvrDataManager.getAllScheduledRecordings();
+ onScheduledRecordingAdded(ScheduledRecording.toArray(scheduledRecordings));
+ List<SeriesRecording> seriesRecordings = mDvrDataManager.getSeriesRecordings();
+ onSeriesRecordingAdded(SeriesRecording.toArray(seriesRecordings));
+ if (mDvrDataManager.isInitialized()) {
+ startEntranceTransition();
+ }
mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
}
@Override
public void onRecordedProgramLoadFinished() {
- startBrowseIfDvrInitialized();
+ for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
+ handleRecordedProgramAdded(recordedProgram, true);
+ }
+ updateRows();
+ if (mDvrDataManager.isInitialized()) {
+ startEntranceTransition();
+ }
mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
}
@@ -281,18 +270,6 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
- private void onScheduledRecordingConflictStatusChanged(ScheduledRecording... schedules) {
- for (ScheduledRecording schedule : schedules) {
- if (needToShowScheduledRecording(schedule)) {
- if (mScheduleAdapter.contains(schedule)) {
- mScheduleAdapter.change(schedule);
- }
- } else {
- mScheduleAdapter.removeWithId(schedule);
- }
- }
- }
-
@Override
public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings));
@@ -318,53 +295,44 @@ public class DvrBrowseFragment extends BrowseFragment implements
super.showTitle(flags);
}
- private void prepareUiElements() {
+ private void setupUiElements() {
setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge));
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(false);
setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null));
- mRowsAdapter = new ArrayObjectAdapter(new DvrListRowPresenter(getContext()));
- setAdapter(mRowsAdapter);
- prepareEntranceTransition();
}
- private boolean startBrowseIfDvrInitialized() {
- if (mDvrDataManager.isInitialized()) {
- // Setup rows
- mRecentAdapter = new RecordedProgramAdapter(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();
- onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
- mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
- // Recorded Programs.
- for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
- handleRecordedProgramAdded(recordedProgram, false);
- }
- // Series Recordings. Series recordings should be added after recorded programs, because
- // 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);
- mRowsAdapter.add(new ListRow(new HeaderItem(
- getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
- mSeriesRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_series)), mSeriesAdapter);
- updateRows();
- // Initialize listeners
- mDvrDataManager.addRecordedProgramListener(this);
- mDvrDataManager.addScheduledRecordingListener(this);
- mDvrDataManager.addSeriesRecordingListener(this);
- mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
- startEntranceTransition();
- return true;
- }
- return false;
+ private void setupAdapters() {
+ mRecentAdapter = new RecordedProgramAdapter(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();
+ onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
+ mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
+ // Recorded Programs.
+ for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
+ handleRecordedProgramAdded(recordedProgram, false);
+ }
+ // Series Recordings. Series recordings should be added after recorded programs, because
+ // we build series recordings' latest program information while adding recorded programs.
+ List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
+ handleSeriesRecordingsAdded(recordings);
+ mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+ mRecentRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_recent)), mRecentAdapter);
+ mRowsAdapter.add(new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
+ mSeriesRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_series)), mSeriesAdapter);
+ updateRows();
+ mDvrDataManager.addRecordedProgramListener(this);
+ mDvrDataManager.addScheduledRecordingListener(this);
+ mDvrDataManager.addSeriesRecordingListener(this);
+ setAdapter(mRowsAdapter);
}
private void handleRecordedProgramAdded(RecordedProgram recordedProgram,
@@ -621,11 +589,10 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public long getId(Object item) {
- // We takes the inverse number for the ID of recorded programs to make the ID stable.
if (item instanceof SeriesRecording) {
return ((SeriesRecording) item).getId();
} else if (item instanceof RecordedProgram) {
- return -((RecordedProgram) item).getId() - 1;
+ return ((RecordedProgram) item).getId();
} else {
return -1;
}
diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
index 880dc8ac..837d8ab2 100644
--- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
@@ -27,7 +27,7 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
index 5985f56f..e7be4d0a 100644
--- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
@@ -34,9 +34,10 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-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.dvr.ConflictChecker;
+import com.android.tv.dvr.ConflictChecker.OnUpcomingConflictChangeListener;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/DvrDetailsActivity.java
index 30c81e83..806c775c 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrDetailsActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.app.Activity;
import android.os.Bundle;
diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/DvrDetailsFragment.java
index 4d3698ef..21f9c4b4 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.content.Context;
import android.content.Intent;
@@ -48,8 +48,8 @@ import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dialog.PinDialogFragment;
-import com.android.tv.dvr.data.RecordedProgram;
-import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
+import com.android.tv.dvr.DvrPlaybackActivity;
+import com.android.tv.dvr.RecordedProgram;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.ToastUtils;
diff --git a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java
new file mode 100644
index 00000000..73ddcdd0
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import android.app.Activity;
+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.text.TextUtils;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrManager;
+
+import java.util.List;
+
+public class DvrForgetStorageErrorFragment extends DvrGuidedStepFragment {
+ private static final int ACTION_CANCEL = 1;
+ private static final int ACTION_FORGET_STORAGE = 2;
+ private String mInputId;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ if (args != null) {
+ mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID);
+ }
+ SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId));
+ super.onCreate(savedInstanceState);
+ }
+
+ @NonNull
+ @Override
+ public Guidance onCreateGuidance(Bundle savedInstanceState) {
+ String title = getResources().getString(R.string.dvr_error_forget_storage_title);
+ String description = getResources().getString(
+ R.string.dvr_error_forget_storage_description);
+ return new Guidance(title, description, null, null);
+ }
+
+ @Override
+ public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+ Activity activity = getActivity();
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_CANCEL)
+ .title(getResources().getString(R.string.dvr_action_error_cancel))
+ .build());
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_FORGET_STORAGE)
+ .title(getResources().getString(R.string.dvr_action_error_forget_storage))
+ .build());
+ }
+
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ if (action.getId() != ACTION_FORGET_STORAGE) {
+ dismissDialog();
+ return;
+ }
+ DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ dvrManager.forgetStorage(mInputId);
+ Activity activity = getActivity();
+ if (activity instanceof DvrDetailsActivity) {
+ // Since we removed everything, just finish the activity.
+ activity.finish();
+ } else {
+ dismissDialog();
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
index 433588da..d26e6836 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
@@ -16,12 +16,10 @@
package com.android.tv.dvr.ui;
-import android.app.Activity;
import android.app.DialogFragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
@@ -31,26 +29,11 @@ import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.dvr.DvrManager;
-
-import java.util.List;
+import com.android.tv.dvr.ui.HalfSizedDialogFragment.OnActionClickListener;
public class DvrGuidedStepFragment extends GuidedStepFragment {
- /**
- * Action ID for "recording/scheduling the program anyway".
- */
- public static final int ACTION_RECORD_ANYWAY = 1;
- /**
- * Action ID for "deleting existed recordings".
- */
- public static final int ACTION_DELETE_RECORDINGS = 2;
- /**
- * Action ID for "cancelling current recording request".
- */
- public static final int ACTION_CANCEL_RECORDING = 3;
-
private DvrManager mDvrManager;
private OnActionClickListener mOnActionClickListener;
@@ -103,35 +86,4 @@ public class DvrGuidedStepFragment extends GuidedStepFragment {
protected void setOnActionClickListener(OnActionClickListener listener) {
mOnActionClickListener = listener;
}
-
- /**
- * 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);
- }
-
- @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());
- }
- }
} \ 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 9054dd03..2b132db8 100644
--- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
@@ -29,7 +29,6 @@ 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;
@@ -167,17 +166,6 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
}
/**
- * 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();
- }
- }
-
- /**
* A dialog fragment to show error message when the current storage is too small to
* support DVR
*/
diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
index 3c5df1a6..3b1dbfa0 100644
--- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
@@ -17,7 +17,6 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
@@ -25,67 +24,19 @@ import android.support.v17.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
+import com.android.tv.dvr.DvrDataManager;
-import java.util.ArrayList;
import java.util.List;
public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
- /**
- * Key for the failed scheduled recordings information.
- */
- public static final String FAILED_SCHEDULED_RECORDING_INFOS =
- "failed_scheduled_recording_infos";
-
- private static final String TAG = "DvrInsufficientSpaceErrorFragment";
-
- private static final int ACTION_VIEW_RECENT_RECORDINGS = 1;
-
- private ArrayList<String> mFailedScheduledRecordingInfos;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- Bundle args = getArguments();
- if (args != null) {
- mFailedScheduledRecordingInfos =
- args.getStringArrayList(FAILED_SCHEDULED_RECORDING_INFOS);
- }
- SoftPreconditions.checkState(
- mFailedScheduledRecordingInfos != null && !mFailedScheduledRecordingInfos.isEmpty(),
- TAG, "failed scheduled recording is null");
- }
+ private static final int ACTION_DONE = 1;
+ private static final int ACTION_OPEN_DVR = 2;
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title;
- 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));
- } 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));
- } 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));
- }
+ String title = getResources().getString(R.string.dvr_error_insufficient_space_title);
+ String description = getResources()
+ .getString(R.string.dvr_error_insufficient_space_description);
return new Guidance(title, description, null, null);
}
@@ -93,21 +44,26 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
actions.add(new GuidedAction.Builder(activity)
- .clickAction(GuidedAction.ACTION_ID_OK)
+ .id(ACTION_DONE)
+ .title(getResources().getString(R.string.dvr_action_error_done))
.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());
+ DvrDataManager dvrDataManager = TvApplication.getSingletons(getContext())
+ .getDvrDataManager();
+ if (!(dvrDataManager.getRecordedPrograms().isEmpty()
+ && dvrDataManager.getStartedRecordings().isEmpty()
+ && dvrDataManager.getNonStartedScheduledRecordings().isEmpty()
+ && dvrDataManager.getSeriesRecordings().isEmpty())) {
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_OPEN_DVR)
+ .title(getResources().getString(R.string.dvr_action_error_open_dvr))
+ .build());
}
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() == ACTION_VIEW_RECENT_RECORDINGS) {
- Intent intent = new Intent(getActivity(), DvrBrowseActivity.class);
+ if (action.getId() == ACTION_OPEN_DVR) {
+ Intent intent = new Intent(getActivity(), DvrActivity.class);
getActivity().startActivity(intent);
}
dismissDialog();
diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/DvrItemPresenter.java
index 317b6af3..339e5d2f 100644
--- a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
+++ b/src/com/android/tv/dvr/ui/DvrItemPresenter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.app.Activity;
import android.support.annotation.CallSuper;
@@ -22,17 +22,16 @@ import android.support.v17.leanback.widget.Presenter;
import android.view.View;
import android.view.View.OnClickListener;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.DvrUiHelper;
import java.util.HashSet;
+import java.util.Iterator;
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 com.android.tv.dvr.data.SeriesRecording}.
+ * {@link DvrBrowseFragment}. DVR items might include: {@link ScheduledRecording},
+ * {@link RecordedProgram}, and {@link SeriesRecording}.
*/
public abstract class DvrItemPresenter extends Presenter {
private final Set<ViewHolder> mBoundViewHolders = new HashSet<>();
@@ -50,8 +49,6 @@ public abstract class DvrItemPresenter extends Presenter {
@CallSuper
public void onUnbindViewHolder(ViewHolder viewHolder) {
mBoundViewHolders.remove(viewHolder);
- viewHolder.view.setTag(null);
- viewHolder.view.setOnClickListener(null);
}
/**
diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
index 8dc9eb4e..2e2c2849 100644
--- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
@@ -17,27 +17,29 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
import android.os.Bundle;
-import android.provider.Settings;
+import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-import android.util.Log;
+import android.text.TextUtils;
import com.android.tv.R;
-import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
+import com.android.tv.common.SoftPreconditions;
import java.util.List;
public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
- private static String TAG = "DvrMissingStorageErrorFragment";
-
- private static final int ACTION_OK = 1;
- private static final int ACTION_OPEN_STORAGE_SETTINGS = 2;
+ private static final int ACTION_CANCEL = 1;
+ private static final int ACTION_FORGET_STORAGE = 2;
+ private String mInputId;
@Override
public void onCreate(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ if (args != null) {
+ mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID);
+ }
+ SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId));
super.onCreate(savedInstanceState);
}
@@ -53,31 +55,25 @@ public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
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)
+ .id(ACTION_CANCEL)
+ .title(getResources().getString(R.string.dvr_action_error_cancel))
.build());
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_OPEN_STORAGE_SETTINGS)
- .title(getResources().getString(R.string.dvr_action_error_storage_settings))
+ .id(ACTION_FORGET_STORAGE)
+ .title(getResources().getString(R.string.dvr_action_error_forget_storage))
.build());
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
- Activity activity = getActivity();
- if (activity instanceof DvrDetailsActivity) {
- activity.finish();
- } else {
- dismissDialog();
- }
- if (action.getId() != ACTION_OPEN_STORAGE_SETTINGS) {
+ if (action.getId() == ACTION_FORGET_STORAGE) {
+ DvrForgetStorageErrorFragment fragment = new DvrForgetStorageErrorFragment();
+ Bundle args = new Bundle();
+ args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, mInputId);
+ fragment.setArguments(args);
+ GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host);
return;
}
- final Intent intent = new Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS);
- try {
- getContext().startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Can't start internal storage settings activity", e);
- }
+ dismissDialog();
}
-}
+} \ 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/DvrPlaybackCardPresenter.java
index 4bd121b1..8c4c856c 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.playback;
+package com.android.tv.dvr.ui;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -25,25 +26,22 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.android.tv.R;
-import com.android.tv.dvr.data.RecordedProgram;
-import com.android.tv.dvr.ui.browse.RecordedProgramPresenter;
-import com.android.tv.dvr.ui.browse.RecordingCardView;
+import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.DvrPlaybackActivity;
import com.android.tv.util.Utils;
/**
* This class is used to generate Views and bind Objects for related recordings in DVR playback.
*/
-class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
+public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
private static final String TAG = "DvrPlaybackCardPresenter";
private static final boolean DEBUG = false;
private final int mRelatedRecordingCardWidth;
private final int mRelatedRecordingCardHeight;
- private final DvrPlaybackOverlayFragment mFragment;
- DvrPlaybackCardPresenter(Context context, DvrPlaybackOverlayFragment fragment) {
+ DvrPlaybackCardPresenter(Context context) {
super(context);
- mFragment = fragment;
mRelatedRecordingCardWidth =
context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width);
mRelatedRecordingCardHeight =
@@ -52,8 +50,9 @@ class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ Resources res = parent.getResources();
RecordingCardView view = new RecordingCardView(
- getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true);
+ getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight);
return new ViewHolder(view);
}
@@ -62,10 +61,6 @@ class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
return new OnClickListener() {
@Override
public void onClick(View v) {
- // Disable fading of overlay fragment to prevent the layout blinking while updating
- // new playback states and info. The fading enabled status will be reset during
- // playback state changing, in DvrPlaybackControlHelper.onStateChanged().
- mFragment.setFadingEnabled(false);
long programId = ((RecordedProgram) v.getTag()).getId();
if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
@@ -74,4 +69,14 @@ class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
}
};
}
+
+ @Override
+ protected String getDescription(RecordedProgram program) {
+ String description = program.getDescription();
+ if (TextUtils.isEmpty(description)) {
+ description =
+ getContext().getResources().getString(R.string.dvr_msg_no_program_description);
+ }
+ return description;
+ }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java
index 4658a328..0bc4ecb1 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
+++ b/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java
@@ -14,26 +14,19 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.playback;
+package com.android.tv.dvr.ui;
import android.app.Activity;
-import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaController.TransportControls;
import android.media.session.PlaybackState;
-import android.media.tv.TvTrackInfo;
-import android.os.Bundle;
import android.support.v17.leanback.app.PlaybackControlGlue;
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
-import android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import android.text.TextUtils;
@@ -44,18 +37,19 @@ import android.view.View;
import com.android.tv.R;
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
* fragment according to information the media session provides.
*/
-class DvrPlaybackControlHelper extends PlaybackControlGlue {
+public class DvrPlaybackControlHelper extends PlaybackControlGlue {
private static final String TAG = "DvrPlaybackControlHelper";
private static final boolean DEBUG = false;
- private static final int AUDIO_ACTION_ID = 1001;
+ /**
+ * Indicates the ID of the media under playback is unknown.
+ */
+ public static int UNKNOWN_MEDIA_ID = -1;
private int mPlaybackState = PlaybackState.STATE_NONE;
private int mPlaybackSpeedLevel;
@@ -66,9 +60,6 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
private final TransportControls mTransportControls;
private final int mExtraPaddingTopForNoDescription;
- private final ArrayObjectAdapter mSecondaryActionsAdapter;
- private final MultiAction mClosedCaptioningAction;
- private final MultiAction mMultiAudioAction;
public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
@@ -77,15 +68,11 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
mTransportControls = mMediaController.getTransportControls();
mExtraPaddingTopForNoDescription = activity.getResources()
.getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
- mSecondaryActionsAdapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
- mClosedCaptioningAction = new ClosedCaptioningAction(activity);
- mMultiAudioAction = new MultiAudioAction(activity);
}
@Override
public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
- controlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
setControlsRow(controlsRow);
AbstractDetailsDescriptionPresenter detailsPresenter =
new AbstractDetailsDescriptionPresenter() {
@@ -129,21 +116,7 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
@Override
public void onActionClicked(Action action) {
if (mReadyToControl) {
- int trackType;
- if (action.getId() == mClosedCaptioningAction.getId()) {
- trackType = TvTrackInfo.TYPE_SUBTITLE;
- } else if (action.getId() == AUDIO_ACTION_ID) {
- trackType = TvTrackInfo.TYPE_AUDIO;
- } else {
- DvrPlaybackControlHelper.super.onActionClicked(action);
- return;
- }
- ArrayList<TvTrackInfo> trackInfos =
- ((DvrPlaybackOverlayFragment) getFragment()).getTracks(trackType);
- if (!trackInfos.isEmpty()) {
- showSideFragment(trackInfos, ((DvrPlaybackOverlayFragment)
- getFragment()).getSelectedTrackId(trackType));
- }
+ DvrPlaybackControlHelper.super.onActionClicked(action);
}
}
});
@@ -185,10 +158,10 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
/**
* Returns the ID of the media under playback.
*/
- public String getMediaId() {
+ public long getMediaId() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? null
- : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ return mediaMetadata == null ? UNKNOWN_MEDIA_ID
+ : mediaMetadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID);
}
@Override
@@ -244,37 +217,6 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
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.
- */
- public void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) {
- if (hasClosedCaption) {
- if (mSecondaryActionsAdapter.indexOf(mClosedCaptioningAction) < 0) {
- mSecondaryActionsAdapter.add(0, mClosedCaptioningAction);
- }
- } else {
- mSecondaryActionsAdapter.remove(mClosedCaptioningAction);
- }
- if (hasMultiAudio) {
- if (mSecondaryActionsAdapter.indexOf(mMultiAudioAction) < 0) {
- mSecondaryActionsAdapter.add(mMultiAudioAction);
- }
- } else {
- mSecondaryActionsAdapter.remove(mMultiAudioAction);
- }
- }
-
- /**
- * Returns if the secondary controls row has any buttons and thus should be shown.
- */
- public boolean hasSecondaryRow() {
- return mSecondaryActionsAdapter.size() != 0;
- }
-
@Override
protected void startPlayback(int speedId) {
if (getCurrentSpeedId() == speedId) {
@@ -309,14 +251,6 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
// Do nothing.
}
- /**
- * Notifies closed caption being enabled/disabled to update related UI.
- */
- void onSubtitleTrackStateChanged(boolean enabled) {
- mClosedCaptioningAction.setIndex(enabled ?
- ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
- }
-
private void onStateChanged(int state, long positionMs, int speedLevel) {
if (DEBUG) Log.d(TAG, "onStateChanged");
getControlsRow().setCurrentTime((int) positionMs);
@@ -363,19 +297,6 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
onStateChanged();
}
- private void showSideFragment(ArrayList<TvTrackInfo> trackInfos, String selectedTrackId) {
- Bundle args = new Bundle();
- args.putParcelableArrayList(DvrPlaybackSideFragment.TRACK_INFOS, trackInfos);
- args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId);
- DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment();
- sideFragment.setArguments(args);
- getFragment().getFragmentManager().beginTransaction()
- .hide(getFragment())
- .replace(R.id.dvr_playback_side_fragment, sideFragment)
- .addToBackStack(null)
- .commit();
- }
-
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -389,11 +310,4 @@ class DvrPlaybackControlHelper extends PlaybackControlGlue {
((DvrPlaybackOverlayFragment) getFragment()).onMediaControllerUpdated();
}
}
-
- private static class MultiAudioAction extends MultiAction {
- MultiAudioAction(Context context) {
- super(AUDIO_ACTION_ID);
- 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/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java
index ff907182..51ec93b8 100644
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java
@@ -14,14 +14,13 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.playback;
+package com.android.tv.dvr.ui;
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.TvInputManager;
@@ -31,6 +30,7 @@ import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.SinglePresenterSelector;
@@ -38,25 +38,20 @@ import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
+import android.text.TextUtils;
import android.util.Log;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.BaseProgram;
+import com.android.tv.dvr.RecordedProgram;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.data.RecordedProgram;
-import com.android.tv.dvr.data.SeriesRecording;
-import com.android.tv.dvr.ui.SortedArrayAdapter;
-import com.android.tv.dvr.ui.browse.DvrListRowPresenter;
+import com.android.tv.dvr.DvrPlayer;
+import com.android.tv.dvr.DvrPlaybackMediaSessionHelper;
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;
-
public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
// TODO: Handles audio focus. Deals with block and ratings.
private static final String TAG = "DvrPlaybackOverlayFragment";
@@ -67,7 +62,6 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
// mProgram is only used to store program from intent. Don't use it elsewhere.
private RecordedProgram mProgram;
- private DvrPlayer mDvrPlayer;
private DvrPlaybackMediaSessionHelper mMediaSessionHelper;
private DvrPlaybackControlHelper mPlaybackControlHelper;
private ArrayObjectAdapter mRowsAdapter;
@@ -78,30 +72,19 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
private TvView mTvView;
private View mBlockScreenView;
private ListRow mRelatedRecordingsRow;
- private int mPaddingWithoutRelatedRow;
- private int mPaddingWithoutSecondaryRow;
+ private int mExtraPaddingNoRelatedRow;
private int mWindowWidth;
private int mWindowHeight;
private float mAppliedAspectRatio;
private float mWindowAspectRatio;
private boolean mPinChecked;
- private DvrPlayer.OnTrackSelectedListener mOnSubtitleTrackSelectedListener =
- new DvrPlayer.OnTrackSelectedListener() {
- @Override
- public void onTrackSelected(String selectedTrackId) {
- mPlaybackControlHelper.onSubtitleTrackStateChanged(selectedTrackId != null);
- mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
- }
- };
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
- 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);
+ mExtraPaddingNoRelatedRow = getActivity().getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_fragment_extra_padding_top);
mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
mContentRatingsManager = TvApplication.getSingletons(getContext())
.getTvInputManagerHelper().getContentRatingsManager();
@@ -127,31 +110,13 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
super.onActivityCreated(savedInstanceState);
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);
+ getActivity(), MEDIA_SESSION_TAG, new DvrPlayer(mTvView), this);
mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
setUpRows();
- mDvrPlayer.setOnTracksAvailabilityChangedListener(
- new DvrPlayer.OnTracksAvailabilityChangedListener() {
- @Override
- public void onTracksAvailabilityChanged(boolean hasClosedCaption,
- boolean hasMultiAudio) {
- mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio);
- if (hasClosedCaption) {
- mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE,
- mOnSubtitleTrackSelectedListener);
- selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE);
- } else {
- mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null);
- }
- if (hasMultiAudio) {
- selectBestMatchedTrack(TvTrackInfo.TYPE_AUDIO);
- }
- onMediaControllerUpdated();
- }
- });
- mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() {
+ preparePlayback(getActivity().getIntent());
+ DvrPlayer dvrPlayer = mMediaSessionHelper.getDvrPlayer();
+ dvrPlayer.setAspectRatioChangedListener(new DvrPlayer.AspectRatioChangedListener() {
@Override
public void onAspectRatioChanged(float videoAspectRatio) {
updateAspectRatio(videoAspectRatio);
@@ -159,7 +124,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
});
mPinChecked = getActivity().getIntent()
.getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
- mDvrPlayer.setOnContentBlockedListener(new DvrPlayer.OnContentBlockedListener() {
+ dvrPlayer.setContentBlockedListener(new DvrPlayer.ContentBlockedListener() {
@Override
public void onContentBlocked(TvContentRating rating) {
if (mPinChecked) {
@@ -184,7 +149,6 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
.show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
}
});
- preparePlayback(getActivity().getIntent());
}
@Override
@@ -236,9 +200,6 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
updateAspectRatio(mAppliedAspectRatio);
}
- /**
- * 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()) {
@@ -248,92 +209,16 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
}
- /**
- * 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}.
- */
- public ArrayList<TvTrackInfo> getTracks(int trackType) {
- if (trackType == TvTrackInfo.TYPE_AUDIO) {
- return mDvrPlayer.getAudioTracks();
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- return mDvrPlayer.getSubtitleTracks();
- }
- return null;
- }
-
- /**
- * 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}.
- * @return {@code null} if no language has been set for the given track type.
- */
- TvTrackInfo getTrackSetting(int trackType) {
- return TvSettings.getDvrPlaybackTrackSettings(getContext(), trackType);
- }
-
- /**
- * Selects the given audio or subtitle track for DVR playback.
- * @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.
- */
- void selectTrack(int trackType, TvTrackInfo selectedTrack) {
- if (mDvrPlayer.isPlaybackPrepared()) {
- mDvrPlayer.selectTrack(trackType, selectedTrack);
- }
- }
-
- /**
- * Notifies the content of controls row or related recordings row is changed and the UI should
- * be updated according to the change.
- */
void onMediaControllerUpdated() {
- updateVerticalPosition();
- mRowsAdapter.notifyArrayItemRangeChanged(0, 2);
- }
-
- 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()))) {
- selectTrack(trackType, bestMatchedTrack);
- return;
- }
- }
- if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- // Disables closed captioning if there's no matched language.
- selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
- }
+ mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
}
private void updateAspectRatio(float videoAspectRatio) {
- if (videoAspectRatio <= 0) {
- // We don't have video's width or height information, use window's aspect ratio.
- videoAspectRatio = mWindowAspectRatio;
- }
if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
// No need to change
return;
}
- if (Math.abs(mWindowAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
- ((ViewGroup) mTvView.getParent()).setPadding(0, 0, 0, 0);
- } else if (videoAspectRatio < mWindowAspectRatio) {
+ if (videoAspectRatio < mWindowAspectRatio) {
int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2;
((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0);
} else {
@@ -345,7 +230,6 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
private void preparePlayback(Intent intent) {
mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
- mPlaybackControlHelper.updateSecondaryRow(false, false);
getActivity().getMediaController().getTransportControls().prepare();
updateRelatedRecordingsRow();
}
@@ -355,35 +239,24 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
mRelatedRecordingsRowAdapter.clear();
long programId = mProgram.getId();
String seriesId = mProgram.getSeriesId();
- SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
- if (seriesRecording != null) {
+ if (!TextUtils.isEmpty(seriesId)) {
if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
- List<RecordedProgram> relatedPrograms =
- mDvrDataManager.getRecordedPrograms(seriesRecording.getId());
- for (RecordedProgram program : relatedPrograms) {
- if (programId != program.getId()) {
+ for (RecordedProgram program : mDvrDataManager.getRecordedPrograms()) {
+ if (seriesId.equals(program.getSeriesId()) && programId != program.getId()) {
mRelatedRecordingsRowAdapter.add(program);
}
}
}
+ View view = getView();
if (mRelatedRecordingsRowAdapter.size() == 0) {
mRowsAdapter.remove(mRelatedRecordingsRow);
+ view.setPadding(view.getPaddingLeft(), mExtraPaddingNoRelatedRow,
+ view.getPaddingRight(), view.getPaddingBottom());
} else if (wasEmpty){
mRowsAdapter.add(mRelatedRecordingsRow);
+ view.setPadding(view.getPaddingLeft(), 0,
+ view.getPaddingRight(), view.getPaddingBottom());
}
- onMediaControllerUpdated();
- }
-
- private void updateVerticalPosition() {
- int verticalPadding = 0;
- verticalPadding +=
- mRelatedRecordingsRowAdapter.size() == 0 ? mPaddingWithoutRelatedRow : 0;
- verticalPadding +=
- mPlaybackControlHelper.hasSecondaryRow() ? 0 : mPaddingWithoutSecondaryRow;
- if (DEBUG) Log.d(TAG, "New controls padding: " + verticalPadding);
- View view = getView();
- view.setPadding(view.getPaddingLeft(), verticalPadding,
- view.getPaddingRight(), view.getPaddingBottom());
}
private void setUpRows() {
@@ -392,7 +265,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
ClassPresenterSelector selector = new ClassPresenterSelector();
selector.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter);
- selector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
+ selector.addClassPresenter(ListRow.class, new ListRowPresenter());
mRowsAdapter = new ArrayObjectAdapter(selector);
mRowsAdapter.add(mPlaybackControlHelper.getControlsRow());
@@ -401,7 +274,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
private ListRow getRelatedRecordingsRow() {
- mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity(), this);
+ mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
HeaderItem header = new HeaderItem(0,
getActivity().getString(R.string.dvr_playback_related_recordings));
@@ -424,7 +297,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
@Override
- public long getId(BaseProgram item) {
+ long getId(BaseProgram item) {
return item.getId();
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
index d6008315..da6d1637 100644
--- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
@@ -32,8 +32,9 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.util.Utils;
@@ -47,26 +48,18 @@ import java.util.List;
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrScheduleFragment extends DvrGuidedStepFragment {
- /**
- * 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";
private static final int ACTION_RECORD_EPISODE = 1;
private static final int ACTION_RECORD_SERIES = 2;
private Program mProgram;
- private boolean mAddCurrentProgramToSeries;
@Override
public void onCreate(Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
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,
@@ -146,10 +139,8 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
.build();
getDvrManager().updateSeriesRecording(seriesRecording);
}
-
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- seriesRecording.getId(), null, true, true, true,
- mAddCurrentProgramToSeries ? mProgram : null);
+ seriesRecording.getId(), null, true, true, true);
dismissDialog();
}
}
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java
index a0410bb3..f6e6ac26 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.list;
+package com.android.tv.dvr.ui;
import android.app.Activity;
import android.app.ProgressDialog;
@@ -24,13 +24,15 @@ import android.support.annotation.IntDef;
import com.android.tv.R;
import com.android.tv.TvApplication;
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 com.android.tv.dvr.EpisodicProgramLoadTask;
+import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.SeriesRecordingScheduler;
+import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
+import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -70,47 +72,33 @@ public class DvrSchedulesActivity extends Activity {
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) {
- // 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);
- // To get programs faster, hold the update of the series schedules.
- SeriesRecordingScheduler.getInstance(this).pauseUpdate();
- new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) {
- @Override
- protected void onPostExecute(List<Program> programs) {
- SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this)
- .resumeUpdate();
- dialog.dismiss();
- Bundle args = getIntent().getExtras();
- BigArguments.reset();
- BigArguments.setArgument(
- DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS,
- programs == null ? Collections.EMPTY_LIST : programs);
- showDvrSeriesSchedulesFragment(args);
- }
- }.setLoadCurrentProgram(true)
- .setLoadDisallowedProgram(true)
- .setLoadScheduledEpisode(true)
- .setIgnoreChannelOption(true)
- .execute();
- }
+ final ProgressDialog dialog = ProgressDialog.show(this, null, getString(
+ R.string.dvr_series_schedules_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)) {
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this).resumeUpdate();
+ dialog.dismiss();
+ Bundle args = getIntent().getExtras();
+ args.putParcelableArrayList(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, new ArrayList<>(programs));
+ DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
+ schedulesFragment.setArguments(args);
+ getFragmentManager().beginTransaction().add(
+ R.id.fragment_container, schedulesFragment).commit();
+ }
+ }.setLoadCurrentProgram(true)
+ .setLoadDisallowedProgram(true)
+ .setLoadScheduledEpisode(true)
+ .setIgnoreChannelOption(true)
+ .execute();
} else {
finish();
}
}
-
- private void showDvrSeriesSchedulesFragment(Bundle args) {
- DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
- schedulesFragment.setArguments(args);
- getFragmentManager().beginTransaction().add(
- R.id.fragment_container, schedulesFragment).commit();
- }
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
index 667af34a..f57e4b05 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
@@ -22,6 +22,9 @@ import android.support.v17.leanback.app.GuidedStepFragment;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.ui.SeriesDeletionFragment;
+import com.android.tv.ui.sidepanel.SettingsFragment;
/**
* Activity to show details view in DVR.
@@ -39,7 +42,7 @@ public class DvrSeriesDeletionActivity extends Activity {
setContentView(R.layout.activity_dvr_series_settings);
// Check savedInstanceState to prevent that activity is being showed with animation.
if (savedInstanceState == null) {
- DvrSeriesDeletionFragment deletionFragment = new DvrSeriesDeletionFragment();
+ SeriesDeletionFragment deletionFragment = new SeriesDeletionFragment();
deletionFragment.setArguments(getIntent().getExtras());
GuidedStepFragment.addAsRoot(this, deletionFragment, R.id.dvr_settings_view_frame);
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
index 8f880f16..1173df46 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
@@ -25,29 +25,22 @@ import android.support.v17.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrDataManager;
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.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
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}>
- */
- public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs";
-
private final static long SERIES_RECORDING_ID_NOT_SET = -1;
private final static int ACTION_VIEW_SCHEDULES = 1;
+ private DvrScheduleManager mDvrScheduleManager;
private SeriesRecording mSeriesRecording;
private boolean mShowViewScheduleOption;
- private List<Program> mPrograms;
private int mSchedulesAddedCount = 0;
private boolean mHasConflict = false;
@@ -65,25 +58,22 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
}
mShowViewScheduleOption = getArguments().getBoolean(
DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION);
+ mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager();
mSeriesRecording = TvApplication.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();
- DvrScheduleManager dvrScheduleManager =
- TvApplication.getSingletons(context).getDvrScheduleManager();
List<ScheduledRecording> conflictingRecordings =
- dvrScheduleManager.getConflictingSchedules(mSeriesRecording);
+ mDvrScheduleManager.getConflictingSchedules(mSeriesRecording);
mHasConflict = !conflictingRecordings.isEmpty();
for (ScheduledRecording recording : conflictingRecordings) {
if (recording.getSeriesRecordingId() == mSeriesRecording.getId()) {
++mInThisSeriesConflictCount;
- } else if (recording.getPriority() < mSeriesRecording.getPriority()) {
+ } else {
++mOutThisSeriesConflictCount;
}
}
@@ -123,9 +113,6 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
.TYPE_SERIES_SCHEDULE);
intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
mSeriesRecording);
- BigArguments.reset();
- BigArguments.setArgument(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms);
startActivity(intent);
}
getActivity().finish();
@@ -134,30 +121,30 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
private String getDescription() {
if (!mHasConflict) {
return getResources().getQuantityString(
- R.plurals.dvr_series_scheduled_no_conflict, mSchedulesAddedCount,
+ R.plurals.dvr_series_recording_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,
+ return getResources().getQuantityString(R.plurals
+ .dvr_series_recording_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,
+ 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,
+ return getResources().getQuantityString(R.plurals
+ .dvr_series_recording_scheduled_only_other_series_one_conflict,
mSchedulesAddedCount, mSchedulesAddedCount,
mSeriesRecording.getTitle());
} else {
- return getResources().getQuantityString(
- R.plurals.dvr_series_scheduled_only_other_series_many_conflicts,
+ return getResources().getQuantityString(R.plurals
+ .dvr_series_recording_scheduled_only_other_series_conflict,
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..3f7671b3 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
@@ -17,6 +17,7 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
+import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
@@ -37,34 +38,25 @@ public class DvrSeriesSettingsActivity extends Activity {
/**
* Name of the boolean flag to decide if the series recording with empty schedule and recording
* 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
*/
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 channel id list. If the channel list is given, we show the channels
+ * from the values in channel option.
+ * Type: Long array
*/
- public static final String PROGRAM_LIST = "program_list";
+ public static final String CHANNEL_ID_LIST = "channel_id_list";
/**
* Name of the boolean flag to check if the confirm dialog should show view schedule option.
- * Type: boolean
*/
public static final String SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG =
"show_view_schedule_option_in_dialog";
- /**
- * Name of the current program added to series. The current program will be recorded only when
- * the series recording is initialized from media controller. But for other case, the current
- * program won't be recorded.
- */
- public static final String CURRENT_PROGRAM = "current_program";
-
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
@@ -74,7 +66,7 @@ public class DvrSeriesSettingsActivity extends Activity {
SoftPreconditions.checkArgument(seriesRecordingId != -1);
if (savedInstanceState == null) {
- DvrSeriesSettingsFragment settingFragment = new DvrSeriesSettingsFragment();
+ SeriesSettingsFragment settingFragment = new SeriesSettingsFragment();
settingFragment.setArguments(getIntent().getExtras());
GuidedStepFragment.addAsRoot(this, settingFragment, R.id.dvr_settings_view_frame);
}
diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
index b476fff7..c3867886 100644
--- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
@@ -33,7 +33,7 @@ import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -131,8 +131,15 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
String title = getString(R.string.dvr_stop_recording_dialog_title);
String description;
if (mStopReason == REASON_ON_CONFLICT) {
+ String programTitle = mSchedule.getProgramTitle();
+ if (TextUtils.isEmpty(programTitle)) {
+ ChannelDataManager channelDataManager =
+ TvApplication.getSingletons(getActivity()).getChannelDataManager();
+ Channel channel = channelDataManager.getChannel(mSchedule.getChannelId());
+ programTitle = channel.getDisplayName();
+ }
description = getString(R.string.dvr_stop_recording_dialog_description_on_conflict,
- mSchedule.getProgramDisplayTitle(getContext()));
+ mSchedule.getProgramTitle());
} else {
description = getString(R.string.dvr_stop_recording_dialog_description);
}
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
index fe3a4a60..feaa2357 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
@@ -31,8 +31,8 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
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 com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/FadeBackground.java b/src/com/android/tv/dvr/ui/FadeBackground.java
deleted file mode 100644
index 4f06ebcf..00000000
--- a/src/com/android/tv/dvr/ui/FadeBackground.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.dvr.ui;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.transition.Transition;
-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.
- */
-public class FadeBackground extends Transition {
- private final int mMode;
-
- public FadeBackground(Context context, AttributeSet attrs) {
- super(context, attrs);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadeBackground);
- mMode = a.getInt(R.styleable.FadeBackground_fadingMode, Visibility.MODE_IN);
- a.recycle();
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) { }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) { }
-
- @Override
- 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));
- return mMode == Visibility.MODE_OUT
- ? ObjectAnimator.ofArgb(background, "color", transparentColor)
- : ObjectAnimator.ofArgb(background, "color", transparentColor, color);
- }
- return null;
- }
-}
diff --git a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java
index 311137a9..d4d4d8ab 100644
--- a/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
+++ b/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
/**
* Special object for schedule preview;
diff --git a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java
index 6d4763d4..7dd85f45 100644
--- a/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.content.Context;
-import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.Presenter;
import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.data.ScheduledRecording;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.Collections;
@@ -33,31 +33,23 @@ import java.util.List;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-class FullSchedulesCardPresenter extends DvrItemPresenter {
- private Context mContext;
- private final Drawable mIconDrawable;
- private final String mCardTitleText;
-
- public FullSchedulesCardPresenter(Context context) {
- mContext = context;
- mIconDrawable = mContext.getDrawable(R.drawable.dvr_full_schedule);
- mCardTitleText = mContext.getString(R.string.dvr_full_schedule_card_view_title);
- }
-
+public class FullSchedulesCardPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
Context context = parent.getContext();
RecordingCardView view = new RecordingCardView(context);
- return new ViewHolder(view);
+ return new ScheduledRecordingViewHolder(view);
}
@Override
- public void onBindViewHolder(ViewHolder vh, Object o) {
- final RecordingCardView cardView = (RecordingCardView) vh.view;
+ public void onBindViewHolder(ViewHolder baseHolder, Object o) {
+ final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
+ final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
+ final Context context = viewHolder.view.getContext();
- cardView.setImage(mIconDrawable);
- cardView.setTitle(mCardTitleText);
- List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(mContext)
+ cardView.setImage(context.getDrawable(R.drawable.dvr_full_schedule));
+ cardView.setTitle(context.getString(R.string.dvr_full_schedule_card_view_title));
+ List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(context)
.getDvrDataManager().getAvailableScheduledRecordings();
int fullDays = 0;
if (!scheduledRecordings.isEmpty()) {
@@ -65,24 +57,28 @@ class FullSchedulesCardPresenter extends DvrItemPresenter {
Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR)
.getStartTimeMs()) + 1;
}
- cardView.setContent(mContext.getResources().getQuantityString(
+ cardView.setContent(context.getResources().getQuantityString(
R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null);
- super.onBindViewHolder(vh, o);
+
+ View.OnClickListener clickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ DvrUiHelper.startSchedulesActivity(context, null);
+ }
+ };
+ baseHolder.view.setOnClickListener(clickListener);
}
@Override
- public void onUnbindViewHolder(ViewHolder vh) {
- ((RecordingCardView) vh.view).reset();
- super.onUnbindViewHolder(vh);
+ public void onUnbindViewHolder(ViewHolder baseHolder) {
+ ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
+ final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
+ cardView.reset();
}
- @Override
- protected View.OnClickListener onCreateOnClickListener() {
- return new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- DvrUiHelper.startSchedulesActivity(mContext, null);
- }
- };
+ private static final class ScheduledRecordingViewHolder extends ViewHolder {
+ ScheduledRecordingViewHolder(RecordingCardView view) {
+ super(view);
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dialog/HalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java
index 315c6a93..d320816e 100644
--- a/src/com/android/tv/dialog/HalfSizedDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.tv.dialog;
+package com.android.tv.dvr.ui;
-import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
@@ -26,6 +25,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
+import com.android.tv.dialog.SafeDismissDialogFragment;
import java.util.concurrent.TimeUnit;
@@ -54,6 +54,13 @@ public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
@Override
public void onStart() {
super.onStart();
+ getDialog().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;
+ }
+ });
mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
}
@@ -74,19 +81,6 @@ 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;
- }
- });
- return dialog;
- }
-
- @Override
public int getTheme() {
return R.style.Theme_TV_dialog_HalfSizedDialog;
}
diff --git a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java
index 562898a3..158bd824 100644
--- a/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java
@@ -33,7 +33,7 @@ import com.android.tv.TvApplication;
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 com.android.tv.dvr.SeriesRecording;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +41,7 @@ import java.util.List;
/**
* Fragment for DVR series recording settings.
*/
-public class DvrPrioritySettingsFragment extends GuidedStepFragment {
+public class PrioritySettingsFragment extends GuidedStepFragment {
/**
* Name of series recording id starting the fragment.
* Type: Long
@@ -162,6 +162,7 @@ public class DvrPrioritySettingsFragment extends GuidedStepFragment {
return;
}
if (action.getId() < 0) {
+ int selectedPosition = mSeriesRecordings.indexOf(mSelectedRecording);
mSelectedRecording = null;
for (int i = 0; i < mSeriesRecordings.size(); ++i) {
updateItem(i);
@@ -247,4 +248,4 @@ public class DvrPrioritySettingsFragment extends GuidedStepFragment {
titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL);
}
}
-} \ 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/RecordedProgramDetailsFragment.java
index fe9b9de5..e698b8a2 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.content.res.Resources;
import android.media.tv.TvInputManager;
@@ -30,10 +30,10 @@ import com.android.tv.data.Channel;
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;
+import com.android.tv.dvr.RecordedProgram;
/**
- * {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR.
+ * {@link DetailsFragment} for recorded program in DVR.
*/
public class RecordedProgramDetailsFragment extends DvrDetailsFragment
implements DvrDataManager.RecordedProgramListener {
diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java
index ee978797..1bf34310 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
+++ b/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java
@@ -14,26 +14,31 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
+import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
+import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
+import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.dvr.RecordedProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
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;
+import java.util.concurrent.TimeUnit;
+
/**
* Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}.
*/
@@ -45,7 +50,6 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
private String mYesterdayString;
private final int mProgressBarColor;
private final boolean mShowEpisodeTitle;
- private final boolean mExpandTitleWhenFocused;
private static final class RecordedProgramViewHolder extends ViewHolder
implements WatchedPositionChangedListener {
@@ -75,27 +79,25 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
}
}
- public RecordedProgramPresenter(Context context, boolean showEpisodeTitle,
- boolean expandTitleWhenFocused) {
+ public RecordedProgramPresenter(Context context, boolean showEpisodeTitle) {
mContext = context;
- mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager();
- mTodayString = mContext.getString(R.string.dvr_date_today);
- mYesterdayString = mContext.getString(R.string.dvr_date_yesterday);
+ mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
+ mTodayString = context.getString(R.string.dvr_date_today);
+ mYesterdayString = context.getString(R.string.dvr_date_yesterday);
mDvrWatchedPositionManager =
- TvApplication.getSingletons(mContext).getDvrWatchedPositionManager();
- mProgressBarColor = mContext.getResources()
+ TvApplication.getSingletons(context).getDvrWatchedPositionManager();
+ mProgressBarColor = context.getResources()
.getColor(R.color.play_controls_progress_bar_watched);
mShowEpisodeTitle = showEpisodeTitle;
- mExpandTitleWhenFocused = expandTitleWhenFocused;
}
public RecordedProgramPresenter(Context context) {
- this(context, false, false);
+ this(context, false);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- RecordingCardView view = new RecordingCardView(mContext, mExpandTitleWhenFocused);
+ RecordingCardView view = new RecordingCardView(mContext);
return new RecordedProgramViewHolder(view, mProgressBarColor);
}
@@ -130,7 +132,8 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
isChannelLogo = true;
}
cardView.setImageUri(imageUri, isChannelLogo);
- int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis()));
+ int durationMinutes =
+ Math.max(1, (int) TimeUnit.MILLISECONDS.toMinutes(program.getDurationMillis()));
String durationString = getContext().getResources().getQuantityString(
R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
cardView.setContent(getDescription(program), durationString);
diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/RecordingCardView.java
index 7b0a8cb9..51c3b03b 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
+++ b/src/com/android/tv/dvr/ui/RecordingCardView.java
@@ -14,69 +14,51 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v17.leanback.widget.BaseCardView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
-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.dvr.RecordedProgram;
import com.android.tv.util.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.ScheduledRecording} or
+ * {@link RecordedProgram} or
+ * {@link com.android.tv.dvr.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;
+class RecordingCardView extends BaseCardView {
private final ImageView mImageView;
private final int mImageWidth;
private final int mImageHeight;
private String mImageUri;
+ private final TextView mTitleView;
private final TextView mMajorContentView;
private final TextView mMinorContentView;
private final ProgressBar mProgressBar;
private final View mAffiliatedIconContainer;
private final ImageView mAffiliatedIcon;
private final Drawable mDefaultImage;
- private final FrameLayout mTitleArea;
- private final TextView mFoldedTitleView;
- private final TextView mExpandedTitleView;
- private final ValueAnimator mExpandTitleAnimator;
- private final int mFoldedTitleHeight;
- private final int mExpandedTitleHeight;
- private final boolean mExpandTitleWhenFocused;
- private boolean mExpanded;
- public RecordingCardView(Context context) {
- this(context, false);
+ RecordingCardView(Context context) {
+ this(context,
+ context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_width),
+ context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_height));
}
- 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),
- expandTitleWhenFocused);
- }
-
- public RecordingCardView(Context context, int imageWidth, int imageHeight,
- boolean expandTitleWhenFocused) {
+ RecordingCardView(Context context, int imageWidth, int imageHeight) {
super(context);
//TODO(dvr): move these to the layout XML.
setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA);
@@ -93,73 +75,13 @@ public class RecordingCardView extends BaseCardView {
mProgressBar = (ProgressBar) findViewById(R.id.recording_progress);
mAffiliatedIconContainer = findViewById(R.id.affiliated_icon_container);
mAffiliatedIcon = (ImageView) findViewById(R.id.affiliated_icon);
+ mTitleView = (TextView) findViewById(R.id.title);
mMajorContentView = (TextView) findViewById(R.id.content_major);
mMinorContentView = (TextView) findViewById(R.id.content_minor);
- 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);
- 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));
- }
- });
- mExpandTitleWhenFocused = expandTitleWhenFocused;
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mExpandTitleWhenFocused) {
- if (gainFocus) {
- expandTitle(true, true);
- } else {
- expandTitle(false, true);
- }
- }
- }
-
- /**
- * Expands/folds the title area to show program title with two/one lines.
- *
- * @param expand {@code true} to expand the title area, or {@code false} to fold it.
- * @param withAnimation {@code true} to expand/fold with animation.
- */
- public void expandTitle(boolean expand, boolean withAnimation) {
- if (expand != mExpanded && mFoldedTitleView.getLayout().getEllipsisCount(0) > 0) {
- if (withAnimation) {
- if (expand) {
- mExpandTitleAnimator.start();
- } else {
- mExpandTitleAnimator.reverse();
- }
- } else {
- if (expand) {
- mFoldedTitleView.setAlpha(0.0f);
- mExpandedTitleView.setAlpha(1.0f);
- ViewUtils.setLayoutHeight(mTitleArea, mExpandedTitleHeight);
- } else {
- mFoldedTitleView.setAlpha(1.0f);
- mExpandedTitleView.setAlpha(0.0f);
- ViewUtils.setLayoutHeight(mTitleArea, mFoldedTitleHeight);
- }
- }
- mExpanded = expand;
- }
}
void setTitle(CharSequence title) {
- mFoldedTitleView.setText(title);
- mExpandedTitleView.setText(title);
+ mTitleView.setText(title);
}
void setContent(CharSequence majorContent, CharSequence minorContent) {
@@ -256,9 +178,8 @@ public class RecordingCardView extends BaseCardView {
}
public void reset() {
- mFoldedTitleView.setText(null);
- mExpandedTitleView.setText(null);
+ mTitleView.setText(null);
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/RecordingDetailsFragment.java
index a877e05f..4e19ec3f 100644
--- a/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.os.Bundle;
import android.support.v17.leanback.app.DetailsFragment;
@@ -26,7 +26,7 @@ import android.text.style.TextAppearanceSpan;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
/**
* {@link DetailsFragment} for recordings in DVR.
diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java
index eb0f4f0d..60816bb5 100644
--- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.content.res.Resources;
import android.os.Bundle;
@@ -26,7 +26,7 @@ import android.text.TextUtils;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.DvrUiHelper;
/**
* {@link RecordingDetailsFragment} for scheduled recording in DVR.
diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java
index efc8785a..5f447f13 100644
--- a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
+import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.os.Handler;
@@ -31,7 +32,7 @@ import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.concurrent.TimeUnit;
@@ -39,7 +40,7 @@ import java.util.concurrent.TimeUnit;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-class ScheduledRecordingPresenter extends DvrItemPresenter {
+public class ScheduledRecordingPresenter extends DvrItemPresenter {
private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
private final ChannelDataManager mChannelDataManager;
@@ -91,17 +92,18 @@ class ScheduledRecordingPresenter extends DvrItemPresenter {
}
public ScheduledRecordingPresenter(Context context) {
- mContext = context;
- ApplicationSingletons singletons = TvApplication.getSingletons(mContext);
+ ApplicationSingletons singletons = TvApplication.getSingletons(context);
mChannelDataManager = singletons.getChannelDataManager();
mDvrManager = singletons.getDvrManager();
- mProgressBarColor = mContext.getResources()
+ mContext = context;
+ mProgressBarColor = context.getResources()
.getColor(R.color.play_controls_recording_icon_color_on_focus);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- RecordingCardView view = new RecordingCardView(mContext);
+ Context context = parent.getContext();
+ RecordingCardView view = new RecordingCardView(context);
return new ScheduledRecordingViewHolder(view, mProgressBarColor);
}
@@ -142,8 +144,9 @@ class ScheduledRecordingPresenter extends DvrItemPresenter {
public void onUnbindViewHolder(ViewHolder baseHolder) {
ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
viewHolder.stopUpdateProgressBar();
+ final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
viewHolder.mScheduledRecording = null;
- ((RecordingCardView) viewHolder.view).reset();
+ cardView.reset();
super.onUnbindViewHolder(viewHolder);
}
@@ -171,4 +174,4 @@ class ScheduledRecordingPresenter extends DvrItemPresenter {
cardView.setTitle(title);
cardView.setImageUri(imageUri, isChannelLogo);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java
index 8bf8560f..36e3cfc1 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
+++ b/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java
@@ -33,10 +33,9 @@ import com.android.tv.common.SoftPreconditions;
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;
-import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.SeriesRecording;
import com.android.tv.ui.GuidedActionsStylistWithDivider;
-import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -48,7 +47,7 @@ import java.util.concurrent.TimeUnit;
/**
* Fragment for DVR series recording settings.
*/
-public class DvrSeriesDeletionFragment extends GuidedStepFragment {
+public class SeriesDeletionFragment extends GuidedStepFragment {
private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
// Since recordings' IDs are used as its check actions' IDs, which are random positive numbers,
@@ -219,8 +218,8 @@ 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));
+ Math.max(1, TimeUnit.MILLISECONDS.toMinutes(watchedPositionMs)),
+ TimeUnit.MILLISECONDS.toMinutes(durationMs));
} else {
return getResources().getString(R.string.dvr_series_watched_info_seconds,
Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)),
diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java
index f7b60b50..e9e391d4 100644
--- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -28,6 +28,7 @@ import android.support.v17.leanback.widget.DetailsOverviewRow;
import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
@@ -38,11 +39,12 @@ import com.android.tv.TvApplication;
import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.DvrUiHelper;
import com.android.tv.dvr.DvrWatchedPositionManager;
-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 com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import java.util.Collections;
import java.util.Comparator;
@@ -83,7 +85,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mWatchLabel = getString(R.string.dvr_detail_watch);
mResumeLabel = getString(R.string.dvr_detail_series_resume);
mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null);
- mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true, true);
+ mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true);
super.onCreate(savedInstanceState);
}
@@ -156,7 +158,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
DetailsOverviewRowPresenter rowPresenter) {
ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
- presenterSelector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
+ presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
return presenterSelector;
}
@@ -201,7 +203,10 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mDvrDataManager.removeSeriesRecordingListener(this);
mDvrDataManager.removeRecordedProgramListener(this);
if (mSeries != null) {
- mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeries.getId());
+ DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
+ if (dvrManager.canRemoveSeriesRecording(mSeries.getId())) {
+ dvrManager.removeSeriesRecording(mSeries.getId());
+ }
}
mRecordedProgramPresenter.unbindAllViewHolders();
}
@@ -260,6 +265,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
for (SeriesRecording series : seriesRecordings) {
if (series.getId() == mSeries.getId()) {
+ mSeries = null;
getActivity().finish();
return;
}
@@ -366,4 +372,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/SeriesRecordingPresenter.java
index af6ecc19..c2c0f596 100644
--- a/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui.browse;
+package com.android.tv.dvr.ui;
+import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
@@ -33,16 +34,16 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener;
-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.RecordedProgram;
+import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import java.util.List;
/**
* Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}.
*/
-class SeriesRecordingPresenter extends DvrItemPresenter {
+public class SeriesRecordingPresenter extends DvrItemPresenter {
private final ChannelDataManager mChannelDataManager;
private final DvrDataManager mDvrDataManager;
private final DvrManager mDvrManager;
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java
index f28382da..6c05c9c6 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java
@@ -17,13 +17,19 @@
package com.android.tv.dvr.ui;
import android.app.FragmentManager;
+import android.app.ProgressDialog;
import android.content.Context;
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.Log;
import android.util.LongSparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
import com.android.tv.R;
import com.android.tv.TvApplication;
@@ -32,14 +38,14 @@ import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
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.SeasonEpisodeNumber;
-import com.android.tv.dvr.data.SeriesRecording;
-import com.android.tv.dvr.data.SeriesRecording.ChannelOption;
-import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
-
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.EpisodicProgramLoadTask;
+import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.SeriesRecording.ChannelOption;
+import com.android.tv.dvr.SeriesRecordingScheduler;
+import com.android.tv.dvr.SeriesRecordingScheduler.OnSeriesRecordingUpdatedListener;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -47,7 +53,7 @@ import java.util.Set;
/**
* Fragment for DVR series recording settings.
*/
-public class DvrSeriesSettingsFragment extends GuidedStepFragment
+public class SeriesSettingsFragment extends GuidedStepFragment
implements DvrDataManager.SeriesRecordingListener {
private static final String TAG = "SeriesSettingsFragment";
private static final boolean DEBUG = false;
@@ -60,13 +66,15 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500;
private DvrDataManager mDvrDataManager;
+ private ChannelDataManager mChannelDataManager;
+ private DvrManager mDvrManager;
private SeriesRecording mSeriesRecording;
private long mSeriesRecordingId;
@ChannelOption int mChannelOption;
+ private Comparator<Channel> mChannelComparator;
private long mSelectedChannelId;
private int mBackStackCount;
private boolean mShowViewScheduleOptionInDialog;
- private Program mCurrentProgram;
private String mFragmentTitle;
private String mProrityActionTitle;
@@ -76,7 +84,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
private String mChannelsActionAllText;
private LongSparseArray<Channel> mId2Channel = new LongSparseArray<>();
private List<Channel> mChannels = new ArrayList<>();
- private List<Program> mPrograms;
+ private EpisodicProgramLoadTask mEpisodicProgramLoadTask;
private GuidedAction mPriorityGuidedAction;
private GuidedAction mChannelsGuidedAction;
@@ -92,24 +100,22 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
getActivity().finish();
return;
}
+ mDvrManager = TvApplication.getSingletons(context).getDvrManager();
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);
- BigArguments.reset();
- if (mPrograms == null) {
- getActivity().finish();
- return;
- }
- Set<Long> channelIds = new HashSet<>();
- ChannelDataManager channelDataManager =
- TvApplication.getSingletons(context).getChannelDataManager();
- for (Program program : mPrograms) {
- long channelId = program.getChannelId();
- if (channelIds.add(channelId)) {
- Channel channel = channelDataManager.getChannel(channelId);
+ long[] channelIds = getArguments().getLongArray(DvrSeriesSettingsActivity.CHANNEL_ID_LIST);
+ mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
+ if (channelIds == null) {
+ Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId());
+ if (channel != null) {
+ mId2Channel.put(channel.getId(), channel);
+ mChannels.add(channel);
+ }
+ collectChannelsInBackground();
+ } else {
+ for (long channelId : channelIds) {
+ Channel channel = mChannelDataManager.getChannel(channelId);
if (channel != null) {
mId2Channel.put(channel.getId(), channel);
mChannels.add(channel);
@@ -119,14 +125,16 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
mChannelOption = mSeriesRecording.getChannelOption();
mSelectedChannelId = Channel.INVALID_ID;
if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) {
- Channel channel = channelDataManager.getChannel(mSeriesRecording.getChannelId());
+ Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId());
if (channel != null) {
mSelectedChannelId = channel.getId();
} else {
mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL;
}
}
- mChannels.sort(Channel.CHANNEL_NUMBER_COMPARATOR);
+ mChannelComparator = new Channel.DefaultComparator(context,
+ TvApplication.getSingletons(context).getTvInputManagerHelper());
+ mChannels.sort(mChannelComparator);
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);
@@ -136,23 +144,23 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
}
@Override
- public void onResume() {
- super.onResume();
- // To avoid the order of series's priority has changed, but series doesn't get update.
- updatePriorityGuidedAction();
- }
-
- @Override
public void onDetach() {
super.onDetach();
mDvrDataManager.removeSeriesRecordingListener(this);
+ if (mEpisodicProgramLoadTask != null) {
+ mEpisodicProgramLoadTask.cancel(true);
+ mEpisodicProgramLoadTask = null;
+ }
}
@Override
public void onDestroy() {
- if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments()
- .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) {
- mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId);
+ DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
+ if (getFragmentManager().getBackStackEntryCount() == mBackStackCount
+ && getArguments()
+ .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)
+ && dvrManager.canRemoveSeriesRecording(mSeriesRecordingId)) {
+ dvrManager.removeSeriesRecording(mSeriesRecordingId);
}
super.onDestroy();
}
@@ -170,6 +178,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
.id(ACTION_ID_PRIORITY)
.title(mProrityActionTitle)
.build();
+ updatePriorityGuidedAction(false);
actions.add(mPriorityGuidedAction);
mChannelsGuidedAction = new GuidedAction.Builder(getActivity())
@@ -195,6 +204,10 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
public void onGuidedActionClicked(GuidedAction action) {
long actionId = action.getId();
if (actionId == GuidedAction.ACTION_ID_OK) {
+ if (mEpisodicProgramLoadTask != null) {
+ mEpisodicProgramLoadTask.cancel(true);
+ mEpisodicProgramLoadTask = null;
+ }
if (mChannelOption != mSeriesRecording.getChannelOption()
|| mSeriesRecording.isStopped()
|| (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE
@@ -205,14 +218,28 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
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.addSchedule(mCurrentProgram);
- }
- updateSchedulesToSeries();
- showConfirmDialog();
+ TvApplication.getSingletons(getContext()).getDvrManager()
+ .updateSeriesRecording(builder.build());
+ SeriesRecordingScheduler scheduler =
+ SeriesRecordingScheduler.getInstance(getContext());
+ // Since dialog is used even after the fragment is closed, we should
+ // use application context.
+ ProgressDialog dialog = ProgressDialog.show(getContext(), null, getString(
+ R.string.dvr_series_schedules_progress_message_updating_programs));
+ scheduler.addOnSeriesRecordingUpdatedListener(
+ new OnSeriesRecordingUpdatedListener() {
+ @Override
+ public void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording seriesRecording : seriesRecordings) {
+ if (seriesRecording.getId() == mSeriesRecordingId) {
+ dialog.dismiss();
+ scheduler.removeOnSeriesRecordingUpdatedListener(this);
+ showConfirmDialog();
+ return;
+ }
+ }
+ }
+ });
} else {
showConfirmDialog();
}
@@ -220,9 +247,9 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
finishGuidedStepFragments();
} else if (actionId == ACTION_ID_PRIORITY) {
FragmentManager fragmentManager = getFragmentManager();
- DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment();
+ PrioritySettingsFragment fragment = new PrioritySettingsFragment();
Bundle args = new Bundle();
- args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
+ args.putLong(PrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
mSeriesRecording.getId());
fragment.setArguments(args);
GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame);
@@ -254,7 +281,7 @@ 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){
+ } else {
mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId)
.getDisplayText());
}
@@ -263,7 +290,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
}
}
- private void updatePriorityGuidedAction() {
+ private void updatePriorityGuidedAction(boolean notifyActionChanged) {
int totalSeriesCount = 0;
int priorityOrder = 0;
for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) {
@@ -285,38 +312,49 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
mPriorityGuidedAction.setDescription(getString(
R.string.dvr_series_settings_priority_rank, priorityOrder + 1));
}
- notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
+ if (notifyActionChanged) {
+ notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
+ }
}
- private void updateSchedulesToSeries() {
- List<Program> recordingCandidates = new ArrayList<>();
- Set<SeasonEpisodeNumber> scheduledEpisodes = new HashSet<>();
- 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()));
- }
+ private void collectChannelsInBackground() {
+ if (mEpisodicProgramLoadTask != null) {
+ mEpisodicProgramLoadTask.cancel(true);
}
- 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()))) {
- recordingCandidates.add(program);
+ mEpisodicProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ mEpisodicProgramLoadTask = null;
+ Set<Long> channelIds = new HashSet<>();
+ for (Program program : programs) {
+ channelIds.add(program.getChannelId());
+ }
+ boolean channelAdded = false;
+ for (Long channelId : channelIds) {
+ if (mId2Channel.get(channelId) != null) {
+ continue;
+ }
+ Channel channel = mChannelDataManager.getChannel(channelId);
+ if (channel != null) {
+ channelAdded = true;
+ mId2Channel.put(channelId, channel);
+ mChannels.add(channel);
+ if (DEBUG) Log.d(TAG, "Added channel: " + channel);
+ }
+ }
+ if (!channelAdded) {
+ return;
+ }
+ mChannels.sort(mChannelComparator);
+ mChannelsGuidedAction.setSubActions(buildChannelSubAction());
+ notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL));
+ if (DEBUG) Log.d(TAG, "Complete EpisodicProgramLoadTask");
}
- }
- if (recordingCandidates.isEmpty()) {
- return;
- }
- List<Program> programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode(
- mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates)
- .get(mSeriesRecordingId);
- if (!programsToSchedule.isEmpty()) {
- TvApplication.getSingletons(getContext()).getDvrManager()
- .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule);
- }
+ }.setLoadCurrentProgram(true)
+ .setLoadDisallowedProgram(true)
+ .setLoadScheduledEpisode(true)
+ .setIgnoreChannelOption(true);
+ mEpisodicProgramLoadTask.execute();
}
private List<GuidedAction> buildChannelSubAction() {
@@ -335,8 +373,8 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
}
private void showConfirmDialog() {
- DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording,
- mShowViewScheduleOptionInDialog, mPrograms);
+ DvrUiHelper.StartSeriesScheduledDialogActivity(
+ getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog);
finishGuidedStepFragments();
}
@@ -344,23 +382,16 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment
public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { }
@Override
- public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
- for (SeriesRecording series : seriesRecordings) {
- if (series.getId() == mSeriesRecording.getId()) {
- finishGuidedStepFragments();
- return;
- }
- }
- }
+ public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { }
@Override
public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
for (SeriesRecording seriesRecording : seriesRecordings) {
if (seriesRecording.getId() == mSeriesRecordingId) {
mSeriesRecording = seriesRecording;
- updatePriorityGuidedAction();
+ updatePriorityGuidedAction(true);
return;
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
index 8c0af9ed..393a5ff3 100644
--- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
+++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
@@ -20,15 +20,11 @@ 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;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* Keeps a set of items sorted
@@ -39,18 +35,16 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
private final Comparator<T> mComparator;
private final int mMaxItemCount;
private int mExtraItemCount;
- private final Set<Long> mIds = new HashSet<>();
- public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
+ SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
this(presenterSelector, comparator, Integer.MAX_VALUE);
}
- public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
+ SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
int maxItemCount) {
super(presenterSelector);
mComparator = comparator;
mMaxItemCount = maxItemCount;
- setHasStableIds(true);
}
/**
@@ -62,12 +56,7 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
final void setInitialItems(List<T> items) {
List<T> itemsCopy = new ArrayList<>(items);
Collections.sort(itemsCopy, mComparator);
- for (T item : itemsCopy) {
- add(item, true);
- if (size() == mMaxItemCount) {
- break;
- }
- }
+ addAll(0, itemsCopy.subList(0, Math.min(mMaxItemCount, itemsCopy.size())));
}
/**
@@ -93,9 +82,6 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* the end to save search time.
*/
public final void add(T item, boolean insertToEnd) {
- long newItemId = getId(item);
- SoftPreconditions.checkState(!mIds.contains(newItemId));
- mIds.add(newItemId);
int i;
if (insertToEnd) {
i = findInsertPosition(item);
@@ -103,9 +89,8 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
i = findInsertPositionBinary(item);
}
super.add(i, item);
- if (mMaxItemCount < Integer.MAX_VALUE && size() > mMaxItemCount + mExtraItemCount) {
- Object removedItem = get(mMaxItemCount);
- remove(removedItem);
+ if (size() > mMaxItemCount + mExtraItemCount) {
+ removeItems(mMaxItemCount, size() - mMaxItemCount - mExtraItemCount);
}
}
@@ -115,97 +100,48 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* They will be presented in their insertion order.
*/
public int addExtraItem(T item) {
- long newItemId = getId(item);
- SoftPreconditions.checkState(!mIds.contains(newItemId));
- mIds.add(newItemId);
super.add(item);
return ++mExtraItemCount;
}
- @Override
- public boolean remove(Object item) {
- return removeWithId((T) 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;
- }
-
- @Override
- public int removeItems(int position, int count) {
- int upperBound = Math.min(position + count, size());
- for (int i = position; i < upperBound; i++) {
- mIds.remove(getId((T) get(i)));
- }
- if (upperBound > size() - mExtraItemCount) {
- mExtraItemCount -= upperBound - Math.max(size() - mExtraItemCount, position);
- }
- return super.removeItems(position, count);
- }
-
- @Override
- public void replace(int position, Object item) {
- boolean wasExtra = position >= size() - mExtraItemCount;
- removeItems(position, 1);
- if (!wasExtra) {
- add(item);
- } else {
- addExtraItem((T) item);
- }
- }
-
- @Override
- public void clear() {
- mIds.clear();
- super.clear();
+ int index = indexWithTypeAndId(item);
+ return index >= 0 && index < size() && remove(get(index));
}
/**
- * Changes an item in the list.
+ * Change an item in the list.
* @param item The item to change.
*/
public final void change(T item) {
- int oldIndex = indexWithId(item);
+ int oldIndex = indexWithTypeAndId(item);
if (oldIndex != -1) {
T old = (T) get(oldIndex);
if (mComparator.compare(old, item) == 0) {
replace(oldIndex, item);
return;
}
- remove(old);
+ removeItems(oldIndex, 1);
}
add(item);
}
/**
- * Checks whether the item is in the list.
- */
- public final boolean contains(T item) {
- return indexWithId(item) != -1;
- }
-
- @Override
- public long getId(int position) {
- return getId((T) get(position));
- }
-
- /**
* 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.
*/
- protected abstract long getId(T item);
+ abstract long getId(T item);
- private int indexWithId(T item) {
+ private int indexWithTypeAndId(T item) {
long id = getId(item);
for (int i = 0; i < size() - mExtraItemCount; i++) {
T r = (T) get(i);
- if (getId(r) == id) {
+ if (r.getClass() == item.getClass() && getId(r) == id) {
return i;
}
}
diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
deleted file mode 100644
index c8f6a03f..00000000
--- a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
+++ /dev/null
@@ -1,120 +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.dvr.ui.browse;
-
-import android.content.Context;
-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.dialog.HalfSizedDialogFragment;
-import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrManager;
-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.
- */
-public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
- private static final int ACTION_STOP_RECORDING = 1;
-
- private DvrDataManager mDvrDataManger;
- private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
- new DvrDataManager.ScheduledRecordingListener() {
- @Override
- public void onScheduledRecordingAdded(ScheduledRecording... schedules) { }
-
- @Override
- public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
- for (ScheduledRecording schedule : schedules) {
- if (schedule.getId() == getRecording().getId()) {
- getActivity().finish();
- return;
- }
- }
- }
-
- @Override
- public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) {
- for (ScheduledRecording schedule : schedules) {
- if (schedule.getId() == getRecording().getId()
- && schedule.getState()
- != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
- getActivity().finish();
- return;
- }
- }
- }
- };
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mDvrDataManger = TvApplication.getSingletons(context).getDvrDataManager();
- mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener);
- }
-
- @Override
- protected SparseArrayObjectAdapter onCreateActionsAdapter() {
- SparseArrayObjectAdapter adapter =
- new SparseArrayObjectAdapter(new ActionPresenterSelector());
- Resources res = getResources();
- adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
- res.getString(R.string.epg_dvr_dialog_message_stop_recording), null,
- res.getDrawable(R.drawable.lb_ic_stop)));
- return adapter;
- }
-
- @Override
- protected OnActionClickedListener onCreateOnActionClickedListener() {
- return new OnActionClickedListener() {
- @Override
- public void onActionClicked(Action action) {
- if (action.getId() == ACTION_STOP_RECORDING) {
- DvrUiHelper.showStopRecordingDialog(getActivity(),
- getRecording().getChannelId(),
- DvrStopRecordingFragment.REASON_USER_STOP,
- new HalfSizedDialogFragment.OnActionClickListener() {
- @Override
- public void onActionClick(long actionId) {
- if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
- DvrManager dvrManager =
- TvApplication.getSingletons(getContext())
- .getDvrManager();
- dvrManager.stopRecording(getRecording());
- getActivity().finish();
- }
- }
- });
- }
- }
- };
- }
-
- @Override
- public void onDetach() {
- if (mDvrDataManger != null) {
- mDvrDataManger.removeScheduledRecordingListener(mScheduledRecordingListener);
- }
- super.onDetach();
- }
-}
diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
deleted file mode 100644
index 37a72eaf..00000000
--- a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
+++ /dev/null
@@ -1,34 +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.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. */
-public class DvrListRowPresenter extends ListRowPresenter {
- public DvrListRowPresenter(Context context) {
- super();
- setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
- setExpandedRowHeight(
- context.getResources()
- .getDimensionPixelSize(R.dimen.dvr_library_expanded_row_height));
- }
-}
diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
index 5abd52a1..d28f026c 100644
--- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
@@ -29,7 +29,7 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
/**
* A base fragment to show the list of schedule recordings.
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
index 3cbb500a..722c9b6e 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
@@ -18,9 +18,12 @@ package com.android.tv.dvr.ui.list;
import android.os.Bundle;
import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
import com.android.tv.R;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
/**
diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
index 57e7a88f..42a1e72b 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
@@ -17,7 +17,6 @@
package com.android.tv.dvr.ui.list;
import android.annotation.TargetApi;
-import android.content.Context;
import android.database.ContentObserver;
import android.media.tv.TvContract.Programs;
import android.net.Uri;
@@ -36,13 +35,11 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
-import com.android.tv.dvr.DvrDataManager;
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 com.android.tv.dvr.EpisodicProgramLoadTask;
+import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter;
-import java.util.Collections;
import java.util.List;
/**
@@ -50,22 +47,20 @@ import java.util.List;
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
+ private static final String TAG = "DvrSeriesSchedulesFragment";
/**
* 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 belong to the series recording whose scheduled recording
+ * list will be displayed.
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS =
"series_schedules_key_series_programs";
private ChannelDataManager mChannelDataManager;
- private DvrDataManager mDvrDataManager;
private SeriesRecording mSeriesRecording;
private List<Program> mPrograms;
private EpisodicProgramLoadTask mProgramLoadTask;
@@ -92,22 +87,20 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
&& getRowsAdapter() instanceof SeriesScheduleRowAdapter) {
((SeriesScheduleRowAdapter) getRowsAdapter())
.onSeriesRecordingUpdated(r);
- mSeriesRecording = r;
- updateEmptyMessage();
return;
}
}
}
};
- 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(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ executeProgramLoadingTask();
+ }
+ };
private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() {
@Override
@@ -127,28 +120,17 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
}
@Override
- public void onAttach(Context context) {
- super.onAttach(context);
+ public void onCreate(Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
mSeriesRecording = args.getParcelable(SERIES_SCHEDULES_KEY_SERIES_RECORDING);
- mPrograms = (List<Program>) BigArguments.getArgument(
- SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
- BigArguments.reset();
+ mPrograms = args.getParcelableArrayList(SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
}
- if (args == null || mPrograms == null) {
- getActivity().finish();
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
+ singletons.getDvrDataManager().addSeriesRecordingListener(mSeriesRecordingListener);
mChannelDataManager = singletons.getChannelDataManager();
mChannelDataManager.addListener(mChannelListener);
- mDvrDataManager = singletons.getDvrDataManager();
- mDvrDataManager.addSeriesRecordingListener(mSeriesRecordingListener);
getContext().getContentResolver().registerContentObserver(Programs.CONTENT_URI, true,
mContentObserver);
}
@@ -162,16 +144,8 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
private void onProgramsUpdated() {
((SeriesScheduleRowAdapter) getRowsAdapter()).setPrograms(mPrograms);
- updateEmptyMessage();
- }
-
- private void updateEmptyMessage() {
if (mPrograms == null || mPrograms.isEmpty()) {
- if (mSeriesRecording.getState() == SeriesRecording.STATE_SERIES_STOPPED) {
- showEmptyMessage(R.string.dvr_series_schedules_stopped_empty_state);
- } else {
- showEmptyMessage(R.string.dvr_series_schedules_empty_state);
- }
+ showEmptyMessage(R.string.dvr_series_schedules_empty_state);
} else {
hideEmptyMessage();
}
@@ -184,15 +158,15 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
mProgramLoadTask = null;
}
getContext().getContentResolver().unregisterContentObserver(mContentObserver);
- mHandler.removeCallbacksAndMessages(null);
mChannelDataManager.removeListener(mChannelListener);
- mDvrDataManager.removeSeriesRecordingListener(mSeriesRecordingListener);
+ TvApplication.getSingletons(getContext()).getDvrDataManager()
+ .removeSeriesRecordingListener(mSeriesRecordingListener);
super.onDestroy();
}
@Override
public SchedulesHeaderRowPresenter onCreateHeaderRowPresenter() {
- return new SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter(getContext());
+ return new SeriesRecordingHeaderRowPresenter(getContext());
}
@Override
@@ -221,7 +195,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
mProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
@Override
protected void onPostExecute(List<Program> programs) {
- mPrograms = programs == null ? Collections.EMPTY_LIST : programs;
+ mPrograms = programs;
onProgramsUpdated();
}
};
@@ -231,4 +205,4 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
.setIgnoreChannelOption(true)
.execute();
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
index 9b0ad105..23aebf59 100644
--- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
+++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
@@ -19,13 +19,13 @@ 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.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording.Builder;
/**
* A class for the episodic program.
*/
-class EpisodicProgramRow extends ScheduleRow {
+public class EpisodicProgramRow extends ScheduleRow {
private final String mInputId;
private final 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 66e96f94..3fc92e8a 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
@@ -20,12 +20,12 @@ 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.ScheduledRecording;
/**
* A class for schedule recording row.
*/
-class ScheduleRow {
+public class ScheduleRow {
private final SchedulesHeaderRow mHeaderRow;
@Nullable private ScheduledRecording mSchedule;
private boolean mStopRecordingRequested;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
index 97d60473..9cc82653 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
@@ -30,8 +30,8 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
-import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit;
/**
* An adapter for {@link ScheduleRow}.
*/
-class ScheduleRowAdapter extends ArrayObjectAdapter {
+public class ScheduleRowAdapter extends ArrayObjectAdapter {
private static final String TAG = "ScheduleRowAdapter";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
index dc4e3c41..1257e725 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
@@ -42,24 +42,25 @@ 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.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.ui.HalfSizedDialogFragment;
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;
+import java.util.concurrent.TimeUnit;
/**
* A RowPresenter for {@link ScheduleRow}.
*/
@TargetApi(Build.VERSION_CODES.N)
-class ScheduleRowPresenter extends RowPresenter {
+public class ScheduleRowPresenter extends RowPresenter {
private static final String TAG = "ScheduleRowPresenter";
@Retention(RetentionPolicy.SOURCE)
@@ -344,9 +345,7 @@ class ScheduleRowPresenter extends RowPresenter {
viewHolder.mInfoContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- if (isInfoClickable(row)) {
- onInfoClicked(row);
- }
+ onInfoClicked(row);
}
});
@@ -367,7 +366,8 @@ class ScheduleRowPresenter extends RowPresenter {
viewHolder.mTimeView.setText(onGetRecordingTimeText(row));
String programInfoText = onGetProgramInfoText(row);
if (TextUtils.isEmpty(programInfoText)) {
- int durationMins = Math.max(1, Utils.getRoundOffMinsFromMs(row.getDuration()));
+ int durationMins =
+ Math.max((int) TimeUnit.MILLISECONDS.toMinutes(row.getDuration()), 1);
programInfoText = mContext.getResources().getQuantityString(
R.plurals.dvr_schedules_recording_duration, durationMins, durationMins);
}
@@ -403,7 +403,6 @@ class ScheduleRowPresenter extends RowPresenter {
} else {
viewHolder.whiteBackInfo();
}
- viewHolder.mInfoContainer.setFocusable(isInfoClickable(row));
updateActionContainer(viewHolder, viewHolder.isSelected());
}
@@ -455,13 +454,11 @@ class ScheduleRowPresenter extends RowPresenter {
/**
* 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());
+ protected void onInfoClicked(ScheduleRow scheduleRow) {
+ ScheduledRecording schedule = scheduleRow.getSchedule();
+ if (schedule != null) {
+ DvrUiHelper.startDetailsActivity((Activity) mContext, schedule, null, true);
+ }
}
/**
@@ -548,7 +545,7 @@ class ScheduleRowPresenter extends RowPresenter {
// This row has been deleted.
return;
}
- if (row.isRecordingInProgress() && !row.isStopRecordingRequested()) {
+ if (row.isOnAir() && row.isRecordingInProgress() && !row.isStopRecordingRequested()) {
row.setStopRecordingRequested(true);
mDvrManager.stopRecording(row.getSchedule());
CharSequence deletedInfo = onGetProgramInfoText(row);
@@ -673,9 +670,10 @@ class ScheduleRowPresenter extends RowPresenter {
hideActionView(viewHolder.mFirstActionContainer, View.GONE);
}
};
- mLastFocusedViewId = R.id.info_container;
- SoftPreconditions.checkState(viewHolder.mInfoContainer.isFocusable(), TAG,
- "No focusable view in this row: " + viewHolder);
+ if (mLastFocusedViewId == R.id.action_first_container
+ || mLastFocusedViewId == R.id.action_second_container) {
+ mLastFocusedViewId = R.id.info_container;
+ }
break;
}
View view = viewHolder.view.findViewById(mLastFocusedViewId);
@@ -685,10 +683,8 @@ class ScheduleRowPresenter extends RowPresenter {
// requestFocus() explicitly.
if (view.hasFocus()) {
viewHolder.mPendingAnimationRunnable.run();
- } else if (view.isFocusable()){
- view.requestFocus();
} else {
- viewHolder.view.requestFocus();
+ view.requestFocus();
}
}
} else {
@@ -741,10 +737,10 @@ class ScheduleRowPresenter extends RowPresenter {
@ScheduleRowAction
protected int[] getAvailableActions(ScheduleRow row) {
if (row.getSchedule() != null) {
- if (row.isRecordingInProgress()) {
- return new int[]{ACTION_STOP_RECORDING};
- } else if (row.isOnAir()) {
- if (row.isRecordingNotStarted()) {
+ if (row.isOnAir()) {
+ if (row.isRecordingInProgress()) {
+ return new int[] {ACTION_STOP_RECORDING};
+ } else if (row.isRecordingNotStarted()) {
if (canResolveConflict()) {
// The "START" action can change the conflict states.
return new int[] {ACTION_REMOVE_SCHEDULE, ACTION_START_RECORDING};
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
index 715ecb8c..0fb0924d 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
@@ -16,15 +16,12 @@
package com.android.tv.dvr.ui.list;
-import com.android.tv.data.Program;
-import com.android.tv.dvr.data.SeriesRecording;
-
-import java.util.List;
+import com.android.tv.dvr.SeriesRecording;
/**
* A base class for the rows for schedules' header.
*/
-abstract class SchedulesHeaderRow {
+public abstract class SchedulesHeaderRow {
private String mTitle;
private String mDescription;
private int mItemCount;
@@ -101,20 +98,11 @@ abstract class SchedulesHeaderRow {
*/
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) {
+ SeriesRecording series) {
super(title, description, itemCount);
mSeriesRecording = series;
- mPrograms = programs;
- }
-
- /**
- * Returns the list of programs which belong to the series.
- */
- public List<Program> getPrograms() {
- return mPrograms;
}
/**
@@ -131,4 +119,4 @@ abstract class SchedulesHeaderRow {
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..69c33a96 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
@@ -30,14 +30,15 @@ import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.data.SeriesRecording;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.ui.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
/**
* A base class for RowPresenter for {@link SchedulesHeaderRow}
*/
-abstract class SchedulesHeaderRowPresenter extends RowPresenter {
+public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
private Context mContext;
public SchedulesHeaderRowPresenter(Context context) {
@@ -78,7 +79,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
/**
- * A presenter for {@link SchedulesHeaderRow.DateHeaderRow}.
+ * A presenter for {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}.
*/
public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter {
public DateHeaderRowPresenter(Context context) {
@@ -92,7 +93,7 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
/**
* A ViewHolder for
- * {@link SchedulesHeaderRow.DateHeaderRow}.
+ * {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}.
*/
public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
public DateHeaderRowViewHolder(Context context, ViewGroup parent) {
@@ -151,9 +152,9 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
headerViewHolder.mSeriesSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
+ // TODO: pass channel list for settings.
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(),
- header.getPrograms(), false, false, false, null);
+ header.getSeriesRecording().getId(), null, false, false, false);
}
});
headerViewHolder.mToggleStartStopButton.setOnClickListener(new OnClickListener() {
@@ -168,9 +169,9 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
.build();
TvApplication.getSingletons(getContext()).getDvrManager()
.updateSeriesRecording(seriesRecording);
+ // TODO: pass channel list for settings.
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(),
- header.getPrograms(), false, false, false, null);
+ header.getSeriesRecording().getId(), null, false, false, false);
} else {
DvrUiHelper.showCancelAllSeriesRecordingDialog(
(DvrSchedulesActivity) view.getContext(),
@@ -181,8 +182,11 @@ abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
private void setTextDrawable(TextView textView, Drawable drawableStart) {
- textView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, null,
- null);
+ if (mLtr) {
+ textView.setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null);
+ } else {
+ textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawableStart, 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..3b493774 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
@@ -31,8 +31,8 @@ import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
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 com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.SeriesRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
import com.android.tv.util.Utils;
@@ -46,7 +46,7 @@ import java.util.Map;
* An adapter for series schedule row.
*/
@TargetApi(Build.VERSION_CODES.N)
-class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
+public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
private static final String TAG = "SeriesRowAdapter";
private static final boolean DEBUG = false;
@@ -96,7 +96,7 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
Collections.sort(sortedPrograms);
List<EpisodicProgramRow> rows = new ArrayList<>();
mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(),
- null, sortedPrograms.size(), mSeriesRecording, programs);
+ null, sortedPrograms.size(), mSeriesRecording);
for (Program program : sortedPrograms) {
ScheduledRecording schedule =
mDataManager.getScheduledRecordingForProgramId(program.getId());
@@ -145,7 +145,7 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
if (index != -1) {
EpisodicProgramRow row = (EpisodicProgramRow) get(index);
if (!row.isStartRecordingRequested()) {
- setScheduleToRow(row, schedule);
+ row.setSchedule(schedule);
notifyArrayItemRangeChanged(index, 1);
}
}
@@ -195,10 +195,12 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
if (!isStartOrStopRequested()) {
executePendingUpdate();
}
- setScheduleToRow(row, schedule);
+ row.setSchedule(schedule);
}
+ } else if (willBeKept(schedule)) {
+ row.setSchedule(schedule);
} else {
- setScheduleToRow(row, schedule);
+ row.setSchedule(null);
}
notifyArrayItemRangeChanged(index, 1);
}
@@ -211,14 +213,6 @@ class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
}
}
- private void setScheduleToRow(ScheduleRow row, ScheduledRecording schedule) {
- if (schedule != null && willBeKept(schedule)) {
- row.setSchedule(schedule);
- } else {
- row.setSchedule(null);
- }
- }
-
private int findRowIndexByProgramId(long programId) {
for (int i = 0; i < size(); i++) {
Object item = get(i);
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
index c8503e0d..5d88579a 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
@@ -22,13 +22,13 @@ 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.dvr.DvrUiHelper;
import com.android.tv.util.Utils;
/**
* A RowPresenter for series schedule row.
*/
-class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
+public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
private static final String TAG = "SeriesRowPresenter";
private boolean mLtr;
@@ -74,8 +74,13 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
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);
+ if (mLtr) {
+ viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
+ } else {
+ viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(
+ 0, 0, R.drawable.ic_warning_gray600_36dp, 0);
+ }
} else {
viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
@@ -83,7 +88,9 @@ class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
@Override
protected void onInfoClicked(ScheduleRow row) {
- DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule());
+ if (row.getSchedule() != null) {
+ DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule());
+ }
}
@Override
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
deleted file mode 100644
index e49870f1..00000000
--- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
+++ /dev/null
@@ -1,154 +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.dvr.ui.playback;
-
-import android.media.tv.TvTrackInfo;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.text.TextUtils;
-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.
- */
-public class DvrPlaybackSideFragment extends GuidedStepFragment {
- /**
- * 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.
- */
- public static final String SELECTED_TRACK_ID = "dvr_key_selected_track_id";
-
- private static final int ACTION_ID_NO_SUBTITLE = -1;
- private static final int CHECK_SET_ID = 1;
-
- private List<TvTrackInfo> mTrackInfos;
- private String mSelectedTrackId;
- private TvTrackInfo mSelectedTrack;
- private int mTrackType;
- private DvrPlaybackOverlayFragment mOverlayFragment;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- 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));
- super.onCreate(savedInstanceState);
- }
-
- @Override
- 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));
- 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());
- }
- 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();
- actions.add(action);
- if (checked) {
- mSelectedTrack = info;
- }
- }
- }
-
- @Override
- public void onGuidedActionFocused(GuidedAction action) {
- int actionId = (int) action.getId();
- mOverlayFragment.selectTrack(mTrackType, actionId < 0 ? null : mTrackInfos.get(actionId));
- }
-
- @Override
- public void onGuidedActionClicked(GuidedAction action) {
- int actionId = (int) action.getId();
- mSelectedTrack = actionId < 0 ? null : mTrackInfos.get(actionId);
- TvSettings.setDvrPlaybackTrackSettings(getContext(), mTrackType, mSelectedTrack);
- getFragmentManager().popBackStack();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- // Workaround: when overlay fragment is faded out, any focus will lost due to overlay
- // fragment's implementation. So we disable overlay fragment's fading here to prevent
- // losing focus while users are interacting with the side fragment.
- mOverlayFragment.setFadingEnabled(false);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- // We disable fading of overlay fragment to prevent side fragment from losing focus,
- // therefore we should resume it here.
- mOverlayFragment.setFadingEnabled(true);
- mOverlayFragment.selectTrack(mTrackType, mSelectedTrack);
- }
-
- private String getTrackLabel(TvTrackInfo track, int trackIndex) {
- 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)
- : getString(R.string.multi_audio_unknown_language);
- }
-
- @Override
- protected void onProvideFragmentTransitions() {
- super.onProvideFragmentTransitions();
- // Excludes the background scrim from transition to prevent the blinking caused by
- // hiding the overlay fragment and sliding in the side fragment at the same time.
- Transition t = getEnterTransition();
- if (t != null) {
- t.excludeTarget(R.id.guidedstep_background, true);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/experiments/ExperimentFlag.java b/src/com/android/tv/experiments/ExperimentFlag.java
index c0cbd643..8f60c2b5 100644
--- a/src/com/android/tv/experiments/ExperimentFlag.java
+++ b/src/com/android/tv/experiments/ExperimentFlag.java
@@ -16,19 +16,12 @@
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;
- }
+ private final T mDefaultValue;
/** Returns a boolean experiment */
public static ExperimentFlag<Boolean> createFlag(
@@ -37,11 +30,6 @@ public final class ExperimentFlag<T> {
defaultValue);
}
- private final T mDefaultValue;
-
- private T mOverrideValue = null;
- private boolean mOverridden = false;
-
private ExperimentFlag(
T defaultValue) {
mDefaultValue = defaultValue;
@@ -49,22 +37,6 @@ public final class ExperimentFlag<T> {
/** Returns value for this experiment */
public T get() {
- return sAllowOverrides && mOverridden ? mOverrideValue : mDefaultValue;
+ return 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
index e17fc300..f16c8d1e 100644
--- a/src/com/android/tv/experiments/Experiments.java
+++ b/src/com/android/tv/experiments/Experiments.java
@@ -23,16 +23,11 @@ import com.android.tv.common.BuildConfig;
/**
* Set of experiments visible in AOSP.
*
- * <p>This file is maintained by hand.
+ * <p>
+ * This file is maintained by hand.
*/
public final class Experiments {
public static final ExperimentFlag<Boolean> CLOUD_EPG = createFlag(
- true);
-
- /**
- * Use network tuner if it is available and there is no other tuner types.
- */
- public static final ExperimentFlag<Boolean> NETWORK_TUNER = createFlag(
false);
/**
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java
index bec4e462..120b3dba 100644
--- a/src/com/android/tv/guide/ProgramGuide.java
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -48,7 +48,7 @@ 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.analytics.DurationTimer;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.ChannelDataManager;
diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java
index d5fb418f..4c7a4404 100644
--- a/src/com/android/tv/guide/ProgramItemView.java
+++ b/src/com/android/tv/guide/ProgramItemView.java
@@ -44,8 +44,8 @@ import com.android.tv.analytics.Tracker;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.data.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.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.guide.ProgramManager.TableEntry;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
@@ -106,19 +106,18 @@ public class ProgramItemView extends TextView {
}, 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())) {
+ } else if (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() {
- @Override
- public void run() {
- DvrUiHelper.requestRecordingFutureProgram(tvActivity,
- entry.program, false);
- }
- });
+ if (DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity,
+ channel.getInputId())
+ && DvrUiHelper.handleCreateSchedule(tvActivity, entry.program)) {
+ String msg = view.getContext().getString(
+ R.string.dvr_msg_program_scheduled, entry.program.getTitle());
+ ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT);
+ }
} else {
dvrManager.removeScheduledRecording(entry.scheduledRecording);
String msg = view.getResources().getString(
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index e543fd05..e3d919df 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -29,7 +29,7 @@ import com.android.tv.data.ProgramDataManager;
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.dvr.ScheduledRecording;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -439,24 +439,11 @@ public class ProgramManager {
mChannels = mChannelDataManager.getBrowsableChannelList();
mSelectedGenreId = GenreItems.ID_ALL_CHANNELS;
mFilteredChannels = mChannels;
- updateTableEntriesWithoutNotification(clearPreviousTableEntries);
- // Channel update notification should be called after updating table entries, so that
- // the listener can get the entries.
notifyChannelsUpdated();
- notifyTableEntriesUpdated();
- buildGenreFilters();
+ updateTableEntries(clearPreviousTableEntries);
}
private void updateTableEntries(boolean clear) {
- updateTableEntriesWithoutNotification(clear);
- notifyTableEntriesUpdated();
- buildGenreFilters();
- }
-
- /**
- * Updates the table entries without notifying the change.
- */
- private void updateTableEntriesWithoutNotification(boolean clear) {
if (clear) {
mChannelIdEntriesMap.clear();
}
@@ -504,6 +491,9 @@ public class ProgramManager {
}
}
}
+
+ notifyTableEntriesUpdated();
+ buildGenreFilters();
}
private void notifyGenresUpdated() {
diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java
index b9a0593d..e4a67972 100644
--- a/src/com/android/tv/guide/ProgramTableAdapter.java
+++ b/src/com/android/tv/guide/ProgramTableAdapter.java
@@ -45,21 +45,19 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
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.common.feature.CommonFeatures;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.data.Program.CriticScore;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
@@ -243,6 +241,16 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte
notifyItemChanged(channelIndex, true);
}
+ @Override
+ public void onViewAttachedToWindow(ProgramRowHolder holder) {
+ holder.onAttachedToWindow();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(ProgramRowHolder holder) {
+ holder.onDetachedFromWindow();
+ }
+
// TODO: make it static
public class ProgramRowHolder extends RecyclerView.ViewHolder
implements ProgramRow.ChildFocusListener {
@@ -304,40 +312,11 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte
private final ImageView mInputLogoView;
private boolean mIsInputLogoVisible;
- private AccessibilityStateChangeListener mAccessibilityStateChangeListener =
- new AccessibilityManager.AccessibilityStateChangeListener() {
- @Override
- public void onAccessibilityStateChanged(boolean enable) {
- enable &= !TvCommonUtils.isRunningInTest();
- mDetailView.setFocusable(enable);
- mChannelHeaderView.setFocusable(enable);
- }
- };
public ProgramRowHolder(View itemView) {
super(itemView);
mContainer = (ViewGroup) itemView;
- mContainer.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mContainer
- .getViewTreeObserver()
- .addOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
- mAccessibilityManager.addAccessibilityStateChangeListener(
- mAccessibilityStateChangeListener);
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mContainer
- .getViewTreeObserver()
- .removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
- mAccessibilityManager.removeAccessibilityStateChangeListener(
- mAccessibilityStateChangeListener);
- }
- });
mProgramRow = (ProgramRow) mContainer.findViewById(R.id.row);
mDetailView = (ViewGroup) mContainer.findViewById(R.id.detail);
@@ -360,11 +339,16 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte
mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo);
mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block);
mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo);
- // TODO: Find a better way to handle talk back.
- boolean accessibilityEnabled = mAccessibilityManager.isEnabled()
- && !TvCommonUtils.isRunningInTest();
- mDetailView.setFocusable(accessibilityEnabled);
- mChannelHeaderView.setFocusable(accessibilityEnabled);
+ mDetailView.setFocusable(mAccessibilityManager.isEnabled());
+ mChannelHeaderView.setFocusable(mAccessibilityManager.isEnabled());
+ mAccessibilityManager.addAccessibilityStateChangeListener(
+ new AccessibilityManager.AccessibilityStateChangeListener() {
+ @Override
+ public void onAccessibilityStateChanged(boolean enable) {
+ mDetailView.setFocusable(enable);
+ mChannelHeaderView.setFocusable(enable);
+ }
+ });
}
public void onBind(int position) {
@@ -524,6 +508,16 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte
});
}
+ private void onAttachedToWindow() {
+ mContainer.getViewTreeObserver()
+ .addOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
+ }
+
+ private void onDetachedFromWindow() {
+ mContainer.getViewTreeObserver()
+ .removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
+ }
+
private void updateDetailView() {
if (mSelectedEntry == null) {
// The view holder is never on focus before.
diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java
index 82907a2d..868fed46 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;
@@ -26,10 +25,8 @@ 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;
/**
@@ -38,28 +35,16 @@ import java.util.concurrent.TimeUnit;
*/
public class TimeListAdapter extends RecyclerView.Adapter<TimeListAdapter.TimeViewHolder> {
private static final long TIME_UNIT_MS = TimeUnit.MINUTES.toMillis(30);
-
- // Ex. 3:00 AM
- private static final String TIME_PATTERN_SAME_DAY = "h:mm a";
- // Ex. Oct 21, 3:00 AM
- private static final String TIME_PATTERN_DIFFERENT_DAY = "MMM d, h:mm a";
-
private static int sRowHeaderOverlapping;
// Nearest half hour at or before the start time.
private long mStartUtcMs;
- private final String mTimePatternSameDay;
- private final String mTimePatternDifferentDay;
public TimeListAdapter(Resources res) {
if (sRowHeaderOverlapping == 0) {
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);
- mTimePatternDifferentDay =
- DateFormat.getBestDateTimePattern(locale, TIME_PATTERN_DIFFERENT_DAY);
}
public void update(long startTimeMs) {
@@ -83,14 +68,10 @@ public class TimeListAdapter extends RecyclerView.Adapter<TimeListAdapter.TimeVi
long endTime = startTime + TIME_UNIT_MS;
View itemView = holder.itemView;
- Date timeDate = new Date(startTime);
- String timeString;
- if (Utils.isInGivenDay(System.currentTimeMillis(), startTime)) {
- timeString = DateFormat.format(mTimePatternSameDay, timeDate).toString();
- } else {
- timeString = DateFormat.format(mTimePatternDifferentDay, timeDate).toString();
- }
- ((TextView) itemView.findViewById(R.id.time)).setText(timeString);
+
+ TextView textView = (TextView) itemView.findViewById(R.id.time);
+ String time = DateFormat.getTimeFormat(itemView.getContext()).format(new Date(startTime));
+ textView.setText(time);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
lp.width = GuideUtils.convertMillisToPixel(startTime, endTime);
diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java
index 2fd70bfb..54892cac 100644
--- a/src/com/android/tv/menu/ActionCardView.java
+++ b/src/com/android/tv/menu/ActionCardView.java
@@ -19,8 +19,8 @@ package com.android.tv.menu;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.tv.R;
@@ -28,7 +28,7 @@ import com.android.tv.R;
/**
* A view to render an item of TV options.
*/
-public class ActionCardView extends RelativeLayout implements ItemListRowView.CardView<MenuAction> {
+public class ActionCardView extends FrameLayout implements ItemListRowView.CardView<MenuAction> {
private static final String TAG = MenuView.TAG;
private static final boolean DEBUG = MenuView.DEBUG;
@@ -66,7 +66,7 @@ public class ActionCardView extends RelativeLayout implements ItemListRowView.Ca
}
mIconView.setImageDrawable(action.getDrawable(getContext()));
mLabelView.setText(action.getActionName(getContext()));
- mStateView.setText(action.getActionDescription());
+ mStateView.setText(action.getActionDescription(getContext()));
if (action.isEnabled()) {
setEnabled(true);
setFocusable(true);
diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java
index d23d9a00..bfb5e3f1 100644
--- a/src/com/android/tv/menu/AppLinkCardView.java
+++ b/src/com/android/tv/menu/AppLinkCardView.java
@@ -24,7 +24,6 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.support.v7.graphics.Palette;
import android.text.TextUtils;
@@ -56,6 +55,7 @@ public class AppLinkCardView extends BaseCardView<Channel> {
private final int mIconColorFilter;
private ImageView mImageView;
+ private View mGradientView;
private TextView mAppInfoView;
private View mMetaViewHolder;
private Channel mChannel;
@@ -102,115 +102,35 @@ public class AppLinkCardView extends BaseCardView<Channel> {
int linkType = mChannel.getAppLinkType(getContext());
mIntent = mChannel.getAppLinkIntent(getContext());
- CharSequence appLabel = null;
- mImageView.setForeground(null);
switch (linkType) {
case Channel.APP_LINK_TYPE_CHANNEL:
setText(mChannel.getAppLinkText());
mAppInfoView.setVisibility(VISIBLE);
+ mGradientView.setVisibility(VISIBLE);
mAppInfoView.setCompoundDrawablePadding(mIconPadding);
- mAppInfoView.setCompoundDrawablesRelative(null, null, null, null);
- appLabel = mTvInputManagerHelper.getTvInputApplicationLabel(channel.getInputId());
- if (appLabel != null) {
- mAppInfoView.setText(appLabel);
- } else {
- new AsyncTask<Void, Void, CharSequence>() {
- private final String mLoadTvInputId = mChannel.getInputId();
-
- @Override
- protected CharSequence doInBackground(Void... params) {
- if (appInfo != null) {
- return mPackageManager.getApplicationLabel(appInfo);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(CharSequence appLabel) {
- mTvInputManagerHelper.setTvInputApplicationLabel(
- mLoadTvInputId, appLabel);
- if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) {
- return;
- }
- mAppInfoView.setText(appLabel);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
+ mAppInfoView.setCompoundDrawables(null, null, null, null);
+ mAppInfoView.setText(mPackageManager.getApplicationLabel(appInfo));
if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) {
mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON,
- mIconWidth, mIconHeight,
- createChannelLogoCallback(
- this, mChannel, 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());
- if (appIcon != null) {
- BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
- appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
- mAppInfoView.setCompoundDrawablesRelative(appIcon, null, null, null);
- } else {
- new AsyncTask<Void, Void, Drawable>() {
- private final String mLoadTvInputId = mChannel.getInputId();
-
- @Override
- protected Drawable doInBackground(Void... params) {
- if (appInfo != null) {
- return mPackageManager.getApplicationIcon(appInfo);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Drawable appIcon) {
- mTvInputManagerHelper.setTvInputApplicationIcon(
- mLoadTvInputId, appIcon);
- if (mLoadTvInputId != mChannel.getInputId()
- || !isAttachedToWindow()) {
- return;
- }
- BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
- appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
- mAppInfoView.setCompoundDrawablesRelative(appIcon, null, null, null);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
+ Drawable appIcon = mPackageManager.getApplicationIcon(appInfo);
+ BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
+ appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
+ mAppInfoView.setCompoundDrawables(appIcon, null, null, null);
}
break;
case Channel.APP_LINK_TYPE_APP:
- appLabel = mTvInputManagerHelper.getTvInputApplicationLabel(mChannel.getInputId());
- if (appLabel != null) {
- setText(getContext()
- .getString(R.string.channels_item_app_link_app_launcher, appLabel));
- } else {
- new AsyncTask<Void, Void, CharSequence>() {
- private final String mLoadTvInputId = mChannel.getInputId();
-
- @Override
- protected CharSequence doInBackground(Void... params) {
- if (appInfo != null) {
- return mPackageManager.getApplicationLabel(appInfo);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(CharSequence appLabel) {
- mTvInputManagerHelper.setTvInputApplicationLabel(
- mLoadTvInputId, appLabel);
- if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) {
- return;
- }
- setText(getContext()
- .getString(
- R.string.channels_item_app_link_app_launcher,
- appLabel));
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
+ setText(getContext().getString(
+ R.string.channels_item_app_link_app_launcher,
+ mPackageManager.getApplicationLabel(appInfo)));
mAppInfoView.setVisibility(GONE);
+ mGradientView.setVisibility(GONE);
break;
default:
mAppInfoView.setVisibility(GONE);
+ mGradientView.setVisibility(GONE);
Log.d(TAG, "Should not be here.");
}
@@ -228,6 +148,8 @@ public class AppLinkCardView extends BaseCardView<Channel> {
} else {
setCardImageWithBanner(appInfo);
}
+ // Call super.onBind() at the end intentionally. In order to correctly handle extension of
+ // text view, text should be set before calling super.onBind.
super.onBind(channel, selected);
}
@@ -260,14 +182,13 @@ public class AppLinkCardView extends BaseCardView<Channel> {
}
}
BitmapUtils.setColorFilterToDrawable(mIconColorFilter, drawable);
- mAppInfoView.setCompoundDrawablesRelative(drawable, null, null, null);
+ mAppInfoView.setCompoundDrawables(drawable, null, null, null);
} else if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART) {
if (bitmap == null) {
setCardImageWithBanner(
mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId()));
} else {
mImageView.setImageBitmap(bitmap);
- mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient));
if (mChannel.getAppLinkColor() == 0) {
extractAndSetMetaViewBackgroundColor(bitmap);
}
@@ -279,6 +200,7 @@ public class AppLinkCardView extends BaseCardView<Channel> {
protected void onFinishInflate() {
super.onFinishInflate();
mImageView = (ImageView) findViewById(R.id.image);
+ mGradientView = findViewById(R.id.image_gradient);
mAppInfoView = (TextView) findViewById(R.id.app_info);
mMetaViewHolder = findViewById(R.id.app_link_text_holder);
}
@@ -287,85 +209,37 @@ public class AppLinkCardView extends BaseCardView<Channel> {
// 1) Provided poster art image, 2) Activity banner, 3) Activity icon, 4) Application banner,
// 5) Application icon, and 6) default image.
private void setCardImageWithBanner(ApplicationInfo appInfo) {
- new AsyncTask<Void, Void, Drawable>() {
- private String mLoadTvInputId = mChannel.getInputId();
- @Override
- protected Drawable doInBackground(Void... params) {
- Drawable banner = null;
- if (mIntent != null) {
- try {
- banner = mPackageManager.getActivityBanner(mIntent);
- if (banner == null) {
- banner = mPackageManager.getActivityIcon(mIntent);
- }
- } catch (PackageManager.NameNotFoundException e) {
- // do nothing.
- }
+ Drawable banner = null;
+ if (mIntent != null) {
+ try {
+ banner = mPackageManager.getActivityBanner(mIntent);
+ if (banner == null) {
+ banner = mPackageManager.getActivityIcon(mIntent);
}
- return banner;
+ } catch (PackageManager.NameNotFoundException e) {
+ // do nothing.
}
+ }
- @Override
- protected void onPostExecute(Drawable banner) {
- if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) {
- return;
- }
- if (banner != null) {
- setCardImageWithBannerInternal(banner);
- } else {
- setCardImageWithApplicationInfoBanner(appInfo);
- }
+ if (banner == null && appInfo != null) {
+ if (appInfo.banner != 0) {
+ banner = mPackageManager.getApplicationBanner(appInfo);
+ }
+ if (banner == null && appInfo.icon != 0) {
+ banner = mPackageManager.getApplicationIcon(appInfo);
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- private void setCardImageWithApplicationInfoBanner(ApplicationInfo appInfo) {
- Drawable appBanner =
- mTvInputManagerHelper.getTvInputApplicationBanner(mChannel.getInputId());
- if (appBanner != null) {
- setCardImageWithBannerInternal(appBanner);
- } else {
- new AsyncTask<Void, Void, Drawable>() {
- private final String mLoadTvInputId = mChannel.getInputId();
- @Override
- protected Drawable doInBackground(Void... params) {
- Drawable banner = null;
- if (appInfo != null) {
- if (appInfo.banner != 0) {
- banner = mPackageManager.getApplicationBanner(appInfo);
- }
- if (banner == null && appInfo.icon != 0) {
- banner = mPackageManager.getApplicationIcon(appInfo);
- }
- }
- return banner;
- }
-
- @Override
- protected void onPostExecute(Drawable banner) {
- mTvInputManagerHelper.setTvInputApplicationBanner(
- mLoadTvInputId, banner);
- if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) {
- return;
- }
- setCardImageWithBannerInternal(banner);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- }
- private void setCardImageWithBannerInternal(Drawable banner) {
if (banner == null) {
mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
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);
mImageView.setImageDrawable(banner);
- mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient));
if (mChannel.getAppLinkColor() == 0) {
extractAndSetMetaViewBackgroundColor(bitmap);
}
diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java
index fa74ce3e..c6a34a5d 100644
--- a/src/com/android/tv/menu/BaseCardView.java
+++ b/src/com/android/tv/menu/BaseCardView.java
@@ -57,7 +57,6 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
private TextView mTextViewFocused;
private final int mCardImageWidth;
private final float mCardHeight;
- private boolean mSelected;
public BaseCardView(Context context) {
this(context, null);
@@ -104,9 +103,23 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
/**
* Called when the view is displayed.
+ *
+ * Before onBind is called, this view's text should be set to determine if it'll be extended
+ * or not in focus state.
*/
@Override
public void onBind(T item, boolean selected) {
+ if (mTextView != null && mTextViewFocused != null) {
+ mTextViewFocused.measure(
+ MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1;
+ if (mExtendViewOnFocus) {
+ setTextViewFocusedAlpha(selected ? 1f : 0f);
+ } else {
+ setTextViewFocusedAlpha(1f);
+ }
+ }
setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
}
@@ -115,7 +128,6 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
@Override
public void onSelected() {
- mSelected = true;
if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
startFocusAnimation(SCALE_FACTOR_1F);
} else {
@@ -126,7 +138,6 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
@Override
public void onDeselected() {
- mSelected = false;
if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
startFocusAnimation(SCALE_FACTOR_0F);
} else {
@@ -145,7 +156,6 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
if (mTextView != null) {
mTextView.setText(resId);
}
- onTextViewUpdated();
}
/**
@@ -158,22 +168,6 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
if (mTextView != null) {
mTextView.setText(text);
}
- onTextViewUpdated();
- }
-
- private void onTextViewUpdated() {
- if (mTextView != null && mTextViewFocused != null) {
- mTextViewFocused.measure(
- MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1;
- if (mExtendViewOnFocus) {
- setTextViewFocusedAlpha(mSelected ? 1f : 0f);
- } else {
- setTextViewFocusedAlpha(1f);
- }
- }
- setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
}
/**
@@ -215,18 +209,12 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo
setScaleX(scale);
setScaleY(scale);
setTranslationZ(mFocusTranslationZ * animatedValue);
- if (mTextView != null && mTextViewFocused != null) {
+ if (mExtendViewOnFocus) {
ViewGroup.LayoutParams params = mTextView.getLayoutParams();
- int height = mExtendViewOnFocus ? Math.round(mTextViewHeight
- + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue)
- : (int) mTextViewHeight;
- if (height != params.height) {
- params.height = height;
- setTextViewLayoutParams(params);
- }
- if (mExtendViewOnFocus) {
- setTextViewFocusedAlpha(animatedValue);
- }
+ params.height = Math.round(mTextViewHeight
+ + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue);
+ setTextViewLayoutParams(params);
+ setTextViewFocusedAlpha(animatedValue);
}
}
diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java
index 4ee56892..1c8015a6 100644
--- a/src/com/android/tv/menu/ChannelCardView.java
+++ b/src/com/android/tv/menu/ChannelCardView.java
@@ -45,6 +45,7 @@ public class ChannelCardView extends BaseCardView<Channel> {
private final int mCardImageHeight;
private ImageView mImageView;
+ private View mGradientView;
private TextView mChannelNumberNameView;
private ProgressBar mProgressBar;
private Channel mChannel;
@@ -70,6 +71,7 @@ public class ChannelCardView extends BaseCardView<Channel> {
protected void onFinishInflate() {
super.onFinishInflate();
mImageView = (ImageView) findViewById(R.id.image);
+ mGradientView = findViewById(R.id.image_gradient);
mChannelNumberNameView = (TextView) findViewById(R.id.channel_number_and_name);
mProgressBar = (ProgressBar) findViewById(R.id.progress);
}
@@ -86,7 +88,7 @@ public class ChannelCardView extends BaseCardView<Channel> {
mChannelNumberNameView.setVisibility(VISIBLE);
mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
mImageView.setBackgroundResource(R.color.channel_card);
- mImageView.setForeground(null);
+ mGradientView.setVisibility(View.GONE);
mProgressBar.setVisibility(GONE);
setTextViewEnabled(true);
@@ -99,6 +101,8 @@ public class ChannelCardView extends BaseCardView<Channel> {
}
updateProgramInformation();
+ // Call super.onBind() at the end intentionally. In order to correctly handle extension of
+ // text view, text should be set before calling super.onBind.
super.onBind(channel, selected);
}
@@ -119,7 +123,7 @@ public class ChannelCardView extends BaseCardView<Channel> {
private void updatePosterArt(Bitmap posterArt) {
mImageView.setImageBitmap(posterArt);
- mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient));
+ mGradientView.setVisibility(View.VISIBLE);
}
private void updateProgramInformation() {
diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java
index 490d73de..dedf0993 100644
--- a/src/com/android/tv/menu/ChannelsRow.java
+++ b/src/com/android/tv/menu/ChannelsRow.java
@@ -26,14 +26,8 @@ import com.android.tv.recommendation.Recommender;
public class ChannelsRow extends ItemListRow {
public static final String ID = ChannelsRow.class.getName();
- /**
- * Minimum count for recent channels.
- */
- public static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5;
- /**
- * Maximum count for recent channels.
- */
- public static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10;
+ private static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5;
+ private static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10;
private Recommender mTvRecommendation;
private ChannelsRowAdapter mChannelsAdapter;
diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java
index 4ba6a93a..c8e1bd05 100644
--- a/src/com/android/tv/menu/ChannelsRowAdapter.java
+++ b/src/com/android/tv/menu/ChannelsRowAdapter.java
@@ -31,6 +31,7 @@ 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 com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -129,6 +130,8 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel>
@Override
public void onBindViewHolder(MyViewHolder viewHolder, int position) {
+ super.onBindViewHolder(viewHolder, position);
+
int viewType = getItemViewType(position);
if (viewType == R.layout.menu_card_guide) {
viewHolder.itemView.setOnClickListener(mGuideOnClickListener);
@@ -144,7 +147,6 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel>
viewHolder.itemView.setTag(getItemList().get(position));
viewHolder.itemView.setOnClickListener(mChannelOnClickListener);
}
- super.onBindViewHolder(viewHolder, position);
}
@Override
diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java
index 01257628..4919c595 100644
--- a/src/com/android/tv/menu/ItemListRowView.java
+++ b/src/com/android/tv/menu/ItemListRowView.java
@@ -28,7 +28,6 @@ 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;
@@ -195,20 +194,9 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe
return mItemList.size();
}
- /**
- * Returns the position of the item.
- */
- protected int getItemPosition(T item) {
- return mItemList.indexOf(item);
- }
-
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- int resId = getLayoutResId(viewType);
- View view = ViewCache.getInstance().getView(resId);
- if (view == null) {
- view = mLayoutInflater.inflate(resId, parent, false);
- }
+ View view = mLayoutInflater.inflate(getLayoutResId(viewType), parent, false);
return new MyViewHolder(view);
}
diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java
index 25e629c1..1160a5b5 100644
--- a/src/com/android/tv/menu/Menu.java
+++ b/src/com/android/tv/menu/Menu.java
@@ -27,30 +27,23 @@ import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
import com.android.tv.ChannelTuner;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.TvOptionsManager;
+import com.android.tv.analytics.DurationTimer;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.TvCommonUtils;
import com.android.tv.common.WeakHandler;
import com.android.tv.menu.MenuRowFactory.PartnerRow;
+import com.android.tv.menu.MenuRowFactory.PipOptionsRow;
import com.android.tv.menu.MenuRowFactory.TvOptionsRow;
import com.android.tv.ui.TunableTvView;
-import com.android.tv.util.DurationTimer;
-import com.android.tv.util.ViewCache;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* A class which controls the menu.
@@ -88,21 +81,10 @@ public class Menu {
sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_NEXT
}
- 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);
- PRELOAD_VIEW_IDS.put(R.layout.menu_card_dvr, 1);
- PRELOAD_VIEW_IDS.put(R.layout.menu_card_app_link, 1);
- PRELOAD_VIEW_IDS.put(R.layout.menu_card_channel, ChannelsRow.MAX_COUNT_FOR_RECENT_CHANNELS);
- PRELOAD_VIEW_IDS.put(R.layout.menu_card_action, 7);
- }
-
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();
@@ -121,16 +103,15 @@ public class Menu {
@VisibleForTesting
Menu(Context context, IMenuView menuView, MenuRowFactory menuRowFactory,
OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) {
- this(context, null, null, menuView, menuRowFactory, onMenuVisibilityChangeListener);
+ this(context, null, menuView, menuRowFactory, onMenuVisibilityChangeListener);
}
- public Menu(Context context, TunableTvView tvView, TvOptionsManager optionsManager,
- IMenuView menuView, MenuRowFactory menuRowFactory,
+ public Menu(Context context, TunableTvView tvView, IMenuView menuView,
+ MenuRowFactory menuRowFactory,
OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) {
- mContext = context;
mMenuView = menuView;
mTracker = TvApplication.getSingletons(context).getTracker();
- mMenuUpdater = new MenuUpdater(this, tvView, optionsManager);
+ mMenuUpdater = new MenuUpdater(context, tvView, this);
Resources res = context.getResources();
mShowDurationMillis = res.getInteger(R.integer.menu_show_duration);
mOnMenuVisibilityChangeListener = onMenuVisibilityChangeListener;
@@ -149,6 +130,7 @@ public class Menu {
addMenuRow(menuRowFactory.createMenuRow(this, ChannelsRow.class));
addMenuRow(menuRowFactory.createMenuRow(this, PartnerRow.class));
addMenuRow(menuRowFactory.createMenuRow(this, TvOptionsRow.class));
+ addMenuRow(menuRowFactory.createMenuRow(this, PipOptionsRow.class));
mMenuView.setMenuRows(mMenuRows);
}
@@ -178,23 +160,6 @@ public class Menu {
}
/**
- * Preloads the item view used for the menu.
- */
- public void preloadItemViews() {
- LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- // Use a fake parent to make the layoutParams set correctly.
- ViewGroup fakeParent = new LinearLayout(mContext);
- for (int id : PRELOAD_VIEW_IDS.keySet()) {
- int count = PRELOAD_VIEW_IDS.get(id);
- for (int i = 0; i < count; i++) {
- View view = inflater.inflate(id, fakeParent, false);
- ViewCache.getInstance().putView(id, view);
- }
- }
- }
-
- /**
* Shows the main menu.
*
* @param reason A reason why this is called. See {@link MenuShowReason}
diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java
index b4356059..0d59552a 100644
--- a/src/com/android/tv/menu/MenuAction.java
+++ b/src/com/android/tv/menu/MenuAction.java
@@ -20,9 +20,9 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import com.android.tv.MainActivity;
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.
@@ -36,9 +36,12 @@ public class MenuAction {
public static final MenuAction SELECT_DISPLAY_MODE_ACTION =
new MenuAction(R.string.options_item_display_mode, TvOptionsManager.OPTION_DISPLAY_MODE,
R.drawable.ic_tvoption_aspect);
+ public static final MenuAction PIP_IN_APP_ACTION =
+ new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_IN_APP_PIP,
+ R.drawable.ic_tvoption_pip);
public static final MenuAction SYSTEMWIDE_PIP_ACTION =
new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_SYSTEMWIDE_PIP,
- R.drawable.ic_tvoption_pip);
+ R.drawable.ic_pip_option_layout2);
public static final MenuAction SELECT_AUDIO_LANGUAGE_ACTION =
new MenuAction(R.string.options_item_multi_audio, TvOptionsManager.OPTION_MULTI_AUDIO,
R.drawable.ic_tvoption_multi_track);
@@ -48,36 +51,34 @@ public class MenuAction {
public static final MenuAction DEV_ACTION =
new MenuAction(R.string.options_item_developer,
TvOptionsManager.OPTION_DEVELOPER, R.drawable.ic_developer_mode_tv_white_48dp);
+ // TODO: Change the icon.
public static final MenuAction SETTINGS_ACTION =
new MenuAction(R.string.options_item_settings, TvOptionsManager.OPTION_SETTINGS,
R.drawable.ic_settings);
+ // Actions in the PIP option row.
+ public static final MenuAction PIP_SELECT_INPUT_ACTION =
+ new MenuAction(R.string.pip_options_item_source, TvOptionsManager.OPTION_PIP_INPUT,
+ R.drawable.ic_pip_option_input);
+ public static final MenuAction PIP_SWAP_ACTION =
+ new MenuAction(R.string.pip_options_item_swap, TvOptionsManager.OPTION_PIP_SWAP,
+ R.drawable.ic_pip_option_swap);
+ public static final MenuAction PIP_SOUND_ACTION =
+ new MenuAction(R.string.pip_options_item_sound, TvOptionsManager.OPTION_PIP_SOUND,
+ R.drawable.ic_pip_option_swap_audio);
+ public static final MenuAction PIP_LAYOUT_ACTION =
+ new MenuAction(R.string.pip_options_item_layout, TvOptionsManager.OPTION_PIP_LAYOUT,
+ R.drawable.ic_pip_option_layout1);
+ public static final MenuAction PIP_SIZE_ACTION =
+ new MenuAction(R.string.pip_options_item_size, TvOptionsManager.OPTION_PIP_SIZE,
+ R.drawable.ic_pip_option_size);
private final String mActionName;
private final int mActionNameResId;
- @OptionType private final int mType;
- private String mActionDescription;
+ private final int mType;
private Drawable mDrawable;
private int mDrawableResId;
private boolean mEnabled = true;
- /**
- * 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.
- */
- public static boolean setEnabled(MenuAction action, boolean enabled) {
- boolean changed = action.mEnabled != enabled;
- action.mEnabled = enabled;
- return changed;
- }
-
public MenuAction(int actionNameResId, int type, int drawableResId) {
mActionName = null;
mActionNameResId = actionNameResId;
@@ -101,11 +102,11 @@ public class MenuAction {
return context.getString(mActionNameResId);
}
- public String getActionDescription() {
- return mActionDescription;
+ public String getActionDescription(Context context) {
+ return ((MainActivity) context).getTvOptionsManager().getOptionString(mType);
}
- @OptionType public int getType() {
+ public int getType() {
return mType;
}
@@ -119,10 +120,28 @@ public class MenuAction {
return mDrawable;
}
+ /**
+ * Sets drawable resource id.
+ *
+ * @return {@code true} if drawable is changed.
+ */
+ public boolean setDrawableResId(int resId) {
+ if (mDrawableResId == resId) {
+ return false;
+ }
+ mDrawable = null;
+ mDrawableResId = resId;
+ return true;
+ }
+
public boolean isEnabled() {
return mEnabled;
}
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
public int getActionNameResId() {
return mActionNameResId;
}
diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java
index a16ac197..6c767247 100644
--- a/src/com/android/tv/menu/MenuLayoutManager.java
+++ b/src/com/android/tv/menu/MenuLayoutManager.java
@@ -384,15 +384,10 @@ public class MenuLayoutManager {
mSelectedPosition = position;
if (DEBUG) dumpChildren("startRowAnimation()");
+ MenuRowView currentView = mMenuRowViews.get(position);
// Show the children of the next row.
- final MenuRowView currentView = mMenuRowViews.get(position);
- TextView currentTitleView = currentView.getTitleView();
- View currentContentsView = currentView.getContentsView();
- currentTitleView.setVisibility(View.VISIBLE);
- currentContentsView.setVisibility(View.VISIBLE);
- if (currentView instanceof PlayControlsRowView) {
- ((PlayControlsRowView) currentView).onPreselected();
- }
+ currentView.getTitleView().setVisibility(View.VISIBLE);
+ currentView.getContentsView().setVisibility(View.VISIBLE);
// Request focus after the new contents view shows up.
mMenuView.requestFocus();
if (mTempTitleViewForOld == null) {
@@ -412,7 +407,7 @@ public class MenuLayoutManager {
// Old row.
MenuRow oldRow = mMenuRows.get(oldPosition);
- final MenuRowView oldView = mMenuRowViews.get(oldPosition);
+ MenuRowView oldView = mMenuRowViews.get(oldPosition);
View oldContentsView = oldView.getContentsView();
// Old contents view.
animators.add(createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn)
@@ -473,6 +468,8 @@ public class MenuLayoutManager {
}
// Current row.
Rect currentLayoutRect = new Rect(layouts.get(position));
+ TextView currentTitleView = currentView.getTitleView();
+ View currentContentsView = currentView.getContentsView();
currentContentsView.setAlpha(0.0f);
if (scrollDown) {
// Current title view.
@@ -575,8 +572,9 @@ public class MenuLayoutManager {
for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) {
holder.property.set(holder.view, holder.value);
}
- oldView.onDeselected();
- currentView.onSelected(true);
+ oldTitleView.setVisibility(View.VISIBLE);
+ mMenuRowViews.get(oldPosition).onDeselected();
+ mMenuRowViews.get(position).onSelected(true);
mTempTitleViewForOld.setVisibility(View.GONE);
mTempTitleViewForCurrent.setVisibility(View.GONE);
layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(),
diff --git a/src/com/android/tv/menu/MenuRowFactory.java b/src/com/android/tv/menu/MenuRowFactory.java
index 2d5453fe..c67a0e04 100644
--- a/src/com/android/tv/menu/MenuRowFactory.java
+++ b/src/com/android/tv/menu/MenuRowFactory.java
@@ -67,6 +67,8 @@ public class MenuRowFactory {
} else if (TvOptionsRow.class.equals(key)) {
return new TvOptionsRow(mMainActivity, menu, mTvCustomizationManager
.getCustomActions(TvCustomizationManager.ID_OPTIONS_ROW));
+ } else if (PipOptionsRow.class.equals(key)) {
+ return new PipOptionsRow(mMainActivity, menu);
}
return null;
}
@@ -75,9 +77,6 @@ public class MenuRowFactory {
* A menu row which represents the TV options row.
*/
public static class TvOptionsRow extends ItemListRow {
- /** 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,
new TvOptionsRowAdapter(context, customActions));
@@ -92,6 +91,25 @@ public class MenuRowFactory {
}
/**
+ * A menu row which represents the PIP options row.
+ */
+ public static class PipOptionsRow extends ItemListRow {
+ private final MainActivity mMainActivity;
+
+ private PipOptionsRow(Context context, Menu menu) {
+ super(context, menu, R.string.menu_title_pip_options, R.dimen.action_card_height,
+ new PipOptionsRowAdapter(context));
+ mMainActivity = (MainActivity) context;
+ }
+
+ @Override
+ public boolean isVisible() {
+ // TODO: Remove the dependency on MainActivity.
+ return super.isVisible() && mMainActivity.isPipEnabled();
+ }
+ }
+
+ /**
* A menu row which represents the partner row.
*/
public static class PartnerRow extends ItemListRow {
diff --git a/src/com/android/tv/menu/MenuUpdater.java b/src/com/android/tv/menu/MenuUpdater.java
index 7ad38e74..075b299e 100644
--- a/src/com/android/tv/menu/MenuUpdater.java
+++ b/src/com/android/tv/menu/MenuUpdater.java
@@ -16,14 +16,11 @@
package com.android.tv.menu;
+import android.content.Context;
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.menu.MenuRowFactory.TvOptionsRow;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener;
@@ -33,10 +30,10 @@ import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener;
* <p>As the menu is updated when it shows up, this class handles only the dynamic updates.
*/
public class MenuUpdater {
- private final Menu mMenu;
// Can be null for testing.
- @Nullable private final TunableTvView mTvView;
- @Nullable private final TvOptionsManager mOptionsManager;
+ @Nullable
+ private final TunableTvView mTvView;
+ private final Menu mMenu;
private ChannelTuner mChannelTuner;
private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() {
@@ -45,7 +42,7 @@ public class MenuUpdater {
@Override
public void onBrowsableChannelListChanged() {
- mMenu.update(ChannelsRow.ID);
+ mMenu.update();
}
@Override
@@ -56,17 +53,10 @@ public class MenuUpdater {
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;
+ public MenuUpdater(Context context, TunableTvView tvView, Menu menu) {
mTvView = tvView;
- mOptionsManager = optionsManager;
+ mMenu = menu;
if (mTvView != null) {
mTvView.setOnScreenBlockedListener(new OnScreenBlockingChangedListener() {
@Override
@@ -75,18 +65,11 @@ public class MenuUpdater {
}
});
}
- if (mOptionsManager != null) {
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS,
- mOptionChangeListener);
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE,
- mOptionChangeListener);
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO,
- mOptionChangeListener);
- }
}
/**
- * Sets the instance of {@link ChannelTuner}. Call this method when the channel tuner is ready.
+ * Sets the instance of {@link ChannelTuner}. Call this method when the channel tuner is ready
+ * or not available any more.
*/
public void setChannelTuner(ChannelTuner channelTuner) {
if (mChannelTuner != null) {
@@ -96,6 +79,7 @@ public class MenuUpdater {
if (mChannelTuner != null) {
mChannelTuner.addListener(mChannelTunerListener);
}
+ mMenu.update();
}
/**
@@ -108,10 +92,5 @@ public class MenuUpdater {
if (mTvView != null) {
mTvView.setOnScreenBlockedListener(null);
}
- if (mOptionsManager != null) {
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS, null);
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE, null);
- mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO, null);
- }
}
}
diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java
index dd6194a1..93bd0a4d 100644
--- a/src/com/android/tv/menu/OptionsRowAdapter.java
+++ b/src/com/android/tv/menu/OptionsRowAdapter.java
@@ -21,6 +21,8 @@ import android.view.View;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.TvOptionsManager;
+import com.android.tv.TvOptionsManager.OptionChangedListener;
import com.android.tv.analytics.Tracker;
import java.util.List;
@@ -64,9 +66,12 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<
public void update() {
if (mActionList == null) {
mActionList = createActions();
+ updateActions();
setItemList(mActionList);
} else {
- updateActions();
+ if (updateActions()) {
+ setItemList(mActionList);
+ }
}
}
@@ -76,7 +81,7 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<
}
protected abstract List<MenuAction> createActions();
- protected abstract void updateActions();
+ protected abstract boolean updateActions();
protected abstract void executeAction(int type);
/**
@@ -88,6 +93,37 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<
return mActionList.get(position);
}
+ /**
+ * Sets the action at the given position.
+ * Note that action at the position may differ from returned by {@link #createActions}.
+ * See {@link CustomizableOptionsRowAdapter}
+ */
+ protected void setAction(int position, MenuAction action) {
+ mActionList.set(position, action);
+ }
+
+ /**
+ * Adds an action to the given position.
+ * Note that action at the position may differ from returned by {@link #createActions}.
+ * See {@link CustomizableOptionsRowAdapter}
+ */
+ protected void addAction(int position, MenuAction action) {
+ mActionList.add(position, action);
+ }
+
+ /**
+ * Removes an action at the given position.
+ * Note that action at the position may differ from returned by {@link #createActions}.
+ * See {@link CustomizableOptionsRowAdapter}
+ */
+ protected void removeAction(int position) {
+ mActionList.remove(position);
+ }
+
+ protected int getActionSize() {
+ return mActionList.size();
+ }
+
@Override
public void onBindViewHolder(MyViewHolder viewHolder, int position) {
super.onBindViewHolder(viewHolder, position);
@@ -103,4 +139,14 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<
// be preserved.
return mActionList.get(position).getType();
}
+
+ protected void setOptionChangedListener(final MenuAction action) {
+ TvOptionsManager om = getMainActivity().getTvOptionsManager();
+ om.setOptionChangedListener(action.getType(), new OptionChangedListener() {
+ @Override
+ public void onOptionChanged(String newOption) {
+ setItemList(mActionList);
+ }
+ });
+ }
}
diff --git a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java
index c8249a4c..f3e09f80 100644
--- a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java
@@ -38,7 +38,8 @@ public class PartnerOptionsRowAdapter extends CustomizableOptionsRowAdapter {
}
@Override
- protected void updateActions() {
+ protected boolean updateActions() {
// TODO: Support adding description for custom actions.
+ return false;
}
}
diff --git a/src/com/android/tv/menu/PipOptionsRowAdapter.java b/src/com/android/tv/menu/PipOptionsRowAdapter.java
new file mode 100644
index 00000000..87203e9d
--- /dev/null
+++ b/src/com/android/tv/menu/PipOptionsRowAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * 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.menu;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.TvOptionsManager;
+import com.android.tv.ui.TvViewUiManager;
+import com.android.tv.ui.sidepanel.PipInputSelectorFragment;
+import com.android.tv.util.PipInputManager.PipInput;
+import com.android.tv.util.TvSettings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * An adapter of PIP options.
+ */
+public class PipOptionsRowAdapter extends OptionsRowAdapter {
+ private static final int[] DRAWABLE_ID_FOR_LAYOUT = {
+ R.drawable.ic_pip_option_layout1,
+ R.drawable.ic_pip_option_layout2,
+ R.drawable.ic_pip_option_layout3,
+ R.drawable.ic_pip_option_layout4,
+ R.drawable.ic_pip_option_layout5 };
+
+ private final TvOptionsManager mTvOptionsManager;
+ private final TvViewUiManager mTvViewUiManager;
+
+ public PipOptionsRowAdapter(Context context) {
+ super(context);
+ mTvOptionsManager = getMainActivity().getTvOptionsManager();
+ mTvViewUiManager = getMainActivity().getTvViewUiManager();
+ }
+
+ @Override
+ protected List<MenuAction> createActions() {
+ List<MenuAction> actionList = new ArrayList<>();
+ actionList.add(MenuAction.PIP_SELECT_INPUT_ACTION);
+ actionList.add(MenuAction.PIP_SWAP_ACTION);
+ actionList.add(MenuAction.PIP_SOUND_ACTION);
+ actionList.add(MenuAction.PIP_LAYOUT_ACTION);
+ actionList.add(MenuAction.PIP_SIZE_ACTION);
+ for (MenuAction action : actionList) {
+ setOptionChangedListener(action);
+ }
+ return actionList;
+ }
+
+ @Override
+ public boolean updateActions() {
+ boolean changed = false;
+ if (updateSelectInputAction()) {
+ changed = true;
+ }
+ if (updateLayoutAction()) {
+ changed = true;
+ }
+ if (updateSizeAction()) {
+ changed = true;
+ }
+ return changed;
+ }
+
+ private boolean updateSelectInputAction() {
+ String oldInputLabel = mTvOptionsManager.getOptionString(TvOptionsManager.OPTION_PIP_INPUT);
+
+ MainActivity tvActivity = getMainActivity();
+ PipInput newInput = tvActivity.getPipInputManager().getPipInput(tvActivity.getPipChannel());
+ String newInputLabel = newInput == null ? null : newInput.getLabel();
+
+ if (!TextUtils.equals(oldInputLabel, newInputLabel)) {
+ mTvOptionsManager.onPipInputChanged(newInputLabel);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateLayoutAction() {
+ return MenuAction.PIP_LAYOUT_ACTION.setDrawableResId(
+ DRAWABLE_ID_FOR_LAYOUT[mTvViewUiManager.getPipLayout()]);
+ }
+
+ private boolean updateSizeAction() {
+ boolean oldEnabled = MenuAction.PIP_SIZE_ACTION.isEnabled();
+ boolean newEnabled = mTvViewUiManager.getPipLayout() != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE;
+ if (oldEnabled != newEnabled) {
+ MenuAction.PIP_SIZE_ACTION.setEnabled(newEnabled);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void executeAction(int type) {
+ switch (type) {
+ case TvOptionsManager.OPTION_PIP_INPUT:
+ getMainActivity().getOverlayManager().getSideFragmentManager().show(
+ new PipInputSelectorFragment());
+ break;
+ case TvOptionsManager.OPTION_PIP_SWAP:
+ getMainActivity().swapPip();
+ break;
+ case TvOptionsManager.OPTION_PIP_SOUND:
+ getMainActivity().togglePipSoundMode();
+ break;
+ case TvOptionsManager.OPTION_PIP_LAYOUT:
+ int oldLayout = mTvViewUiManager.getPipLayout();
+ int newLayout = (oldLayout + 1) % (TvSettings.PIP_LAYOUT_LAST + 1);
+ mTvViewUiManager.setPipLayout(newLayout, true);
+ MenuAction.PIP_LAYOUT_ACTION.setDrawableResId(DRAWABLE_ID_FOR_LAYOUT[newLayout]);
+ break;
+ case TvOptionsManager.OPTION_PIP_SIZE:
+ int oldSize = mTvViewUiManager.getPipSize();
+ int newSize = (oldSize + 1) % (TvSettings.PIP_SIZE_LAST + 1);
+ mTvViewUiManager.setPipSize(newSize, true);
+ break;
+ }
+ }
+}
diff --git a/src/com/android/tv/menu/PlayControlsButton.java b/src/com/android/tv/menu/PlayControlsButton.java
index 77715f28..aff39db3 100644
--- a/src/com/android/tv/menu/PlayControlsButton.java
+++ b/src/com/android/tv/menu/PlayControlsButton.java
@@ -39,9 +39,6 @@ public class PlayControlsButton extends FrameLayout {
private final int mIconColor;
private int mIconFocusedColor;
- private int mImageResourceId;
- private int mTintColor;
-
public PlayControlsButton(Context context) {
this(context, null);
}
@@ -70,21 +67,10 @@ public class PlayControlsButton extends FrameLayout {
* 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) {
- mImageResourceId = imageResId;
- mIcon.setImageResource(imageResId);
- updateTint(newTintColor);
- } else if (newTintColor != mTintColor) {
- updateTint(newTintColor);
- }
- }
-
- private void updateTint(int tintColor) {
- mTintColor = tintColor;
- // Since on focus changing, icons' color should be switched with animation,
+ mIcon.setImageResource(imageResId);
+ // Since on foucus changing, icons' color should be switched with animation,
// as a result, selectors cannot be used to switch colors in this case.
- mIcon.getDrawable().setTint(tintColor);
+ mIcon.getDrawable().setTint(hasFocus() ? mIconFocusedColor : mIconColor);
}
/**
@@ -131,9 +117,7 @@ public class PlayControlsButton extends FrameLayout {
} else {
mIcon.setVisibility(View.GONE);
mLabel.setVisibility(View.VISIBLE);
- if (!TextUtils.equals(mLabel.getText(), label)) {
- mLabel.setText(label);
- }
+ mLabel.setText(label);
}
}
diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java
index 4d766788..a620d4dd 100644
--- a/src/com/android/tv/menu/PlayControlsRowView.java
+++ b/src/com/android/tv/menu/PlayControlsRowView.java
@@ -18,10 +18,10 @@ package com.android.tv.menu;
import android.content.Context;
import android.content.res.Resources;
-import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
@@ -34,16 +34,17 @@ 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.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
-import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.ui.HalfSizedDialogFragment;
import com.android.tv.menu.Menu.MenuShowReason;
import com.android.tv.ui.TunableTvView;
+import com.android.tv.util.Utils;
public class PlayControlsRowView extends MenuRowView {
private static final int NORMAL_WIDTH_MAX_BUTTON_COUNT = 5;
@@ -52,10 +53,14 @@ public class PlayControlsRowView extends MenuRowView {
private final int mTimeTextLeftMargin;
private final int mTimelineWidth;
// Views
- private TextView mBackgroundView;
+ private View mBackgroundView;
private View mTimeIndicator;
private TextView mTimeText;
- private PlaybackProgressBar mProgress;
+ private View mProgressEmptyBefore;
+ private View mProgressWatched;
+ private View mProgressBuffered;
+ private View mProgressEmptyAfter;
+ private View mControlBar;
private PlayControlsButton mJumpPreviousButton;
private PlayControlsButton mRewindButton;
private PlayControlsButton mPlayPauseButton;
@@ -64,6 +69,7 @@ public class PlayControlsRowView extends MenuRowView {
private PlayControlsButton mRecordButton;
private TextView mProgramStartTimeText;
private TextView mProgramEndTimeText;
+ private View mUnavailableMessageText;
private TunableTvView mTvView;
private TimeShiftManager mTimeShiftManager;
private final DvrDataManager mDvrDataManager;
@@ -77,8 +83,6 @@ public class PlayControlsRowView extends MenuRowView {
private final int mNormalButtonMargin;
private final int mCompactButtonMargin;
- private final String mUnavailableMessage;
-
private final ScheduledRecordingListener mScheduledRecordingListener
= new ScheduledRecordingListener() {
@Override
@@ -134,7 +138,6 @@ public class PlayControlsRowView extends MenuRowView {
mDvrManager = null;
}
mMainActivity = (MainActivity) context;
- mUnavailableMessage = res.getString(R.string.play_controls_unavailable);
}
@Override
@@ -168,10 +171,14 @@ public class PlayControlsRowView extends MenuRowView {
super.onFinishInflate();
// Clip the ViewGroup(body) to the rounded rectangle of outline.
findViewById(R.id.body).setClipToOutline(true);
- mBackgroundView = (TextView) findViewById(R.id.background);
+ mBackgroundView = findViewById(R.id.background);
mTimeIndicator = findViewById(R.id.time_indicator);
mTimeText = (TextView) findViewById(R.id.time_text);
- mProgress = (PlaybackProgressBar) findViewById(R.id.progress);
+ mProgressEmptyBefore = findViewById(R.id.timeline_bg_start);
+ mProgressWatched = findViewById(R.id.watched);
+ mProgressBuffered = findViewById(R.id.buffered);
+ mProgressEmptyAfter = findViewById(R.id.timeline_bg_end);
+ mControlBar = findViewById(R.id.play_control_bar);
mJumpPreviousButton = (PlayControlsButton) findViewById(R.id.jump_previous);
mRewindButton = (PlayControlsButton) findViewById(R.id.rewind);
mPlayPauseButton = (PlayControlsButton) findViewById(R.id.play_pause);
@@ -180,6 +187,7 @@ public class PlayControlsRowView extends MenuRowView {
mRecordButton = (PlayControlsButton) findViewById(R.id.record);
mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time);
mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time);
+ mUnavailableMessageText = findViewById(R.id.unavailable_text);
initializeButton(mJumpPreviousButton, R.drawable.lb_ic_skip_previous,
R.string.play_controls_description_skip_previous, null, new Runnable() {
@@ -187,7 +195,7 @@ public class PlayControlsRowView extends MenuRowView {
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.jumpToPrevious();
- updateControls(true);
+ updateControls();
}
}
});
@@ -227,7 +235,7 @@ public class PlayControlsRowView extends MenuRowView {
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.jumpToNext();
- updateControls(true);
+ updateControls();
}
}
});
@@ -257,17 +265,18 @@ public class PlayControlsRowView extends MenuRowView {
if (!(mDvrManager != null && mDvrManager.isChannelRecordable(currentChannel))) {
Toast.makeText(mMainActivity, R.string.dvr_msg_cannot_record_channel,
Toast.LENGTH_SHORT).show();
- } else {
+ } else if (DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity,
+ currentChannel.getInputId())) {
Program program = TvApplication.getSingletons(mMainActivity).getProgramDataManager()
.getCurrentProgram(currentChannel.getId());
- DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity,
- currentChannel.getInputId(), new Runnable() {
- @Override
- public void run() {
- DvrUiHelper.requestRecordingCurrentProgram(mMainActivity,
- currentChannel, program, true);
- }
- });
+ if (program == null) {
+ DvrUiHelper.showChannelRecordDurationOptions(mMainActivity, currentChannel);
+ } else if (DvrUiHelper.handleCreateSchedule(mMainActivity, program)) {
+ String msg = mMainActivity.getString(R.string.dvr_msg_current_program_scheduled,
+ program.getTitle(),
+ Utils.toTimeString(program.getEndTimeUtcMillis(), false));
+ Toast.makeText(mMainActivity, msg, Toast.LENGTH_SHORT).show();
+ }
}
} else if (currentChannel != null) {
DvrUiHelper.showStopRecordingDialog(mMainActivity, currentChannel.getId(),
@@ -309,37 +318,39 @@ public class PlayControlsRowView extends MenuRowView {
@Override
public void onAvailabilityChanged() {
updateMenuVisibility();
- PlayControlsRowView.this.updateAll(false);
+ if (isShown()) {
+ PlayControlsRowView.this.updateAll();
+ }
}
@Override
public void onPlayStatusChanged(int status) {
updateMenuVisibility();
- if (mTimeShiftManager.isAvailable()) {
- updateControls(false);
+ if (mTimeShiftManager.isAvailable() && isShown()) {
+ updateControls();
}
}
@Override
public void onRecordTimeRangeChanged() {
- if (mTimeShiftManager.isAvailable()) {
- updateControls(false);
+ if (mTimeShiftManager.isAvailable() && isShown()) {
+ updateControls();
}
}
@Override
public void onCurrentPositionChanged() {
- if (mTimeShiftManager.isAvailable()) {
+ if (mTimeShiftManager.isAvailable() && isShown()) {
initializeTimeline();
- updateControls(false);
+ updateControls();
}
}
@Override
public void onProgramInfoChanged() {
- if (mTimeShiftManager.isAvailable()) {
+ if (mTimeShiftManager.isAvailable() && isShown()) {
initializeTimeline();
- updateControls(false);
+ updateControls();
}
}
@@ -361,8 +372,7 @@ public class PlayControlsRowView extends MenuRowView {
}
}
});
- // force update to initialize everything
- updateAll(true);
+ updateAll();
}
private void initializeTimeline() {
@@ -370,8 +380,6 @@ public class PlayControlsRowView extends MenuRowView {
mTimeShiftManager.getCurrentPositionMs());
mProgramStartTimeMs = program.getStartTimeUtcMillis();
mProgramEndTimeMs = program.getEndTimeUtcMillis();
- mProgress.setMax(mProgramEndTimeMs - mProgramStartTimeMs);
- updateRecTimeText();
SoftPreconditions.checkArgument(mProgramStartTimeMs <= mProgramEndTimeMs);
}
@@ -381,13 +389,10 @@ public class PlayControlsRowView extends MenuRowView {
getMenu().setKeepVisible(keepMenuVisible);
}
- public void onPreselected() {
- updateControls(true);
- }
-
@Override
public void onSelected(boolean showTitle) {
super.onSelected(showTitle);
+ updateControls();
postHideRippleAnimation();
}
@@ -469,32 +474,28 @@ public class PlayControlsRowView extends MenuRowView {
* Updates the view contents. It is called from the PlayControlsRow.
*/
public void update() {
- updateAll(false);
+ updateAll();
}
- private void updateAll(boolean forceUpdate) {
+ private void updateAll() {
if (mTimeShiftManager.isAvailable() && !mTvView.isScreenBlocked()) {
setEnabled(true);
initializeTimeline();
mBackgroundView.setEnabled(true);
- setTextIfNeeded(mBackgroundView, null);
} else {
setEnabled(false);
mBackgroundView.setEnabled(false);
- setTextIfNeeded(mBackgroundView, mUnavailableMessage);
}
- // force the controls be updated no matter it's visible or not.
- updateControls(forceUpdate);
+ updateControls();
}
- private void updateControls(boolean forceUpdate) {
- if (forceUpdate || getContentsView().isShown()) {
- updateTime();
- updateProgress();
- updateButtons();
- updateRecordButton();
- updateButtonMargin();
- }
+ private void updateControls() {
+ updateTime();
+ updateProgress();
+ updateRecTimeText();
+ updateButtons();
+ updateRecordButton();
+ updateButtonMargin();
}
private void updateTime() {
@@ -503,39 +504,70 @@ public class PlayControlsRowView extends MenuRowView {
mTimeIndicator.setVisibility(View.VISIBLE);
} else {
mTimeText.setVisibility(View.INVISIBLE);
- mTimeIndicator.setVisibility(View.GONE);
+ mTimeIndicator.setVisibility(View.INVISIBLE);
return;
}
long currentPositionMs = mTimeShiftManager.getCurrentPositionMs();
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) mTimeText.getLayoutParams();
int currentTimePositionPixel =
convertDurationToPixel(currentPositionMs - mProgramStartTimeMs);
- mTimeText.setTranslationX(currentTimePositionPixel + mTimeTextLeftMargin);
- setTextIfNeeded(mTimeText, getTimeString(currentPositionMs));
- mTimeIndicator.setTranslationX(currentTimePositionPixel + mTimeIndicatorLeftMargin);
+ params.leftMargin = currentTimePositionPixel + mTimeTextLeftMargin;
+ mTimeText.setLayoutParams(params);
+ mTimeText.setText(getTimeString(currentPositionMs));
+ params = (ViewGroup.MarginLayoutParams) mTimeIndicator.getLayoutParams();
+ params.leftMargin = currentTimePositionPixel + mTimeIndicatorLeftMargin;
+ mTimeIndicator.setLayoutParams(params);
}
private void updateProgress() {
if (isEnabled()) {
- long progressStartTimeMs = Math.min(mProgramEndTimeMs,
+ mProgressWatched.setVisibility(View.VISIBLE);
+ mProgressBuffered.setVisibility(View.VISIBLE);
+ mProgressEmptyAfter.setVisibility(View.VISIBLE);
+ } else {
+ mProgressWatched.setVisibility(View.INVISIBLE);
+ mProgressBuffered.setVisibility(View.INVISIBLE);
+ mProgressEmptyAfter.setVisibility(View.INVISIBLE);
+ if (mProgramStartTimeMs < mProgramEndTimeMs) {
+ layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, mProgramEndTimeMs);
+ } else {
+ // Not initialized yet.
+ layoutProgress(mProgressEmptyBefore, mTimelineWidth);
+ }
+ return;
+ }
+
+ long progressStartTimeMs = Math.min(mProgramEndTimeMs,
Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs()));
- long currentPlayingTimeMs = Math.min(mProgramEndTimeMs,
+ long currentPlayingTimeMs = Math.min(mProgramEndTimeMs,
Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs()));
- long progressEndTimeMs = Math.min(mProgramEndTimeMs,
+ long progressEndTimeMs = Math.min(mProgramEndTimeMs,
Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs()));
- mProgress.setProgressRange(progressStartTimeMs - mProgramStartTimeMs,
- progressEndTimeMs - mProgramStartTimeMs);
- mProgress.setProgress(currentPlayingTimeMs - mProgramStartTimeMs);
- } else {
- mProgress.setProgressRange(0, 0);
- }
+
+ layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, progressStartTimeMs);
+ layoutProgress(mProgressWatched, progressStartTimeMs, currentPlayingTimeMs);
+ layoutProgress(mProgressBuffered, currentPlayingTimeMs, progressEndTimeMs);
+ }
+
+ private void layoutProgress(View progress, long progressStartTimeMs, long progressEndTimeMs) {
+ layoutProgress(progress, Math.max(0,
+ convertDurationToPixel(progressEndTimeMs - progressStartTimeMs)) + 1);
+ }
+
+ private void layoutProgress(View progress, int width) {
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) progress.getLayoutParams();
+ params.width = width;
+ progress.setLayoutParams(params);
}
private void updateRecTimeText() {
if (isEnabled()) {
mProgramStartTimeText.setVisibility(View.VISIBLE);
- setTextIfNeeded(mProgramStartTimeText, getTimeString(mProgramStartTimeMs));
+ mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs));
mProgramEndTimeText.setVisibility(View.VISIBLE);
- setTextIfNeeded(mProgramEndTimeText, getTimeString(mProgramEndTimeMs));
+ mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs));
} else {
mProgramStartTimeText.setVisibility(View.GONE);
mProgramEndTimeText.setVisibility(View.GONE);
@@ -544,17 +576,11 @@ public class PlayControlsRowView extends MenuRowView {
private void updateButtons() {
if (isEnabled()) {
- mPlayPauseButton.setVisibility(View.VISIBLE);
- mJumpPreviousButton.setVisibility(View.VISIBLE);
- mJumpNextButton.setVisibility(View.VISIBLE);
- mRewindButton.setVisibility(View.VISIBLE);
- mFastForwardButton.setVisibility(View.VISIBLE);
+ mControlBar.setVisibility(View.VISIBLE);
+ mUnavailableMessageText.setVisibility(View.GONE);
} else {
- mPlayPauseButton.setVisibility(View.GONE);
- mJumpPreviousButton.setVisibility(View.GONE);
- mJumpNextButton.setVisibility(View.GONE);
- mRewindButton.setVisibility(View.GONE);
- mFastForwardButton.setVisibility(View.GONE);
+ mControlBar.setVisibility(View.INVISIBLE);
+ mUnavailableMessageText.setVisibility(View.VISIBLE);
return;
}
@@ -596,12 +622,6 @@ public class PlayControlsRowView extends MenuRowView {
}
private void updateRecordButton() {
- if (isEnabled()) {
- mRecordButton.setVisibility(VISIBLE);
- } else {
- mRecordButton.setVisibility(GONE);
- return;
- }
if (!(mDvrManager != null
&& mDvrManager.isChannelRecordable(mMainActivity.getCurrentChannel()))) {
mRecordButton.setVisibility(View.GONE);
@@ -662,10 +682,4 @@ public class PlayControlsRowView extends MenuRowView {
mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener);
}
}
-
- private void setTextIfNeeded(TextView textView, String text) {
- if (!TextUtils.equals(textView.getText(), text)) {
- textView.setText(text);
- }
- }
}
diff --git a/src/com/android/tv/menu/PlaybackProgressBar.java b/src/com/android/tv/menu/PlaybackProgressBar.java
deleted file mode 100644
index e8061bc6..00000000
--- a/src/com/android/tv/menu/PlaybackProgressBar.java
+++ /dev/null
@@ -1,168 +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.menu;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-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.
- */
-public class PlaybackProgressBar extends View {
- private final LayerDrawable mProgressDrawable;
- private final Drawable mPrimaryDrawable;
- private final Drawable mSecondaryDrawable;
- private long mMax = 100;
- private long mProgressStart = 0;
- private long mProgressEnd = 0;
- private long mProgress = 0;
-
- public PlaybackProgressBar(Context context) {
- this(context, null);
- }
-
- public PlaybackProgressBar(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- 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);
- mProgressDrawable =
- (LayerDrawable) a.getDrawable(R.styleable.PlaybackProgressBar_progressDrawable);
- mPrimaryDrawable = mProgressDrawable.findDrawableByLayerId(android.R.id.progress);
- mSecondaryDrawable =
- mProgressDrawable.findDrawableByLayerId(android.R.id.secondaryProgress);
- a.recycle();
- refreshProgress();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- final int saveCount = canvas.save();
- canvas.translate(getPaddingLeft(), getPaddingTop());
- mProgressDrawable.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- refreshProgress();
- }
-
- public void setMax(long max) {
- if (max < 0) {
- max = 0;
- }
- if (max != mMax) {
- mMax = max;
- if (mProgressStart > max) {
- mProgressStart = max;
- }
- if (mProgressEnd > max) {
- mProgressEnd = max;
- }
- if (mProgress > max) {
- mProgress = max;
- }
- refreshProgress();
- }
- }
-
- /**
- * 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);
- mProgress = constrain(mProgress, start, end);
- if (start != mProgressStart || end != mProgressEnd) {
- mProgressStart = start;
- mProgressEnd = end;
- setProgressLevels();
- }
- }
-
- /**
- * Sets the progress position.
- */
- public void setProgress(long progress) {
- progress = constrain(progress, mProgressStart, mProgressEnd);
- if (progress != mProgress) {
- mProgress = progress;
- setProgressLevels();
- }
- }
-
- private long constrain(long value, long min, long max) {
- return Math.min(Math.max(value, min), max);
- }
-
- private void refreshProgress() {
- int width = getWidth() - getPaddingStart() - getPaddingEnd();
- int height = getHeight() - getPaddingTop() - getPaddingBottom();
- mProgressDrawable.setBounds(0, 0, width, height);
- setProgressLevels();
- }
-
- private void setProgressLevels() {
- boolean progressUpdated = setProgressBound(mPrimaryDrawable, mProgressStart, mProgress);
- progressUpdated |= setProgressBound(mSecondaryDrawable, mProgress, mProgressEnd);
- if (progressUpdated) {
- postInvalidate();
- }
- }
-
- private boolean setProgressBound(Drawable drawable, long start, long end) {
- Rect oldBounds = drawable.getBounds();
- if (mMax == 0) {
- if (!isEqualRect(oldBounds, 0, 0, 0, 0)) {
- drawable.setBounds(0, 0, 0, 0);
- return true;
- }
- return false;
- }
- int width = mProgressDrawable.getBounds().width();
- int height = mProgressDrawable.getBounds().height();
- int left = (int) (width * start / mMax);
- int right = (int) (width * end / mMax);
- if (!isEqualRect(oldBounds, left, 0, right, height)) {
- drawable.setBounds(left, 0, right, height);
- return true;
- }
- return false;
- }
-
- private boolean isEqualRect(Rect rect, int left, int top, int right, int bottom) {
- return rect.left == left && rect.top == top && rect.right == right && rect.bottom == bottom;
- }
-}
diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java
index 220fcd3a..fb062246 100644
--- a/src/com/android/tv/menu/TvOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java
@@ -21,6 +21,7 @@ import android.media.tv.TvTrackInfo;
import android.support.annotation.VisibleForTesting;
import com.android.tv.Features;
+import com.android.tv.R;
import com.android.tv.TvOptionsManager;
import com.android.tv.customization.CustomAction;
import com.android.tv.data.DisplayMode;
@@ -29,6 +30,7 @@ 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.PipInputManager;
import java.util.ArrayList;
import java.util.List;
@@ -37,6 +39,12 @@ import java.util.List;
* An adapter of options.
*/
public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
+ private static final boolean ENABLE_IN_APP_PIP = false;
+
+ private int mPositionPipAction;
+ // If mInAppPipAction is false, system-wide PIP is used.
+ private boolean mInAppPipAction = true;
+
public TvOptionsRowAdapter(Context context, List<CustomAction> customActions) {
super(context, customActions);
}
@@ -45,62 +53,123 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
protected List<MenuAction> createBaseActions() {
List<MenuAction> actionList = new ArrayList<>();
actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
+ setOptionChangedListener(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION);
- if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) {
- actionList.add(MenuAction.SYSTEMWIDE_PIP_ACTION);
- }
+ setOptionChangedListener(MenuAction.SELECT_DISPLAY_MODE_ACTION);
+ actionList.add(MenuAction.PIP_IN_APP_ACTION);
+ setOptionChangedListener(MenuAction.PIP_IN_APP_ACTION);
+ mPositionPipAction = actionList.size() - 1;
actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
+ setOptionChangedListener(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
actionList.add(MenuAction.MORE_CHANNELS_ACTION);
if (DeveloperOptionFragment.shouldShow()) {
actionList.add(MenuAction.DEV_ACTION);
}
actionList.add(MenuAction.SETTINGS_ACTION);
- updateClosedCaptionAction();
- updateMultiAudioAction();
- updateDisplayModeAction();
+ if (getCustomActions() != null) {
+ // Adjust Pip action position which will be changed by applying custom actions.
+ for (CustomAction customAction : getCustomActions()) {
+ if (customAction.isFront()) {
+ mPositionPipAction++;
+ }
+ }
+ }
+
return actionList;
}
@Override
- protected void updateActions() {
- if (updateClosedCaptionAction()) {
- notifyItemChanged(getItemPosition(MenuAction.SELECT_CLOSED_CAPTION_ACTION));
+ protected boolean updateActions() {
+ boolean changed = false;
+ if (updatePipAction()) {
+ changed = true;
}
if (updateMultiAudioAction()) {
- notifyItemChanged(getItemPosition(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION));
+ changed = true;
}
if (updateDisplayModeAction()) {
- notifyItemChanged(getItemPosition(MenuAction.SELECT_DISPLAY_MODE_ACTION));
+ changed = true;
}
+ return changed;
}
- @VisibleForTesting
- private boolean updateClosedCaptionAction() {
- return updateActionDescription(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
+ private boolean updatePipAction() {
+ // There are four states.
+ // Case 1. The device doesn't even have any input for PIP. (e.g. OTT box without HDMI input)
+ // => Remove the icon.
+ // Case 2. The device has one or more inputs for PIP but none of them are currently
+ // available.
+ // => Show the icon but disable it.
+ // Case 3. The device has one or more available PIP inputs and now it's tuned off.
+ // => Show the icon with "Off".
+ // Case 4. The device has one or more available PIP inputs but it's already turned on.
+ // => Show the icon with "On".
+
+ boolean changed = false;
+
+ // Case 1
+ PipInputManager pipInputManager = getMainActivity().getPipInputManager();
+ if (ENABLE_IN_APP_PIP && pipInputManager.getPipInputSize(false) > 1) {
+ if (!mInAppPipAction) {
+ removeAction(mPositionPipAction);
+ addAction(mPositionPipAction, MenuAction.PIP_IN_APP_ACTION);
+ mInAppPipAction = true;
+ changed = true;
+ }
+ } else {
+ if (mInAppPipAction) {
+ removeAction(mPositionPipAction);
+ mInAppPipAction = false;
+ if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) {
+ addAction(mPositionPipAction, MenuAction.SYSTEMWIDE_PIP_ACTION);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ // Case 2
+ boolean isPipEnabled = getMainActivity().isPipEnabled();
+ boolean oldEnabled = MenuAction.PIP_IN_APP_ACTION.isEnabled();
+ boolean newEnabled = pipInputManager.getPipInputSize(true) > 0;
+ if (oldEnabled != newEnabled) {
+ // Should not disable the item if the PIP is already turned on so that the user can
+ // force exit it.
+ if (newEnabled || !isPipEnabled) {
+ MenuAction.PIP_IN_APP_ACTION.setEnabled(newEnabled);
+ changed = true;
+ }
+ }
+
+ // Case 3 & 4 - we just need to update the icon.
+ MenuAction.PIP_IN_APP_ACTION.setDrawableResId(
+ isPipEnabled ? R.drawable.ic_tvoption_pip : R.drawable.ic_tvoption_pip_off);
+ return changed;
}
@VisibleForTesting
boolean updateMultiAudioAction() {
List<TvTrackInfo> audioTracks = getMainActivity().getTracks(TvTrackInfo.TYPE_AUDIO);
- boolean enabled = audioTracks != null && audioTracks.size() > 1;
- // Use "|" operator for non-short-circuit evaluation.
- return MenuAction.setEnabled(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION, enabled)
- | updateActionDescription(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
+ boolean oldEnabled = MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled();
+ boolean newEnabled = audioTracks != null && audioTracks.size() > 1;
+ if (oldEnabled != newEnabled) {
+ MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.setEnabled(newEnabled);
+ return true;
+ }
+ return false;
}
private boolean updateDisplayModeAction() {
TvViewUiManager uiManager = getMainActivity().getTvViewUiManager();
- boolean enabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL)
+ boolean oldEnabled = MenuAction.SELECT_DISPLAY_MODE_ACTION.isEnabled();
+ boolean newEnabled = 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()));
+ if (oldEnabled != newEnabled) {
+ MenuAction.SELECT_DISPLAY_MODE_ACTION.setEnabled(newEnabled);
+ return true;
+ }
+ return false;
}
@Override
@@ -114,6 +183,9 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
getMainActivity().getOverlayManager().getSideFragmentManager()
.show(new DisplayModeFragment());
break;
+ case TvOptionsManager.OPTION_IN_APP_PIP:
+ getMainActivity().togglePipView();
+ break;
case TvOptionsManager.OPTION_SYSTEMWIDE_PIP:
getMainActivity().enterPictureInPictureMode();
break;
@@ -133,4 +205,4 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
break;
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java
index f56daec5..7607822c 100644
--- a/src/com/android/tv/onboarding/SetupSourcesFragment.java
+++ b/src/com/android/tv/onboarding/SetupSourcesFragment.java
@@ -38,7 +38,6 @@ 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;
@@ -205,7 +204,6 @@ public class SetupSourcesFragment extends SetupMultiPaneFragment {
mChannelDataManager.addListener(mChannelDataManagerListener);
super.onCreate(savedInstanceState);
mParentFragment = (SetupSourcesFragment) getParentFragment();
- TunerInputController.executeNetworkTunerDiscoveryAsyncTask(getContext());
}
@Override
diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java
index 03d7873f..8d6c5a14 100644
--- a/src/com/android/tv/receiver/BootCompletedReceiver.java
+++ b/src/com/android/tv/receiver/BootCompletedReceiver.java
@@ -27,7 +27,7 @@ import com.android.tv.Features;
import com.android.tv.TvActivity;
import com.android.tv.TvApplication;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.dvr.recorder.DvrRecordingService;
+import com.android.tv.dvr.DvrRecordingService;
import com.android.tv.recommendation.NotificationService;
import com.android.tv.util.OnboardingUtils;
import com.android.tv.util.SetupUtils;
diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/GlobalKeyReceiver.java
index 2d9ee10e..8cd4fdf1 100644
--- a/src/com/android/tv/receiver/GlobalKeyReceiver.java
+++ b/src/com/android/tv/receiver/GlobalKeyReceiver.java
@@ -20,8 +20,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.tv.TvContract;
-import android.os.AsyncTask;
-import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
@@ -33,57 +31,27 @@ import com.android.tv.TvApplication;
public class GlobalKeyReceiver extends BroadcastReceiver {
private static final boolean DEBUG = false;
private static final String TAG = "GlobalKeyReceiver";
-
private static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
- // Settings.Secure.USER_SETUP_COMPLETE is hidden.
- private static final String SETTINGS_USER_SETUP_COMPLETE = "user_setup_complete";
-
- private static boolean sUserSetupComplete;
@Override
public void onReceive(Context context, Intent intent) {
TvApplication.setCurrentRunningProcess(context, true);
- Context appContext = context.getApplicationContext();
- if (DEBUG) Log.d(TAG, "onReceive: " + intent);
- if (sUserSetupComplete) {
- handleIntent(appContext, intent);
- } else {
- new AsyncTask<Void, Void, Boolean>() {
- @Override
- protected Boolean doInBackground(Void... params) {
- return Settings.Secure.getInt(appContext.getContentResolver(),
- SETTINGS_USER_SETUP_COMPLETE, 0) != 0;
- }
-
- @Override
- protected void onPostExecute(Boolean setupComplete) {
- if (DEBUG) Log.d(TAG, "Is setup complete: " + setupComplete);
- sUserSetupComplete = setupComplete;
- if (sUserSetupComplete) {
- handleIntent(appContext, intent);
- }
- }
- }.execute();
- }
- }
-
- private void handleIntent(Context appContext, Intent intent) {
if (ACTION_GLOBAL_BUTTON.equals(intent.getAction())) {
KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- if (DEBUG) Log.d(TAG, "handleIntent: " + event);
+ if (DEBUG) Log.d(TAG, "onReceive: " + event);
int keyCode = event.getKeyCode();
int action = event.getAction();
if (action == KeyEvent.ACTION_UP) {
switch (keyCode) {
case KeyEvent.KEYCODE_GUIDE:
- appContext.startActivity(
+ context.startActivity(
new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI));
break;
case KeyEvent.KEYCODE_TV:
- ((TvApplication) appContext).handleTvKey();
+ ((TvApplication) context.getApplicationContext()).handleTvKey();
break;
case KeyEvent.KEYCODE_TV_INPUT:
- ((TvApplication) appContext).handleTvInputKey();
+ ((TvApplication) context.getApplicationContext()).handleTvInputKey();
break;
default:
// Do nothing
diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java
index 2d3f8705..26d000e7 100644
--- a/src/com/android/tv/receiver/PackageIntentsReceiver.java
+++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java
@@ -19,10 +19,8 @@ package com.android.tv.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.net.Uri;
import com.android.tv.TvApplication;
-import com.android.tv.util.Partner;
/**
* A class for handling the broadcast intents from PackageManager.
@@ -33,9 +31,5 @@ public class PackageIntentsReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
TvApplication.setCurrentRunningProcess(context, true);
((TvApplication) context.getApplicationContext()).handleInputCountChanged();
-
- Uri uri = intent.getData();
- final String packageName = (uri != null ? uri.getSchemeSpecificPart() : null);
- Partner.reset(context, packageName);
}
}
diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java
index a472f559..30ec73e3 100644
--- a/src/com/android/tv/recommendation/NotificationService.java
+++ b/src/com/android/tv/recommendation/NotificationService.java
@@ -426,7 +426,6 @@ public class NotificationService extends Service implements Recommender.Listener
: 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.
diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java
index d90908f1..5f89a21a 100644
--- a/src/com/android/tv/search/DataManagerSearch.java
+++ b/src/com/android/tv/search/DataManagerSearch.java
@@ -265,7 +265,9 @@ public class DataManagerSearch implements SearchInterface {
}
private String buildIntentData(long channelId) {
- return TvContract.buildChannelUri(channelId).toString();
+ return TvContract.buildChannelUri(channelId).buildUpon()
+ .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH)
+ .build().toString();
}
private boolean isRatingBlocked(TvContentRating[] ratings) {
diff --git a/src/com/android/tv/search/SearchInterface.java b/src/com/android/tv/search/SearchInterface.java
index c9a63128..caa45812 100644
--- a/src/com/android/tv/search/SearchInterface.java
+++ b/src/com/android/tv/search/SearchInterface.java
@@ -24,6 +24,8 @@ import java.util.List;
* Interface for channel and program search.
*/
public interface SearchInterface {
+ String SOURCE_TV_SEARCH = "TvSearch";
+
int ACTION_TYPE_AMBIGUOUS = 1;
int ACTION_TYPE_SWITCH_CHANNEL = 2;
int ACTION_TYPE_SWITCH_INPUT = 3;
diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java
index ea144786..2ceec19a 100644
--- a/src/com/android/tv/search/TvProviderSearch.java
+++ b/src/com/android/tv/search/TvProviderSearch.java
@@ -38,8 +38,6 @@ import com.android.tv.search.LocalSearchProvider.SearchResult;
import com.android.tv.util.PermissionUtils;
import com.android.tv.util.Utils;
-import junit.framework.Assert;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -191,10 +189,6 @@ public class TvProviderSearch implements SearchInterface {
@WorkerThread
private List<SearchResult> searchChannels(String query, String[] columnForExactMatching,
String[] columnForPartialMatching, Set<Long> channelsFound, int limit) {
- Assert.assertTrue(
- (columnForExactMatching != null && columnForExactMatching.length > 0) ||
- (columnForPartialMatching != null && columnForPartialMatching.length > 0));
-
String[] projection = {
Channels._ID,
Channels.COLUMN_DISPLAY_NUMBER,
@@ -314,10 +308,6 @@ public class TvProviderSearch implements SearchInterface {
String[] columnForPartialMatching, Set<Long> channelsFound, int limit) {
if (DEBUG) Log.d(TAG, "Searching programs: '" + query + "'");
long time = SystemClock.elapsedRealtime();
- Assert.assertTrue(
- (columnForExactMatching != null && columnForExactMatching.length > 0) ||
- (columnForPartialMatching != null && columnForPartialMatching.length > 0));
-
String[] projection = {
Programs.COLUMN_CHANNEL_ID,
Programs.COLUMN_TITLE,
@@ -412,7 +402,9 @@ public class TvProviderSearch implements SearchInterface {
}
private String buildIntentData(long channelId) {
- return TvContract.buildChannelUri(channelId).toString();
+ return TvContract.buildChannelUri(channelId).buildUpon()
+ .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH)
+ .build().toString();
}
private boolean isRatingBlocked(String ratings) {
diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java
index 8b06aaa9..2dd36074 100644
--- a/src/com/android/tv/tuner/ChannelScanFileParser.java
+++ b/src/com/android/tv/tuner/ChannelScanFileParser.java
@@ -18,7 +18,7 @@ package com.android.tv.tuner;
import android.util.Log;
-import com.android.tv.tuner.data.Channel;
+import com.android.tv.tuner.data.nano.Channel;
import java.io.BufferedReader;
import java.io.IOException;
diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java
index 64394ea3..de19766e 100644
--- a/src/com/android/tv/tuner/TunerHal.java
+++ b/src/com/android/tv/tuner/TunerHal.java
@@ -20,9 +20,6 @@ import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.StringDef;
import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.Features;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -51,7 +48,6 @@ public abstract class TunerHal implements AutoCloseable {
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;
@@ -73,33 +69,31 @@ public abstract class TunerHal implements AutoCloseable {
*/
public synchronized static TunerHal createInstance(Context context) {
TunerHal tunerHal = null;
- if (useBuiltInTuner(context)) {
+ if (getTunerType(context) == TUNER_TYPE_BUILT_IN) {
}
- if (tunerHal == null && UsbTunerHal.getNumberOfDevices(context) > 0) {
+ if (tunerHal == null) {
tunerHal = new UsbTunerHal(context);
}
- return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null;
+ if (tunerHal.openFirstAvailable()) {
+ return tunerHal;
+ }
+ return null;
}
/**
* Gets the number of tuner devices currently present.
*/
- public static Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
- if (useBuiltInTuner(context)) {
+ public static int getTunerCount(Context context) {
+ if (getTunerType(context) == TUNER_TYPE_BUILT_IN) {
}
- int usbTunerCount = UsbTunerHal.getNumberOfDevices(context);
- if (usbTunerCount > 0) {
- return new Pair<>(TUNER_TYPE_USB, usbTunerCount);
- }
- return new Pair<>(null, 0);
+ return UsbTunerHal.getNumberOfDevices(context);
}
/**
- * Returns if tuner input service would use built-in tuners instead of USB tuners or network
- * tuners.
+ * Gets the type of tuner devices currently used.
*/
- static boolean useBuiltInTuner(Context context) {
- return false;
+ public static int getTunerType(Context context) {
+ return TUNER_TYPE_USB;
}
protected TunerHal(Context context) {
@@ -112,14 +106,6 @@ public abstract class TunerHal implements AutoCloseable {
return mIsStreaming;
}
- /**
- * 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();
@@ -145,12 +131,9 @@ public abstract class TunerHal implements AutoCloseable {
*
* @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) {
+ public synchronized boolean tune(int frequency, @ModulationType String modulation) {
if (!isDeviceOpen()) {
Log.e(TAG, "There's no available device");
return false;
diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java
index 65bbbdd0..d89b6a0c 100644
--- a/src/com/android/tv/tuner/TunerInputController.java
+++ b/src/com/android/tv/tuner/TunerInputController.java
@@ -16,40 +16,30 @@
package com.android.tv.tuner;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.AsyncTask;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.SystemClock;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
+import android.support.v4.os.BuildCompat;
import android.util.Log;
import android.widget.Toast;
import com.android.tv.Features;
-import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.tuner.R;
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 java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
/**
* Controls the package visibility of {@link TunerTvInputService}.
@@ -61,39 +51,10 @@ import java.util.concurrent.TimeUnit;
public class TunerInputController extends BroadcastReceiver {
private static final boolean DEBUG = true;
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";
-
- /**
- * Action of {@link Intent} to check network connection repeatedly when it is necessary.
- */
- public static final String CHECKING_NETWORK_CONNECTION =
- "com.android.tv.action.CHECKING_NETWORK_CONNECTION";
-
- /**
- * Action of {@link Intent} when network tuner is attached.
- */
- public static final String NETWORK_TUNER_ATTACHED =
- "com.android.tv.action.NETWORK_TUNER_ATTACHED";
-
- /**
- * Action of {@link Intent} when network tuner is detached.
- */
- public static final String NETWORK_TUNER_DETACHED =
- "com.android.tv.action.NETWORK_TUNER_DETACHED";
-
- private static final String EXTRA_CHECKING_DURATION =
- "com.android.tv.action.extra.CHECKING_DURATION";
-
- 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 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)
+ new TunerDevice(0x2040, 0xb123), // WinTV-HVR-955Q
+ new TunerDevice(0x07ca, 0x0837) // AverTV Volar Hybrid Q
};
private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
@@ -109,9 +70,7 @@ public class TunerInputController extends BroadcastReceiver {
if (mDvbDeviceAccessor == null) {
mDvbDeviceAccessor = new DvbDeviceAccessor(context);
}
- boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable();
- enableTunerTvInputService(
- context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null);
+ enableTunerTvInputService(context, mDvbDeviceAccessor.isDvbDeviceAvailable());
break;
}
}
@@ -125,35 +84,14 @@ public class TunerInputController extends BroadcastReceiver {
private final int vendorId;
private final int productId;
- // security patch level from which the specific tuner type is supported.
- private final String minSecurityLevel;
-
- private TunerDevice(int vendorId, int productId, String minSecurityLevel) {
+ private TunerDevice(int vendorId, int productId) {
this.vendorId = vendorId;
this.productId = productId;
- this.minSecurityLevel = minSecurityLevel;
}
private boolean equals(UsbDevice device) {
return device.getVendorId() == vendorId && device.getProductId() == productId;
}
-
- private boolean isSupported(String currentSecurityLevel) {
- if (minSecurityLevel == null) {
- return true;
- }
-
- long supportSecurityLevelTimeStamp = 0;
- long currentSecurityLevelTimestamp = 0;
- try {
- SimpleDateFormat format = new SimpleDateFormat(SECURITY_PATCH_LEVEL_FORMAT);
- supportSecurityLevelTimeStamp = format.parse(minSecurityLevel).getTime();
- currentSecurityLevelTimestamp = format.parse(currentSecurityLevel).getTime();
- } catch (ParseException e) {
- }
- return supportSecurityLevelTimeStamp != 0
- && supportSecurityLevelTimeStamp <= currentSecurityLevelTimestamp;
- }
}
@Override
@@ -161,20 +99,17 @@ public class TunerInputController extends BroadcastReceiver {
if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
TvApplication.setCurrentRunningProcess(context, true);
if (!Features.TUNER.isEnabled(context)) {
- enableTunerTvInputService(context, false, false, null);
+ enableTunerTvInputService(context, false);
return;
}
- SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(context);
switch (intent.getAction()) {
case Intent.ACTION_BOOT_COMPLETED:
- executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS);
case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
case UsbManager.ACTION_USB_DEVICE_DETACHED:
- if (TunerHal.useBuiltInTuner(context)) {
- enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN);
+ if (TunerInputInfoUtils.isBuiltInTuner(context)) {
+ enableTunerTvInputService(context, true);
break;
}
// Falls back to the below to check USB tuner devices.
@@ -188,41 +123,7 @@ public class TunerInputController extends BroadcastReceiver {
mHandler.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);
- break;
- }
- enableTunerTvInputService(context, false, false, TextUtils
- .equals(intent.getAction(), UsbManager.ACTION_USB_DEVICE_DETACHED) ?
- TunerHal.TUNER_TYPE_USB : null);
- }
- 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));
- break;
- case NETWORK_TUNER_ATTACHED:
- // 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);
- break;
- case NETWORK_TUNER_DETACHED:
- 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);
+ enableTunerTvInputService(context, false);
}
break;
}
@@ -237,15 +138,12 @@ public class TunerInputController extends BroadcastReceiver {
private boolean isUsbTunerConnected(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);
-
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.equals(device)) {
Log.i(TAG, "Tuner found");
return true;
}
@@ -260,8 +158,7 @@ public class TunerInputController extends BroadcastReceiver {
* @param context {@link Context} instance
* @param enabled {@code true} to enable the service; otherwise {@code false}
*/
- private void enableTunerTvInputService(Context context, boolean enabled,
- boolean forceDontKillApp, Integer tunerType) {
+ private void enableTunerTvInputService(Context context, boolean enabled) {
if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled);
PackageManager pm = context.getPackageManager();
ComponentName componentName = new ComponentName(context, TunerTvInputService.class);
@@ -273,8 +170,7 @@ public class TunerInputController extends BroadcastReceiver {
// 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()
+ int flags = TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
? PackageManager.DONT_KILL_APP : 0;
int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
@@ -283,67 +179,14 @@ public class TunerInputController extends BroadcastReceiver {
TunerSetupActivity.onTvInputEnabled(context, enabled);
// 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();
- }
+ if (!enabled) {
+ Toast.makeText(
+ context, R.string.msg_usb_device_detached, Toast.LENGTH_SHORT).show();
}
if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
} else if (enabled) {
- // When # of tuners is changed or the tuner input service is switching from/to using
- // network tuners or the device just boots.
+ // When # of USB tuners is changed or the device just boots.
TunerInputInfoUtils.updateTunerInputInfo(context);
}
}
-
- /**
- * Discovers a network tuner. If the network connection is down, it won't repeatedly checking.
- */
- public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
- executeNetworkTunerDiscoveryAsyncTask(context, 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.
- */
- private static void executeNetworkTunerDiscoveryAsyncTask(final Context context,
- final long repeatedDurationMs) {
- if (!Features.NETWORK_TUNER.isEnabled(context)) {
- return;
- }
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- if (isNetworkConnected(context)) {
- // 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, TunerInputController.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 null;
- }
- }.execute();
- }
-
- private static boolean isNetworkConnected(Context context) {
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = cm.getActiveNetworkInfo();
- return networkInfo != null && networkInfo.isConnected();
- }
}
diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java
index a387be74..1547e3ae 100644
--- a/src/com/android/tv/tuner/TunerPreferences.java
+++ b/src/com/android/tv/tuner/TunerPreferences.java
@@ -39,7 +39,6 @@ public class 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";
@@ -87,7 +86,8 @@ public class TunerPreferences {
/**
* Releases the resources.
*/
- public static synchronized void release(Context context) {
+ @MainThread
+ public static void release(Context context) {
if (useContentProvider(context) && sContentObserver != null) {
context.getContentResolver().unregisterContentObserver(sContentObserver);
}
@@ -99,8 +99,7 @@ public class TunerPreferences {
* This preferences is used across processes, so the preferences should be loaded again when the
* databases changes.
*/
- @MainThread
- public static void loadPreferences(Context context) {
+ public static synchronized void loadPreferences(Context context) {
if (sLoadPreferencesTask != null
&& sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) {
sLoadPreferencesTask.cancel(true);
@@ -114,7 +113,8 @@ public class TunerPreferences {
return TisConfiguration.isPackagedWithLiveChannels(context);
}
- public static synchronized int getChannelDataVersion(Context context) {
+ @MainThread
+ public static int getChannelDataVersion(Context context) {
SoftPreconditions.checkState(sInitialized);
if (useContentProvider(context)) {
return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION,
@@ -126,7 +126,8 @@ public class TunerPreferences {
}
}
- public static synchronized void setChannelDataVersion(Context context, int version) {
+ @MainThread
+ public static void setChannelDataVersion(Context context, int version) {
if (useContentProvider(context)) {
setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version);
} else {
@@ -136,7 +137,8 @@ public class TunerPreferences {
}
}
- public static synchronized int getScannedChannelCount(Context context) {
+ @MainThread
+ public static int getScannedChannelCount(Context context) {
SoftPreconditions.checkState(sInitialized);
if (useContentProvider(context)) {
return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT);
@@ -146,7 +148,8 @@ public class TunerPreferences {
}
}
- public static synchronized void setScannedChannelCount(Context context, int channelCount) {
+ @MainThread
+ public static void setScannedChannelCount(Context context, int channelCount) {
if (useContentProvider(context)) {
setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount);
} else {
@@ -156,25 +159,8 @@ public class TunerPreferences {
}
}
- 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) {
+ @MainThread
+ public static boolean isScanDone(Context context) {
SoftPreconditions.checkState(sInitialized);
if (useContentProvider(context)) {
return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE);
@@ -184,7 +170,8 @@ public class TunerPreferences {
}
}
- public static synchronized void setScanDone(Context context) {
+ @MainThread
+ public static void setScanDone(Context context) {
if (useContentProvider(context)) {
setPreference(context, PREFS_KEY_SCAN_DONE, true);
} else {
@@ -194,7 +181,8 @@ public class TunerPreferences {
}
}
- public static synchronized boolean shouldShowSetupActivity(Context context) {
+ @MainThread
+ public static boolean shouldShowSetupActivity(Context context) {
SoftPreconditions.checkState(sInitialized);
if (useContentProvider(context)) {
return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP);
@@ -204,7 +192,8 @@ public class TunerPreferences {
}
}
- public static synchronized void setShouldShowSetupActivity(Context context, boolean need) {
+ @MainThread
+ public static void setShouldShowSetupActivity(Context context, boolean need) {
if (useContentProvider(context)) {
setPreference(context, PREFS_KEY_LAUNCH_SETUP, need);
} else {
@@ -214,7 +203,8 @@ public class TunerPreferences {
}
}
- public static synchronized boolean getStoreTsStream(Context context) {
+ @MainThread
+ public static boolean getStoreTsStream(Context context) {
SoftPreconditions.checkState(sInitialized);
if (useContentProvider(context)) {
return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
@@ -224,7 +214,8 @@ public class TunerPreferences {
}
}
- public static synchronized void setStoreTsStream(Context context, boolean shouldStore) {
+ @MainThread
+ public static void setStoreTsStream(Context context, boolean shouldStore) {
if (useContentProvider(context)) {
setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore);
} else {
@@ -238,23 +229,8 @@ public class TunerPreferences {
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, 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) {
+ @MainThread
+ private static void setPreference(final Context context, final String key, final String value) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
@@ -273,6 +249,18 @@ public class TunerPreferences {
}.execute();
}
+ @MainThread
+ private static void setPreference(Context context, String key, int value) {
+ sPreferenceValues.putInt(key, value);
+ setPreference(context, key, Integer.toString(value));
+ }
+
+ @MainThread
+ private static void setPreference(Context context, String key, boolean value) {
+ sPreferenceValues.putBoolean(key, value);
+ setPreference(context, key, Boolean.toString(value));
+ }
+
private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> {
private final Context mContext;
private LoadPreferencesTask(Context context) {
@@ -304,9 +292,6 @@ public class TunerPreferences {
case PREFS_KEY_STORE_TS_STREAM:
bundle.putBoolean(key, Boolean.parseBoolean(value));
break;
- case PREFS_KEY_LAST_POSTAL_CODE:
- bundle.putString(key, value);
- break;
}
}
}
@@ -318,10 +303,8 @@ public class TunerPreferences {
}
@Override
- protected synchronized void onPostExecute(Bundle bundle) {
- if (bundle != null) {
- sPreferenceValues.putAll(bundle);
- }
+ protected void onPostExecute(Bundle bundle) {
+ sPreferenceValues.putAll(bundle);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/tuner/UsbTunerHal.java b/src/com/android/tv/tuner/UsbTunerHal.java
index b1608ede..22e35ea1 100644
--- a/src/com/android/tv/tuner/UsbTunerHal.java
+++ b/src/com/android/tv/tuner/UsbTunerHal.java
@@ -169,10 +169,6 @@ public class UsbTunerHal extends TunerHal {
* 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;
- }
+ return (new DvbDeviceAccessor(context)).getNumOfDvbDevices();
}
}
diff --git a/src/com/android/tv/tuner/cc/CaptionLayout.java b/src/com/android/tv/tuner/cc/CaptionLayout.java
index c41f1014..a88538df 100644
--- a/src/com/android/tv/tuner/cc/CaptionLayout.java
+++ b/src/com/android/tv/tuner/cc/CaptionLayout.java
@@ -19,7 +19,7 @@ package com.android.tv.tuner.cc;
import android.content.Context;
import android.util.AttributeSet;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.layout.ScaledLayout;
/**
diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
index 3aa40982..3c75caa9 100644
--- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
+++ b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
@@ -27,7 +27,7 @@ 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.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java
index c43fe512..92ab0620 100644
--- a/src/com/android/tv/tuner/cc/Cea708Parser.java
+++ b/src/com/android/tv/tuner/cc/Cea708Parser.java
@@ -140,7 +140,6 @@ public class Cea708Parser {
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() {
@@ -333,14 +332,12 @@ public class Cea708Parser {
mDiscoveredNumBytes.put(
serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0));
}
- if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()
- || !mFirstServiceNumberDiscovered) {
+ if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()) {
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();
diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java
index 2c8a52db..67700c6a 100644
--- a/src/com/android/tv/tuner/data/PsiData.java
+++ b/src/com/android/tv/tuner/data/PsiData.java
@@ -17,8 +17,8 @@
package com.android.tv.tuner.data;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import java.util.List;
diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java
index ac7fdedb..aead4be8 100644
--- a/src/com/android/tv/tuner/data/PsipData.java
+++ b/src/com/android/tv/tuner/data/PsipData.java
@@ -20,11 +20,11 @@ import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.text.format.DateUtils;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+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 com.android.tv.tuner.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java
index 41f66e7d..89079d77 100644
--- a/src/com/android/tv/tuner/data/TunerChannel.java
+++ b/src/com/android/tv/tuner/data/TunerChannel.java
@@ -19,11 +19,12 @@ package com.android.tv.tuner.data;
import android.support.annotation.NonNull;
import android.util.Log;
-import com.android.tv.tuner.data.Channel.TunerChannelProto;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+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.android.tv.tuner.util.StringUtils;
import com.google.protobuf.nano.MessageNano;
import java.io.IOException;
@@ -39,11 +40,6 @@ import java.util.Objects;
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",
@@ -67,7 +63,6 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
// 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,
@@ -150,44 +145,6 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE);
}
- /**
- * 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(programNumber, Collections.EMPTY_LIST);
- 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;
}
@@ -236,7 +193,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return mProto.videoPid;
}
- synchronized public void setVideoPid(int videoPid) {
+ public void setVideoPid(int videoPid) {
mProto.videoPid = videoPid;
}
@@ -262,7 +219,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return Ints.asList(mProto.audioPids);
}
- synchronized public void setAudioPids(List<Integer> audioPids) {
+ public void setAudioPids(List<Integer> audioPids) {
mProto.audioPids = Ints.toArray(audioPids);
}
@@ -270,7 +227,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return Ints.asList(mProto.audioStreamTypes);
}
- synchronized public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
+ public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
}
@@ -282,32 +239,32 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return mProto.type;
}
- synchronized public void setFilepath(String filepath) {
- mProto.filepath = filepath == null ? "" : filepath;
+ public void setFilepath(String filepath) {
+ mProto.filepath = filepath;
}
public String getFilepath() {
return mProto.filepath;
}
- synchronized public void setVirtualMajor(int virtualMajor) {
+ public void setVirtualMajor(int virtualMajor) {
mProto.virtualMajor = virtualMajor;
}
- synchronized public void setVirtualMinor(int virtualMinor) {
+ public void setVirtualMinor(int virtualMinor) {
mProto.virtualMinor = virtualMinor;
}
- synchronized public void setShortName(String shortName) {
- mProto.shortName = shortName == null ? "" : shortName;
+ public void setShortName(String shortName) {
+ mProto.shortName = shortName;
}
- synchronized public void setFrequency(int frequency) {
+ public void setFrequency(int frequency) {
mProto.frequency = frequency;
}
- synchronized public void setModulation(String modulation) {
- mProto.modulation = modulation == null ? "" : modulation;
+ public void setModulation(String modulation) {
+ mProto.modulation = modulation;
}
public boolean hasVideo() {
@@ -322,18 +279,13 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return mProto.channelId;
}
- synchronized public void setChannelId(long channelId) {
+ 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);
+ if (mProto.virtualMajor != 0 && mProto.virtualMinor != 0) {
+ return String.format("%d-%d", mProto.virtualMajor, mProto.virtualMinor);
} else if (mProto.virtualMajor != 0) {
return Integer.toString(mProto.virtualMajor);
} else {
@@ -346,7 +298,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
}
@Override
- synchronized public void setHasCaptionTrack() {
+ public void setHasCaptionTrack() {
mProto.hasCaptionTrack = true;
}
@@ -360,7 +312,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
}
- synchronized public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
+ public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
}
@@ -369,11 +321,11 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
}
- synchronized public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
+ public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
}
- synchronized public void selectAudioTrack(int index) {
+ public void selectAudioTrack(int index) {
if (0 <= index && index < mProto.audioPids.length) {
mProto.audioTrackIndex = index;
} else {
@@ -381,22 +333,6 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
}
}
- 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) {
@@ -423,10 +359,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
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());
}
@@ -441,19 +374,12 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks
@Override
public int hashCode() {
- return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath());
+ return Objects.hash(getFrequency(), getProgramNumber(), 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 byte[] toByteArray() {
+ return MessageNano.toByteArray(mProto);
}
public static TunerChannel parseFrom(byte[] data) {
diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
index 89641530..c105e222 100644
--- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
+++ b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
@@ -23,29 +23,17 @@ 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.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.extractor.ExtractorSampleSource;
+import com.google.android.exoplayer.extractor.ExtractorSampleSource.EventListener;
+import com.google.android.exoplayer.upstream.Allocator;
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.extractor.DefaultExtractorsFactory;
-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.ac3.Ac3DefaultTrackRenderer;
+import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer;
@@ -54,11 +42,10 @@ 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;
+import java.util.concurrent.atomic.AtomicLong;
/**
* A class that extracts samples from a live broadcast stream while storing the sample on the disk.
@@ -67,7 +54,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ExoPlayerSampleExtractor implements SampleExtractor {
private static final String TAG = "ExoPlayerSampleExtracto";
- private static final int INVALID_TRACK_INDEX = -1;
+ // Buffer segment size for memory allocator. Copied from demo implementation of ExoPlayer.
+ private static final int BUFFER_SEGMENT_SIZE_IN_BYTES = 64 * 1024;
+ // Buffer segment count for sample source. Copied from demo implementation of ExoPlayer.
+ private static final int BUFFER_SEGMENT_COUNT = 256;
+
private final HandlerThread mSourceReaderThread;
private final long mId;
@@ -79,69 +70,36 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
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,
+ public ExoPlayerSampleExtractor(Uri uri, DataSource source, BufferManager bufferManager,
PlaybackBufferListener bufferListener, boolean isRecording) {
// It'll be used as a timeshift file chunk name's prefix.
mId = System.currentTimeMillis();
+ Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE_IN_BYTES);
EventListener eventListener = new EventListener() {
+
@Override
- public void onLoadError(IOException error) {
- mError = error;
+ public void onLoadError(int sourceId, IOException e) {
+ mError = e;
}
};
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 DefaultExtractorsFactory(),
+ mSourceReaderWorker = new SourceReaderWorker(new ExtractorSampleSource(uri, source,
+ allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE_IN_BYTES,
// Do not create a handler if we not on a looper. e.g. test.
- Looper.myLooper() != null ? new Handler() : null, eventListener));
+ Looper.myLooper() != null ? new Handler() : null,
+ eventListener, 0));
if (isRecording) {
mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, false,
RecordingSampleBuffer.BUFFER_REASON_RECORDING);
} else {
- if (bufferManager == null) {
+ if (bufferManager == null || bufferManager.isDisabled()) {
mSampleBuffer = new SimpleSampleBuffer(bufferListener);
} else {
mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, true,
@@ -156,141 +114,43 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
mOnCompletionListenerHandler = handler;
}
- private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback {
+ private class SourceReaderWorker implements Handler.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 final SampleSource mSampleSource;
+ private SampleSource.SampleSourceReader mSampleSourceReader;
private boolean[] mTrackMetEos;
private boolean mMetEos = false;
private long mCurrentPosition;
- private DecoderInputBuffer mDecoderInputBuffer;
- private SampleHolder mSampleHolder;
- private boolean mPrepareRequested;
- public SourceReaderWorker(MediaSource sampleSource) {
+ public SourceReaderWorker(SampleSource 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;
- }
+ mPrepared = prepare();
+ if (!mPrepared && mExceptionOnPrepare == null) {
+ mSourceReaderHandler
+ .sendEmptyMessageDelayed(MSG_PREPARE, RETRY_INTERVAL_MS);
+ } else{
+ mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
}
return true;
case MSG_FETCH_SAMPLES:
boolean didSomething = false;
+ SampleHolder sample = new SampleHolder(
+ SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
ConditionVariable conditionVariable = new ConditionVariable();
- int trackCount = mStreams.length;
+ int trackCount = mSampleSourceReader.getTrackCount();
for (int i = 0; i < trackCount; ++i) {
- if (!mTrackMetEos[i] && C.RESULT_NOTHING_READ
- != fetchSample(i, mSampleHolder, conditionVariable)) {
+ if (!mTrackMetEos[i] && SampleSource.NOTHING_READ
+ != fetchSample(i, sample, conditionVariable)) {
if (mMetEos) {
// If mMetEos was on during fetchSample() due to an error,
// fetching from other tracks is not necessary.
@@ -299,7 +159,6 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
didSomething = true;
}
}
- mMediaPeriod.continueLoading(mCurrentPosition);
if (!mMetEos) {
if (didSomething) {
mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
@@ -312,10 +171,17 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
}
return true;
case MSG_RELEASE:
- if (mMediaPeriod != null) {
- mSampleSource.releasePeriod(mMediaPeriod);
- mSampleSource.releaseSource();
- mMediaPeriod = null;
+ if (mSampleSourceReader != null) {
+ if (mPrepared) {
+ // ExtractorSampleSource expects all the tracks should be disabled
+ // before releasing.
+ int count = mSampleSourceReader.getTrackCount();
+ for (int i = 0; i < count; ++i) {
+ mSampleSourceReader.disable(i);
+ }
+ }
+ mSampleSourceReader.release();
+ mSampleSourceReader = null;
}
cleanUp();
mSourceReaderHandler.removeCallbacksAndMessages(null);
@@ -324,109 +190,91 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
return false;
}
+ private boolean prepare() {
+ if (mSampleSourceReader == null) {
+ mSampleSourceReader = mSampleSource.register();
+ }
+ if(!mSampleSourceReader.prepare(0)) {
+ return false;
+ }
+ if (mTrackFormats == null) {
+ int trackCount = mSampleSourceReader.getTrackCount();
+ mTrackMetEos = new boolean[trackCount];
+ List<MediaFormat> trackFormats = new ArrayList<>();
+ for (int i = 0; i < trackCount; i++) {
+ trackFormats.add(mSampleSourceReader.getFormat(i));
+ mSampleSourceReader.enable(i, 0);
+
+ }
+ 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 false;
+ }
+ }
+ return true;
+ }
+
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;
+ mSampleSourceReader.continueBuffering(track, mCurrentPosition);
+
+ MediaFormatHolder formatHolder = new MediaFormatHolder();
+ sample.clearData();
+ int ret = mSampleSourceReader.readData(track, mCurrentPosition, formatHolder, sample);
+ if (ret == SampleSource.SAMPLE_READ) {
+ if (mCurrentPosition < sample.timeUs) {
+ mCurrentPosition = sample.timeUs;
}
try {
Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track);
if (lastExtractedPositionUs == null) {
- mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs);
+ mLastExtractedPositionUsMap.put(track, sample.timeUs);
} else {
mLastExtractedPositionUsMap.put(track,
- Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs));
+ Math.max(lastExtractedPositionUs, sample.timeUs));
}
- queueSample(track, conditionVariable);
+ queueSample(track, sample, conditionVariable);
} catch (IOException e) {
mLastExtractedPositionUsMap.clear();
mMetEos = true;
mSampleBuffer.setEos();
}
- } else if (ret == C.RESULT_END_OF_INPUT) {
+ } else if (ret == SampleSource.END_OF_STREAM) {
mTrackMetEos[track] = true;
for (int i = 0; i < mTrackMetEos.length; ++i) {
if (!mTrackMetEos[i]) {
break;
}
- if (i == mTrackMetEos.length - 1) {
+ if (i == mTrackMetEos.length -1) {
mMetEos = true;
mSampleBuffer.setEos();
}
}
}
- // TODO: Handle C.RESULT_FORMAT_READ for dynamic resolution change. b/28169263
+ // TODO: Handle SampleSource.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
- - Ac3DefaultTrackRenderer.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();
- }
+ private void queueSample(int index, SampleHolder sample, ConditionVariable conditionVariable)
+ throws IOException {
+ long writeStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ mSampleBuffer.writeSample(index, sample, conditionVariable);
+
+ // Checks whether the storage has enough bandwidth for recording samples.
+ if (mSampleBuffer.isWriteSpeedSlow(sample.size,
+ SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) {
+ mSampleBuffer.handleWriteSpeedSlow();
}
}
@@ -480,7 +328,7 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
}
@Override
- public boolean continueBuffering(long positionUs) {
+ public boolean continueBuffering(long positionUs) {
return mSampleBuffer.continueBuffering(positionUs);
}
@@ -538,14 +386,12 @@ public class ExoPlayerSampleExtractor implements SampleExtractor {
}
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());
- }
+ long lastExtractedPositionUs = Long.MAX_VALUE;
+ for (long value : mLastExtractedPositionUsMap.values()) {
+ lastExtractedPositionUs = Math.min(lastExtractedPositionUs, value);
}
- if (lastExtractedPositionUs == Long.MIN_VALUE) {
- lastExtractedPositionUs = com.google.android.exoplayer.C.UNKNOWN_TIME_US;
+ if (lastExtractedPositionUs == Long.MAX_VALUE) {
+ lastExtractedPositionUs = 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
index b7e42a7c..ec7b4b16 100644
--- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
+++ b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
@@ -25,6 +25,7 @@ import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
import com.android.tv.tuner.tvinput.PlaybackBufferListener;
import android.os.Handler;
+import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
@@ -60,17 +61,18 @@ public class FileSampleExtractor implements SampleExtractor{
@Override
public boolean prepare() throws IOException {
- List<BufferManager.TrackFormat> trackFormatList = mBufferManager.readTrackInfoFiles();
- if (trackFormatList == null || trackFormatList.isEmpty()) {
+ ArrayList<Pair<String, android.media.MediaFormat>> trackInfos =
+ mBufferManager.readTrackInfoFiles();
+ if (trackInfos == null || trackInfos.isEmpty()) {
throw new IOException("Cannot find meta files for the recording.");
}
- mTrackCount = trackFormatList.size();
+ mTrackCount = trackInfos.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));
+ Pair<String, android.media.MediaFormat> pair = trackInfos.get(i);
+ ids.add(pair.first);
+ mTrackFormats.add(MediaFormatUtil.createMediaFormat(pair.second));
}
mSampleBuffer = new RecordingSampleBuffer(mBufferManager, mBufferListener, true,
RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK);
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
index ba0edf20..381b22e9 100644
--- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
+++ b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
@@ -39,8 +39,8 @@ 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.ac3.Ac3DefaultTrackRenderer;
-import com.android.tv.tuner.exoplayer.ac3.Ac3MediaCodecTrackRenderer;
+import com.android.tv.tuner.exoplayer.ac3.Ac3PassthroughTrackRenderer;
+import com.android.tv.tuner.exoplayer.ac3.Ac3TrackRenderer;
import com.android.tv.tuner.source.TsDataSource;
import com.android.tv.tuner.source.TsDataSourceManager;
import com.android.tv.tuner.tvinput.EventDetector;
@@ -48,12 +48,11 @@ import com.android.tv.tuner.tvinput.EventDetector;
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,
- Ac3DefaultTrackRenderer.EventListener,
- Ac3MediaCodecTrackRenderer.Ac3EventListener {
+/**
+ * MPEG-2 TS stream player implementation using ExoPlayer.
+ */
+public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener,
+ Ac3PassthroughTrackRenderer.EventListener, Ac3TrackRenderer.Ac3EventListener {
private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER;
/**
@@ -305,10 +304,8 @@ public class MpegTsPlayer
SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed()));
mPlayer.setPlayWhenReady(true);
mTrickplayRunning = true;
- if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer,
- Ac3DefaultTrackRenderer.MSG_SET_PLAYBACK_SPEED,
+ if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) {
+ mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_PLAYBACK_SPEED,
playbackParams.getSpeed());
} else {
mPlayer.sendMessage(mAudioRenderer,
@@ -320,9 +317,10 @@ public class MpegTsPlayer
private void stopSmoothTrickplay(boolean calledBySeek) {
if (mTrickplayRunning) {
mTrickplayRunning = false;
- if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer, Ac3DefaultTrackRenderer.MSG_SET_PLAYBACK_SPEED, 1.0f);
+ if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) {
+ mPlayer.sendMessage(mAudioRenderer,
+ Ac3PassthroughTrackRenderer.MSG_SET_PLAYBACK_SPEED,
+ 1.0f);
} else {
mPlayer.sendMessage(mAudioRenderer,
MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS,
@@ -425,8 +423,8 @@ public class MpegTsPlayer
*/
public void setVolume(float volume) {
mVolume = volume;
- if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) {
- mPlayer.sendMessage(mAudioRenderer, Ac3DefaultTrackRenderer.MSG_SET_VOLUME, volume);
+ if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) {
+ mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_VOLUME, volume);
} else {
mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
volume);
@@ -439,9 +437,9 @@ public class MpegTsPlayer
* @param enable enables the audio when {@code true}, disables otherwise.
*/
public void setAudioTrack(boolean enable) {
- if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer, Ac3DefaultTrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0);
+ if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) {
+ mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_AUDIO_TRACK,
+ enable ? 1 : 0);
} else {
mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
enable ? mVolume : 0.0f);
@@ -497,28 +495,6 @@ public class MpegTsPlayer
}
/**
- * 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() {
@@ -674,4 +650,4 @@ public class MpegTsPlayer
}
}
}
-} \ 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
index a1a97d3d..0e46c9cf 100644
--- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
+++ b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
@@ -21,10 +21,9 @@ import android.content.Context;
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.ac3.Ac3DefaultTrackRenderer;
+import com.android.tv.tuner.exoplayer.ac3.Ac3PassthroughTrackRenderer;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.tvinput.PlaybackBufferListener;
@@ -53,12 +52,10 @@ public class MpegTsRendererBuilder implements RendererBuilder {
SampleSource sampleSource = new MpegTsSampleSource(extractor);
MpegTsVideoTrackRenderer videoRenderer = new MpegTsVideoTrackRenderer(mContext,
sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer);
- // TODO: Only using Ac3DefaultTrackRenderer for A/V sync issue. We will use
- // {@link Ac3MediaCodecTrackRenderer} when we use ExoPlayer's extractor.
- TrackRenderer audioRenderer =
- new Ac3DefaultTrackRenderer(
- sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer,
- !Features.AC3_SOFTWARE_DECODE.isEnabled(mContext));
+ // TODO: Only using Ac3PassthroughTrackRenderer for A/V sync issue. We will use
+ // {@link Ac3TrackRenderer} when we use ExoPlayer's extractor.
+ TrackRenderer audioRenderer = new Ac3PassthroughTrackRenderer(sampleSource,
+ mpegTsPlayer.getMainHandler(), mpegTsPlayer);
Cea708TextTrackRenderer textRenderer = new Cea708TextTrackRenderer(sampleSource);
TrackRenderer[] renderers = new TrackRenderer[MpegTsPlayer.RENDERER_COUNT];
diff --git a/src/com/android/tv/tuner/exoplayer/ac3/Ac3DefaultTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java
index d442fde8..9dae2e34 100644
--- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3DefaultTrackRenderer.java
+++ b/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java
@@ -23,15 +23,16 @@ 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.MediaCodecAudioTrackRenderer;
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.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.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.android.tv.tuner.tvinput.TunerDebug;
import java.io.IOException;
@@ -39,9 +40,9 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
- * Decodes and renders AC3 audio. Supports passthrough playback and ffmpeg based software decoding.
+ * Decodes and renders AC3 audio.
*/
-public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock {
+public class Ac3PassthroughTrackRenderer 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;
@@ -50,14 +51,7 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
// One AC3 sample has 1536 frames, and its duration is 32ms.
public static final long AC3_SAMPLE_DURATION_US = 32000;
- // 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 = "Ac3DefaultTrackRenderer";
+ private static final String TAG = "Ac3PassthroughTrackRenderer";
private static final boolean DEBUG = false;
/**
@@ -99,8 +93,6 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
private final AudioClock mAudioClock;
private MediaFormat mFormat;
- private boolean mFormatConfigured;
- private int mSampleSize;
private final ByteBuffer mOutputBuffer;
private boolean mOutputReady;
private int mTrackIndex;
@@ -114,15 +106,10 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
private long mInterpolatedTimeUs;
private long mPreviousPositionUs;
private boolean mIsStopped;
- private boolean mEnabled = true;
- private boolean mIsMuted;
private ArrayList<Integer> mTracksIndex;
- public Ac3DefaultTrackRenderer(
- SampleSource source,
- Handler eventHandler,
- EventListener listener,
- boolean usePassthrough) {
+ public Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler,
+ EventListener listener) {
mSource = source.register();
mEventHandler = eventHandler;
mEventListener = listener;
@@ -338,38 +325,14 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
}
}
- 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 {
- mFormat = formatHolder.format;
- mFormatConfigured = true;
+ mFormat = formatHolder.format;
if (DEBUG) {
Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString());
}
clearDecodeState();
- 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);
+ AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16());
}
private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
@@ -396,11 +359,8 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
return false;
}
default: {
- if (mSampleHolder.size != mSampleSize && mFormatConfigured) {
- onSampleSizeChanged(mSampleHolder.size);
- }
mSampleHolder.data.flip();
- decodeDone(mSampleHolder.data, mSampleHolder.timeUs);
+ decodeDone(mSampleHolder.data, mSampleHolder.timeUs);
return true;
}
}
@@ -551,29 +511,24 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
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);
+ AUDIO_TRACK.setVolume((Float) message);
break;
case MSG_SET_AUDIO_TRACK:
- mEnabled = (Integer) message == 1;
- setStatus(mEnabled);
+ boolean enabled = (Integer) message == 1;
+ 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());
+ }
break;
case MSG_SET_PLAYBACK_SPEED:
mAudioClock.setPlaybackSpeed((Float) message);
@@ -582,21 +537,4 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock
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/ac3/Ac3MediaCodecTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java
index 604959d1..2bf86b5a 100644
--- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3MediaCodecTrackRenderer.java
+++ b/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java
@@ -25,14 +25,14 @@ 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.
+ * <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 Ac3MediaCodecTrackRenderer extends MediaCodecAudioTrackRenderer {
- private final String TAG = "Ac3MediaCodecTrackRenderer";
+public class Ac3TrackRenderer extends MediaCodecAudioTrackRenderer {
+ private final String TAG = "Ac3TrackRenderer";
private final boolean DEBUG = false;
private final Ac3EventListener mListener;
@@ -47,11 +47,8 @@ public class Ac3MediaCodecTrackRenderer extends MediaCodecAudioTrackRenderer {
void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e);
}
- public Ac3MediaCodecTrackRenderer(
- SampleSource source,
- MediaCodecSelector mediaCodecSelector,
- Handler eventHandler,
- EventListener eventListener) {
+ public Ac3TrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector,
+ Handler eventHandler, EventListener eventListener) {
super(source, mediaCodecSelector, eventHandler, eventListener);
mListener = (Ac3EventListener) eventListener;
}
diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java
index 6f152490..bfdf08ac 100644
--- a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java
+++ b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java
@@ -98,8 +98,8 @@ public class AudioTrackMonitor {
long now = SystemClock.elapsedRealtime();
if (mExpireMs != 0 && now >= mExpireMs) {
if (DEBUG) {
- long sampleDuration =
- (mTotalCount - 1) * Ac3DefaultTrackRenderer.AC3_SAMPLE_DURATION_US / 1000;
+ long sampleDuration = (mTotalCount - 1) *
+ Ac3PassthroughTrackRenderer.AC3_SAMPLE_DURATION_US / 1000;
long totalDuration = now - mStartMs;
StringBuilder ptsBuilder = new StringBuilder();
ptsBuilder.append("PTS received ").append(mSampleCount).append(", ")
diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java
index 393e12c3..bc3c5d00 100644
--- a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java
+++ b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java
@@ -18,7 +18,6 @@ package com.android.tv.tuner.exoplayer.ac3;
import android.media.MediaFormat;
-import com.google.android.exoplayer.C;
import com.google.android.exoplayer.audio.AudioTrack;
import java.nio.ByteBuffer;
@@ -29,10 +28,6 @@ import java.nio.ByteBuffer;
* 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 =
- Ac3DefaultTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK;
private final AudioTrack mAudioTrack = new AudioTrack();
private int mAudioSessionID;
private boolean mIsEnabled;
@@ -111,7 +106,7 @@ public class AudioTrackWrapper {
mAudioTrack.setVolume(volume);
}
- public void reconfigure(MediaFormat format, int audioBufferSize) {
+ public void reconfigure(MediaFormat format) {
if (!mIsEnabled || format == null) {
return;
}
@@ -122,9 +117,9 @@ public class AudioTrackWrapper {
try {
pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING);
} catch (Exception e) {
- pcmEncoding = C.ENCODING_PCM_16BIT;
+ pcmEncoding = com.google.android.exoplayer.MediaFormat.NO_VALUE;
}
- // TODO: Handle non-AC3.
+ // TODO: Handle non-AC3 or non-passthrough audio.
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,
@@ -132,14 +127,7 @@ public class AudioTrackWrapper {
// 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);
+ mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding);
}
public void handleDiscontinuity() {
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
index 112e9dc4..eb596e93 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
@@ -25,14 +25,13 @@ 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.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -60,8 +59,7 @@ public class BufferManager {
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, SortedMap<Long, SampleChunk>> mChunkMap = new ArrayMap<>();
private final Map<String, Long> mStartPositionMap = new ArrayMap<>();
private final Map<String, ChunkEvictedListener> mEvictListeners = new ArrayMap<>();
private final StorageManager mStorageManager;
@@ -79,11 +77,13 @@ public class BufferManager {
}
};
+ private volatile boolean mClosed = false;
private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK;
private long mTotalWriteSize;
private long mTotalWriteTimeNs;
private float mWriteBandwidth = 0.0f;
private volatile int mSpeedCheckCount;
+ private boolean mDisabled = false;
public interface ChunkEvictedListener {
void onChunkEvicted(String id, long createdTimeMs);
@@ -174,66 +174,6 @@ public class BufferManager {
}
/**
- * 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 {
@@ -246,6 +186,11 @@ public class BufferManager {
File getBufferDir();
/**
+ * Cleans up storage.
+ */
+ void clearStorage();
+
+ /**
* Informs whether the storage is used for persistent use. (eg. dvr recording/play)
*
* @return {@code true} if stored files are persistent
@@ -275,27 +220,29 @@ public class BufferManager {
* Reads track name & {@link MediaFormat} from storage.
*
* @param isAudio {@code true} if it is for audio track
- * @return {@link List} of TrackFormat
+ * @return {@link Pair} of track name & {@link MediaFormat}
+ * @throws IOException
*/
- List<TrackFormat> readTrackInfoFiles(boolean isAudio);
+ Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException;
/**
- * Reads key sample positions for each written sample from storage.
+ * Reads sample indexes 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;
+ ArrayList<Long> readIndexFile(String trackId) throws IOException;
/**
* Writes track information to storage.
*
- * @param formatList {@list List} of TrackFormat
+ * @param trackId track name
+ * @param format {@link android.media.MediaFormat} of the track
* @param isAudio {@code true} if it is for audio track
* @throws IOException
*/
- void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio)
+ void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio)
throws IOException;
/**
@@ -305,7 +252,7 @@ public class BufferManager {
* @param index {@link SampleChunk} container
* @throws IOException
*/
- void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
+ void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index)
throws IOException;
}
@@ -360,6 +307,7 @@ public class BufferManager {
SampleChunk.SampleChunkCreator sampleChunkCreator) {
mStorageManager = storageManager;
mSampleChunkCreator = sampleChunkCreator;
+ clearBuffer(true);
}
public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) {
@@ -370,44 +318,44 @@ public class BufferManager {
mEvictListeners.remove(id);
}
+ private void clearBuffer(boolean deleteFiles) {
+ mChunkMap.clear();
+ if (deleteFiles) {
+ mStorageManager.clearStorage();
+ }
+ mBufferSize = 0;
+ }
+
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.
+ * Creates a new {@link SampleChunk} for caching samples.
*
* @param id the name of the track
- * @param positionUs current position to write a sample in micro seconds.
+ * @param positionUs starting position of the {@link SampleChunk} 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 {
+ public SampleChunk createNewWriteFile(String id, long positionUs,
+ SamplePool samplePool) throws IOException {
if (!maybeEvictChunk()) {
throw new IOException("Not enough storage space");
}
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
+ SortedMap<Long, SampleChunk> 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;
- }
+ File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs));
+ SampleChunk sampleChunk = mSampleChunkCreator.createSampleChunk(samplePool, file,
+ positionUs, mChunkCallback);
+ map.put(positionUs, sampleChunk);
+ return sampleChunk;
}
/**
@@ -418,10 +366,10 @@ public class BufferManager {
* @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;
+ ArrayList<Long> keyPositions = mStorageManager.readIndexFile(trackId);
+ long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0) : 0;
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(trackId);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(trackId);
if (map == null) {
map = new TreeMap<>();
mChunkMap.put(trackId, map);
@@ -429,15 +377,11 @@ public class BufferManager {
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));
+ for (long positionUs: keyPositions) {
+ chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool,
+ mStorageManager.getBufferDir(), getFileName(trackId, positionUs), positionUs,
+ mChunkCallback, chunk);
+ map.put(positionUs, chunk);
}
}
@@ -448,19 +392,19 @@ public class BufferManager {
* @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);
+ public SampleChunk getReadFile(String id, long positionUs) {
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(id);
if (map == null) {
return null;
}
- Pair<SampleChunk, Integer> ret;
- SortedMap<Long, Pair<SampleChunk, Integer>> headMap = map.headMap(positionUs + 1);
+ SampleChunk sampleChunk;
+ SortedMap<Long, SampleChunk> headMap = map.headMap(positionUs + 1);
if (!headMap.isEmpty()) {
- ret = headMap.get(headMap.lastKey());
+ sampleChunk = headMap.get(headMap.lastKey());
} else {
- ret = map.get(map.firstKey());
+ sampleChunk = map.get(map.firstKey());
}
- return ret;
+ return sampleChunk;
}
/**
@@ -495,16 +439,15 @@ public class BufferManager {
// Since chunks are persistent, we cannot evict chunks.
return false;
}
- SortedMap<Long, Pair<SampleChunk, Integer>> earliestChunkMap = null;
+ SortedMap<Long, SampleChunk> 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();
+ for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
+ SortedMap<Long, SampleChunk> map = entry.getValue();
if (map.isEmpty()) {
continue;
}
- SampleChunk chunk = map.get(map.firstKey()).first;
+ SampleChunk chunk = map.get(map.firstKey());
if (earliestChunk == null
|| chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) {
earliestChunkMap = map;
@@ -530,9 +473,8 @@ public class BufferManager {
}
pendingDelete = mPendingDelete.getSize();
}
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
+ for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
+ SortedMap<Long, SampleChunk> map = entry.getValue();
if (map.isEmpty()) {
continue;
}
@@ -547,74 +489,70 @@ public class BufferManager {
* @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");
+ public ArrayList<Pair<String, MediaFormat>> readTrackInfoFiles() throws IOException {
+ ArrayList<Pair<String, MediaFormat>> trackInfos = new ArrayList<>();
+ try {
+ trackInfos.add(mStorageManager.readTrackInfoFile(false));
+ } catch (FileNotFoundException e) {
+ // There can be a single track only recording. (eg. audio-only, video-only)
+ // So the exception should not stop the read.
}
- return trackFormatList;
+ try {
+ trackInfos.add(mStorageManager.readTrackInfoFile(true));
+ } catch (FileNotFoundException e) {
+ // See above catch block.
+ }
+ return trackInfos;
}
/**
* Writes track information and index information for all tracks.
*
- * @param audios list of audio track information
- * @param videos list of audio track information
+ * @param audio audio information.
+ * @param video video information.
* @throws IOException
*/
- public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos)
+ public void writeMetaFiles(Pair<String, MediaFormat> audio, Pair<String, MediaFormat> video)
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 (audio != null) {
+ mStorageManager.writeTrackInfoFile(audio.first, audio.second, true);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(audio.first);
+ if (map == null) {
+ throw new IOException("Audio track index missing");
}
+ mStorageManager.writeIndexFile(audio.first, 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);
+ if (video != null) {
+ mStorageManager.writeTrackInfoFile(video.first, video.second, false);
+ SortedMap<Long, SampleChunk> map = mChunkMap.get(video.first);
+ if (map == null) {
+ throw new IOException("Video track index missing");
}
+ mStorageManager.writeIndexFile(video.first, map);
}
}
/**
+ * Marks it is closed and it is not used anymore.
+ */
+ public void close() {
+ // Clean-up may happen after this is called.
+ mClosed = true;
+ }
+
+ /**
* 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());
- }
- }
+ mPendingDelete.release();
+ for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) {
+ for (SampleChunk chunk : entry.getValue().values()) {
+ SampleChunk.IoState.release(chunk, !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());
+ }
+ mChunkMap.clear();
+ if (mClosed) {
+ clearBuffer(!mStorageManager.isPersistent());
}
}
@@ -673,6 +611,20 @@ public class BufferManager {
}
/**
+ * Marks {@link BufferManager} object disabled to prevent it from the future use.
+ */
+ public void disable() {
+ mDisabled = true;
+ }
+
+ /**
+ * Returns if {@link BufferManager} object is disabled.
+ */
+ public boolean isDisabled() {
+ return mDisabled;
+ }
+
+ /**
* Returns if {@link BufferManager} has checked the write speed,
* which is suitable for Trickplay.
*/
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
index bea3defd..6a0502a7 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
@@ -17,12 +17,8 @@
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.Track.AtscCaptionTrack;
-import com.google.protobuf.nano.MessageNano;
-
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -32,25 +28,18 @@ 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.
@@ -70,6 +59,18 @@ public class DvrStorageManager implements BufferManager.StorageManager {
}
@Override
+ public void clearStorage() {
+ if (mIsRecording) {
+ File[] files = mBufferDir.listFiles();
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ file.delete();
+ }
+ }
+ }
+ }
+
+ @Override
public File getBufferDir() {
return mBufferDir;
}
@@ -131,17 +132,6 @@ public class DvrStorageManager implements BufferManager.StorageManager {
}
}
- 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) {
@@ -165,104 +155,39 @@ public class DvrStorageManager implements BufferManager.StorageManager {
}
@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;
+ public Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException {
+ File file = new File(getBufferDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX);
+ 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);
}
- 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;
+ readFormatLong(in, format, MediaFormat.KEY_DURATION);
+ return new Pair<>(name, format);
}
}
- private ArrayList<BufferManager.PositionHolder> readNewIndexFile(File indexFile)
- throws IOException {
- ArrayList<BufferManager.PositionHolder> indices = new ArrayList<>();
- try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) {
+ @Override
+ public ArrayList<Long> readIndexFile(String trackId) throws IOException {
+ ArrayList<Long> indices = new ArrayList<>();
+ File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX);
+ try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
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));
+ indices.add(in.readLong());
}
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)) {
@@ -329,63 +254,33 @@ public class DvrStorageManager implements BufferManager.StorageManager {
}
@Override
- public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio)
+ public void writeTrackInfoFile(String trackId, MediaFormat format, 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);
+ File file = new File(getBufferDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX);
+ try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
+ writeString(out, trackId);
+ writeFormatString(out, format, MediaFormat.KEY_MIME);
+ writeFormatInt(out, format, MediaFormat.KEY_MAX_INPUT_SIZE);
+ writeFormatInt(out, format, MediaFormat.KEY_WIDTH);
+ writeFormatInt(out, format, MediaFormat.KEY_HEIGHT);
+ writeFormatInt(out, format, MediaFormat.KEY_CHANNEL_COUNT);
+ writeFormatInt(out, format, MediaFormat.KEY_SAMPLE_RATE);
+ writeFormatFloat(out, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
+ for (int i = 0; i < 3; ++i) {
+ writeFormatByteBuffer(out, format, "csd-" + i);
}
+ writeFormatLong(out, format, MediaFormat.KEY_DURATION);
}
}
@Override
- public void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
+ public void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index)
throws IOException {
- File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2);
+ File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX);
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);
+ for (Long key : index.keySet()) {
+ out.writeLong(key);
}
}
}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
index af0c3f0d..4869b49f 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
@@ -66,14 +66,9 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
public static final int BUFFER_REASON_RECORDING = 2;
/**
- * The minimum duration to support seek in Trickplay.
+ * The duration of a chunk of samples, {@link SampleChunk}.
*/
- 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
+ static final long CHUNK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500);
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);
@@ -84,6 +79,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
private int mTrackCount;
private boolean[] mTrackSelected;
+ private List<String> mIds;
private List<SampleQueue> mReadSampleQueues;
private final SamplePool mSamplePool = new SamplePool();
private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US;
@@ -134,6 +130,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
if (mTrackCount <= 0) {
throw new IOException("No tracks to initialize");
}
+ mIds = ids;
mTrackSelected = new boolean[mTrackCount];
mReadSampleQueues = new ArrayList<>();
mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason,
@@ -142,9 +139,6 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
mReadSampleQueues.add(i, new SampleQueue(mSamplePool));
}
mSampleChunkIoHelper.init();
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.registerChunkEvictedListener(ids.get(i), RecordingSampleBuffer.this);
- }
}
@Override
@@ -152,6 +146,8 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
if (!mTrackSelected[index]) {
mTrackSelected[index] = true;
mReadSampleQueues.get(index).clear();
+ mBufferManager.registerChunkEvictedListener(mIds.get(index),
+ RecordingSampleBuffer.this);
mSampleChunkIoHelper.openRead(index, mCurrentPlaybackPositionUs);
}
}
@@ -161,7 +157,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
if (mTrackSelected[index]) {
mTrackSelected[index] = false;
mReadSampleQueues.get(index).clear();
- mSampleChunkIoHelper.closeRead(index);
+ mBufferManager.unregisterChunkEvictedListener(mIds.get(index));
}
}
@@ -197,6 +193,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
}
// Disables buffering samples afterwards, and notifies the disk speed is slow.
Log.w(TAG, "Disk is too slow for trickplay");
+ mBufferManager.disable();
mBufferListener.onDiskTooSlow();
}
@@ -208,7 +205,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
private boolean maybeReadSample(SampleQueue queue, int index) {
if (queue.getLastQueuedPositionUs() != null
&& queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US
- && queue.isDurationGreaterThan(MIN_SEEK_DURATION_US)) {
+ && queue.isDurationGreaterThan(CHUNK_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.
@@ -303,7 +300,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
public void onChunkEvicted(String id, long createdTimeMs) {
if (mBufferListener != null) {
mBufferListener.onBufferStartTimeChanged(
- createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US));
+ createdTimeMs + TimeUnit.MICROSECONDS.toMillis(CHUNK_DURATION_US));
}
}
}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
index ab6d1a75..552caaef 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
@@ -151,23 +151,18 @@ public class SampleChunk {
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 {
+ void openRead(SampleChunk chunk) throws IOException {
if (mChunk != null) {
mChunk.closeRead();
}
chunk.openRead();
- reset(chunk, offset);
+ reset(chunk);
}
/**
@@ -246,20 +241,6 @@ public class SampleChunk {
}
/**
- * 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
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
index ca97a91a..37ae4022 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
@@ -21,7 +21,6 @@ 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;
@@ -32,9 +31,7 @@ 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;
/**
@@ -49,13 +46,11 @@ public class SampleChunkIoHelper implements Handler.Callback {
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 static final int MSG_CLOSE_WRITE = 3;
+ private static final int MSG_READ = 4;
+ private static final int MSG_WRITE = 5;
+ private static final int MSG_RELEASE = 6;
- private final long mSampleChunkDurationUs;
private final int mTrackCount;
private final List<String> mIds;
private final List<MediaFormat> mMediaFormats;
@@ -67,11 +62,9 @@ public class SampleChunkIoHelper implements Handler.Callback {
private Handler mIoHandler;
private final ConcurrentLinkedQueue<SampleHolder> mReadSampleBuffers[];
private final ConcurrentLinkedQueue<SampleHolder> mHandlerReadSampleBuffers[];
- private final long[] mWriteIndexEndPositionUs;
- private final long[] mWriteChunkEndPositionUs;
+ private final long[] mWriteEndPositionUs;
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;
@@ -136,20 +129,11 @@ public class SampleChunkIoHelper implements Handler.Callback {
mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
- mWriteIndexEndPositionUs = new long[mTrackCount];
- mWriteChunkEndPositionUs = new long[mTrackCount];
+ mWriteEndPositionUs = 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;
+ mWriteEndPositionUs[i] = RecordingSampleBuffer.CHUNK_DURATION_US;
mReadIoStates[i] = new SampleChunk.IoState();
mWriteIoStates[i] = new SampleChunk.IoState();
}
@@ -220,15 +204,6 @@ public class SampleChunkIoHelper implements Handler.Callback {
}
/**
- * 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() {
@@ -254,19 +229,21 @@ public class SampleChunkIoHelper implements Handler.Callback {
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<>();
+ Pair<String, android.media.MediaFormat> audio = null, video = null;
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));
+ if (audio == null && MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) {
+ audio = new Pair<>(mIds.get(i), format);
+ } else if (video == null && MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) {
+ video = new Pair<>(mIds.get(i), format);
+ }
+ if (audio != null && video != null) {
+ break;
}
}
- mBufferManager.writeMetaFiles(audios, videos);
+ mBufferManager.writeMetaFiles(audio, video);
}
} finally {
mBufferManager.release();
@@ -288,9 +265,6 @@ public class SampleChunkIoHelper implements Handler.Callback {
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;
@@ -317,16 +291,14 @@ public class SampleChunkIoHelper implements Handler.Callback {
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) {
+ SampleChunk chunk = mBufferManager.getReadFile(mIds.get(index), params.positionUs);
+ if (chunk == null) {
String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs
+ "is not found";
- SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage);
+ SoftPreconditions.checkNotNull(chunk, TAG, errorMessage);
throw new IOException(errorMessage);
}
- mSelectedTracks.add(index);
- mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second);
+ mReadIoStates[index].openRead(chunk);
if (mHandlerReadSampleBuffers[index] != null) {
SampleHolder sample;
while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) {
@@ -338,22 +310,10 @@ public class SampleChunkIoHelper implements Handler.Callback {
}
private void doOpenWrite(int index) throws IOException {
- SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0,
- mSamplePool, null, 0);
+ SampleChunk chunk = mBufferManager.createNewWriteFile(mIds.get(index), 0, mSamplePool);
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) {
@@ -397,21 +357,13 @@ public class SampleChunkIoHelper implements Handler.Callback {
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;
- }
+
+ if (sample.timeUs >= mWriteEndPositionUs[index]) {
+ nextChunk = mBufferManager.createNewWriteFile(mIds.get(index),
+ mWriteEndPositionUs[index], mSamplePool);
+ mWriteEndPositionUs[index] =
+ ((sample.timeUs / RecordingSampleBuffer.CHUNK_DURATION_US) + 1) *
+ RecordingSampleBuffer.CHUNK_DURATION_US;
}
}
mWriteIoStates[params.index].write(params.sample, nextChunk);
@@ -439,22 +391,15 @@ public class SampleChunkIoHelper implements Handler.Callback {
mIoHandler.removeCallbacksAndMessages(null);
mFinished = true;
conditionVariable.open();
- mSelectedTracks.clear();
}
private void releaseEvictedChunks() {
- if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK
- || mSelectedTracks.isEmpty()) {
+ if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK) {
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);
+ mReadIoStates[i].getStartPositionUs());
mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs);
}
}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
index 75eac5a2..7b098f40 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
@@ -43,7 +43,6 @@ public class SampleQueue {
if (sampleFromQueue == null) {
return SampleSource.NOTHING_READ;
}
- sample.ensureSpaceForWrite(sampleFromQueue.size);
sample.size = sampleFromQueue.size;
sample.flags = sampleFromQueue.flags;
sample.timeUs = sampleFromQueue.timeUs;
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
index 0b219b41..40c4ef95 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
@@ -19,7 +19,6 @@ 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;
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
index 9fe921b8..258a5cd0 100644
--- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
+++ b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
@@ -17,23 +17,20 @@
package com.android.tv.tuner.exoplayer.buffer;
import android.content.Context;
+import android.media.MediaFormat;
import android.os.AsyncTask;
+import android.os.Looper;
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)
@@ -46,68 +43,53 @@ public class TrickplayStorageManager implements BufferManager.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 File mBufferDir;
private final long mMaxBufferSize;
+ private final long mStorageBufferBytes;
- 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;
+ private static long getStorageBufferBytes(Context context, File path) {
long lowPercentage = Settings.Global.getInt(context.getContentResolver(),
SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
- long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100;
+ long lowBytes = 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);
+ return Math.min(lowBytes, maxLowBytes);
}
- public TrickplayStorageManager(Context context, @NonNull File baseDir, long maxBufferSize) {
- initParamsIfNeeded(context, new File(baseDir, BUFFER_DIR));
- sBufferDir.mkdirs();
+ public TrickplayStorageManager(Context context, File baseDir, long maxBufferSize) {
+ mBufferDir = new File(baseDir, BUFFER_DIR);
+ mBufferDir.mkdirs();
mMaxBufferSize = maxBufferSize;
clearStorage();
+ mStorageBufferBytes = getStorageBufferBytes(context, mBufferDir);
}
- private void clearStorage() {
- long now = System.currentTimeMillis();
- if (sLastCacheCleanUpTask != null) {
- sLastCacheCleanUpTask.cancel(true);
+ @Override
+ public void clearStorage() {
+ File files[] = mBufferDir.listFiles();
+ if (files == null || files.length == 0) {
+ return;
}
- 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) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ for (File file : files) {
file.delete();
}
+ return null;
}
- return null;
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ for (File file : files) {
+ file.delete();
}
- };
- sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
}
@Override
public File getBufferDir() {
- return sBufferDir;
+ return mBufferDir;
}
@Override
@@ -122,26 +104,25 @@ public class TrickplayStorageManager implements BufferManager.StorageManager {
@Override
public boolean hasEnoughBuffer(long pendingDelete) {
- return sBufferDir.getUsableSpace() + pendingDelete >= sStorageBufferBytes;
+ return mBufferDir.getUsableSpace() + pendingDelete >= mStorageBufferBytes;
}
@Override
- public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) {
+ public Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) {
return null;
}
@Override
- public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId) {
+ public ArrayList<Long> readIndexFile(String trackId) {
return null;
}
@Override
- public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio) {
+ public void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) {
}
@Override
- public void writeIndexFile(String trackName,
- SortedMap<Long, Pair<SampleChunk, Integer>> index) {
+ public void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index) {
}
}
diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
index 53678a85..97d9ece3 100644
--- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
+++ b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
@@ -36,24 +36,6 @@ public class ConnectionTypeFragment extends SetupMultiPaneFragment {
"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();
}
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 a4dd494c..00000000
--- a/src/com/android/tv/tuner/setup/PostalCodeFragment.java
+++ /dev/null
@@ -1,184 +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.Spanned;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.TextView;
-import android.widget.Toast;
-
-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 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();
- if (postalCode != null && postalCode.length() == 5) {
- 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) {
- mEditedActionView = getView().findViewById(R.id.guidedactions_editable);
- ((TextView) mEditedActionView.findViewById(R.id.guidedactions_item_title))
- .setFilters(new InputFilter[]{new InputFilter() {
- @Override
- public CharSequence filter(CharSequence source, int start,
- int end, Spanned dest, int dstart, int dend) {
- try {
- Integer.parseInt(source.toString());
- return null;
- } catch (NumberFormatException e) {
- return "";
- }
- }
- }, new InputFilter.LengthFilter(5)});
- }
- 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
index 75b28e32..3b61debb 100644
--- a/src/com/android/tv/tuner/setup/ScanFragment.java
+++ b/src/com/android/tv/tuner/setup/ScanFragment.java
@@ -21,7 +21,6 @@ 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;
@@ -36,13 +35,14 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.tv.common.AutoCloseableUtils;
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.R;
import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.Channel;
+import com.android.tv.tuner.data.nano.Channel;
import com.android.tv.tuner.data.PsipData;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.source.FileTsStreamer;
@@ -51,6 +51,7 @@ 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 com.android.tv.tuner.util.TunerInputInfoUtils;
import junit.framework.Assert;
@@ -66,7 +67,6 @@ import java.util.concurrent.TimeUnit;
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;
@@ -98,7 +98,6 @@ public class ScanFragment extends SetupFragment {
@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());
@@ -121,19 +120,13 @@ public class ScanFragment extends SetupFragment {
}
});
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);
+ if (TunerInputInfoUtils.isBuiltInTuner(getActivity())){
+ scanTitleView.setText(R.string.bt_channel_scan);
+ } else {
+ scanTitleView.setText(R.string.ut_channel_scan);
}
return view;
}
@@ -154,14 +147,12 @@ public class ScanFragment extends SetupFragment {
}
@Override
- public void onPause() {
- Log.d(TAG, "onPause");
+ public void onDetach() {
if (mChannelScanTask != null) {
// Ensure scan task will stop.
- Log.w(TAG, "The activity went to the background. Stopping channel scan.");
mChannelScanTask.stopScan();
}
- super.onPause();
+ super.onDetach();
}
/**
@@ -177,9 +168,7 @@ public class ScanFragment extends SetupFragment {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
- if (mChannelScanTask != null) {
- mChannelScanTask.showFinishingProgressDialog();
- }
+ mChannelScanTask.showFinishingProgressDialog();
}
}, SHOW_PROGRESS_DIALOG_DELAY_MS);
@@ -266,7 +255,7 @@ public class ScanFragment extends SetupFragment {
if (FAKE_MODE) {
mScanTsStreamer = new FakeTsStreamer(this);
} else {
- TunerHal hal = ((TunerSetupActivity) mActivity).getTunerHal();
+ TunerHal hal = TunerHal.createInstance(mActivity.getApplicationContext());
if (hal == null) {
throw new RuntimeException("Failed to open a DVB device");
}
@@ -327,17 +316,10 @@ public class ScanFragment extends SetupFragment {
@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]);
- }
+ mProgressBar.setProgress(values[0]);
}
private void stopScan() {
- if (mLatch != null) {
- mLatch.countDown();
- }
mConditionStopped.open();
}
@@ -378,7 +360,11 @@ public class ScanFragment extends SetupFragment {
if (mConditionStopped.block(-1)) {
break;
}
- publishProgress(MAX_PROGRESS * i++ / mScanChannelList.size());
+ onProgressUpdate(MAX_PROGRESS * i++ / mScanChannelList.size());
+ }
+ if (mScanTsStreamer instanceof TunerTsStreamer) {
+ AutoCloseableUtils.closeQuietly(
+ ((TunerTsStreamer) mScanTsStreamer).getTunerHal());
}
mChannelDataManager.notifyScanCompleted();
if (!mConditionStopped.block(-1)) {
@@ -468,13 +454,7 @@ public class ScanFragment extends SetupFragment {
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();
- }
+ onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH);
mChannelScanTask = null;
}
}
diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java
index 3b8cd823..068543cd 100644
--- a/src/com/android/tv/tuner/setup/ScanResultFragment.java
+++ b/src/com/android/tv/tuner/setup/ScanResultFragment.java
@@ -26,7 +26,6 @@ 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;
@@ -77,19 +76,11 @@ public class ScanResultFragment extends SetupMultiPaneFragment {
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);
+ if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) {
+ description = getString(R.string.bt_result_not_found_description);
+ } else {
+ description = getString(R.string.ut_result_not_found_description);
}
breadcrumb = getString(R.string.ut_setup_breadcrumb);
}
diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java
index f618c699..78121bc5 100644
--- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java
+++ b/src/com/android/tv/tuner/setup/TunerSetupActivity.java
@@ -29,53 +29,35 @@ 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.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
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.TvApplication;
-import com.android.tv.common.AutoCloseableUtils;
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 com.android.tv.util.LocationUtils;
-
-import java.util.Locale;
-import java.util.concurrent.Executor;
+import com.android.tv.tuner.util.TunerInputInfoUtils;
/**
* 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";
-
+ private final String TAG = "TunerSetupActivity";
// For the recommendation card
private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity";
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,
@@ -87,13 +69,9 @@ public class TunerSetupActivity extends SetupActivity {
R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256};
private ScanFragment mLastScanFragment;
- private Integer mTunerType;
- private TunerHalFactory mTunerHalFactory;
- private boolean mNeedToShowPostalCodeFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate");
TvApplication.setCurrentRunningProcess(this, false);
super.onCreate(savedInstanceState);
// TODO: check {@link shouldShowRequestPermissionRationale}.
@@ -101,49 +79,13 @@ public class TunerSetupActivity extends SetupActivity {
!= PackageManager.PERMISSION_GRANTED) {
// No need to check the request result.
requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
- PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
- }
- mTunerType = TunerHal.getTunerTypeAndCount(this).first;
- if (mTunerType == null) {
- finish();
- } else {
- mTunerHalFactory = new TunerHalFactory(getApplicationContext());
- }
- try {
- // Updating postal code takes time, therefore we called it here for "warm-up".
- 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
- }
- }
+ 0);
}
}
@Override
protected Fragment onCreateInitialFragment() {
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;
@@ -160,41 +102,33 @@ public class TunerSetupActivity extends SetupActivity {
finish();
break;
default: {
- if (mNeedToShowPostalCodeFragment
- || Locale.US.getCountry().equalsIgnoreCase(
- LocationUtils.getCurrentCountry(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();
- }
+ SetupFragment fragment = new ConnectionTypeFragment();
+ fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
+ | SetupFragment.FRAGMENT_RETURN_TRANSITION);
+ showFragment(fragment, true);
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.get() == null) {
+ TunerHal hal = TunerHal.createInstance(getApplicationContext());
+ if (hal == null) {
finish();
Toast.makeText(getApplicationContext(),
R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show();
return true;
}
+ try {
+ hal.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Tuner hal close failed", e);
+ return true;
+ }
mLastScanFragment = new ScanFragment();
- Bundle args1 = new Bundle();
- args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
+ Bundle args = new Bundle();
+ args.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
CHANNEL_MAP_SCAN_FILE[actionId]);
- args1.putInt(KEY_TUNER_TYPE, mTunerType);
- mLastScanFragment.setArguments(args1);
+ mLastScanFragment.setArguments(args);
showFragment(mLastScanFragment, true);
return true;
case ScanFragment.ACTION_CATEGORY:
@@ -203,11 +137,7 @@ public class TunerSetupActivity extends SetupActivity {
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);
@@ -283,7 +213,7 @@ public class TunerSetupActivity extends SetupActivity {
String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(),
TunerTvInputService.class.getName()));
- // Make an intent to launch the setup activity of TV tuner input.
+ // Make an intent to launch the setup activity of USB tuner TV input.
Intent intent = TvCommonUtils.createSetupIntent(
new Intent(context, TunerSetupActivity.class), inputId);
intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId);
@@ -294,27 +224,6 @@ public class TunerSetupActivity extends SetupActivity {
}
/**
- * Gets the currently used tuner HAL.
- */
- TunerHal getTunerHal() {
- return mTunerHalFactory.get();
- }
-
- /**
- * 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
@@ -333,19 +242,12 @@ public class TunerSetupActivity extends SetupActivity {
Resources resources = context.getResources();
String focusedTitle = resources.getString(
R.string.ut_setup_recommendation_card_focused_title);
- int titleStringId = 0;
- switch (TunerHal.getTunerTypeAndCount(context).first) {
- case TunerHal.TUNER_TYPE_BUILT_IN:
- titleStringId = R.string.bt_setup_recommendation_card_title;
- break;
- case TunerHal.TUNER_TYPE_USB:
- titleStringId = R.string.ut_setup_recommendation_card_title;
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- titleStringId = R.string.nt_setup_recommendation_card_title;
- break;
+ String title;
+ if (TunerInputInfoUtils.isBuiltInTuner(context)) {
+ title = resources.getString(R.string.bt_setup_recommendation_card_title);
+ } else {
+ title = resources.getString(R.string.ut_setup_recommendation_card_title);
}
- String title = resources.getString(titleStringId);
Bitmap largeIcon = BitmapFactory.decodeResource(resources,
R.drawable.recommendation_antenna);
@@ -367,20 +269,6 @@ public class TunerSetupActivity extends SetupActivity {
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 recommendation card.
*
@@ -391,80 +279,4 @@ public class TunerSetupActivity extends SetupActivity {
.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.
- */
- TunerHal get() {
- 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.
- */
- void generate() {
- if (mGenerateTunerHalTask == null && mTunerHal == null) {
- mGenerateTunerHalTask = new GenerateTunerHalTask();
- mGenerateTunerHalTask.executeOnExecutor(mExecutor);
- }
- }
-
- /**
- * Clears the currently used tuner hal.
- */
- void clear() {
- if (mGenerateTunerHalTask != null) {
- mGenerateTunerHalTask.cancel(true);
- mGenerateTunerHalTask = null;
- }
- if (mTunerHal != null) {
- AutoCloseableUtils.closeQuietly(mTunerHal);
- mTunerHal = null;
- }
- }
-
- 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
index a3dddc72..7e809411 100644
--- a/src/com/android/tv/tuner/setup/WelcomeFragment.java
+++ b/src/com/android/tv/tuner/setup/WelcomeFragment.java
@@ -27,7 +27,6 @@ import android.view.ViewGroup;
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;
@@ -42,9 +41,7 @@ public class WelcomeFragment extends SetupMultiPaneFragment {
@Override
protected SetupGuidedStepFragment onCreateContentFragment() {
- ContentFragment fragment = new ContentFragment();
- fragment.setArguments(getArguments());
- return fragment;
+ return new ContentFragment();
}
@Override
@@ -73,33 +70,20 @@ public class WelcomeFragment extends SetupMultiPaneFragment {
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);
+ if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) {
+ title = getString(R.string.bt_setup_new_title);
+ description = getString(R.string.bt_setup_new_description);
+ } else {
+ title = getString(R.string.ut_setup_new_title);
+ description = getString(R.string.ut_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);
+ if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) {
+ description = getString(R.string.bt_setup_again_description);
+ } else {
+ description = getString(R.string.ut_setup_again_description);
}
}
return new Guidance(title, description, null, null);
diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java
index 80ec8a56..14997ee4 100644
--- a/src/com/android/tv/tuner/source/FileTsStreamer.java
+++ b/src/com/android/tv/tuner/source/FileTsStreamer.java
@@ -256,7 +256,7 @@ public class FileTsStreamer implements TsStreamer {
* Returns whether the current pid filter is empty or not.
*/
public boolean isFilterEmpty() {
- return mPids.size() == 0;
+ return mPids.size() > 0;
}
/**
diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java
index 32504b95..ccbb75ba 100644
--- a/src/com/android/tv/tuner/source/TsDataSourceManager.java
+++ b/src/com/android/tv/tuner/source/TsDataSourceManager.java
@@ -17,10 +17,8 @@
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.Channel;
+import com.android.tv.tuner.data.nano.Channel;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.tvinput.EventDetector;
@@ -129,14 +127,6 @@ public class TsDataSourceManager {
}
/**
- * Add tuner hal into TunerTsStreamerManager for test.
- */
- @VisibleForTesting
- public void addTunerHalForTest(TunerHal tunerHal) {
- mTunerStreamerManager.addTunerHal(tunerHal, mId);
- }
-
- /**
* Releases persistent resources.
*/
public void release() {
diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java
index 65b11a5a..b24048e6 100644
--- a/src/com/android/tv/tuner/source/TunerTsStreamer.java
+++ b/src/com/android/tv/tuner/source/TunerTsStreamer.java
@@ -42,17 +42,15 @@ public class TunerTsStreamer implements TsStreamer {
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 mEndOfStreamSent;
private boolean mStreaming;
private final TunerHal mTunerHal;
@@ -61,7 +59,6 @@ public class TunerTsStreamer implements TsStreamer {
private final EventDetector mEventDetector;
private final TsStreamWriter mTsStreamWriter;
- private String mChannelNumber;
public static class TunerDataSource extends TsDataSource {
private final TunerTsStreamer mTsStreamer;
@@ -106,15 +103,6 @@ public class TunerTsStreamer implements TsStreamer {
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;
}
@@ -126,10 +114,7 @@ public class TunerTsStreamer implements TsStreamer {
*/
public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) {
mTunerHal = tunerHal;
- mEventDetector = new EventDetector(mTunerHal);
- if (eventListener != null) {
- mEventDetector.registerListener(eventListener);
- }
+ mEventDetector = new EventDetector(mTunerHal, eventListener);
mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ?
new TsStreamWriter(context) : null;
}
@@ -140,8 +125,7 @@ public class TunerTsStreamer implements TsStreamer {
@Override
public boolean startStream(TunerChannel channel) {
- if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(),
- channel.getDisplayNumber(false))) {
+ if (mTunerHal.tune(channel.getFrequency(), channel.getModulation())) {
if (channel.hasVideo()) {
mTunerHal.addPidFilter(channel.getVideoPid(),
TunerHal.FILTER_TYPE_VIDEO);
@@ -164,7 +148,6 @@ public class TunerTsStreamer implements TsStreamer {
channel.getProgramNumber());
}
mChannel = channel;
- mChannelNumber = channel.getDisplayNumber();
synchronized (mCircularBufferMonitor) {
if (mStreaming) {
Log.w(TAG, "Streaming should be stopped before start streaming");
@@ -173,6 +156,7 @@ public class TunerTsStreamer implements TsStreamer {
mStreaming = true;
mBytesFetched = 0;
mLastReadPosition.set(0L);
+ mEndOfStreamSent = false;
}
if (mTsStreamWriter != null) {
mTsStreamWriter.setChannel(mChannel);
@@ -188,7 +172,7 @@ public class TunerTsStreamer implements TsStreamer {
@Override
public boolean startStream(ChannelScanFileParser.ScanChannel channel) {
- if (mTunerHal.tune(channel.frequency, channel.modulation, null)) {
+ if (mTunerHal.tune(channel.frequency, channel.modulation)) {
mEventDetector.startDetecting(
channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS);
synchronized (mCircularBufferMonitor) {
@@ -199,6 +183,7 @@ public class TunerTsStreamer implements TsStreamer {
mStreaming = true;
mBytesFetched = 0;
mLastReadPosition.set(0L);
+ mEndOfStreamSent = false;
}
mStreamingThread = new StreamingThread();
mStreamingThread.start();
@@ -273,22 +258,6 @@ public class TunerTsStreamer implements TsStreamer {
}
}
- public String getStreamerInfo() {
- return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming;
- }
-
- public void registerListener(EventListener listener) {
- if (mEventDetector != null && listener != null) {
- mEventDetector.registerListener(listener);
- }
- }
-
- public void unregisterListener(EventListener listener) {
- if (mEventDetector != null) {
- mEventDetector.unregisterListener(listener);
- }
- }
-
private class StreamingThread extends Thread {
@Override
public void run() {
@@ -352,14 +321,21 @@ public class TunerTsStreamer implements TsStreamer {
* @throws IOException
*/
public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException {
+ long readStartTime = System.currentTimeMillis();
while (true) {
synchronized (mCircularBufferMonitor) {
- if (!mStreaming) {
- return READ_ERROR_STREAMING_ENDED;
+ if (mEndOfStreamSent || !mStreaming) {
+ return -1;
}
if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) {
- Log.w(TAG, "Demux is requesting the data which is already overwritten.");
- return READ_ERROR_BUFFER_OVERWRITTEN;
+ Log.e(TAG, "Demux is requesting the data which is already overwritten.");
+ return -1;
+ }
+ if (System.currentTimeMillis() - readStartTime > READ_TIMEOUT_MS) {
+ // Nothing was received during READ_TIMEOUT_MS before.
+ mEndOfStreamSent = true;
+ mCircularBufferMonitor.notifyAll();
+ return -1;
}
if (mBytesFetched < pos + amount) {
try {
diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
index fcd14116..cf1f6dcf 100644
--- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
+++ b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
@@ -42,7 +42,6 @@ class TunerTsStreamerManager {
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;
@@ -69,8 +68,6 @@ class TunerTsStreamerManager {
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;
}
@@ -86,8 +83,6 @@ class TunerTsStreamerManager {
if (!creator.isCancelledLocked()) {
mStreamerFinder.putLocked(channel, sessionId, streamer);
TsDataSource source = streamer.createDataSource();
- mListeners.put(sessionId, listener);
- streamer.registerListener(listener);
mSourceToStreamerMap.put(source, streamer);
return source;
}
@@ -109,8 +104,6 @@ class TunerTsStreamerManager {
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);
@@ -132,13 +125,6 @@ class TunerTsStreamerManager {
}
}
- /**
- * 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);
}
@@ -275,16 +261,16 @@ class TunerTsStreamerManager {
}
private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) {
- if (!reuse || !hal.isReusable()) {
+ if (!reuse) {
AutoCloseableUtils.closeQuietly(hal);
return;
}
TunerHal cachedHal = mTunerHals.get(sessionId);
if (cachedHal != hal) {
mTunerHals.put(sessionId, hal);
- if (cachedHal != null) {
- AutoCloseableUtils.closeQuietly(cachedHal);
- }
+ }
+ if (cachedHal != null && cachedHal != hal) {
+ AutoCloseableUtils.closeQuietly(cachedHal);
}
}
@@ -297,9 +283,5 @@ class TunerTsStreamerManager {
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
index fe972cd1..8c1f6a1b 100644
--- a/src/com/android/tv/tuner/ts/SectionParser.java
+++ b/src/com/android/tv/tuner/ts/SectionParser.java
@@ -22,7 +22,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import com.android.tv.tuner.data.Channel;
+import com.android.tv.tuner.data.nano.Channel;
import com.android.tv.tuner.data.PsiData.PatItem;
import com.android.tv.tuner.data.PsiData.PmtItem;
import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor;
@@ -39,8 +39,8 @@ import com.android.tv.tuner.data.PsipData.RatingRegion;
import com.android.tv.tuner.data.PsipData.RegionalRating;
import com.android.tv.tuner.data.PsipData.TsDescriptor;
import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+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.ibm.icu.text.UnicodeDecompressor;
@@ -367,10 +367,6 @@ public class SectionParser {
mParsedEttItems.clear();
}
- public void resetVersionNumbers() {
- mSectionVersionMap.clear();
- }
-
private void parseSection(byte[] data) {
if (!checkSanity(data)) {
Log.d(TAG, "Bad CRC!");
@@ -514,8 +510,10 @@ public class SectionParser {
pos += 11 + descriptorsLength;
results.add(new MgtItem(tableType, tableTypePid));
}
- // Skip the remaining descriptor part which we don't use.
-
+ if ((data[pos] & 0xf0) != 0xf0) {
+ Log.e(TAG, "Broken MGT.");
+ return false;
+ }
if (mListener != null) {
mListener.onMgtParsed(results);
}
@@ -719,9 +717,6 @@ public class SectionParser {
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();
@@ -953,7 +948,6 @@ public class SectionParser {
pos += 3;
boolean ccType = (data[pos] & 0x80) != 0;
if (!ccType) {
- pos +=3;
continue;
}
int captionServiceNumber = data[pos] & 0x3f;
diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java
index 21b5a942..c24c2a21 100644
--- a/src/com/android/tv/tuner/ts/TsParser.java
+++ b/src/com/android/tv/tuner/ts/TsParser.java
@@ -102,7 +102,6 @@ public class TsParser {
}
protected abstract void handleData(byte[] data, boolean startIndicator);
- protected abstract void resetDataVersions();
}
private class SectionStream extends Stream {
@@ -139,11 +138,6 @@ public class TsParser {
mSectionParser.parseSections(mPacket);
}
- @Override
- protected void resetDataVersions() {
- mSectionParser.resetVersionNumbers();
- }
-
private final OutputListener mSectionListener = new OutputListener() {
@Override
public void onPatParsed(List<PatItem> items) {
@@ -457,16 +451,4 @@ public class TsParser {
}
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
index 885cef9f..a16bc522 100644
--- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java
+++ b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java
@@ -30,7 +30,6 @@ import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
import android.support.annotation.Nullable;
-import android.support.v4.os.BuildCompat;
import android.text.format.DateUtils;
import android.util.Log;
@@ -38,7 +37,6 @@ 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;
@@ -194,14 +192,11 @@ public class ChannelDataManager implements Handler.Callback {
public void release() {
mHandler.removeCallbacksAndMessages(null);
- releaseSafely();
+ mHandlerThread.quitSafely();
}
public void releaseSafely() {
mHandlerThread.quitSafely();
- mListener = null;
- mChannelScanListener = null;
- mChannelScanHandler = null;
}
public TunerChannel getChannel(long channelId) {
@@ -440,7 +435,7 @@ public class ChannelDataManager implements Handler.Callback {
}
}
ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert(
- TvContract.Programs.CONTENT_URI), newItem, channel));
+ TvContract.Programs.CONTENT_URI), newItem, channel.getChannelId()));
if (ops.size() >= BATCH_OPERATION_COUNT) {
applyBatch(channel.getName(), ops);
ops.clear();
@@ -510,7 +505,7 @@ public class ChannelDataManager implements Handler.Callback {
continue;
}
ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert(
- TvContract.Programs.CONTENT_URI), item, channel));
+ TvContract.Programs.CONTENT_URI), item, channel.getChannelId()));
if (ops.size() >= BATCH_OPERATION_COUNT) {
applyBatch(channel.getName(), ops);
ops.clear();
@@ -521,13 +516,9 @@ public class ChannelDataManager implements Handler.Callback {
}
private ContentProviderOperation buildContentProviderOperation(
- ContentProviderOperation.Builder builder, EitItem item, TunerChannel channel) {
- if (channel != null) {
- builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId());
- if (BuildCompat.isAtLeastN()) {
- builder.withValue(TvContract.Programs.COLUMN_RECORDING_PROHIBITED,
- channel.isRecordingProhibited() ? 1 : 0);
- }
+ ContentProviderOperation.Builder builder, EitItem item, Long channelId) {
+ if (channelId != null) {
+ builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
}
if (item != null) {
builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText())
@@ -565,10 +556,7 @@ public class ChannelDataManager implements Handler.Callback {
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);
@@ -610,29 +598,13 @@ public class ChannelDataManager implements Handler.Callback {
}
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;
- }
- }
- }
+ 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();
}
}
}
diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java
index 96b20a4b..a132398f 100644
--- a/src/com/android/tv/tuner/tvinput/EventDetector.java
+++ b/src/com/android/tv/tuner/tvinput/EventDetector.java
@@ -21,8 +21,8 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.ts.TsParser;
import com.android.tv.tuner.data.PsiData;
@@ -51,7 +51,7 @@ public class EventDetector {
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 final EventListener mEventListener;
private int mFrequency;
private String mModulation;
private int mProgramNumber = ALL_PROGRAM_NUMBERS;
@@ -105,10 +105,8 @@ public class EventDetector {
item.setHasCaptionTrack();
}
}
- if (tunerChannel != null && !mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onEventDetected(tunerChannel, items);
- }
+ if (tunerChannel != null && mEventListener != null) {
+ mEventListener.onEventDetected(tunerChannel, items);
}
}
@@ -119,10 +117,8 @@ public class EventDetector {
@Override
public void onAllVctItemsParsed() {
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelScanDone();
- }
+ if (mEventListener != null) {
+ mEventListener.onChannelScanDone();
}
}
@@ -165,10 +161,8 @@ public class EventDetector {
if (!found) {
mVctProgramNumberSet.add(channelProgramNumber);
}
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelDetected(tunerChannel, !found);
- }
+ if (mEventListener != null) {
+ mEventListener.onChannelDetected(tunerChannel, !found);
}
}
};
@@ -203,9 +197,11 @@ public class EventDetector {
/**
* Creates a detector for ATSC TV channles and program information.
* @param usbTunerInteface {@link TunerHal}
+ * @param listener for ATSC TV channels and program information
*/
- public EventDetector(TunerHal usbTunerInteface) {
+ public EventDetector(TunerHal usbTunerInteface, EventListener listener) {
mTunerHal = usbTunerInteface;
+ mEventListener = listener;
}
private void reset() {
@@ -262,28 +258,4 @@ public class EventDetector {
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
index 46ff4ea1..61de24f4 100644
--- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java
+++ b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java
@@ -24,8 +24,8 @@ 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.VctItem;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
+import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.source.FileTsStreamer;
import com.android.tv.tuner.ts.TsParser;
diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
index 0be29f25..6ec55e4f 100644
--- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
+++ b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
@@ -33,17 +33,13 @@ 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.dvr.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.Track.AtscCaptionTrack;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
import com.android.tv.tuner.exoplayer.SampleExtractor;
@@ -57,10 +53,10 @@ 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.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
@@ -75,7 +71,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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;
@@ -85,23 +80,20 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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})
+ @IntDef({STATE_IDLE, 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 int STATE_TUNED = 2;
+ private static final int STATE_RECORDING = 3;
private static final long CHANNEL_ID_NONE = -1;
- private static final int MAX_TUNING_RETRY = 6;
private final Context mContext;
private final ChannelDataManager mChannelDataManager;
@@ -116,16 +108,13 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
private long mRecordStartTime;
private long mRecordEndTime;
private boolean mRecorderRunning;
+ private BufferManager mBufferManager;
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());
@@ -168,7 +157,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
if (mChannel == null || mChannel.compareTo(channel) != 0) {
return;
}
- mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget();
mChannelDataManager.notifyEventDetected(channel, items);
}
@@ -190,7 +178,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
@MainThread
public void tune(Uri channelUri) {
mHandler.removeCallbacksAndMessages(null);
- mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget();
+ mHandler.obtainMessage(MSG_TUNE, channelUri).sendToTarget();
}
/**
@@ -223,22 +211,11 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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();
- }
- }
+ mSession.onTuned(channelUri);
+ } else {
+ reset();
}
return true;
}
@@ -304,12 +281,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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;
}
@@ -339,17 +310,20 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
mRecorder.release();
mRecorder = null;
}
+ if (mBufferManager != null) {
+ mBufferManager.close();
+ mBufferManager = 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) {
+ if (mSessionState != STATE_IDLE) {
mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
Log.e(TAG, "Tuning was requested from wrong status.");
return false;
@@ -359,10 +333,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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);
@@ -371,9 +341,9 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
}
mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this);
if (mTunerSource == null) {
- // Retry tuning in this case.
- mSessionState = STATE_TUNING;
- return true;
+ mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY);
+ Log.w(TAG, "Tuner stream cannot be created due to resource shortage.");
+ return false;
}
mSessionState = STATE_TUNED;
return true;
@@ -395,10 +365,10 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
}
// Since tuning might be happened a while ago, shifts the start position of tuned source.
mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition());
+ mBufferManager = new BufferManager(new DvrStorageManager(mStorageDir, true));
mRecordStartTime = System.currentTimeMillis();
- mDvrStorageManager = new DvrStorageManager(mStorageDir, true);
- mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource,
- new BufferManager(mDvrStorageManager), this, true);
+ mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource, mBufferManager, this,
+ true);
mRecorder.setOnCompletionListener(this, mHandler);
mProgramUri = programUri;
mSessionState = STATE_RECORDING;
@@ -422,34 +392,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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;
@@ -624,25 +566,15 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener,
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);
+ Uri uri = insertRecordedProgram(getRecordedProgram(), mChannel.getChannelId(),
+ Uri.fromFile(mStorageDir).toString(), 1024 * 1024, mRecordStartTime,
+ mRecordStartTime + TimeUnit.MICROSECONDS.toMillis(lastExtractedPositionUs));
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);
}
diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java
index d1ee3c6f..5c61402e 100644
--- a/src/com/android/tv/tuner/tvinput/TunerSession.java
+++ b/src/com/android/tv/tuner/tvinput/TunerSession.java
@@ -41,7 +41,7 @@ import com.android.tv.tuner.R;
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.Track.AtscCaptionTrack;
+import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
import com.android.tv.tuner.exoplayer.buffer.BufferManager;
import com.android.tv.tuner.data.TunerChannel;
import com.android.tv.tuner.util.GlobalSettingsUtils;
@@ -81,7 +81,8 @@ public class TunerSession extends TvInputService.Session implements Handler.Call
private boolean mPlayPaused;
private long mTuneStartTimestamp;
- public TunerSession(Context context, ChannelDataManager channelDataManager) {
+ public TunerSession(Context context, ChannelDataManager channelDataManager,
+ BufferManager bufferManager) {
super(context);
mContext = context;
mUiHandler = new Handler(this);
@@ -96,9 +97,12 @@ public class TunerSession extends TvInputService.Session implements Handler.Call
mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE);
mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status);
mAudioStatusView.setVisibility(View.INVISIBLE);
+ mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
+ context.getString(R.string.ut_surround_sound_disabled))));
CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption);
mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout);
- mSessionWorker = new TunerSessionWorker(context, channelDataManager, this);
+ mSessionWorker = new TunerSessionWorker(context, channelDataManager,
+ bufferManager, this);
}
public boolean isReleased() {
@@ -268,13 +272,10 @@ public class TunerSession extends TvInputService.Session implements Handler.Call
// 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))));
+ mAudioStatusView.setVisibility(View.VISIBLE);
} else {
- mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
- mContext.getString(R.string.audio_passthrough_not_supported))));
+ Log.e(TAG, "Audio is unavailable, surround sound setting is " + value);
}
- mAudioStatusView.setVisibility(View.VISIBLE);
return true;
}
case MSG_UI_HIDE_AUDIO_UNPLAYABLE: {
diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
index 41f8ce5f..5230298e 100644
--- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
+++ b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
@@ -35,7 +35,6 @@ 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;
@@ -48,30 +47,28 @@ import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.TvContentRatingCache;
import com.android.tv.tuner.TunerPreferences;
import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.Channel;
import com.android.tv.tuner.data.PsipData.EitItem;
import com.android.tv.tuner.data.PsipData.TvTracksInterface;
-import com.android.tv.tuner.data.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.Track.AtscCaptionTrack;
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.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.io.FileNotFoundException;
+import java.io.IOException;
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;
+import java.util.concurrent.CountDownLatch;
/**
* {@link TunerSessionWorker} implements a handler thread which processes TV input jobs
@@ -85,9 +82,6 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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;
@@ -153,18 +147,10 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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;
-
- // 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 volatile Surface mSurface;
private volatile float mVolume = 1.0f;
private volatile boolean mCaptionEnabled;
@@ -173,7 +159,6 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private volatile Long mRecordingDuration;
private volatile long mRecordStartTimeMs;
private volatile long mBufferStartTimeMs;
- private volatile boolean mTrickplayDisabled;
private String mRecordingId;
private final Handler mHandler;
private int mRetryCount;
@@ -192,19 +177,19 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private TvContentRating mUnblockedContentRating;
private long mLastPositionMs;
private AudioCapabilities mAudioCapabilities;
+ private final CountDownLatch mReleaseLatch = new CountDownLatch(1);
private long mLastLimitInBytes;
+ private long mLastPositionInBytes;
+ private final BufferManager mBufferManager;
private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
private final TunerSession mSession;
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) {
+ BufferManager bufferManager, TunerSession tunerSession) {
if (DEBUG) Log.d(TAG, "TunerSessionWorker created");
mContext = context;
@@ -226,10 +211,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
mCaptionEnabled = captioningManager.isEnabled();
mPlaybackParams.setSpeed(1.0f);
- mMaxTrickplayBufferSizeMb =
- SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
- mTrickplayBufferDir = context.getCacheDir();
- mTrickplayDisabled = mTrickplayBufferDir == null;
+ mBufferManager = bufferManager;
mPreparingStartTimeMs = INVALID_TIME;
mBufferingStartTimeMs = INVALID_TIME;
mReadyStartTimeMs = INVALID_TIME;
@@ -303,21 +285,24 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
private Long getDurationForRecording(String recordingId) {
- DvrStorageManager storageManager =
+ try {
+ 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);
+ Pair<String, MediaFormat> trackInfo = null;
+ try {
+ trackInfo = storageManager.readTrackInfoFile(false);
+ } catch (FileNotFoundException e) {
+ }
+ if (trackInfo == null) {
+ trackInfo = storageManager.readTrackInfoFile(true);
+ }
+ Long durationUs = trackInfo.second.getLong(MediaFormat.KEY_DURATION);
// we need duration by milli for trickplay notification.
return durationUs != null ? durationUs / 1000 : null;
+ } catch (IOException e) {
+ Log.e(TAG, "meta file for recording was not found: " + recordingId);
+ return null;
}
- Log.e(TAG, "meta file for recording was not found: " + recordingId);
- return null;
}
@MainThread
@@ -356,12 +341,16 @@ public class TunerSessionWorker implements PlaybackBufferListener,
@MainThread
public void release() {
if (DEBUG) Log.d(TAG, "release()");
- synchronized (mReleaseLock) {
- mReleaseRequested = true;
- }
mChannelDataManager.setListener(null);
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessage(MSG_RELEASE);
+ try {
+ mReleaseLatch.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Couldn't wait for finish of MSG_RELEASE", e);
+ } finally {
+ mHandler.getLooper().quitSafely();
+ }
}
// MpegTsPlayer.Listener
@@ -378,7 +367,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (playbackState == ExoPlayer.STATE_READY) {
if (DEBUG) Log.d(TAG, "ExoPlayer ready");
if (!mPlayerStarted) {
- sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer));
+ sendMessage(MSG_START_PLAYBACK, mPlayer);
}
mReadyStartTimeMs = SystemClock.elapsedRealtime();
} else if (playbackState == ExoPlayer.STATE_PREPARING) {
@@ -390,7 +379,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// 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));
+ sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
}
}
mPlayerState = playbackState;
@@ -408,8 +397,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// 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();
+ mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer).sendToTarget();
}
}
@@ -427,12 +415,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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();
- }
+ mBufferStartTimeMs = mRecordStartTimeMs =
+ (mRecordingId != null) ? 0 : System.currentTimeMillis();
notifyVideoAvailable();
mReportedDrawnToSurface = true;
@@ -515,8 +499,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
@Override
public void onDiskTooSlow() {
- mTrickplayDisabled = true;
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
+ sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
}
// EventDetector.EventListener
@@ -619,28 +602,6 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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);
@@ -655,8 +616,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
return true;
}
- clearCallbacksAndMessagesSafely();
- mChannelDataManager.removeAllCallbacksAndMessages();
+ mHandler.removeCallbacksAndMessages(null);
if (channel != null) {
mChannelDataManager.requestProgramsData(channel);
}
@@ -664,8 +624,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// TODO: Need to refactor. notifyContentAllowed() should not be called if parental
// control is turned on.
mSession.notifyContentAllowed();
- resetTvTracks();
resetPlayback();
+ resetTvTracks();
mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
return true;
@@ -673,7 +633,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
case MSG_STOP_TUNE: {
if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE");
mChannel = null;
- stopPlayback(true);
+ stopPlayback();
stopCaptionTrack();
resetTvTracks();
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
@@ -682,17 +642,14 @@ public class TunerSessionWorker implements PlaybackBufferListener,
case MSG_RELEASE: {
if (DEBUG) Log.d(TAG, "MSG_RELEASE");
mHandler.removeCallbacksAndMessages(null);
- stopPlayback(true);
+ stopPlayback();
stopCaptionTrack();
mSourceManager.release();
- mHandler.getLooper().quitSafely();
- if (mIsActiveSession) {
- sActiveSessionSemaphore.release();
- }
+ mReleaseLatch.countDown();
return true;
}
case MSG_RETRY_PLAYBACK: {
- if (System.identityHashCode(mPlayer) == (int) msg.obj) {
+ if (mPlayer == 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.
@@ -701,14 +658,13 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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);
+ stopPlayback();
stopCaptionTrack();
notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
@@ -723,14 +679,13 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
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);
+ startPlayback(msg.obj);
}
return true;
}
@@ -835,11 +790,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return true;
}
case MSG_RESCHEDULE_PROGRAMS: {
- if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) {
- mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS);
- } else {
- doReschedulePrograms();
- }
+ doReschedulePrograms();
return true;
}
case MSG_PARENTAL_CONTROLS: {
@@ -863,8 +814,11 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return true;
}
case MSG_SELECT_TRACK: {
- if (mChannel != null || mRecordingId != null) {
+ if (mChannel != null) {
doSelectTrack(msg.arg1, (String) msg.obj);
+ } else if (mRecordingId != null) {
+ // TODO : mChannel == null && mRecordingId != null
+ Log.d(TAG, "track selected for recording");
}
return true;
}
@@ -955,6 +909,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
TsDataSource source = mPlayer.getDataSource();
long limitInBytes = source != null ? source.getBufferedPosition() : 0L;
+ long positionInBytes = source != null ? source.getLastReadPosition() : 0L;
if (TunerDebug.ENABLED) {
TunerDebug.calculateDiff();
mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT,
@@ -972,8 +927,14 @@ public class TunerSessionWorker implements PlaybackBufferListener,
TunerDebug.getVideoPtsUsRate()
)));
}
+ if (DEBUG) {
+ Log.d(TAG, String.format("MSG_CHECK_SIGNAL position: %d, limit: %d",
+ positionInBytes, limitInBytes));
+ }
mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
long currentTime = SystemClock.elapsedRealtime();
+ boolean noBufferRead = positionInBytes == mLastPositionInBytes
+ && limitInBytes == mLastLimitInBytes;
boolean isBufferingTooLong = mBufferingStartTimeMs != INVALID_TIME
&& currentTime - mBufferingStartTimeMs
> PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
@@ -982,11 +943,11 @@ public class TunerSessionWorker implements PlaybackBufferListener,
> PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
boolean isWeakSignal = source != null
&& mChannel.getType() == Channel.TYPE_TUNER
- && (isBufferingTooLong || isPreparingTooLong);
+ && (noBufferRead || 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);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MSG_RETRY_PLAYBACK, mPlayer), PLAYBACK_RETRY_DELAY_MS);
}
if (mPlayer != null) {
mPlayer.setAudioTrack(false);
@@ -1005,6 +966,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
mLastLimitInBytes = limitInBytes;
+ mLastPositionInBytes = positionInBytes;
mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS);
return true;
}
@@ -1037,8 +999,15 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (trackId == null) {
return;
}
- if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) {
- mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId);
+ AtscAudioTrack audioTrack = mAudioTrackMap.get(numTrackId);
+ if (audioTrack == null) {
+ return;
+ }
+ int oldAudioPid = mChannel.getAudioPid();
+ mChannel.selectAudioTrack(audioTrack.index);
+ int newAudioPid = mChannel.getAudioPid();
+ if (oldAudioPid != newAudioPid) {
+ mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, audioTrack.index);
}
mSession.notifyTrackSelected(type, trackId);
} else if (type == TvTrackInfo.TYPE_SUBTITLE) {
@@ -1061,22 +1030,11 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
- private MpegTsPlayer createPlayer(AudioCapabilities capabilities) {
+ private MpegTsPlayer createPlayer(AudioCapabilities capabilities, BufferManager bufferManager) {
if (capabilities == null) {
Log.w(TAG, "No Audio Capabilities");
}
- BufferManager bufferManager = null;
- if (mRecordingId != null) {
- StorageManager storageManager =
- new DvrStorageManager(new File(getRecordingPath()), false);
- bufferManager = new BufferManager(storageManager);
- updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles());
- } else if (!mTrickplayDisabled && 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);
@@ -1111,26 +1069,24 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
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 {
+ 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.getAudioTracks() == null || fromPmt)) {
+ updateAudioTracks(audioTracks);
+ }
+ if (captionTracks == null || captionTracks.isEmpty()) {
+ if (tvTracksInterface.hasCaptionTrack()) {
updateCaptionTracks(captionTracks);
}
+ } else {
+ updateCaptionTracks(captionTracks);
}
}
@@ -1176,24 +1132,25 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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;
+ AtscAudioTrack audioTrack = mAudioTrackMap.get(i);
+ if (audioTrack == null) {
+ continue;
+ }
+ String language = audioTrack.language;
+ if (language == null && mChannel.getAudioTracks() != null
+ && mChannel.getAudioTracks().size() == mAudioTrackMap.size()) {
+ // If a language is not present, use a language field in PMT section parsed.
+ language = mChannel.getAudioTracks().get(i).language;
+ }
+ // Save the index to the audio track.
+ // Later, when an audio track is selected, both the audio pid and its audio stream
+ // type reside in the selected index position of the tuner channel's audio data.
+ audioTrack.index = i;
TvTrackInfo.Builder builder = new TvTrackInfo.Builder(
TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
builder.setLanguage(language);
- builder.setAudioChannelCount(infoFromPlayer.channelCount);
- builder.setAudioSampleRate(infoFromPlayer.sampleRate);
+ builder.setAudioChannelCount(audioTrack.channelCount);
+ builder.setAudioSampleRate(audioTrack.sampleRate);
TvTrackInfo track = builder.build();
mTvTracks.add(track);
}
@@ -1269,10 +1226,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
- private void stopPlayback(boolean removeChannelDataCallbacks) {
- if (removeChannelDataCallbacks) {
- mChannelDataManager.removeAllCallbacksAndMessages();
- }
+ private void stopPlayback() {
+ mChannelDataManager.removeAllCallbacksAndMessages();
if (mPlayer != null) {
mPlayer.setPlayWhenReady(false);
mPlayer.release();
@@ -1289,9 +1244,9 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
- private void startPlayback(int playerHashCode) {
+ private void startPlayback(Object playerObj) {
// TODO: provide hasAudio()/hasVideo() for play recordings.
- if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) {
+ if (mPlayer == null || mPlayer != playerObj) {
return;
}
if (mChannel != null && !mChannel.hasAudio()) {
@@ -1304,7 +1259,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio())
|| (mChannel.hasVideo() && !mPlayer.hasVideo()))) {
// Tracks haven't been detected in the extractor. Try again.
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
+ sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
return;
}
// Since mSurface is volatile, we define a local variable surface to keep the same value
@@ -1331,7 +1286,9 @@ public class TunerSessionWorker implements PlaybackBufferListener,
return;
}
mSourceManager.setKeepTuneStatus(true);
- MpegTsPlayer player = createPlayer(mAudioCapabilities);
+ BufferManager bufferManager = mChannel != null ? mBufferManager : new BufferManager(
+ new DvrStorageManager(new File(getRecordingPath()), false));
+ MpegTsPlayer player = createPlayer(mAudioCapabilities, bufferManager);
player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
player.setVideoEventListener(this);
player.setCaptionServiceNumber(mCaptionTrack != null ?
@@ -1343,8 +1300,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
// 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);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
- System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer),
+ PLAYBACK_RETRY_DELAY_MS);
}
} else {
mPlayer = player;
@@ -1357,7 +1314,7 @@ public class TunerSessionWorker implements PlaybackBufferListener,
private void resetPlayback() {
long timestamp, oldTimestamp;
timestamp = SystemClock.elapsedRealtime();
- stopPlayback(false);
+ stopPlayback();
stopCaptionTrack();
if (ENABLE_PROFILER) {
oldTimestamp = timestamp;
@@ -1379,12 +1336,8 @@ public class TunerSessionWorker implements PlaybackBufferListener,
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();
- }
+ mBufferStartTimeMs = mRecordStartTimeMs =
+ (mRecordingId != null) ? 0 : System.currentTimeMillis();
mLastPositionMs = 0;
mCaptionTrack = null;
mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
@@ -1591,15 +1544,15 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
mChannelBlocked = channelBlocked;
if (mChannelBlocked) {
- clearCallbacksAndMessagesSafely();
- stopPlayback(true);
+ mHandler.removeCallbacksAndMessages(null);
+ stopPlayback();
resetTvTracks();
if (contentRating != null) {
mSession.notifyContentBlocked(contentRating);
}
mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS);
} else {
- clearCallbacksAndMessagesSafely();
+ mHandler.removeCallbacksAndMessages(null);
resetPlayback();
mSession.notifyContentAllowed();
mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
@@ -1609,17 +1562,6 @@ public class TunerSessionWorker implements PlaybackBufferListener,
}
}
- @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;
diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
index 6594e089..684ebdbd 100644
--- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
+++ b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
@@ -28,6 +28,9 @@ 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 com.android.tv.tuner.exoplayer.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
+import com.android.tv.tuner.util.SystemPropertiesProxy;
import java.util.Collections;
import java.util.Set;
@@ -42,6 +45,9 @@ public class TunerTvInputService extends TvInputService
private static final String TAG = "TunerTvInputService";
private static final boolean DEBUG = false;
+ 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
private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100;
// WeakContainer for {@link TvInputSessionImpl}
@@ -49,6 +55,7 @@ public class TunerTvInputService extends TvInputService
private ChannelDataManager mChannelDataManager;
private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
private AudioCapabilities mAudioCapabilities;
+ private BufferManager mBufferManager;
@Override
public void onCreate() {
@@ -58,6 +65,7 @@ public class TunerTvInputService extends TvInputService
mChannelDataManager = new ChannelDataManager(getApplicationContext());
mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this);
mAudioCapabilitiesReceiver.register();
+ mBufferManager = createBufferManager();
if (CommonFeatures.DVR.isEnabled(this)) {
JobScheduler jobScheduler =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
@@ -71,6 +79,11 @@ public class TunerTvInputService extends TvInputService
jobScheduler.schedule(job);
}
}
+ if (mBufferManager == null) {
+ Log.i(TAG, "Trickplay is disabled");
+ } else {
+ Log.i(TAG, "Trickplay is enabled");
+ }
}
@Override
@@ -79,6 +92,9 @@ public class TunerTvInputService extends TvInputService
super.onDestroy();
mChannelDataManager.release();
mAudioCapabilitiesReceiver.unregister();
+ if (mBufferManager != null) {
+ mBufferManager.close();
+ }
}
@Override
@@ -90,7 +106,8 @@ public class TunerTvInputService extends TvInputService
public Session onCreateSession(String inputId) {
if (DEBUG) Log.d(TAG, "onCreateSession");
try {
- final TunerSession session = new TunerSession(this, mChannelDataManager);
+ final TunerSession session = new TunerSession(
+ this, mChannelDataManager, mBufferManager);
mTunerSessions.add(session);
session.setAudioCapabilities(mAudioCapabilities);
session.setOverlayViewEnabled(true);
@@ -112,6 +129,17 @@ public class TunerTvInputService extends TvInputService
}
}
+ private BufferManager createBufferManager() {
+ int maxBufferSizeMb =
+ SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
+ if (maxBufferSizeMb >= MIN_BUFFER_SIZE_DEF) {
+ return new BufferManager(
+ new TrickplayStorageManager(getApplicationContext(), getCacheDir(),
+ 1024L * 1024 * maxBufferSizeMb));
+ }
+ return null;
+ }
+
public static String getInputId(Context context) {
return TvContract.buildInputId(new ComponentName(context, TunerTvInputService.class));
}
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 3942ce95..00000000
--- a/src/com/android/tv/tuner/util/PostalCodeUtils.java
+++ /dev/null
@@ -1,89 +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.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.Locale;
-
-/**
- * A utility class to update, get, and set the last known postal or zip code.
- */
-public class PostalCodeUtils {
- private static final String TAG = "PostalCodeUtils";
- private static final String SUPPORTED_COUNTRY_CODE = Locale.US.getCountry();
-
- /** 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());
- if (TextUtils.equals(address.getCountryCode(), SUPPORTED_COUNTRY_CODE)) {
- 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() {
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/util/StringUtils.java b/src/com/android/tv/tuner/util/StringUtils.java
index 659807e2..15571e75 100644
--- a/src/com/android/tv/util/StringUtils.java
+++ b/src/com/android/tv/tuner/util/StringUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.tuner.util;
/**
* Utility class for handling {@link String}.
diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
index 2817ccbf..62a64361 100644
--- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
+++ b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
@@ -58,20 +58,4 @@ public class SystemPropertiesProxy {
}
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/TunerInputInfoUtils.java b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
index fd9ec77d..5c411f64 100644
--- a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
+++ b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
@@ -25,7 +25,6 @@ import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.os.BuildCompat;
import android.util.Log;
-import android.util.Pair;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.tuner.R;
@@ -44,31 +43,23 @@ public class TunerInputInfoUtils {
*/
@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) {
+ public static TvInputInfo buildTunerInputInfo(Context context, boolean fromBuiltInTuner) {
+ int numOfDevices = TunerHal.getTunerCount(context);
+ if (numOfDevices == 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;
+ TvInputInfo.Builder builder = new TvInputInfo.Builder(context, new ComponentName(context,
+ TunerTvInputService.class));
+ if (fromBuiltInTuner) {
+ builder.setLabel(R.string.bt_app_name);
+ } else {
+ builder.setLabel(R.string.ut_app_name);
}
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)
+ return builder.setCanRecord(CommonFeatures.DVR.isEnabled(context))
+ .setTunerCount(numOfDevices)
.build();
- } catch (IllegalArgumentException | NullPointerException e) {
+ } catch (NullPointerException e) {
// TunerTvInputService is not enabled.
return null;
}
@@ -82,7 +73,7 @@ public class TunerInputInfoUtils {
public static void updateTunerInputInfo(Context context) {
if (BuildCompat.isAtLeastN()) {
if (DEBUG) Log.d(TAG, "updateTunerInputInfo()");
- TvInputInfo info = buildTunerInputInfo(context);
+ TvInputInfo info = buildTunerInputInfo(context, isBuiltInTuner(context));
if (info != null) {
((TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE))
.updateTvInputInfo(info);
@@ -97,4 +88,13 @@ public class TunerInputInfoUtils {
}
}
}
-} \ No newline at end of file
+
+ /**
+ * Returns if the current tuner service is for a built-in tuner.
+ *
+ * @param context {@link Context} instance
+ */
+ public static boolean isBuiltInTuner(Context context) {
+ return TunerHal.getTunerType(context) == TunerHal.TUNER_TYPE_BUILT_IN;
+ }
+}
diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java
index 6a83c541..09acb36b 100644
--- a/src/com/android/tv/ui/AppLayerTvView.java
+++ b/src/com/android/tv/ui/AppLayerTvView.java
@@ -22,7 +22,6 @@ import android.util.AttributeSet;
import android.view.SurfaceView;
import android.view.View;
-import com.android.tv.util.Debug;
import com.android.tv.experiments.Experiments;
/**
@@ -60,13 +59,4 @@ public class AppLayerTvView extends TvView {
}
super.onViewAdded(child);
}
-
- @Override
- public void getLocationOnScreen(int[] outLocation) {
- super.getLocationOnScreen(outLocation);
-
- // The TvView.MySessionCallback.onSessionCreated() will call this method indirectly.
- Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
- "AppLayerTvView.getLocationOnScreen, session created");
- }
}
diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java
index eed536a8..3cf4de83 100644
--- a/src/com/android/tv/ui/ChannelBannerView.java
+++ b/src/com/android/tv/ui/ChannelBannerView.java
@@ -57,7 +57,7 @@ import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.data.StreamInfo;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.util.ImageCache;
import com.android.tv.util.ImageLoader;
@@ -98,8 +98,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private static final String EMPTY_STRING = "";
- private Program mNoProgram;
- private Program mLockedChannelProgram;
+ private static Program sNoProgram;
+ private static Program sLockedChannelProgram;
private static String sClosedCaptionMark;
private final MainActivity mMainActivity;
@@ -123,7 +123,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private String mProgramDescriptionText;
private View mAnchorView;
private Channel mCurrentChannel;
- private boolean mCurrentChannelLogoExists;
private Program mLastUpdatedProgram;
private final Handler mHandler = new Handler();
private final DvrManager mDvrManager;
@@ -131,7 +130,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private TvContentRating mBlockingContentRating;
private int mLockType;
- private boolean mUpdateOnTune;
private Animator mResizeAnimator;
private int mCurrentHeight;
@@ -194,7 +192,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
@Override
public void run() {
removeCallbacks(this);
- updateViews(false);
+ updateViews(null);
}
};
@@ -245,14 +243,18 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
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();
+ if (sNoProgram == null) {
+ sNoProgram = new Program.Builder()
+ .setTitle(context.getString(R.string.channel_banner_no_title))
+ .setDescription(EMPTY_STRING)
+ .build();
+ }
+ if (sLockedChannelProgram == null){
+ sLockedChannelProgram = 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);
}
@@ -343,17 +345,19 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
* Set new lock type.
*
* @param lockType Any of LOCK_NONE, LOCK_PROGRAM_DETAIL, or LOCK_CHANNEL_INFO.
- * @return the previous lock type of the channel banner.
+ * @return {@code true} only if lock type is changed
* @throws IllegalArgumentException if lockType is invalid.
*/
- public int setLockType(int lockType) {
+ public boolean setLockType(int lockType) {
if (lockType != LOCK_NONE && lockType != LOCK_CHANNEL_INFO
&& lockType != LOCK_PROGRAM_DETAIL) {
throw new IllegalArgumentException("No such lock type " + lockType);
}
- int previousLockType = mLockType;
- mLockType = lockType;
- return previousLockType;
+ if (mLockType != lockType) {
+ mLockType = lockType;
+ return true;
+ }
+ return false;
}
/**
@@ -368,34 +372,31 @@ 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.
+ * @param info A StreamInfo that includes stream information.
+ * If it's {@code null}, only program information will be updated.
*/
- public void updateViews(boolean updateOnTune) {
+ public void updateViews(StreamInfo info) {
resetAnimationEffects();
- mUpdateOnTune = updateOnTune;
- if (mUpdateOnTune) {
+ Channel channel = mMainActivity.getCurrentChannel();
+ if (!Objects.equals(mCurrentChannel, channel)) {
+ mBlockingContentRating = null;
if (isShown()) {
scheduleHide();
}
- mBlockingContentRating = null;
- mCurrentChannel = mMainActivity.getCurrentChannel();
- mCurrentChannelLogoExists =
- mCurrentChannel != null && mCurrentChannel.channelLogoExists();
- updateStreamInfo(null);
+ }
+ mCurrentChannel = channel;
+ mChannelView.setVisibility(VISIBLE);
+ if (info != null) {
+ // If the current channels between ChannelTuner and TvView are different,
+ // the stream information should not be seen.
+ updateStreamInfo(channel != null && channel.equals(info.getCurrentChannel()) ? info
+ : null);
updateChannelInfo();
}
updateProgramInfo(mMainActivity.getCurrentProgram());
- mChannelView.setVisibility(VISIBLE);
- mUpdateOnTune = false;
}
- /**
- * Update channel banner view with stream info.
- *
- * @param info A StreamInfo that includes stream information.
- */
- public void updateStreamInfo(StreamInfo info) {
+ private void updateStreamInfo(StreamInfo info) {
// Update stream information in a channel.
if (mLockType != LOCK_CHANNEL_INFO && info != null) {
updateText(mClosedCaptionTextView, info.hasClosedCaption() ? sClosedCaptionMark
@@ -413,6 +414,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mAspectRatioTextView.setVisibility(View.GONE);
mResolutionTextView.setVisibility(View.GONE);
mAudioChannelTextView.setVisibility(View.GONE);
+ for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
+ mContentRatingsTextViews[i].setVisibility(View.GONE);
+ }
}
}
@@ -463,7 +467,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
mChannelLogoImageView.setImageBitmap(null);
mChannelLogoImageView.setVisibility(View.GONE);
- if (mCurrentChannel != null && mCurrentChannelLogoExists) {
+ if (mCurrentChannel != null) {
mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
mChannelLogoImageViewWidth, mChannelLogoImageViewHeight,
createChannelLogoCallback(this, mCurrentChannel));
@@ -546,9 +550,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (mResizeAnimator == null) {
String description = mProgramDescriptionTextView.getText().toString();
- boolean programDescriptionNeedFadeAnimation =
- !description.equals(mProgramDescriptionText) && !mUpdateOnTune;
- updateBannerHeight(programDescriptionNeedFadeAnimation);
+ boolean needFadeAnimation = !description.equals(mProgramDescriptionText);
+ updateBannerHeight(needFadeAnimation);
} else {
mProgramInfoUpdatePendingByResizing = true;
}
@@ -556,9 +559,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private void updateProgramInfo(Program program) {
if (mLockType == LOCK_CHANNEL_INFO) {
- program = mLockedChannelProgram;
+ program = sLockedChannelProgram;
} else if (program == null || !program.isValid() || TextUtils.isEmpty(program.getTitle())) {
- program = mNoProgram;
+ program = sNoProgram;
}
if (mLastUpdatedProgram == null
@@ -587,9 +590,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mProgramDescriptionText = program.getDescription();
}
String description = mProgramDescriptionTextView.getText().toString();
- boolean programDescriptionNeedFadeAnimation = (isProgramChanged
- || !description.equals(mProgramDescriptionText)) && !mUpdateOnTune;
- updateBannerHeight(programDescriptionNeedFadeAnimation);
+ boolean needFadeAnimation = isProgramChanged
+ || !description.equals(mProgramDescriptionText);
+ updateBannerHeight(needFadeAnimation);
} else {
mProgramInfoUpdatePendingByResizing = true;
}
@@ -600,7 +603,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (program == null) {
return;
}
- updateProgramTextView(program == mLockedChannelProgram, program.getTitle(),
+ updateProgramTextView(program == sLockedChannelProgram, program.getTitle(),
program.getEpisodeDisplayTitle(getContext()));
}
@@ -627,8 +630,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mProgramTextView.setText(text);
}
- int width = mProgramDescriptionTextViewWidth + (mCurrentChannelLogoExists ?
- 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
+ int width = mProgramDescriptionTextViewWidth
+ - ((mChannelLogoImageView.getVisibility() != View.VISIBLE)
+ ? 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams();
lp.width = width;
mProgramTextView.setLayoutParams(lp);
@@ -651,27 +655,23 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
private void updateProgramRatings(Program program) {
- if (mLockType == LOCK_CHANNEL_INFO) {
- for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
- mContentRatingsTextViews[i].setVisibility(View.GONE);
- }
- } else if (mBlockingContentRating != null) {
+ if (mBlockingContentRating != null) {
mContentRatingsTextViews[0].setText(
mContentRatingsManager.getDisplayNameForRating(mBlockingContentRating));
mContentRatingsTextViews[0].setVisibility(View.VISIBLE);
for (int i = 1; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
mContentRatingsTextViews[i].setVisibility(View.GONE);
}
- } else {
- TvContentRating[] ratings = (program == null) ? null : program.getContentRatings();
- for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
- if (ratings == null || ratings.length <= i) {
- mContentRatingsTextViews[i].setVisibility(View.GONE);
- } else {
- mContentRatingsTextViews[i].setText(
- mContentRatingsManager.getDisplayNameForRating(ratings[i]));
- mContentRatingsTextViews[i].setVisibility(View.VISIBLE);
- }
+ return;
+ }
+ TvContentRating[] ratings = (program == null) ? null : program.getContentRatings();
+ for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
+ if (ratings == null || ratings.length <= i) {
+ mContentRatingsTextViews[i].setVisibility(View.GONE);
+ } else {
+ mContentRatingsTextViews[i].setText(
+ mContentRatingsManager.getDisplayNameForRating(ratings[i]));
+ mContentRatingsTextViews[i].setVisibility(View.VISIBLE);
}
}
}
@@ -769,7 +769,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mLastUpdatedProgram = program;
}
- private void updateBannerHeight(boolean needProgramDescriptionFadeAnimation) {
+ private void updateBannerHeight(boolean needFadeAnimation) {
Assert.assertNull(mResizeAnimator);
// Need to measure the layout height with the new description text.
CharSequence oldDescription = mProgramDescriptionTextView.getText();
@@ -785,13 +785,12 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
layoutParams.height = targetHeight;
setLayoutParams(layoutParams);
}
- } else if (mCurrentHeight != targetHeight || needProgramDescriptionFadeAnimation) {
+ } else if (mCurrentHeight != targetHeight || needFadeAnimation) {
// Restore description text for fade in/out animation.
- if (needProgramDescriptionFadeAnimation) {
+ if (needFadeAnimation) {
mProgramDescriptionTextView.setText(oldDescription);
}
- mResizeAnimator =
- createResizeAnimator(targetHeight, needProgramDescriptionFadeAnimation);
+ mResizeAnimator = createResizeAnimator(targetHeight, needFadeAnimation);
mResizeAnimator.start();
}
}
diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java
index ac5d841d..abc05bad 100644
--- a/src/com/android/tv/ui/KeypadChannelSwitchView.java
+++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java
@@ -39,7 +39,7 @@ 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.analytics.DurationTimer;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
index 88872a0a..5e25ae43 100644
--- a/src/com/android/tv/ui/SelectInputView.java
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -18,6 +18,7 @@ package com.android.tv.ui;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
@@ -36,13 +37,14 @@ 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.analytics.DurationTimer;
import com.android.tv.analytics.Tracker;
import com.android.tv.data.Channel;
import com.android.tv.util.TvInputManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -56,7 +58,7 @@ public class SelectInputView extends VerticalGridView implements
private final TvInputManagerHelper mTvInputManagerHelper;
private final List<TvInputInfo> mInputList = new ArrayList<>();
- private final TvInputManagerHelper.InputComparator mComparator;
+ private final InputsComparator mComparator = new InputsComparator();
private final Tracker mTracker;
private final DurationTimer mViewDurationTimer = new DurationTimer();
private final TvInputCallback mTvInputCallback = new TvInputCallback() {
@@ -147,7 +149,6 @@ public class SelectInputView extends VerticalGridView implements
ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
mTracker = appSingletons.getTracker();
mTvInputManagerHelper = appSingletons.getTvInputManagerHelper();
- mComparator = new TvInputManagerHelper.InputComparator(context, mTvInputManagerHelper);
Resources resources = context.getResources();
mInputItemHeight = resources.getDimensionPixelSize(R.dimen.input_banner_item_height);
@@ -384,6 +385,72 @@ public class SelectInputView extends VerticalGridView implements
}
}
+ private class InputsComparator implements Comparator<TvInputInfo> {
+ @Override
+ public int compare(TvInputInfo lhs, TvInputInfo rhs) {
+ if (lhs == null) {
+ return (rhs == null) ? 0 : 1;
+ }
+ if (rhs == null) {
+ return -1;
+ }
+
+ boolean enabledL = isInputEnabled(lhs);
+ boolean enabledR = isInputEnabled(rhs);
+ if (enabledL != enabledR) {
+ return enabledL ? -1 : 1;
+ }
+
+ int priorityL = getPriority(lhs);
+ int priorityR = getPriority(rhs);
+ if (priorityL != priorityR) {
+ return priorityR - priorityL;
+ }
+
+ String customLabelL = (String) lhs.loadCustomLabel(getContext());
+ String customLabelR = (String) rhs.loadCustomLabel(getContext());
+ if (!TextUtils.equals(customLabelL, customLabelR)) {
+ customLabelL = customLabelL == null ? "" : customLabelL;
+ customLabelR = customLabelR == null ? "" : customLabelR;
+ return customLabelL.compareToIgnoreCase(customLabelR);
+ }
+
+ String labelL = (String) lhs.loadLabel(getContext());
+ String labelR = (String) rhs.loadLabel(getContext());
+ labelL = labelL == null ? "" : labelL;
+ labelR = labelR == null ? "" : labelR;
+ return labelL.compareToIgnoreCase(labelR);
+ }
+
+ private int getPriority(TvInputInfo info) {
+ switch (info.getType()) {
+ case TvInputInfo.TYPE_TUNER:
+ return 9;
+ case TvInputInfo.TYPE_HDMI:
+ HdmiDeviceInfo hdmiInfo = info.getHdmiDeviceInfo();
+ if (hdmiInfo != null && hdmiInfo.isCecDevice()) {
+ return 8;
+ }
+ return 7;
+ case TvInputInfo.TYPE_DVI:
+ return 6;
+ case TvInputInfo.TYPE_COMPONENT:
+ return 5;
+ case TvInputInfo.TYPE_SVIDEO:
+ return 4;
+ case TvInputInfo.TYPE_COMPOSITE:
+ return 3;
+ case TvInputInfo.TYPE_DISPLAY_PORT:
+ return 2;
+ case TvInputInfo.TYPE_VGA:
+ return 1;
+ case TvInputInfo.TYPE_SCART:
+ default:
+ return 0;
+ }
+ }
+ }
+
/**
* A callback interface for the input selection.
*/
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index 8f4f40f5..cbe459fb 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -19,16 +19,9 @@ package com.android.tv.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
-import android.app.AlertDialog;
-import android.app.ApplicationErrorReport;
+import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.media.PlaybackParams;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
@@ -45,6 +38,7 @@ import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
+import android.support.v4.os.BuildCompat;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.AttributeSet;
@@ -62,20 +56,14 @@ 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.analytics.DurationTimer;
import com.android.tv.analytics.Tracker;
-import com.android.tv.common.BuildConfig;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.data.Channel;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.WatchedHistoryManager;
import com.android.tv.parental.ContentRatingsManager;
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;
@@ -117,13 +105,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private static final int FADING_IN = 2;
private static final int FADING_OUT = 3;
+ // It is too small to see the description text without PIP_BLOCK_SCREEN_SCALE_FACTOR.
+ private static final float PIP_BLOCK_SCREEN_SCALE_FACTOR = 1.2f;
+
private AppLayerTvView mTvView;
private TvViewSession mTvViewSession;
private Channel mCurrentChannel;
private TvInputManagerHelper mInputManagerHelper;
private ContentRatingsManager mContentRatingsManager;
- private ParentalControlSettings mParentalControlSettings;
- private ProgramDataManager mProgramDataManager;
@Nullable
private WatchedHistoryManager mWatchedHistoryManager;
private boolean mStarted;
@@ -137,7 +126,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
private boolean mHasClosedCaption = false;
private boolean mVideoAvailable;
- private boolean mAudioAvailable;
private boolean mScreenBlocked;
private OnScreenBlockingChangedListener mOnScreenBlockedListener;
private TvContentRating mBlockedContentRating;
@@ -148,8 +136,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private boolean mParentControlEnabled;
private int mFixedSurfaceWidth;
private int mFixedSurfaceHeight;
+ private boolean mIsPip;
+ private int mScreenHeight;
+ private int mShrunkenTvViewHeight;
private final boolean mCanModifyParentalControls;
- private boolean mIsUnderShrunken;
@TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE;
private TimeShiftListener mTimeShiftListener;
@@ -166,15 +156,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
// A View to hide screen when there's problem in video playback.
private final BlockScreenView mHideScreenView;
- private final int mHideScreenImageColorFilter;
-
- // A spinner view to show buffering status.
- private final View mBufferingSpinnerView;
- private TuningBlockView mTuningBlockView;
// A View to block screen until onContentAllowed is received if parental control is on.
private final View mBlockScreenForTuneView;
+ // A spinner view to show buffering status.
+ private final View mBufferingSpinnerView;
+
// A View for fade-in/out animation
private final View mDimScreenView;
private int mFadeState = FADED_IN;
@@ -298,66 +286,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@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();
- }
unhideScreenByVideoAvailability();
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();
- }
-
@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 + ")");
- }
hideScreenByVideoAvailability(inputId, reason);
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
@@ -375,8 +311,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onContentAllowed(String inputId) {
mBlockScreenForTuneView.setVisibility(View.GONE);
- mBlockedContentRating = null;
- checkBlockScreenAndMuteNeeded();
+ unblockScreenByContentRating();
if (mOnTuneListener != null) {
mOnTuneListener.onContentAllowed();
}
@@ -384,8 +319,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onContentBlocked(String inputId, TvContentRating rating) {
- mBlockedContentRating = rating;
- checkBlockScreenAndMuteNeeded();
+ blockScreenByContentRating(rating);
if (mOnTuneListener != null) {
mOnTuneListener.onContentBlocked();
}
@@ -393,10 +327,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@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);
}
@@ -448,8 +378,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mHideScreenView = (BlockScreenView) findViewById(R.id.hide_screen);
mHideScreenView.setImageVisibility(false);
mBufferingSpinnerView = findViewById(R.id.buffering_spinner);
- mHideScreenImageColorFilter = getResources().getColor(
- R.color.tvview_block_image_color_filter, null);
mBlockScreenForTuneView = findViewById(R.id.block_screen_for_tune);
mDimScreenView = findViewById(R.id.dim);
mDimScreenView.animate().setListener(new AnimatorListenerAdapter() {
@@ -469,23 +397,27 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
});
}
- public void initialize(AppLayerTvView tvView, TuningBlockView tuningBlockView,
- ProgramDataManager programDataManager, TvInputManagerHelper tvInputManagerHelper) {
+ public void initialize(AppLayerTvView tvView, boolean isPip, int screenHeight,
+ int shrunkenTvViewHeight) {
mTvView = tvView;
- mTuningBlockView = tuningBlockView;
- mProgramDataManager = programDataManager;
- mInputManagerHelper = tvInputManagerHelper;
- mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
- mParentalControlSettings = tvInputManagerHelper.getParentalControlSettings();
if (mInputSessionManager != null) {
mTvViewSession = mInputSessionManager.createTvViewSession(tvView, this, mCallback);
} else {
mTvView.setCallback(mCallback);
}
+ mIsPip = isPip;
+ mScreenHeight = screenHeight;
+ mShrunkenTvViewHeight = shrunkenTvViewHeight;
+ mTvView.setZOrderOnTop(isPip);
copyLayoutParamsToTvView();
}
- public void start() {
+ public void start(TvInputManagerHelper tvInputManagerHelper) {
+ mInputManagerHelper = tvInputManagerHelper;
+ mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
+ if (mStarted) {
+ return;
+ }
mStarted = true;
}
@@ -565,13 +497,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mWatchedHistoryManager = watchedHistoryManager;
}
- /**
- * Sets if the TunableTvView is under shrunken.
- */
- public void setIsUnderShrunken(boolean isUnderShrunken) {
- mIsUnderShrunken = isUnderShrunken;
- }
-
public boolean isPlaying() {
return mStarted;
}
@@ -594,7 +519,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
* 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");
if (!mStarted) {
throw new IllegalStateException("TvView isn't started");
}
@@ -636,7 +560,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mVideoDisplayAspectRatio = 0f;
mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
mHasClosedCaption = false;
- mBlockedContentRating = null;
mTimeShiftCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
// To reduce the IPCs, unregister the callback here and register it when necessary.
mTvView.setTimeShiftPositionCallback(null);
@@ -648,12 +571,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
hideScreenByVideoAvailability(mInputInfo.getId(),
TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
- updateBlockScreenUI(false);
if (mTvViewSession != null) {
mTvViewSession.tune(channel, params, listener);
} else {
mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params);
}
+ unblockScreenByContentRating();
if (channel.isPassthrough()) {
mBlockScreenForTuneView.setVisibility(View.GONE);
} else if (mParentControlEnabled) {
@@ -792,11 +715,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
@Override
- public boolean isVideoOrAudioAvailable() {
- return mVideoAvailable || mAudioAvailable;
- }
-
- @Override
public int getVideoUnavailableReason() {
return mVideoUnavailableReason;
}
@@ -829,7 +747,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}.
+ * Returns if the screen is blocked by {@link #blockScreen()}.
*/
public boolean isScreenBlocked() {
return mScreenBlocked;
@@ -848,18 +766,38 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Blocks/unblocks current TV screen and mutes.
+ * Locks 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.
* TODO: Accept parameter to show lock icon or not.
- *
- * @param blockOrUnblock {@code true} to block the screen, or {@code false} to unblock.
*/
- public void blockOrUnblockScreen(boolean blockOrUnblock) {
- mScreenBlocked = blockOrUnblock;
+ public void blockScreen() {
+ mScreenBlocked = true;
checkBlockScreenAndMuteNeeded();
if (mOnScreenBlockedListener != null) {
- mOnScreenBlockedListener.onScreenBlockingChanged(blockOrUnblock);
+ mOnScreenBlockedListener.onScreenBlockingChanged(true);
+ }
+ }
+
+ private void blockScreenByContentRating(TvContentRating rating) {
+ mBlockedContentRating = rating;
+ checkBlockScreenAndMuteNeeded();
+ }
+
+ @Override
+ @SuppressLint("RtlHardcoded")
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mIsPip) {
+ int height = bottom - top;
+ float scale;
+ if (mBlockScreenType == BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW) {
+ scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mShrunkenTvViewHeight;
+ } else {
+ scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mScreenHeight;
+ }
+ // TODO: need to get UX confirmation.
+ mBlockScreenView.scaleContainerView(scale);
}
}
@@ -881,7 +819,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
|| tvViewLp.gravity != lp.gravity
|| tvViewLp.height != lp.height
|| tvViewLp.width != lp.width) {
- tvViewLp.leftMargin = lp.leftMargin;
+ if (lp.topMargin == tvViewLp.topMargin && lp.leftMargin == tvViewLp.leftMargin
+ && !BuildCompat.isAtLeastN()) {
+ // HACK: If top and left position aren't changed and SurfaceHolder.setFixedSize is
+ // used, SurfaceView doesn't catch the width and height change. It causes a bug that
+ // PIP size change isn't shown when PIP is located TOP|LEFT. So we adjust 1 px for
+ // small size PIP as a workaround.
+ // Note: This framework issue has been fixed from NYC.
+ tvViewLp.leftMargin = lp.leftMargin + 1;
+ } else {
+ tvViewLp.leftMargin = lp.leftMargin;
+ }
tvViewLp.topMargin = lp.topMargin;
tvViewLp.bottomMargin = lp.bottomMargin;
tvViewLp.rightMargin = lp.rightMargin;
@@ -997,109 +945,96 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private void checkBlockScreenAndMuteNeeded() {
updateBlockScreenUI(false);
- updateMuteStatus();
+ if (mScreenBlocked || mBlockedContentRating != null) {
+ mute();
+ if (mIsPip) {
+ // If we don't make mTvView invisible, some frames are leaked when a user changes
+ // PIP layout in options.
+ // Note: When video is unavailable, we keep the mTvView's visibility, because
+ // TIS implementation may not send video available with no surface.
+ mTvView.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ unmuteIfPossible();
+ if (mIsPip) {
+ mTvView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ public void unblockScreen() {
+ mScreenBlocked = false;
+ checkBlockScreenAndMuteNeeded();
+ if (mOnScreenBlockedListener != null) {
+ mOnScreenBlockedListener.onScreenBlockingChanged(false);
+ }
+ }
+
+ private void unblockScreenByContentRating() {
+ mBlockedContentRating = null;
+ checkBlockScreenAndMuteNeeded();
}
@UiThread
private void hideScreenByVideoAvailability(String inputId, int reason) {
mVideoAvailable = false;
- mAudioAvailable = false;
mVideoUnavailableReason = reason;
if (mInternetCheckTask != null) {
mInternetCheckTask.cancel(true);
mInternetCheckTask = null;
}
- if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
- // Tuning block view will apply animation when unhide screen, so let's end the
- // animation if it is running.
- mTuningBlockView.endFadeOutAnimator();
- }
switch (reason) {
case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
mHideScreenView.setVisibility(VISIBLE);
mHideScreenView.setImageVisibility(false);
mHideScreenView.setText(R.string.tvview_msg_audio_only);
- mTuningBlockView.setVisibility(GONE);
mBufferingSpinnerView.setVisibility(GONE);
- mAudioAvailable = true;
+ unmuteIfPossible();
break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
mBufferingSpinnerView.setVisibility(VISIBLE);
+ mute();
break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
mHideScreenView.setVisibility(VISIBLE);
mHideScreenView.setText(R.string.tvview_msg_weak_signal);
- mTuningBlockView.setVisibility(GONE);
mBufferingSpinnerView.setVisibility(GONE);
+ mute();
break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
+ mHideScreenView.setVisibility(VISIBLE);
+ mHideScreenView.setImageVisibility(false);
+ mHideScreenView.setText(null);
mBufferingSpinnerView.setVisibility(VISIBLE);
- if (shouldShowImageForTuning()) {
- mHideScreenView.setVisibility(GONE);
- mTuningBlockView.setVisibility(VISIBLE);
- mTuningBlockView.setImageVisibility(false);
- showImageForTuning();
- } else {
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setImageVisibility(false);
- mHideScreenView.setText(null);
- mTuningBlockView.setVisibility(GONE);
- }
+ mute();
break;
case VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
mHideScreenView.setVisibility(VISIBLE);
mHideScreenView.setImageVisibility(false);
mHideScreenView.setText(null);
- mTuningBlockView.setVisibility(GONE);
mBufferingSpinnerView.setVisibility(GONE);
+ mute();
break;
case VIDEO_UNAVAILABLE_REASON_NO_RESOURCE:
mHideScreenView.setVisibility(VISIBLE);
mHideScreenView.setImageVisibility(false);
mHideScreenView.setText(getTuneConflictMessage(inputId));
- mTuningBlockView.setVisibility(GONE);
mBufferingSpinnerView.setVisibility(GONE);
+ mute();
break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
default:
mHideScreenView.setVisibility(VISIBLE);
mHideScreenView.setImageVisibility(false);
mHideScreenView.setText(null);
- mTuningBlockView.setVisibility(GONE);
mBufferingSpinnerView.setVisibility(GONE);
+ mute();
if (mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) {
mInternetCheckTask = new InternetCheckTask();
mInternetCheckTask.execute();
}
break;
}
- updateMuteStatus();
- }
-
- private boolean shouldShowImageForTuning() {
- if (getWidth() == 0 || getWidth() == 0 || mCurrentChannel == null || !isBundledInput()
- || mIsUnderShrunken || (mParentControlEnabled && (mCurrentChannel.isLocked()))) {
- return false;
- }
- Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId());
- if (currentProgram == null) {
- return false;
- }
- TvContentRating rating =
- mParentalControlSettings.getBlockedRating(currentProgram.getContentRatings());
- return !(mParentControlEnabled && rating != null);
- }
-
- private void showImageForTuning() {
- mTuningBlockView.setImage(null);
- if (mCurrentChannel == null) {
- return;
- }
- Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId());
- if (currentProgram != null) {
- currentProgram.loadPosterArt(getContext(), getWidth(), getHeight(),
- createProgramPosterArtCallback(mTuningBlockView, mCurrentChannel.getId()));
- }
}
private String getTuneConflictMessage(String inputId) {
@@ -1117,43 +1052,25 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private void unhideScreenByVideoAvailability() {
mVideoAvailable = true;
- mAudioAvailable = true;
- mTuningBlockView.hideWithAnimationIfNeeded();
mHideScreenView.setVisibility(GONE);
mBufferingSpinnerView.setVisibility(GONE);
- updateMuteStatus();
- }
-
- private void updateMuteStatus() {
- // Workaround: TunerTvInputService 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
- // the playback is started, because TunerTvInput will remember the muted state and mute
- // itself right way when the playback is going to be started, which results the initial
- // jankiness, too.
- boolean isBundledInput = isBundledInput();
- if ((isBundledInput || mAudioAvailable) && !mScreenBlocked
- && mBlockedContentRating == null) {
- if (mIsMuted) {
- mIsMuted = false;
- mTvView.setStreamVolume(mVolume);
- }
- } else {
- if (!mIsMuted) {
- if ((mInputInfo == null || isBundledInput)
- && !mScreenBlocked && mBlockedContentRating == null) {
- return;
- }
- mIsMuted = true;
- mTvView.setStreamVolume(0);
- }
+ unmuteIfPossible();
+ }
+
+ private void unmuteIfPossible() {
+ if (mVideoAvailable && !mScreenBlocked && mBlockedContentRating == null) {
+ unmute();
}
}
- private boolean isBundledInput() {
- return mInputInfo != null && mInputInfo.getType() == TvInputInfo.TYPE_TUNER
- && Utils.isBundledInput(mInputInfo.getId());
+ private void mute() {
+ mIsMuted = true;
+ mTvView.setStreamVolume(0);
+ }
+
+ private void unmute() {
+ mIsMuted = false;
+ mTvView.setStreamVolume(mVolume);
}
/** Returns true if this view is faded out. */
@@ -1351,25 +1268,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return mTimeShiftCurrentPositionMs;
}
- private ImageLoader.ImageLoaderCallback<TuningBlockView> createProgramPosterArtCallback(
- TuningBlockView view, final long channelId) {
- return new ImageLoader.ImageLoaderCallback<TuningBlockView>(view) {
- @Override
- public void onBitmapLoaded(TuningBlockView view, @Nullable Bitmap posterArt) {
- if (posterArt == null || getCurrentChannel() == null
- || channelId != getCurrentChannel().getId()
- || !shouldShowImageForTuning()) {
- return;
- }
- Drawable drawablePosterArt = new BitmapDrawable(view.getResources(), posterArt);
- drawablePosterArt.mutate().setColorFilter(
- mHideScreenImageColorFilter, PorterDuff.Mode.SRC_OVER);
- view.setImage(drawablePosterArt);
- view.setImageVisibility(true);
- }
- };
- }
-
/**
* Used to receive the time-shift events.
*/
diff --git a/src/com/android/tv/ui/TuningBlockView.java b/src/com/android/tv/ui/TuningBlockView.java
deleted file mode 100644
index 2914b461..00000000
--- a/src/com/android/tv/ui/TuningBlockView.java
+++ /dev/null
@@ -1,113 +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.ui;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.tv.R;
-import com.android.tv.common.SoftPreconditions;
-
-/**
- * A view to block the screen while tuning channels.
- */
-public class TuningBlockView extends FrameLayout{
- private final static String TAG = "TuningBlockView";
-
- private ImageView mImageView;
- private Animator mFadeOut;
-
- public TuningBlockView(Context context) {
- this(context, null, 0);
- }
-
- public TuningBlockView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TuningBlockView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mImageView = (ImageView) findViewById(R.id.image);
- mFadeOut = AnimatorInflater.loadAnimator(
- getContext(), R.animator.tuning_block_view_fade_out);
- mFadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setVisibility(GONE);
- }
- });
- mFadeOut.setTarget(mImageView);
- }
-
- /**
- * Sets image to the image view. This method should be called after finishing inflate the view.
- */
- public void setImage(Drawable imageDrawable) {
- SoftPreconditions.checkState(mImageView != null, TAG, "imageView is null");
- mImageView.setImageDrawable(imageDrawable);
- }
-
- /**
- * Sets the visibility of image view.
- * This method should be called after finishing inflate the view.
- */
- public void setImageVisibility(boolean visible) {
- SoftPreconditions.checkState(mImageView != null, TAG, "imageView is null");
- mImageView.setAlpha(1.0f);
- mImageView.setVisibility(visible ? VISIBLE: GONE);
- }
-
- /**
- * Returns if the image view is visible.
- * This method should be called after finishing inflate the view.
- */
- public boolean isImageArtVisible() {
- SoftPreconditions.checkState(mImageView != null, TAG, "imageView is null");
- return mImageView.getVisibility() == VISIBLE;
- }
-
- /**
- * Hides the view with animation if needed.
- */
- public void hideWithAnimationIfNeeded() {
- if (getVisibility() == VISIBLE && isImageArtVisible()) {
- mFadeOut.start();
- } else {
- setVisibility(GONE);
- }
- }
-
- /**
- * Ends the fade out animator.
- */
- public void endFadeOutAnimator() {
- if (mFadeOut != null && mFadeOut.isRunning()) {
- mFadeOut.end();
- }
- }
-}
diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java
index f86e6e95..e14b286b 100644
--- a/src/com/android/tv/ui/TvOverlayManager.java
+++ b/src/com/android/tv/ui/TvOverlayManager.java
@@ -39,7 +39,6 @@ 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.analytics.Tracker;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.feature.CommonFeatures;
@@ -47,14 +46,13 @@ import com.android.tv.common.ui.setup.OnActionClickListener;
import com.android.tv.common.ui.setup.SetupFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.dialog.DvrHistoryDialogFragment;
import com.android.tv.dialog.FullscreenDialogFragment;
-import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.RecentlyWatchedDialogFragment;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
+import com.android.tv.dvr.ui.DvrActivity;
+import com.android.tv.dvr.ui.HalfSizedDialogFragment;
import com.android.tv.guide.ProgramGuide;
import com.android.tv.menu.Menu;
import com.android.tv.menu.Menu.MenuShowReason;
@@ -165,7 +163,6 @@ public class TvOverlayManager {
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);
AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(SettingsFragment.LicenseActionItem.DIALOG_TAG);
@@ -198,10 +195,10 @@ 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) {
+ TunableTvView tvView, KeypadChannelSwitchView keypadChannelSwitchView,
+ ChannelBannerView channelBannerView, InputBannerView inputBannerView,
+ SelectInputView selectInputView, ViewGroup sceneContainer,
+ ProgramGuideSearchFragment searchFragment) {
mMainActivity = mainActivity;
mChannelTuner = channelTuner;
ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity);
@@ -228,8 +225,7 @@ public class TvOverlayManager {
});
// Menu
MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu);
- mMenu = new Menu(mainActivity, tvView, optionsManager, menuView,
- new MenuRowFactory(mainActivity, tvView),
+ mMenu = new Menu(mainActivity, tvView, menuView, new MenuRowFactory(mainActivity, tvView),
new Menu.OnMenuVisibilityChangeListener() {
@Override
public void onMenuVisibilityChange(boolean visible) {
@@ -545,7 +541,7 @@ public class TvOverlayManager {
* Shows DVR manager.
*/
public void showDvrManager() {
- Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class);
+ Intent intent = new Intent(mMainActivity, DvrActivity.class);
mMainActivity.startActivity(intent);
}
@@ -568,14 +564,6 @@ public class TvOverlayManager {
}
/**
- * Shows DVR history dialog.
- */
- public void showDvrHistoryDialog() {
- showDialogFragment(DvrHistoryDialogFragment.DIALOG_TAG,
- new DvrHistoryDialogFragment(), false);
- }
-
- /**
* Shows banner view.
*/
public void showBanner() {
@@ -686,7 +674,7 @@ public class TvOverlayManager {
}
if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) {
// Keeps side panels.
- } else if (mSideFragmentManager.isActive()) {
+ } else if (mSideFragmentManager.isSidePanelVisible()) {
if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) {
mSideFragmentManager.hideSidePanel(withAnimation);
} else {
diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java
index 8d3b14f7..bf874fc7 100644
--- a/src/com/android/tv/ui/TvViewUiManager.java
+++ b/src/com/android/tv/ui/TvViewUiManager.java
@@ -52,8 +52,9 @@ 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 and PIP
+ * TvViews. It also control the settings regarding TvView UI such as display mode, PIP layout,
+ * and PIP size.
*/
public class TvViewUiManager {
private static final String TAG = "TvViewManager";
@@ -68,11 +69,18 @@ public class TvViewUiManager {
private final Resources mResources;
private final FrameLayout mContentView;
private final TunableTvView mTvView;
+ private final TunableTvView mPipView;
private final TvOptionsManager mTvOptionsManager;
+ private final int mTvViewPapWidth;
private final int mTvViewShrunkenStartMargin;
private final int mTvViewShrunkenEndMargin;
+ private final int mTvViewPapStartMargin;
+ private final int mTvViewPapEndMargin;
private int mWindowWidth;
private int mWindowHeight;
+ private final int mPipViewHorizontalMargin;
+ private final int mPipViewTopMargin;
+ private final int mPipViewBottomMargin;
private final SharedPreferences mSharedPreferences;
private final TimeInterpolator mLinearOutSlowIn;
private final TimeInterpolator mFastOutLinearIn;
@@ -105,6 +113,9 @@ public class TvViewUiManager {
private boolean mIsUnderShrunkenTvView;
private int mTvViewStartMargin;
private int mTvViewEndMargin;
+ private int mPipLayout;
+ private int mPipSize;
+ private boolean mPipStarted;
private ObjectAnimator mTvViewAnimator;
private FrameLayout.LayoutParams mTvViewLayoutParams;
// TV view's position when the display mode is FULL. It is used to compute PIP location relative
@@ -119,11 +130,12 @@ public class TvViewUiManager {
private int mAppliedTvViewEndMargin;
private float mAppliedVideoDisplayAspectRatio;
- public TvViewUiManager(Context context, TunableTvView tvView,
+ public TvViewUiManager(Context context, TunableTvView tvView, TunableTvView pipView,
FrameLayout contentView, TvOptionsManager tvOptionManager) {
mContext = context;
mResources = mContext.getResources();
mTvView = tvView;
+ mPipView = pipView;
mContentView = contentView;
mTvOptionsManager = tvOptionManager;
@@ -135,12 +147,18 @@ public class TvViewUiManager {
mWindowWidth = size.x;
mWindowHeight = size.y;
- // Have an assumption that TvView Shrinking happens only in full screen.
+ // Have an assumption that PIP and TvView Shrinking happens only in full screen.
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);
+ int papMarginHorizontal = mResources
+ .getDimensionPixelOffset(R.dimen.papview_margin_horizontal);
+ int papSpacing = mResources.getDimensionPixelOffset(R.dimen.papview_spacing);
+ mTvViewPapWidth = (mWindowWidth - papSpacing) / 2 - papMarginHorizontal;
+ mTvViewPapStartMargin = papMarginHorizontal + mTvViewPapWidth + papSpacing;
+ mTvViewPapEndMargin = papMarginHorizontal;
mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -149,6 +167,11 @@ public class TvViewUiManager {
.loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
mFastOutLinearIn = AnimationUtils
.loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in);
+
+ mPipViewHorizontalMargin = mResources
+ .getDimensionPixelOffset(R.dimen.pipview_margin_horizontal);
+ mPipViewTopMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_top);
+ mPipViewBottomMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_bottom);
}
public void onConfigurationChanged(final int windowWidth, final int windowHeight) {
@@ -177,11 +200,18 @@ public class TvViewUiManager {
*/
public void startShrunkenTvView() {
mIsUnderShrunkenTvView = true;
- mTvView.setIsUnderShrunken(true);
mTvViewStartMarginBeforeShrunken = mTvViewStartMargin;
mTvViewEndMarginBeforeShrunken = mTvViewEndMargin;
- setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
+ if (mPipStarted && getPipLayout() == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
+ float sidePanelWidth = mResources.getDimensionPixelOffset(R.dimen.side_panel_width);
+ float factor = 1.0f - sidePanelWidth / mWindowWidth;
+ int startMargin = (int) (mTvViewPapStartMargin * factor);
+ int endMargin = (int) (mTvViewPapEndMargin * factor + sidePanelWidth);
+ setTvViewMargin(startMargin, endMargin);
+ } else {
+ setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
+ }
mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true);
}
@@ -191,7 +221,6 @@ public class TvViewUiManager {
*/
public void endShrunkenTvView() {
mIsUnderShrunkenTvView = false;
- mTvView.setIsUnderShrunken(false);
setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken);
setDisplayMode(mDisplayModeBeforeShrunken, false, true);
}
@@ -298,6 +327,120 @@ public class TvViewUiManager {
}
/**
+ * Returns the current PIP layout. The layout should be one of
+ * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT},
+ * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and
+ * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}.
+ */
+ public int getPipLayout() {
+ return mPipLayout;
+ }
+
+ /**
+ * Sets the PIP layout. The layout should be one of
+ * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT},
+ * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and
+ * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}.
+ *
+ * @param storeInPreference if true, the stored value will be restored by
+ * {@link #restorePipLayout()}.
+ */
+ public void setPipLayout(int pipLayout, boolean storeInPreference) {
+ mPipLayout = pipLayout;
+ if (storeInPreference) {
+ TvSettings.setPipLayout(mContext, pipLayout);
+ }
+ updatePipView(mTvViewFrame);
+ if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
+ setTvViewMargin(mTvViewPapStartMargin, mTvViewPapEndMargin);
+ setDisplayMode(DisplayMode.MODE_NORMAL, false, false);
+ } else {
+ setTvViewMargin(0, 0);
+ restoreDisplayMode(false);
+ }
+ mTvOptionsManager.onPipLayoutChanged(pipLayout);
+ }
+
+ /**
+ * Restores the PIP layout which {@link #setPipLayout} lastly stores.
+ */
+ public void restorePipLayout() {
+ setPipLayout(TvSettings.getPipLayout(mContext), false);
+ }
+
+ /**
+ * Called when PIP is started.
+ */
+ public void onPipStart() {
+ mPipStarted = true;
+ updatePipView();
+ mPipView.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Called when PIP is stopped.
+ */
+ public void onPipStop() {
+ setTvViewMargin(0, 0);
+ mPipView.setVisibility(View.GONE);
+ mPipStarted = false;
+ }
+
+ /**
+ * Called when PIP is resumed.
+ */
+ public void showPipForResume() {
+ mPipView.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Called when PIP is paused.
+ */
+ public void hidePipForPause() {
+ if (mPipLayout != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
+ mPipView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Updates PIP view. It is usually called, when video resolution in PIP is updated.
+ */
+ public void updatePipView() {
+ updatePipView(mTvViewFrame);
+ }
+
+ /**
+ * Returns the size of the PIP view.
+ */
+ public int getPipSize() {
+ return mPipSize;
+ }
+
+ /**
+ * Sets PIP size and applies it immediately.
+ *
+ * @param pipSize PIP size. The value should be one of {@link TvSettings#PIP_SIZE_BIG}
+ * and {@link TvSettings#PIP_SIZE_SMALL}.
+ * @param storeInPreference if true, the stored value will be restored by
+ * {@link #restorePipSize()}.
+ */
+ public void setPipSize(int pipSize, boolean storeInPreference) {
+ mPipSize = pipSize;
+ if (storeInPreference) {
+ TvSettings.setPipSize(mContext, pipSize);
+ }
+ updatePipView(mTvViewFrame);
+ mTvOptionsManager.onPipSizeChanged(pipSize);
+ }
+
+ /**
+ * Restores the PIP size which {@link #setPipSize} lastly stores.
+ */
+ public void restorePipSize() {
+ setPipSize(TvSettings.getPipSize(mContext), false);
+ }
+
+ /**
* This margins will be applied when applyDisplayMode is called.
*/
private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
@@ -397,6 +540,113 @@ public class TvViewUiManager {
} else {
mTvView.setLayoutParams(layoutParams);
}
+ updatePipView(mTvViewFrame);
+ }
+ }
+
+ /**
+ * The redlines assume that the ratio of the TV screen is 16:9. If the radio is not 16:9, the
+ * layout of PAP can be broken.
+ */
+ @SuppressLint("RtlHardcoded")
+ private void updatePipView(MarginLayoutParams tvViewFrame) {
+ if (!mPipStarted) {
+ return;
+ }
+ int width;
+ int height;
+ int startMargin;
+ int endMargin;
+ int topMargin;
+ int bottomMargin;
+ int gravity;
+
+ if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
+ gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+ height = tvViewFrame.height;
+ float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio();
+ if (videoDisplayAspectRatio <= 0f) {
+ width = tvViewFrame.width;
+ } else {
+ width = (int) (height * videoDisplayAspectRatio);
+ if (width > tvViewFrame.width) {
+ width = tvViewFrame.width;
+ }
+ }
+ startMargin = mResources.getDimensionPixelOffset(R.dimen.papview_margin_horizontal)
+ * tvViewFrame.width / mTvViewPapWidth + (tvViewFrame.width - width) / 2;
+ endMargin = 0;
+ topMargin = 0;
+ bottomMargin = 0;
+ } else {
+ int tvViewWidth = tvViewFrame.width;
+ int tvViewHeight = tvViewFrame.height;
+ int tvStartMargin = tvViewFrame.getMarginStart();
+ int tvEndMargin = tvViewFrame.getMarginEnd();
+ int tvTopMargin = tvViewFrame.topMargin;
+ int tvBottomMargin = tvViewFrame.bottomMargin;
+ float horizontalScaleFactor = (float) tvViewWidth / mWindowWidth;
+ float verticalScaleFactor = (float) tvViewHeight / mWindowHeight;
+
+ int maxWidth;
+ if (mPipSize == TvSettings.PIP_SIZE_SMALL) {
+ maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_width)
+ * horizontalScaleFactor);
+ height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_height)
+ * verticalScaleFactor);
+ } else if (mPipSize == TvSettings.PIP_SIZE_BIG) {
+ maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_width)
+ * horizontalScaleFactor);
+ height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_height)
+ * verticalScaleFactor);
+ } else {
+ throw new IllegalArgumentException("Invalid PIP size: " + mPipSize);
+ }
+ float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio();
+ if (videoDisplayAspectRatio <= 0f) {
+ width = maxWidth;
+ } else {
+ width = (int) (height * videoDisplayAspectRatio);
+ if (width > maxWidth) {
+ width = maxWidth;
+ }
+ }
+
+ startMargin = tvStartMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor);
+ endMargin = tvEndMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor);
+ topMargin = tvTopMargin + (int) (mPipViewTopMargin * verticalScaleFactor);
+ bottomMargin = tvBottomMargin + (int) (mPipViewBottomMargin * verticalScaleFactor);
+
+ switch (mPipLayout) {
+ case TvSettings.PIP_LAYOUT_TOP_LEFT:
+ gravity = Gravity.TOP | Gravity.LEFT;
+ break;
+ case TvSettings.PIP_LAYOUT_TOP_RIGHT:
+ gravity = Gravity.TOP | Gravity.RIGHT;
+ break;
+ case TvSettings.PIP_LAYOUT_BOTTOM_LEFT:
+ gravity = Gravity.BOTTOM | Gravity.LEFT;
+ break;
+ case TvSettings.PIP_LAYOUT_BOTTOM_RIGHT:
+ gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid PIP location: " + mPipLayout);
+ }
+ }
+
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPipView.getLayoutParams();
+ if (lp.width != width || lp.height != height || lp.getMarginStart() != startMargin
+ || lp.getMarginEnd() != endMargin || lp.topMargin != topMargin
+ || lp.bottomMargin != bottomMargin || lp.gravity != gravity) {
+ lp.width = width;
+ lp.height = height;
+ lp.setMarginStart(startMargin);
+ lp.setMarginEnd(endMargin);
+ lp.topMargin = topMargin;
+ lp.bottomMargin = bottomMargin;
+ lp.gravity = gravity;
+ mPipView.setLayoutParams(lp);
}
}
@@ -446,6 +696,7 @@ public class TvViewUiManager {
mLastAnimatedTvViewFrame = new MarginLayoutParams(0, 0);
interpolateMarginsRelative(mLastAnimatedTvViewFrame,
mOldTvViewFrame, mTvViewFrame, fraction);
+ updatePipView(mLastAnimatedTvViewFrame);
}
});
}
diff --git a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
index e5c23372..d6ccdf6b 100644
--- a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
@@ -82,9 +82,8 @@ public class ClosedCaptionFragment extends SideFragment {
}
mItems.add(item);
- for (int i = 0; i < tracks.size(); i++) {
- final TvTrackInfo track = tracks.get(i);
- item = new ClosedCaptionOptionItem(getLabel(track, i),
+ for (final TvTrackInfo track : tracks) {
+ item = new ClosedCaptionOptionItem(getLabel(track),
CaptionSettings.OPTION_ON, track.getId(), track.getLanguage());
if (isEnabled && track.getId().equals(trackId)) {
item.setChecked(true);
@@ -173,11 +172,11 @@ public class ClosedCaptionFragment extends SideFragment {
super.onDestroyView();
}
- private String getLabel(TvTrackInfo track, int trackIndex) {
+ private String getLabel(TvTrackInfo track) {
if (track.getLanguage() != null) {
return new Locale(track.getLanguage()).getDisplayName();
}
- return getString(R.string.closed_caption_unknown_language, trackIndex + 1);
+ return getString(R.string.default_language);
}
private class ClosedCaptionOptionItem extends RadioButtonItem {
diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
index fac24696..0d189cca 100644
--- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
@@ -18,6 +18,8 @@ package com.android.tv.ui.sidepanel;
import android.accounts.Account;
import android.app.Activity;
+import android.app.ApplicationErrorReport;
+import android.content.Intent;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
@@ -25,7 +27,6 @@ import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.BuildConfig;
-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;
@@ -53,14 +54,6 @@ public class DeveloperOptionFragment extends SideFragment {
@Override
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();
- }
- });
- }
if (BuildConfig.ENG) {
items.add(new ActionItem(getString(R.string.dev_item_watch_history)) {
@Override
@@ -69,6 +62,18 @@ public class DeveloperOptionFragment extends SideFragment {
}
});
}
+ items.add(new ActionItem(getString(R.string.dev_item_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 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)) {
diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java
index 4e47e75b..00f16427 100644
--- a/src/com/android/tv/ui/sidepanel/Item.java
+++ b/src/com/android/tv/ui/sidepanel/Item.java
@@ -24,7 +24,6 @@ import android.view.ViewGroup;
public abstract class Item {
private View mItemView;
private boolean mEnabled = true;
- private boolean mClickable = true;
public void setEnabled(boolean enabled) {
if (mEnabled != enabled) {
@@ -36,16 +35,6 @@ public abstract class Item {
}
/**
- * Sets the item to be clickable or not.
- */
- public void setClickable(boolean clickable) {
- mClickable = clickable;
- if (mItemView != null) {
- mItemView.setClickable(clickable);
- }
- }
-
- /**
* Returns whether this item is enabled.
*/
public boolean isEnabled() {
@@ -75,7 +64,6 @@ public abstract class Item {
*/
protected void onUpdate() {
setEnabledInternal(mItemView, mEnabled);
- mItemView.setClickable(mClickable);
}
protected abstract void onSelected();
diff --git a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java
new file mode 100644
index 00000000..dec017a8
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java
@@ -0,0 +1,170 @@
+/*
+ * 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.ui.sidepanel;
+
+import android.media.tv.TvInputInfo;
+import android.os.Bundle;
+import android.text.TextUtils;
+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.util.PipInputManager;
+import com.android.tv.util.PipInputManager.PipInput;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class PipInputSelectorFragment extends SideFragment {
+ private static final String TAG = "PipInputSelector";
+ private static final String TRACKER_LABEL = "PIP input source";
+
+ private final List<Item> mInputItems = new ArrayList<>();
+ private PipInputManager mPipInputManager;
+ private PipInput mInitialPipInput;
+ private boolean mSelected;
+
+ private final PipInputManager.Listener mPipInputListener = new PipInputManager.Listener() {
+ @Override
+ public void onPipInputStateUpdated() {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onPipInputListUpdated() {
+ refreshInputList();
+ setItems(mInputItems);
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mPipInputManager = getMainActivity().getPipInputManager();
+ mPipInputManager.addListener(mPipInputListener);
+ getMainActivity().startShrunkenTvView(false, false);
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mInitialPipInput = mPipInputManager.getPipInput(getMainActivity().getPipChannel());
+ if (mInitialPipInput == null) {
+ Log.w(TAG, "PIP should be on");
+ closeFragment();
+ }
+ int count = 0;
+ for (Item item : mInputItems) {
+ InputItem inputItem = (InputItem) item;
+ if (Objects.equals(inputItem.mPipInput, mInitialPipInput)) {
+ setSelectedPosition(count);
+ break;
+ }
+ ++count;
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mPipInputManager.removeListener(mPipInputListener);
+ if (!mSelected) {
+ getMainActivity().tuneToChannelForPip(mInitialPipInput.getChannel());
+ }
+ getMainActivity().endShrunkenTvView();
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.side_panel_title_pip_input_source);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ refreshInputList();
+ return mInputItems;
+ }
+
+ private void refreshInputList() {
+ mInputItems.clear();
+ for (PipInput input : mPipInputManager.getPipInputList(false)) {
+ mInputItems.add(new InputItem(input));
+ }
+ }
+
+ private class InputItem extends RadioButtonItem {
+ private final PipInput mPipInput;
+
+ private InputItem(PipInput input) {
+ super(input.getLongLabel());
+ mPipInput = input;
+ setEnabled(isAvailable());
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setEnabled(mPipInput.isAvailable());
+ setChecked(mPipInput == mInitialPipInput);
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ if (isEnabled()) {
+ getMainActivity().tuneToChannelForPip(mPipInput.getChannel());
+ }
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ if (isEnabled()) {
+ mSelected = true;
+ closeFragment();
+ }
+ }
+
+ private boolean isAvailable() {
+ if (!mPipInput.isAvailable()) {
+ return false;
+ }
+
+ // If this input shares the same parent with the current main input, you cannot select
+ // it. (E.g. two HDMI CEC devices that are connected to HDMI port 1 through an A/V
+ // receiver.)
+ PipInput pipInput = mPipInputManager.getPipInput(getMainActivity().getCurrentChannel());
+ if (pipInput == null) {
+ return false;
+ }
+ TvInputInfo mainInputInfo = pipInput.getInputInfo();
+ TvInputInfo pipInputInfo = mPipInput.getInputInfo();
+ return mainInputInfo == null || pipInputInfo == null
+ || !TextUtils.equals(mainInputInfo.getId(), pipInputInfo.getId())
+ && !TextUtils.equals(mainInputInfo.getParentId(), pipInputInfo.getParentId());
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
index f6aa4f86..e8033a22 100644
--- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
@@ -16,8 +16,6 @@
package com.android.tv.ui.sidepanel;
-import android.app.ApplicationErrorReport;
-import android.content.Intent;
import android.view.View;
import android.widget.Toast;
@@ -151,26 +149,12 @@ public class SettingsFragment extends SideFragment {
// But, we may be able to turn on channel lock feature regardless of the permission.
// It's TBD.
}
- 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 (LicenseUtils.hasLicenses(activity.getAssets())) {
items.add(new LicenseActionItem(activity));
}
// Show version.
- SimpleActionItem version = new SimpleActionItem(getString(R.string.settings_menu_version),
- ((TvApplication) activity.getApplicationContext()).getVersionName());
- version.setClickable(false);
- items.add(version);
+ items.add(new SimpleItem(getString(R.string.settings_menu_version),
+ ((TvApplication) activity.getApplicationContext()).getVersionName()));
return items;
}
diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java
index bb815eb8..8df56cd2 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragment.java
@@ -26,26 +26,24 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
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.analytics.DurationTimer;
import com.android.tv.analytics.HasTrackerLabel;
import com.android.tv.analytics.Tracker;
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 extends Fragment implements HasTrackerLabel {
public static final int INVALID_POSITION = -1;
- private static final int PRELOADED_VIEW_SIZE = 7;
+ private static final int RECYCLED_VIEW_POOL_SIZE = 7;
private static final int[] PRELOADED_VIEW_IDS = {
R.layout.option_item_radio_button,
R.layout.option_item_channel_lock,
@@ -53,8 +51,7 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
R.layout.option_item_channel_check
};
- private static RecyclerView.RecycledViewPool sRecycledViewPool =
- new RecyclerView.RecycledViewPool();
+ private static RecyclerView.RecycledViewPool sRecycledViewPool;
private VerticalGridView mListView;
private ItemAdapter mAdapter;
@@ -92,6 +89,13 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ if (sRecycledViewPool == null) {
+ // sRecycledViewPool should be initialized by calling preloadRecycledViews()
+ // before the entering animation of this fragment starts,
+ // because it takes long time and if it is called after the animation starts (e.g. here)
+ // it can affect the animation.
+ throw new IllegalStateException("The RecyclerView pool has not been initialized.");
+ }
View view = inflater.inflate(getFragmentLayoutResourceId(), container, false);
TextView textView = (TextView) view.findViewById(R.id.side_panel_title);
@@ -232,27 +236,30 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
}
/**
- * Preloads the item views.
+ * Preloads the view holders.
*/
- public static void preloadItemViews(Context context) {
+ public static void preloadRecycledViews(Context context) {
+ if (sRecycledViewPool != null) {
+ return;
+ }
+ sRecycledViewPool = new RecyclerView.RecycledViewPool();
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- // Use a fake parent to make the layoutParams set correctly.
- ViewGroup fakeParent = new LinearLayout(context);
for (int id : PRELOADED_VIEW_IDS) {
- sRecycledViewPool.setMaxRecycledViews(id, PRELOADED_VIEW_SIZE);
- for (int j = 0; j < PRELOADED_VIEW_SIZE; ++j) {
- View view = inflater.inflate(id, fakeParent, false);
- ViewCache.getInstance().putView(id, view);
+ sRecycledViewPool.setMaxRecycledViews(id, RECYCLED_VIEW_POOL_SIZE);
+ for (int j = 0; j < RECYCLED_VIEW_POOL_SIZE; ++j) {
+ ItemAdapter.ViewHolder viewHolder = new ItemAdapter.ViewHolder(
+ inflater.inflate(id, null, false));
+ sRecycledViewPool.putRecycledView(viewHolder);
}
}
}
/**
- * Releases the recycled view pool.
+ * Releases the pre-loaded view holders.
*/
- public static void releaseRecycledViewPool() {
- sRecycledViewPool.clear();
+ public static void releasePreloadedRecycledViews() {
+ sRecycledViewPool = null;
}
private static class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
@@ -271,11 +278,7 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = ViewCache.getInstance().getView(viewType);
- if (view == null) {
- view = mLayoutInflater.inflate(viewType, parent, false);
- }
- return new ViewHolder(view);
+ return new ViewHolder(mLayoutInflater.inflate(viewType, parent, false));
}
@Override
diff --git a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
index 4398b3f3..553cd9d7 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
@@ -99,6 +99,7 @@ public class SideFragmentManager {
* Shows the given {@link SideFragment}.
*/
public void show(SideFragment sideFragment, boolean showEnterAnimation) {
+ SideFragment.preloadRecycledViews(mActivity);
if (isHiding()) {
mHideAnimator.end();
}
@@ -177,6 +178,7 @@ public class SideFragmentManager {
* @param withAnimation specifies if animation should be shown.
*/
public void showSidePanel(boolean withAnimation) {
+ SideFragment.preloadRecycledViews(mActivity);
if (mFragmentCount == 0) {
return;
}
diff --git a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java b/src/com/android/tv/ui/sidepanel/SimpleItem.java
index 42553b66..52a5f13f 100644
--- a/src/com/android/tv/ui/sidepanel/SimpleActionItem.java
+++ b/src/com/android/tv/ui/sidepanel/SimpleItem.java
@@ -19,12 +19,12 @@ package com.android.tv.ui.sidepanel;
/**
* A simple item which shows title and description.
*/
-public class SimpleActionItem extends ActionItem {
- public SimpleActionItem(String title) {
+public class SimpleItem extends ActionItem {
+ public SimpleItem(String title) {
super(title);
}
- public SimpleActionItem(String title, String description) {
+ public SimpleItem(String title, String description) {
super(title, description);
}
diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java
index 3cc91e40..78243642 100644
--- a/src/com/android/tv/util/AsyncDbTask.java
+++ b/src/com/android/tv/util/AsyncDbTask.java
@@ -31,7 +31,7 @@ import android.util.Range;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.RecordedProgram;
import java.util.ArrayList;
import java.util.List;
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/LocationUtils.java b/src/com/android/tv/util/LocationUtils.java
index 2ae6db18..8e3b59e9 100644
--- a/src/com/android/tv/util/LocationUtils.java
+++ b/src/com/android/tv/util/LocationUtils.java
@@ -16,9 +16,7 @@
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;
@@ -27,7 +25,6 @@ import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
-import com.android.tv.tuner.util.PostalCodeUtils;
import java.io.IOException;
import java.util.List;
@@ -42,7 +39,6 @@ public class LocationUtils {
private static Context sApplicationContext;
private static Address sAddress;
- private static String sCountry;
private static IOException sError;
/**
@@ -63,19 +59,6 @@ public class LocationUtils {
return null;
}
- /**
- * Returns the current country.
- */
- public static synchronized String getCurrentCountry(Context context) {
- if (sCountry != null) {
- return sCountry;
- }
- if (sCountry == null) {
- 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) {
@@ -85,14 +68,9 @@ public class LocationUtils {
try {
List<Address> addresses = geocoder.getFromLocation(
location.getLatitude(), location.getLongitude(), 1);
- if (addresses != null && !addresses.isEmpty()) {
+ if (addresses != null) {
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");
}
diff --git a/src/com/android/tv/util/Partner.java b/src/com/android/tv/util/Partner.java
deleted file mode 100644
index e3688392..00000000
--- a/src/com/android/tv/util/Partner.java
+++ /dev/null
@@ -1,181 +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.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-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;
-
-/**
- * This file refers to Partner.java in LeanbackLauncher. Interact with partner customizations. There
- * can only be one set of customizations on a device, and it must be bundled with the system.
- */
-public class Partner {
- private static final String TAG = "Partner";
- /** Marker action used to discover partner */
- private static final String ACTION_PARTNER_CUSTOMIZATION =
- "com.google.android.leanbacklauncher.action.PARTNER_CUSTOMIZATION";
-
- /** 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";
- public static final String INPUT_TYPE_CEC_PLAYBACK = "input_type_cec_playback";
- public static final String INPUT_TYPE_MHL_MOBILE = "input_type_mhl_mobile";
- public static final String INPUT_TYPE_HDMI = "input_type_hdmi";
- public static final String INPUT_TYPE_DVI = "input_type_dvi";
- public static final String INPUT_TYPE_COMPONENT = "input_type_component";
- public static final String INPUT_TYPE_SVIDEO = "input_type_svideo";
- public static final String INPUT_TYPE_COMPOSITE = "input_type_composite";
- public static final String INPUT_TYPE_DISPLAY_PORT = "input_type_displayport";
- public static final String INPUT_TYPE_VGA = "input_type_vga";
- public static final String INPUT_TYPE_SCART = "input_type_scart";
- public static final String INPUT_TYPE_OTHER = "input_type_other";
-
- private static final String INPUTS_ORDER = "home_screen_inputs_ordering";
- private static final String TYPE_ARRAY = "array";
-
- private static Partner sPartner;
- private static final Object sLock = new Object();
-
- private final String mPackageName;
- private final String mReceiverName;
- 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);
- INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_LOGICAL, TvInputManagerHelper.TYPE_CEC_DEVICE);
- INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_RECORDER, TvInputManagerHelper.TYPE_CEC_DEVICE_RECORDER);
- INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_PLAYBACK, TvInputManagerHelper.TYPE_CEC_DEVICE_PLAYBACK);
- INPUT_TYPE_MAP.put(INPUT_TYPE_MHL_MOBILE, TvInputManagerHelper.TYPE_MHL_MOBILE);
- INPUT_TYPE_MAP.put(INPUT_TYPE_HDMI, TvInputInfo.TYPE_HDMI);
- INPUT_TYPE_MAP.put(INPUT_TYPE_DVI, TvInputInfo.TYPE_DVI);
- INPUT_TYPE_MAP.put(INPUT_TYPE_COMPONENT, TvInputInfo.TYPE_COMPONENT);
- INPUT_TYPE_MAP.put(INPUT_TYPE_SVIDEO, TvInputInfo.TYPE_SVIDEO);
- INPUT_TYPE_MAP.put(INPUT_TYPE_COMPOSITE, TvInputInfo.TYPE_COMPOSITE);
- INPUT_TYPE_MAP.put(INPUT_TYPE_DISPLAY_PORT, TvInputInfo.TYPE_DISPLAY_PORT);
- INPUT_TYPE_MAP.put(INPUT_TYPE_VGA, TvInputInfo.TYPE_VGA);
- INPUT_TYPE_MAP.put(INPUT_TYPE_SCART, TvInputInfo.TYPE_SCART);
- INPUT_TYPE_MAP.put(INPUT_TYPE_OTHER, TvInputInfo.TYPE_OTHER);
- }
-
- private Partner(String packageName, String receiverName, Resources res) {
- mPackageName = packageName;
- mReceiverName = receiverName;
- mResources = res;
- }
-
- /** Returns partner instance. */
- public static Partner getInstance(Context context) {
- PackageManager pm = context.getPackageManager();
- synchronized (sLock) {
- ResolveInfo info = getPartnerResolveInfo(pm);
- if (info != null) {
- final String packageName = info.activityInfo.packageName;
- final String receiverName = info.activityInfo.name;
- try {
- final Resources res = pm.getResourcesForApplication(packageName);
- sPartner = new Partner(packageName, receiverName, res);
- sPartner.sendInitBroadcast(context);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to find resources for " + packageName);
- }
- }
- if (sPartner == null) {
- sPartner = new Partner(null, null, null);
- }
- }
- return sPartner;
- }
-
- /** Resets the Partner instance to handle the partner package has changed. */
- public static void reset(Context context, String packageName) {
- synchronized (sLock) {
- if (sPartner != null && !TextUtils.isEmpty(packageName)) {
- if (packageName.equals(sPartner.mPackageName)) {
- // Force a refresh, so we send an Init to the updated package
- sPartner = null;
- getInstance(context);
- }
- }
- }
- }
-
- /** This method is used to send init broadcast to the new/changed partner package. */
- private void sendInitBroadcast(Context context) {
- if (!TextUtils.isEmpty(mPackageName) && !TextUtils.isEmpty(mReceiverName)) {
- Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
- final ComponentName componentName = new ComponentName(mPackageName, mReceiverName);
- intent.setComponent(componentName);
- intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- context.sendBroadcast(intent);
- }
- }
-
- /** Returns the order of inputs. */
- public Map<Integer, Integer> getInputsOrderMap() {
- HashMap<Integer, Integer> map = new HashMap<>();
- if (mResources != null && !TextUtils.isEmpty(mPackageName)) {
- String[] inputsArray = null;
- final int resId = mResources.getIdentifier(INPUTS_ORDER, TYPE_ARRAY, mPackageName);
- if (resId != 0) {
- inputsArray = mResources.getStringArray(resId);
- }
- if (inputsArray != null) {
- int priority = 0;
- for (String input : inputsArray) {
- Integer type = INPUT_TYPE_MAP.get(input);
- if (type != null) {
- map.put(type, priority++);
- }
- }
- }
- }
- return map;
- }
-
- private static ResolveInfo getPartnerResolveInfo(PackageManager pm) {
- final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
- ResolveInfo partnerInfo = null;
- for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
- if (isSystemApp(info)) {
- partnerInfo = info;
- break;
- }
- }
- return partnerInfo;
- }
-
- protected static boolean isSystemApp(ResolveInfo info) {
- return (info.activityInfo != null
- && info.activityInfo.applicationInfo != null
- && (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
- }
-}
diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java
new file mode 100644
index 00000000..2c51d5a0
--- /dev/null
+++ b/src/com/android/tv/util/PipInputManager.java
@@ -0,0 +1,432 @@
+/*
+ * 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.content.Context;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputManager.TvInputCallback;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.tv.ChannelTuner;
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+
+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.Set;
+
+/**
+ * A class that manages inputs for PIP. All tuner inputs are represented to one tuner input for PIP.
+ * Hidden inputs should not be visible to the users.
+ */
+public class PipInputManager {
+ private static final String TAG = "PipInputManager";
+
+ // Tuner inputs aren't distinguished each other in PipInput. They are handled as one input.
+ // Therefore, we define a fake input id for the unified input.
+ private static final String TUNER_INPUT_ID = "tuner_input_id";
+
+ private final Context mContext;
+ private final TvInputManagerHelper mInputManager;
+ private final ChannelTuner mChannelTuner;
+ private boolean mStarted;
+ private final Map<String, PipInput> mPipInputMap = new HashMap<>(); // inputId -> PipInput
+ private final Set<Listener> mListeners = new ArraySet<>();
+
+ private final TvInputCallback mTvInputCallback = new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+ if (input.isPassthroughInput()) {
+ boolean available = mInputManager.getInputState(input)
+ == TvInputManager.INPUT_STATE_CONNECTED;
+ mPipInputMap.put(inputId, new PipInput(inputId, available));
+ } else if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) {
+ boolean available = mChannelTuner.getBrowsableChannelCount() != 0;
+ mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available));
+ } else {
+ return;
+ }
+ for (Listener l : mListeners) {
+ l.onPipInputListUpdated();
+ }
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ PipInput pipInput = mPipInputMap.remove(inputId);
+ if (pipInput == null) {
+ if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) {
+ Log.w(TAG, "A TV input (" + inputId + ") isn't tracked in PipInputManager");
+ return;
+ }
+ if (mInputManager.getTunerTvInputSize() > 0) {
+ return;
+ }
+ mPipInputMap.remove(TUNER_INPUT_ID);
+ }
+ for (Listener l : mListeners) {
+ l.onPipInputListUpdated();
+ }
+ }
+
+ @Override
+ public void onInputStateChanged(String inputId, int state) {
+ PipInput pipInput = mPipInputMap.get(inputId);
+ if (pipInput == null) {
+ // For tuner input, state change is handled in mChannelTunerListener.
+ return;
+ }
+ pipInput.updateAvailability();
+ }
+ };
+
+ private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() {
+ @Override
+ public void onLoadFinished() { }
+
+ @Override
+ public void onCurrentChannelUnavailable(Channel channel) { }
+
+ @Override
+ public void onBrowsableChannelListChanged() {
+ PipInput tunerInput = mPipInputMap.get(TUNER_INPUT_ID);
+ if (tunerInput == null) {
+ return;
+ }
+ tunerInput.updateAvailability();
+ }
+
+ @Override
+ public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
+ if (previousChannel != null && currentChannel != null
+ && !previousChannel.isPassthrough() && !currentChannel.isPassthrough()) {
+ // Channel change between channels for tuner inputs.
+ return;
+ }
+ PipInput previousMainInput = getPipInput(previousChannel);
+ if (previousMainInput != null) {
+ previousMainInput.updateAvailability();
+ }
+ PipInput currentMainInput = getPipInput(currentChannel);
+ if (currentMainInput != null) {
+ currentMainInput.updateAvailability();
+ }
+ }
+ };
+
+ public PipInputManager(Context context, TvInputManagerHelper inputManager,
+ ChannelTuner channelTuner) {
+ mContext = context;
+ mInputManager = inputManager;
+ mChannelTuner = channelTuner;
+ }
+
+ /**
+ * Starts {@link PipInputManager}.
+ */
+ public void start() {
+ if (mStarted) {
+ return;
+ }
+ mStarted = true;
+ mInputManager.addCallback(mTvInputCallback);
+ mChannelTuner.addListener(mChannelTunerListener);
+ initializePipInputList();
+ }
+
+ /**
+ * Stops {@link PipInputManager}.
+ */
+ public void stop() {
+ if (!mStarted) {
+ return;
+ }
+ mStarted = false;
+ mInputManager.removeCallback(mTvInputCallback);
+ mChannelTuner.removeListener(mChannelTunerListener);
+ mPipInputMap.clear();
+ }
+
+ /**
+ * Adds a {@link PipInputManager.Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a {@link PipInputManager.Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Gets the size of inputs for PIP.
+ *
+ * <p>The hidden inputs are not counted.
+ *
+ * @param availableOnly If {@code true}, it counts only available PIP inputs. Please see {@link
+ * PipInput#isAvailable()} for the details of availability.
+ */
+ public int getPipInputSize(boolean availableOnly) {
+ int count = 0;
+ for (PipInput pipInput : mPipInputMap.values()) {
+ if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) {
+ ++count;
+ }
+ if (pipInput.isPassthrough()) {
+ TvInputInfo info = pipInput.getInputInfo();
+ // Do not count HDMI ports if a CEC device is directly connected to the port.
+ if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) {
+ --count;
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Gets the list of inputs for PIP..
+ *
+ * <p>The hidden inputs are excluded.
+ *
+ * @param availableOnly If true, it returns only available PIP inputs. Please see {@link
+ * PipInput#isAvailable()} for the details of availability.
+ */
+ public List<PipInput> getPipInputList(boolean availableOnly) {
+ List<PipInput> pipInputs = new ArrayList<>();
+ List<PipInput> removeInputs = new ArrayList<>();
+ for (PipInput pipInput : mPipInputMap.values()) {
+ if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) {
+ pipInputs.add(pipInput);
+ }
+ if (pipInput.isPassthrough()) {
+ TvInputInfo info = pipInput.getInputInfo();
+ // Do not show HDMI ports if a CEC device is directly connected to the port.
+ if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) {
+ removeInputs.add(mPipInputMap.get(info.getParentId()));
+ }
+ }
+ }
+ if (!removeInputs.isEmpty()) {
+ pipInputs.removeAll(removeInputs);
+ }
+ Collections.sort(pipInputs, new Comparator<PipInput>() {
+ @Override
+ public int compare(PipInput lhs, PipInput rhs) {
+ if (!lhs.mIsPassthrough) {
+ return -1;
+ }
+ if (!rhs.mIsPassthrough) {
+ return 1;
+ }
+ String a = lhs.getLabel();
+ String b = rhs.getLabel();
+ return a.compareTo(b);
+ }
+ });
+ return pipInputs;
+ }
+
+ /**
+ * Returns an PIP input corresponding to {@code channel}.
+ */
+ public PipInput getPipInput(Channel channel) {
+ if (channel == null) {
+ return null;
+ }
+ if (channel.isPassthrough()) {
+ return mPipInputMap.get(channel.getInputId());
+ } else {
+ return mPipInputMap.get(TUNER_INPUT_ID);
+ }
+ }
+
+ /**
+ * Returns true, if {@code channel1} and {@code channel2} belong to the same input. For example,
+ * two channels from different tuner inputs are also in the same input "Tuner" from PIP
+ * point of view.
+ */
+ public boolean areInSamePipInput(Channel channel1, Channel channel2) {
+ PipInput input1 = getPipInput(channel1);
+ PipInput input2 = getPipInput(channel2);
+ return input1 != null && input2 != null
+ && getPipInput(channel1).equals(getPipInput(channel2));
+ }
+
+ private void initializePipInputList() {
+ boolean hasTunerInput = false;
+ for (TvInputInfo input : mInputManager.getTvInputInfos(false, false)) {
+ if (input.isPassthroughInput()) {
+ boolean available = mInputManager.getInputState(input)
+ == TvInputManager.INPUT_STATE_CONNECTED;
+ mPipInputMap.put(input.getId(), new PipInput(input.getId(), available));
+ } else if (!hasTunerInput) {
+ hasTunerInput = true;
+ boolean available = mChannelTuner.getBrowsableChannelCount() != 0;
+ mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available));
+ }
+ }
+ PipInput input = getPipInput(mChannelTuner.getCurrentChannel());
+ if (input != null) {
+ input.updateAvailability();
+ }
+ for (Listener l : mListeners) {
+ l.onPipInputListUpdated();
+ }
+ }
+
+ /**
+ * Listeners to notify PIP input state changes.
+ */
+ public interface Listener {
+ /**
+ * Called when the state (availability) of PIP inputs is changed.
+ */
+ void onPipInputStateUpdated();
+
+ /**
+ * Called when the list of PIP inputs is changed.
+ */
+ void onPipInputListUpdated();
+ }
+
+ /**
+ * Input class for PIP. It has useful methods for PIP handling.
+ */
+ public class PipInput {
+ private final String mInputId;
+ private final boolean mIsPassthrough;
+ private final TvInputInfo mInputInfo;
+ private boolean mAvailable;
+
+ private PipInput(String inputId, boolean available) {
+ mInputId = inputId;
+ mIsPassthrough = !mInputId.equals(TUNER_INPUT_ID);
+ if (mIsPassthrough) {
+ mInputInfo = mInputManager.getTvInputInfo(mInputId);
+ } else {
+ mInputInfo = null;
+ }
+ mAvailable = available;
+ }
+
+ /**
+ * Returns the {@link TvInputInfo} object that matches to this PIP input.
+ */
+ public TvInputInfo getInputInfo() {
+ return mInputInfo;
+ }
+
+ /**
+ * Returns {@code true}, if the input is available for PIP. If a channel of an input is
+ * already played or an input is not connected state or there is no browsable channel, the
+ * input is unavailable.
+ */
+ public boolean isAvailable() {
+ return mAvailable;
+ }
+
+ /**
+ * Returns true, if the input is a passthrough TV input.
+ */
+ public boolean isPassthrough() {
+ return mIsPassthrough;
+ }
+
+ /**
+ * Gets a channel to play in a PIP view.
+ */
+ public Channel getChannel() {
+ if (mIsPassthrough) {
+ return Channel.createPassthroughChannel(mInputId);
+ } else {
+ return mChannelTuner.findNearestBrowsableChannel(
+ Utils.getLastWatchedChannelId(mContext));
+ }
+ }
+
+ /**
+ * Gets a label of the input.
+ */
+ public String getLabel() {
+ if (mIsPassthrough) {
+ return mInputInfo.loadLabel(mContext).toString();
+ } else {
+ return mContext.getString(R.string.input_selector_tuner_label);
+ }
+ }
+
+ /**
+ * Gets a long label including a customized label.
+ */
+ public String getLongLabel() {
+ if (mIsPassthrough) {
+ String customizedLabel = Utils.loadLabel(mContext, mInputInfo);
+ String label = getLabel();
+ if (label.equals(customizedLabel)) {
+ return customizedLabel;
+ }
+ return customizedLabel + " (" + label + ")";
+ } else {
+ return mContext.getString(R.string.input_long_label_for_tuner);
+ }
+ }
+
+ /**
+ * Updates availability. It returns true, if availability is changed.
+ */
+ private void updateAvailability() {
+ boolean available;
+ // current playing input cannot be available for PIP.
+ Channel currentChannel = mChannelTuner.getCurrentChannel();
+ if (mIsPassthrough) {
+ if (currentChannel != null && currentChannel.getInputId().equals(mInputId)) {
+ available = false;
+ } else {
+ available = mInputManager.getInputState(mInputId)
+ == TvInputManager.INPUT_STATE_CONNECTED;
+ }
+ } else {
+ if (currentChannel != null && !currentChannel.isPassthrough()) {
+ available = false;
+ } else {
+ available = mChannelTuner.getBrowsableChannelCount() > 0;
+ }
+ }
+ if (mAvailable != available) {
+ mAvailable = available;
+ for (Listener l : mListeners) {
+ l.onPipInputStateUpdated();
+ }
+ }
+ }
+
+ private boolean isHidden() {
+ // mInputInfo is null for the tuner input and it's always visible.
+ return mInputInfo != null && mInputInfo.isHidden(mContext);
+ }
+ }
+}
diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java
index 324afe73..4135bd4e 100644
--- a/src/com/android/tv/util/RecurringRunner.java
+++ b/src/com/android/tv/util/RecurringRunner.java
@@ -57,15 +57,12 @@ public final class RecurringRunner {
mHandler = new Handler(mContext.getMainLooper());
}
- public void start(boolean restNextRunTime) {
+ public void start() {
SoftPreconditions.checkState(!mRunning, TAG, mName + " start is called twice.");
if (mRunning) {
return;
}
mRunning = true;
- if (restNextRunTime) {
- resetNextRunTime();
- }
new AsyncTask<Void, Void, Long>() {
@Override
protected Long doInBackground(Void... params) {
@@ -79,10 +76,6 @@ public final class RecurringRunner {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- public void start() {
- start(false);
- }
-
public void stop() {
mRunning = false;
mHandler.removeCallbacksAndMessages(null);
diff --git a/src/com/android/tv/util/SearchManagerHelper.java b/src/com/android/tv/util/SearchManagerHelper.java
new file mode 100644
index 00000000..b6e34d7a
--- /dev/null
+++ b/src/com/android/tv/util/SearchManagerHelper.java
@@ -0,0 +1,61 @@
+/*
+ * 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.app.SearchManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A convenience class for calling methods in android.app.SearchManager.
+ */
+public final class SearchManagerHelper {
+ private static final String TAG = "SearchManagerHelper";
+
+ private static final Object sLock = new Object();
+ private static SearchManagerHelper sInstance;
+
+ private final SearchManager mSearchManager;
+
+ private SearchManagerHelper(Context context) {
+ mSearchManager = ((android.app.SearchManager) context.getSystemService(
+ Context.SEARCH_SERVICE));
+ }
+
+ public static SearchManagerHelper getInstance(Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new SearchManagerHelper(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+ }
+
+ public void launchAssistAction() {
+ try {
+ SearchManager.class.getDeclaredMethod("launchLegacyAssist", String.class, Integer.TYPE,
+ Bundle.class).invoke(mSearchManager, null, UserHandle.myUserId(), null);
+ } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException
+ | InvocationTargetException e) {
+ Log.e(TAG, "Fail to call SearchManager.launchAssistAction", e);
+ }
+ }
+}
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
index 0dabe7c0..8223a81c 100644
--- a/src/com/android/tv/util/SetupUtils.java
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -114,7 +114,7 @@ public class SetupUtils {
@Override
public void onLoadFinished() {
manager.removeListener(this);
- updateChannelsAfterSetup(mTvApplication, inputId, postRunnable);
+ updateChannelBrowsable(mTvApplication, inputId, postRunnable);
}
@Override
@@ -124,18 +124,17 @@ public class SetupUtils {
public void onChannelBrowsableChanged() { }
});
} else {
- updateChannelsAfterSetup(mTvApplication, inputId, postRunnable);
+ updateChannelBrowsable(mTvApplication, inputId, postRunnable);
}
}
- private static void updateChannelsAfterSetup(Context context, final String inputId,
+ private static void updateChannelBrowsable(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)) {
@@ -143,14 +142,8 @@ public class SetupUtils {
manager.updateBrowsable(channel.getId(), true, true);
browsableChanged = true;
}
- if (firstChannelForInput == null) {
- firstChannelForInput = channel;
- }
}
}
- if (firstChannelForInput != null) {
- Utils.setLastWatchedChannel(context, firstChannelForInput);
- }
if (browsableChanged) {
manager.notifyChannelBrowsableChanged();
manager.applyUpdatedValuesToDb();
@@ -392,7 +385,10 @@ public class SetupUtils {
// Start fetching program guide data for internal tuners.
Context context = mTvApplication.getApplicationContext();
if (Utils.isInternalTvInput(context, inputId)) {
- EpgFetcher.getInstance(context).startImmediately(true);
+ if (context.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED && Experiments.CLOUD_EPG.get()) {
+ EpgFetcher.getInstance(context).startImmediately();
+ }
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java
index 67970ed6..121f56ed 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -18,26 +18,20 @@ package com.android.tv.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Handler;
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.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -48,64 +42,14 @@ import java.util.Map;
public class TvInputManagerHelper {
private static final String TAG = "TvInputManagerHelper";
private static final boolean DEBUG = false;
-
- /**
- * 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;
- public static final int TYPE_MHL_MOBILE = -6;
-
- 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 META_LABEL_SORT_KEY = "input_sort_key";
-
- /**
- * 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);
- DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE);
- DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_RECORDER);
- DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_PLAYBACK);
- DEFAULT_TV_INPUT_PRIORITY.add(TYPE_MHL_MOBILE);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_HDMI);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DVI);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPONENT);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SVIDEO);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPOSITE);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DISPLAY_PORT);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_VGA);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SCART);
- DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER);
- }
-
private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
};
- private static final String[] TESTABLE_INPUTS = {
- "com.android.tv.testinput/.TestTvInputService"
- };
-
private final Context mContext;
- private final PackageManager mPackageManager;
private final TvInputManager 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<>();
- private final Map<String, String> mTvInputCustomLabels = new ArrayMap<>();
private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
-
- private final Map<String, CharSequence> mTvInputApplicationLabels = new ArrayMap<>();
- 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) {
@@ -128,11 +72,6 @@ public class TvInputManagerHelper {
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());
- }
mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
}
@@ -146,11 +85,6 @@ public class TvInputManagerHelper {
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();
@@ -169,14 +103,6 @@ public class TvInputManagerHelper {
}
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);
}
@@ -188,11 +114,6 @@ public class TvInputManagerHelper {
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);
}
@@ -210,7 +131,6 @@ public class TvInputManagerHelper {
public TvInputManagerHelper(Context context) {
mContext = context.getApplicationContext();
- mPackageManager = context.getPackageManager();
mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
mContentRatingsManager = new ContentRatingsManager(context);
mParentalControlSettings = new ParentalControlSettings(context);
@@ -225,11 +145,6 @@ public class TvInputManagerHelper {
mStarted = true;
mTvInputManager.registerCallback(mInternalCallback, mHandler);
mInputMap.clear();
- mTvInputLabels.clear();
- mTvInputCustomLabels.clear();
- mTvInputApplicationLabels.clear();
- mTvInputApplicationIcons.clear();
- mTvInputAppliactionBanners.clear();
mInputStateMap.clear();
mInputIdToPartnerInputMap.clear();
for (TvInputInfo input : mTvInputManager.getTvInputList()) {
@@ -256,23 +171,9 @@ public class TvInputManagerHelper {
mStarted = false;
mInputStateMap.clear();
mInputMap.clear();
- mTvInputLabels.clear();
- mTvInputCustomLabels.clear();
- mTvInputApplicationLabels.clear();
- mTvInputApplicationIcons.clear();
- mTvInputAppliactionBanners.clear();;
mInputIdToPartnerInputMap.clear();
}
- /**
- * Clears the TvInput labels map.
- */
- public void clearTvInputLabels() {
- mTvInputLabels.clear();
- mTvInputCustomLabels.clear();
- mTvInputApplicationLabels.clear();
- }
-
public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
ArrayList<TvInputInfo> list = new ArrayList<>();
for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
@@ -344,69 +245,7 @@ public class TvInputManagerHelper {
*/
@VisibleForTesting
public String loadLabel(TvInputInfo info) {
- String label = mTvInputLabels.get(info.getId());
- if (label == null) {
- label = info.loadLabel(mContext).toString();
- mTvInputLabels.put(info.getId(), label);
- }
- return label;
- }
-
- /**
- * Loads custom label of {@code info}
- */
- public String loadCustomLabel(TvInputInfo info) {
- String customLabel = mTvInputCustomLabels.get(info.getId());
- if (customLabel == null) {
- CharSequence customLabelCharSequence = info.loadCustomLabel(mContext);
- if (customLabelCharSequence != null) {
- customLabel = customLabelCharSequence.toString();
- mTvInputCustomLabels.put(info.getId(), customLabel);
- }
- }
- return customLabel;
- }
-
- /**
- * Gets the tv input application's label.
- */
- public CharSequence getTvInputApplicationLabel(CharSequence inputId) {
- return mTvInputApplicationLabels.get(inputId);
- }
-
- /**
- * 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.
- */
- public Drawable getTvInputApplicationIcon(String inputId) {
- return mTvInputApplicationIcons.get(inputId);
- }
-
- /**
- * 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.
- */
- public Drawable getTvInputApplicationBanner(String inputId) {
- return mTvInputAppliactionBanners.get(inputId);
- }
-
- /**
- * Stores the tv input application's banner.
- */
- public void setTvInputApplicationBanner(String inputId, Drawable banner) {
- mTvInputAppliactionBanners.put(inputId, banner);
+ return info.loadLabel(mContext).toString();
}
/**
@@ -482,54 +321,14 @@ public class TvInputManagerHelper {
return mContentRatingsManager;
}
- private int getInputSortKey(TvInputInfo input) {
- return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY,
- Integer.MAX_VALUE);
- }
-
- private boolean isInputPhysicalTuner(TvInputInfo input) {
- String packageName = input.getServiceInfo().packageName;
- if (Arrays.asList(mPhysicalTunerBlackList).contains(packageName)) {
- return false;
- }
-
- if (input.createSetupIntent() == null) {
- return false;
- } else {
- 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) {
- return false;
- }
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
- }
- return true;
- }
-
private boolean isInBlackList(String inputId) {
- if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
- for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
- if (inputId.contains(disabledTunerInputPrefix)) {
- return true;
- }
- }
+ if (!Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
+ return false;
}
- if (TvCommonUtils.isRunningInTest()) {
- for (String testableInput : TESTABLE_INPUTS) {
- if (testableInput.equals(inputId)) {
- return false;
- }
+ for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
+ if (inputId.contains(disabledTunerInputPrefix)) {
+ return true;
}
- return true;
}
return false;
}
@@ -558,123 +357,4 @@ public class TvInputManagerHelper {
return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
}
}
-
- /**
- * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of
- * TV inputs.
- */
- public static class InputComparator implements Comparator<TvInputInfo> {
- private Map<Integer, Integer> mTypePriorities = new HashMap<>();
- private final TvInputManagerHelper mTvInputManagerHelper;
- private final Context mContext;
-
- public InputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) {
- mContext = context;
- mTvInputManagerHelper = tvInputManagerHelper;
- setupDeviceTypePriorities();
- }
-
- @Override
- public int compare(TvInputInfo lhs, TvInputInfo rhs) {
- if (lhs == null) {
- return (rhs == null) ? 0 : 1;
- }
- if (rhs == null) {
- return -1;
- }
-
- 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;
- }
-
- int priorityL = getPriority(lhs);
- int priorityR = getPriority(rhs);
- if (priorityL != priorityR) {
- return priorityL - priorityR;
- }
-
- if (lhs.getType() == TvInputInfo.TYPE_TUNER
- && rhs.getType() == TvInputInfo.TYPE_TUNER) {
- boolean isPhysicalL = mTvInputManagerHelper.isInputPhysicalTuner(lhs);
- boolean isPhysicalR = mTvInputManagerHelper.isInputPhysicalTuner(rhs);
- if (isPhysicalL != isPhysicalR) {
- return isPhysicalL ? -1 : 1;
- }
- }
-
- int sortKeyL = mTvInputManagerHelper.getInputSortKey(lhs);
- int sortKeyR = mTvInputManagerHelper.getInputSortKey(rhs);
- if (sortKeyL != sortKeyR) {
- return sortKeyR - sortKeyL;
- }
-
- 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()))
- : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId()));
-
- if (!TextUtils.equals(parentLabelL, parentLabelR)) {
- return parentLabelL.compareToIgnoreCase(parentLabelR);
- }
- return getLabel(lhs).compareToIgnoreCase(getLabel(rhs));
- }
-
- private String getLabel(TvInputInfo input) {
- if (input == null) {
- return "";
- }
- String label = mTvInputManagerHelper.loadCustomLabel(input);
- if (TextUtils.isEmpty(label)) {
- label = mTvInputManagerHelper.loadLabel(input);
- }
- return label;
- }
-
- private int getPriority(TvInputInfo info) {
- Integer priority = null;
- if (mTypePriorities != null) {
- priority = mTypePriorities.get(getTvInputTypeForPriority(info));
- }
- if (priority != null) {
- return priority;
- }
- return Integer.MAX_VALUE;
- }
-
- private void setupDeviceTypePriorities() {
- mTypePriorities = Partner.getInstance(mContext).getInputsOrderMap();
-
- // Fill in any missing priorities in the map we got from the OEM
- int priority = mTypePriorities.size();
- for (int type : DEFAULT_TV_INPUT_PRIORITY) {
- if (!mTypePriorities.containsKey(type)) {
- mTypePriorities.put(type, priority++);
- }
- }
- }
-
- private int getTvInputTypeForPriority(TvInputInfo info) {
- if (info.getHdmiDeviceInfo() != null) {
- if (info.getHdmiDeviceInfo().isCecDevice()) {
- switch (info.getHdmiDeviceInfo().getDeviceType()) {
- case HdmiDeviceInfo.DEVICE_RECORDER:
- return TYPE_CEC_DEVICE_RECORDER;
- case HdmiDeviceInfo.DEVICE_PLAYBACK:
- return TYPE_CEC_DEVICE_PLAYBACK;
- default:
- return TYPE_CEC_DEVICE;
- }
- } else if (info.getHdmiDeviceInfo().isMhlDevice()) {
- return TYPE_MHL_MOBILE;
- }
- }
- return info.getType();
- }
- }
}
diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java
index 970cd055..97ff59d6 100644
--- a/src/com/android/tv/util/TvSettings.java
+++ b/src/com/android/tv/util/TvSettings.java
@@ -17,8 +17,6 @@
package com.android.tv.util;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.media.tv.TvTrackInfo;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
@@ -28,6 +26,7 @@ import java.util.Collections;
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.
@@ -36,21 +35,44 @@ public final class TvSettings {
private TvSettings() {}
public static final String PREF_DISPLAY_MODE = "display_mode"; // int value
+ public static final String PREF_PIP_LAYOUT = "pip_layout"; // int value
+ public static final String PREF_PIP_SIZE = "pip_size"; // int value
public static final String PREF_PIN = "pin"; // 4-digit string value. Otherwise, it's not set.
+ // PIP sounds
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ PIP_SOUND_MAIN, PIP_SOUND_PIP_WINDOW })
+ public @interface PipSound {}
+ public static final int PIP_SOUND_MAIN = 0;
+ public static final int PIP_SOUND_PIP_WINDOW = PIP_SOUND_MAIN + 1;
+
+ // PIP layouts
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ PIP_LAYOUT_BOTTOM_RIGHT, PIP_LAYOUT_TOP_RIGHT, PIP_LAYOUT_TOP_LEFT,
+ PIP_LAYOUT_BOTTOM_LEFT, PIP_LAYOUT_SIDE_BY_SIDE })
+ public @interface PipLayout {}
+ public static final int PIP_LAYOUT_BOTTOM_RIGHT = 0;
+ public static final int PIP_LAYOUT_TOP_RIGHT = PIP_LAYOUT_BOTTOM_RIGHT + 1;
+ public static final int PIP_LAYOUT_TOP_LEFT = PIP_LAYOUT_TOP_RIGHT + 1;
+ public static final int PIP_LAYOUT_BOTTOM_LEFT = PIP_LAYOUT_TOP_LEFT + 1;
+ public static final int PIP_LAYOUT_SIDE_BY_SIDE = PIP_LAYOUT_BOTTOM_LEFT + 1;
+ public static final int PIP_LAYOUT_LAST = PIP_LAYOUT_SIDE_BY_SIDE;
+
+ // PIP sizes
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ PIP_SIZE_SMALL, PIP_SIZE_BIG })
+ public @interface PipSize {}
+ public static final int PIP_SIZE_SMALL = 0;
+ public static final int PIP_SIZE_BIG = PIP_SIZE_SMALL + 1;
+ public static final int PIP_SIZE_LAST = PIP_SIZE_BIG;
+
// Multi-track audio settings
private static final String PREF_MULTI_AUDIO_ID = "pref.multi_audio_id";
private static final String PREF_MULTI_AUDIO_LANGUAGE = "pref.multi_audio_language";
private static final String PREF_MULTI_AUDIO_CHANNEL_COUNT = "pref.multi_audio_channel_count";
- // DVR Multi-audio and subtitle settings
- private static final String PREF_DVR_MULTI_AUDIO_ID = "pref.dvr_multi_audio_id";
- private static final String PREF_DVR_MULTI_AUDIO_LANGUAGE = "pref.dvr_multi_audio_language";
- private static final String PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT =
- "pref.dvr_multi_audio_channel_count";
- private static final String PREF_DVR_SUBTITLE_ID = "pref.dvr_subtitle_id";
- private static final String PREF_DVR_SUBTITLE_LANGUAGE = "pref.dvr_subtitle_language";
-
// Parental Control settings
private static final String PREF_CONTENT_RATING_SYSTEMS = "pref.content_rating_systems";
private static final String PREF_CONTENT_RATING_LEVEL = "pref.content_rating_level";
@@ -67,6 +89,59 @@ public final class TvSettings {
public static final int CONTENT_RATING_LEVEL_LOW = 3;
public static final int CONTENT_RATING_LEVEL_CUSTOM = 4;
+ // PIP settings
+ /**
+ * Returns the layout of the PIP window stored in the shared preferences.
+ *
+ * @return the saved layout of the PIP window. This value is one of
+ * {@link #PIP_LAYOUT_TOP_LEFT}, {@link #PIP_LAYOUT_TOP_RIGHT},
+ * {@link #PIP_LAYOUT_BOTTOM_LEFT}, {@link #PIP_LAYOUT_BOTTOM_RIGHT} and
+ * {@link #PIP_LAYOUT_SIDE_BY_SIDE}. If the preference value does not exist,
+ * {@link #PIP_LAYOUT_BOTTOM_RIGHT} is returned.
+ */
+ @SuppressWarnings("ResourceType")
+ @PipLayout
+ public static int getPipLayout(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getInt(
+ PREF_PIP_LAYOUT, PIP_LAYOUT_BOTTOM_RIGHT);
+ }
+
+ /**
+ * Stores the layout of PIP window to the shared preferences.
+ *
+ * @param pipLayout This value should be one of {@link #PIP_LAYOUT_TOP_LEFT},
+ * {@link #PIP_LAYOUT_TOP_RIGHT}, {@link #PIP_LAYOUT_BOTTOM_LEFT},
+ * {@link #PIP_LAYOUT_BOTTOM_RIGHT} and {@link #PIP_LAYOUT_SIDE_BY_SIDE}.
+ */
+ public static void setPipLayout(Context context, @PipLayout int pipLayout) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(
+ PREF_PIP_LAYOUT, pipLayout).apply();
+ }
+
+ /**
+ * Returns the size of the PIP view stored in the shared preferences.
+ *
+ * @return the saved size of the PIP view. This value is one of
+ * {@link #PIP_SIZE_SMALL} and {@link #PIP_SIZE_BIG}. If the preference value does not
+ * exist, {@link #PIP_SIZE_SMALL} is returned.
+ */
+ @SuppressWarnings("ResourceType")
+ @PipSize
+ public static int getPipSize(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getInt(
+ PREF_PIP_SIZE, PIP_SIZE_SMALL);
+ }
+
+ /**
+ * Stores the size of PIP view to the shared preferences.
+ *
+ * @param pipSize This value should be one of {@link #PIP_SIZE_SMALL} and {@link #PIP_SIZE_BIG}.
+ */
+ public static void setPipSize(Context context, @PipSize int pipSize) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(
+ PREF_PIP_SIZE, pipSize).apply();
+ }
+
// Multi-track audio settings
public static String getMultiAudioId(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getString(
@@ -98,57 +173,6 @@ public final class TvSettings {
PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount).apply();
}
- 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();
- } else {
- 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();
- }
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- if (info == null) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .remove(PREF_DVR_SUBTITLE_ID).apply();
- } else {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putString(PREF_DVR_SUBTITLE_LANGUAGE, info.getLanguage())
- .putString(PREF_DVR_SUBTITLE_ID, info.getId()).apply();
- }
- }
- }
-
- public static TvTrackInfo getDvrPlaybackTrackSettings(Context context,
- int trackType) {
- String language;
- String trackId;
- int channelCount;
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
- if (trackType == TvTrackInfo.TYPE_AUDIO) {
- trackId = pref.getString(PREF_DVR_MULTI_AUDIO_ID, null);
- if (trackId == null) {
- return null;
- }
- 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();
- } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
- trackId = pref.getString(PREF_DVR_SUBTITLE_ID, null);
- if (trackId == null) {
- return null;
- }
- language = pref.getString(PREF_DVR_SUBTITLE_LANGUAGE, null);
- return new TvTrackInfo.Builder(trackType, trackId).setLanguage(language).build();
- } else {
- return null;
- }
- }
-
// Parental Control settings
public static void addContentRatingSystems(Context context, Set<String> ids) {
Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
@@ -230,4 +254,4 @@ public final class TvSettings {
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..c004f001 100644
--- a/src/com/android/tv/util/TvTrackInfoUtils.java
+++ b/src/com/android/tv/util/TvTrackInfoUtils.java
@@ -52,22 +52,35 @@ 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);
- if (lhsLangMatch && rhsLangMatch) {
- 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));
+ boolean lhsLangMatch = language == null || Utils.isEqualLanguage(lhs.getLanguage(),
+ language);
+ if (rhsLangMatch) {
+ if (lhsLangMatch) {
+ boolean rhsCountMatch = rhs.getAudioChannelCount() == channelCount;
+ boolean lhsCountMatch = lhs.getAudioChannelCount() == channelCount;
+ if (rhsCountMatch) {
+ if (lhsCountMatch) {
+ boolean rhsIdMatch = rhs.getId().equals(id);
+ boolean lhsIdMatch = lhs.getId().equals(id);
+ if (rhsIdMatch) {
+ return lhsIdMatch ? 0 : -1;
+ } else {
+ return lhsIdMatch ? 1 : 0;
+ }
+
+ } else {
+ return -1;
+ }
+ } else {
+ return lhsCountMatch ? 1 : 0;
+ }
} else {
- return Boolean.compare(lhsCountMatch, rhsCountMatch);
+ return -1;
}
} else {
- return Boolean.compare(lhsLangMatch, rhsLangMatch);
+ return lhsLangMatch ? 1 : 0;
}
}
};
@@ -99,4 +112,4 @@ public class TvTrackInfoUtils {
private TvTrackInfoUtils() {
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java
index 3fe2ec3d..99d34431 100644
--- a/src/com/android/tv/util/Utils.java
+++ b/src/com/android/tv/util/Utils.java
@@ -44,13 +44,11 @@ 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.common.SoftPreconditions;
import com.android.tv.data.Channel;
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 java.text.SimpleDateFormat;
@@ -85,6 +83,9 @@ public class Utils {
public static final String EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED =
"recorded_program_pin_checked";
+ // Query parameter in the intent of starting MainActivity.
+ public static final String PARAM_SOURCE = "source";
+
private static final String PATH_CHANNEL = "channel";
private static final String PATH_PROGRAM = "program";
@@ -96,8 +97,6 @@ public class Utils {
"last_watched_tuner_input_id";
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";
private static final int VIDEO_SD_WIDTH = 704;
private static final int VIDEO_SD_HEIGHT = 480;
@@ -115,7 +114,6 @@ public class Utils {
private static final int AUDIO_CHANNEL_SURROUND_8 = 8;
private static final long RECORDING_FAILED_REASON_NONE = 0;
- 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.
@@ -209,28 +207,6 @@ public class Utils {
}
/**
- * 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,
- failedScheduledRecordingInfoSet)
- .apply();
- }
-
- /**
- * Clears the failed scheduled recording info set.
- */
- public static void clearFailedScheduledRecordingInfoSet(Context context) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .remove(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET)
- .apply();
- }
-
- /**
* Clears recording failed reason.
*/
public static void clearRecordingFailedReason(Context context, int reason) {
@@ -270,14 +246,6 @@ public class Utils {
}
/**
- * 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.
*/
public static boolean hasRecordingFailedReason(Context context, int reason) {
@@ -365,14 +333,6 @@ public class Utils {
}
/**
- * 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.
@@ -432,18 +392,16 @@ public class Utils {
: 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.
- */
+ @VisibleForTesting
public static boolean isInGivenDay(long dayToMatchInMillis, long subjectTimeInMillis) {
+ final long DAY_IN_MS = TimeUnit.DAYS.toMillis(1);
TimeZone timeZone = Calendar.getInstance().getTimeZone();
long offset = timeZone.getRawOffset();
if (timeZone.inDaylightTime(new Date(dayToMatchInMillis))) {
offset += timeZone.getDSTSavings();
}
- return Utils.floorTime(dayToMatchInMillis + offset, ONE_DAY_MS)
- == Utils.floorTime(subjectTimeInMillis + offset, ONE_DAY_MS);
+ return Utils.floorTime(dayToMatchInMillis + offset, DAY_IN_MS)
+ == Utils.floorTime(subjectTimeInMillis + offset, DAY_IN_MS);
}
/**
@@ -565,7 +523,7 @@ public class Utils {
if (track.getType() != TvTrackInfo.TYPE_AUDIO) {
throw new IllegalArgumentException("Not an audio track: " + track);
}
- String language = context.getString(R.string.multi_audio_unknown_language);
+ String language = context.getString(R.string.default_language);
if (!TextUtils.isEmpty(track.getLanguage())) {
language = new Locale(track.getLanguage()).getDisplayName();
} else {
@@ -902,11 +860,4 @@ public class Utils {
}
return Genres.encode(genres);
}
-
- /**
- * Returns true if the current user is a developer.
- */
- public static boolean isDeveloper() {
- return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get();
- }
}
diff --git a/src/com/android/tv/util/ViewCache.java b/src/com/android/tv/util/ViewCache.java
deleted file mode 100644
index 113bda27..00000000
--- a/src/com/android/tv/util/ViewCache.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.tv.util;
-
-import android.util.SparseArray;
-import android.view.View;
-
-import java.util.ArrayList;
-
-/**
- * A cache for the views.
- */
-public class ViewCache {
- private final static SparseArray<ArrayList<View>> mViews = new SparseArray();
-
- private static ViewCache sViewCache;
-
- private ViewCache() { }
-
- /**
- * Returns an instance of the view cache.
- */
- public static ViewCache getInstance() {
- if (sViewCache == null) {
- return new ViewCache();
- } else {
- return sViewCache;
- }
- }
-
- /**
- * Returns if the view cache is empty.
- */
- public boolean isEmpty() {
- return mViews.size() == 0;
- }
-
- /**
- * Stores a view into this view cache.
- */
- public void putView(int resId, View view) {
- ArrayList<View> views = mViews.get(resId);
- if (views == null) {
- views = new ArrayList();
- mViews.put(resId, views);
- }
- views.add(view);
- }
-
- /**
- * Returns the view for specific resource id.
- */
- public View getView(int resId) {
- ArrayList<View> views = mViews.get(resId);
- if (views != null && !views.isEmpty()) {
- View view = views.remove(views.size() - 1);
- if (views.isEmpty()) {
- mViews.remove(resId);
- }
- return view;
- } else {
- return null;
- }
- }
-
- /**
- * Clears the view cache.
- */
- public void clear() {
- mViews.clear();
- }
-}