From 658559699f5cd482bb19ade298db43a65d750664 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 11 Oct 2014 17:43:07 +0200 Subject: Moved core classes into subproject --- .../main/java/de/danoeh/antennapod/PodcastApp.java | 1 - .../antennapod/activity/AudioplayerActivity.java | 4 +- .../antennapod/activity/FeedInfoActivity.java | 2 +- .../danoeh/antennapod/activity/MainActivity.java | 2 +- .../antennapod/activity/MediaplayerActivity.java | 2 +- .../activity/OpmlImportBaseActivity.java | 4 +- .../antennapod/activity/PreferenceActivity.java | 2 +- .../antennapod/asynctask/OpmlExportWorker.java | 118 ++ .../antennapod/asynctask/OpmlFeedQueuer.java | 69 + .../antennapod/asynctask/OpmlImportWorker.java | 116 ++ .../config/ApplicationCallbacksImpl.java | 23 + .../antennapod/config/ClientConfigurator.java | 19 + .../config/DownloadServiceCallbacksImpl.java | 49 + .../antennapod/config/FlattrCallbacksImpl.java | 53 + .../antennapod/config/GpodnetCallbacksImpl.java | 22 + .../config/PlaybackServiceCallbacksImpl.java | 21 + .../antennapod/config/StorageCallbacksImpl.java | 107 ++ .../de/danoeh/antennapod/core/ClientConfig.java | 21 - .../antennapod/core/DownloadServiceCallbacks.java | 43 - .../de/danoeh/antennapod/core/FlattrCallbacks.java | 24 - .../danoeh/antennapod/core/GpodnetCallbacks.java | 26 - .../antennapod/core/PlaybackServiceCallbacks.java | 20 - .../danoeh/antennapod/core/StorageCallbacks.java | 27 - .../core/asynctask/DownloadObserver.java | 177 --- .../antennapod/core/asynctask/FeedRemover.java | 74 -- .../core/asynctask/FlattrClickWorker.java | 238 ---- .../core/asynctask/FlattrStatusFetcher.java | 47 - .../core/asynctask/FlattrTokenFetcher.java | 95 -- .../core/asynctask/OpmlExportWorker.java | 114 -- .../antennapod/core/asynctask/OpmlFeedQueuer.java | 69 - .../core/asynctask/OpmlImportWorker.java | 116 -- .../core/asynctask/PicassoImageResource.java | 37 - .../antennapod/core/asynctask/PicassoProvider.java | 152 --- .../antennapod/core/backup/OpmlBackupAgent.java | 211 --- .../antennapod/core/dialog/ConfirmationDialog.java | 64 - .../dialog/DownloadRequestErrorDialogCreator.java | 30 - .../danoeh/antennapod/core/dialog/TimeDialog.java | 138 -- .../de/danoeh/antennapod/core/feed/Chapter.java | 55 - .../antennapod/core/feed/EventDistributor.java | 140 -- .../java/de/danoeh/antennapod/core/feed/Feed.java | 445 ------- .../danoeh/antennapod/core/feed/FeedComponent.java | 66 - .../de/danoeh/antennapod/core/feed/FeedFile.java | 105 -- .../de/danoeh/antennapod/core/feed/FeedImage.java | 71 - .../de/danoeh/antennapod/core/feed/FeedItem.java | 332 ----- .../de/danoeh/antennapod/core/feed/FeedMedia.java | 408 ------ .../antennapod/core/feed/FeedPreferences.java | 89 -- .../de/danoeh/antennapod/core/feed/ID3Chapter.java | 36 - .../de/danoeh/antennapod/core/feed/MediaType.java | 5 - .../danoeh/antennapod/core/feed/SearchResult.java | 34 - .../danoeh/antennapod/core/feed/SimpleChapter.java | 25 - .../antennapod/core/feed/VorbisCommentChapter.java | 109 -- .../antennapod/core/gpoddernet/GpodnetService.java | 718 ---------- .../GpodnetServiceAuthenticationException.java | 21 - .../GpodnetServiceBadStatusCodeException.java | 12 - .../core/gpoddernet/GpodnetServiceException.java | 19 - .../core/gpoddernet/model/GpodnetDevice.java | 72 - .../core/gpoddernet/model/GpodnetPodcast.java | 65 - .../model/GpodnetSubscriptionChange.java | 41 - .../core/gpoddernet/model/GpodnetTag.java | 46 - .../model/GpodnetUploadChangesResponse.java | 56 - .../danoeh/antennapod/core/opml/OpmlElement.java | 46 - .../de/danoeh/antennapod/core/opml/OpmlReader.java | 87 -- .../danoeh/antennapod/core/opml/OpmlSymbols.java | 21 - .../de/danoeh/antennapod/core/opml/OpmlWriter.java | 65 - .../core/preferences/GpodnetPreferences.java | 246 ---- .../core/preferences/PlaybackPreferences.java | 146 -- .../core/preferences/UserPreferences.java | 577 -------- .../core/receiver/AlarmUpdateReceiver.java | 33 - .../core/receiver/ConnectivityActionReceiver.java | 46 - .../core/receiver/FeedUpdateReceiver.java | 46 - .../core/receiver/MediaButtonReceiver.java | 32 - .../antennapod/core/receiver/PlayerWidget.java | 50 - .../core/service/GpodnetSyncService.java | 245 ---- .../core/service/download/APRedirectHandler.java | 54 - .../service/download/AntennapodHttpClient.java | 96 -- .../core/service/download/DownloadRequest.java | 209 --- .../core/service/download/DownloadService.java | 1230 ----------------- .../core/service/download/DownloadStatus.java | 181 --- .../core/service/download/Downloader.java | 69 - .../core/service/download/DownloaderCallback.java | 10 - .../core/service/download/HttpDownloader.java | 246 ---- .../core/service/playback/PlaybackService.java | 1080 --------------- .../playback/PlaybackServiceMediaPlayer.java | 979 -------------- .../playback/PlaybackServiceTaskManager.java | 384 ------ .../core/service/playback/PlayerStatus.java | 14 - .../core/service/playback/PlayerWidgetService.java | 190 --- .../danoeh/antennapod/core/storage/DBReader.java | 908 ------------- .../de/danoeh/antennapod/core/storage/DBTasks.java | 895 ------------- .../danoeh/antennapod/core/storage/DBWriter.java | 974 -------------- .../core/storage/DownloadRequestException.java | 25 - .../antennapod/core/storage/DownloadRequester.java | 366 ----- .../core/storage/FeedItemStatistics.java | 70 - .../antennapod/core/storage/FeedSearcher.java | 57 - .../antennapod/core/storage/PodDBAdapter.java | 1391 -------------------- .../core/syndication/handler/FeedHandler.java | 34 - .../syndication/handler/FeedHandlerResult.java | 19 - .../core/syndication/handler/HandlerState.java | 98 -- .../core/syndication/handler/SyndHandler.java | 126 -- .../core/syndication/handler/TypeGetter.java | 111 -- .../handler/UnsupportedFeedtypeException.java | 38 - .../core/syndication/namespace/NSContent.java | 25 - .../core/syndication/namespace/NSITunes.java | 51 - .../core/syndication/namespace/NSMedia.java | 68 - .../core/syndication/namespace/NSRSS20.java | 141 -- .../syndication/namespace/NSSimpleChapters.java | 42 - .../core/syndication/namespace/Namespace.java | 21 - .../core/syndication/namespace/SyndElement.java | 22 - .../core/syndication/namespace/atom/AtomText.java | 46 - .../core/syndication/namespace/atom/NSAtom.java | 194 --- .../core/syndication/util/SyndDateUtils.java | 153 --- .../core/syndication/util/SyndTypeUtils.java | 42 - .../danoeh/antennapod/core/util/ChapterUtils.java | 261 ---- .../de/danoeh/antennapod/core/util/Converter.java | 103 -- .../danoeh/antennapod/core/util/DownloadError.java | 52 - .../de/danoeh/antennapod/core/util/DuckType.java | 117 -- .../danoeh/antennapod/core/util/EpisodeFilter.java | 49 - .../antennapod/core/util/FeedtitleComparator.java | 15 - .../antennapod/core/util/FileNameGenerator.java | 36 - .../antennapod/core/util/InvalidFeedException.java | 21 - .../de/danoeh/antennapod/core/util/LangUtils.java | 120 -- .../danoeh/antennapod/core/util/NetworkUtils.java | 69 - .../danoeh/antennapod/core/util/QueueAccess.java | 93 -- .../de/danoeh/antennapod/core/util/ShareUtils.java | 34 - .../antennapod/core/util/ShownotesProvider.java | 16 - .../danoeh/antennapod/core/util/StorageUtils.java | 66 - .../de/danoeh/antennapod/core/util/ThemeUtils.java | 22 - .../de/danoeh/antennapod/core/util/URIUtil.java | 35 - .../de/danoeh/antennapod/core/util/URLChecker.java | 51 - .../antennapod/core/util/UndoBarController.java | 137 -- .../comparator/ChapterStartTimeComparator.java | 20 - .../util/comparator/DownloadStatusComparator.java | 15 - .../util/comparator/FeedItemPubdateComparator.java | 19 - .../PlaybackCompletionDateComparator.java | 19 - .../comparator/SearchResultValueComparator.java | 14 - .../util/exception/MediaFileNotFoundException.java | 23 - .../core/util/flattr/FlattrServiceCreator.java | 25 - .../antennapod/core/util/flattr/FlattrStatus.java | 68 - .../antennapod/core/util/flattr/FlattrThing.java | 7 - .../antennapod/core/util/flattr/FlattrUtils.java | 305 ----- .../core/util/flattr/SimpleFlattrThing.java | 30 - .../core/util/gui/FeedItemUndoToken.java | 55 - .../core/util/id3reader/ChapterReader.java | 118 -- .../antennapod/core/util/id3reader/ID3Reader.java | 250 ---- .../core/util/id3reader/ID3ReaderException.java | 20 - .../core/util/id3reader/model/FrameHeader.java | 17 - .../core/util/id3reader/model/Header.java | 29 - .../core/util/id3reader/model/TagHeader.java | 26 - .../core/util/menuhandler/FeedItemMenuHandler.java | 191 --- .../core/util/menuhandler/FeedMenuHandler.java | 86 -- .../core/util/menuhandler/MenuItemUtils.java | 31 - .../core/util/menuhandler/NavDrawerActivity.java | 9 - .../antennapod/core/util/playback/AudioPlayer.java | 34 - .../core/util/playback/ExternalMedia.java | 235 ---- .../antennapod/core/util/playback/IPlayer.java | 69 - .../core/util/playback/MediaPlayerError.java | 23 - .../antennapod/core/util/playback/Playable.java | 207 --- .../core/util/playback/PlaybackController.java | 784 ----------- .../antennapod/core/util/playback/Timeline.java | 161 --- .../antennapod/core/util/playback/VideoPlayer.java | 67 - .../core/util/syndication/FeedDiscoverer.java | 78 -- .../util/vorbiscommentreader/OggInputStream.java | 81 -- .../VorbisCommentChapterReader.java | 101 -- .../vorbiscommentreader/VorbisCommentHeader.java | 26 - .../vorbiscommentreader/VorbisCommentReader.java | 194 --- .../VorbisCommentReaderException.java | 24 - .../danoeh/antennapod/dialog/FeedItemDialog.java | 2 +- .../de/danoeh/antennapod/dialog/TimeDialog.java | 138 ++ .../antennapod/fragment/ItemlistFragment.java | 6 +- .../antennapod/fragment/NewEpisodesFragment.java | 4 +- .../fragment/PlaybackHistoryFragment.java | 4 +- .../danoeh/antennapod/fragment/QueueFragment.java | 4 +- .../danoeh/antennapod/fragment/SearchFragment.java | 4 +- .../fragment/gpodnet/PodcastListFragment.java | 4 +- .../fragment/gpodnet/SearchListFragment.java | 4 +- .../fragment/gpodnet/TagListFragment.java | 4 +- .../menuhandler/FeedItemMenuHandler.java | 191 +++ .../antennapod/menuhandler/FeedMenuHandler.java | 86 ++ .../antennapod/menuhandler/MenuItemUtils.java | 31 + .../antennapod/menuhandler/NavDrawerActivity.java | 9 + .../danoeh/antennapod/receiver/PlayerWidget.java | 49 + .../antennapod/service/PlayerWidgetService.java | 192 +++ 181 files changed, 1319 insertions(+), 21777 deletions(-) create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java create mode 100644 app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/ClientConfig.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlExportWorker.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlFeedQueuer.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlImportWorker.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/dialog/TimeDialog.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/Feed.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/ID3Chapter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/SimpleChapter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/opml/OpmlElement.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/receiver/ConnectivityActionReceiver.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerWidgetService.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequestException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/Converter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/DuckType.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/InvalidFeedException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/UndoBarController.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3ReaderException.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedItemMenuHandler.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedMenuHandler.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/MenuItemUtils.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/NavDrawerActivity.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java delete mode 100644 app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReaderException.java create mode 100644 app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java create mode 100644 app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java create mode 100644 app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java create mode 100644 app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java create mode 100644 app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java create mode 100644 app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java create mode 100644 app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java (limited to 'app/src/main/java/de/danoeh/antennapod') diff --git a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java index 25aa9fb68..12213d6f9 100644 --- a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java +++ b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java @@ -12,7 +12,6 @@ import de.danoeh.antennapod.spa.SPAUtil; public class PodcastApp extends Application { private static final String TAG = "PodcastApp"; - public static final String EXPORT_DIR = "export/"; private static float LOGICAL_DENSITY; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java index 5622bc987..f9001adad 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -43,8 +43,8 @@ import de.danoeh.antennapod.fragment.ItemDescriptionFragment; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java index 80484df37..3000cfaeb 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -21,7 +21,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.util.LangUtils; -import de.danoeh.antennapod.core.util.menuhandler.FeedMenuHandler; +import de.danoeh.antennapod.menuhandler.FeedMenuHandler; /** * Displays information about a feed. diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 2c660019c..7029fd32c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -31,7 +31,7 @@ import de.danoeh.antennapod.fragment.*; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.StorageUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import java.util.List; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 249a3c5c3..14cb2727f 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -22,7 +22,7 @@ import com.doomonafireball.betterpickers.hmspicker.HmsPickerBuilder; import com.doomonafireball.betterpickers.hmspicker.HmsPickerDialogFragment; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.dialog.TimeDialog; +import de.danoeh.antennapod.dialog.TimeDialog; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java index 2e66978fd..d974e0e1b 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java @@ -4,8 +4,8 @@ import android.content.Intent; import android.support.v7.app.ActionBarActivity; import android.util.Log; import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.asynctask.OpmlFeedQueuer; -import de.danoeh.antennapod.core.asynctask.OpmlImportWorker; +import de.danoeh.antennapod.asynctask.OpmlFeedQueuer; +import de.danoeh.antennapod.asynctask.OpmlImportWorker; import de.danoeh.antennapod.core.opml.OpmlElement; import java.io.Reader; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index 65efcc230..484550a6a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -22,7 +22,7 @@ import android.widget.Toast; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.asynctask.FlattrClickWorker; -import de.danoeh.antennapod.core.asynctask.OpmlExportWorker; +import de.danoeh.antennapod.asynctask.OpmlExportWorker; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog; import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java new file mode 100644 index 000000000..6bba956a6 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java @@ -0,0 +1,118 @@ +package de.danoeh.antennapod.asynctask; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.opml.OpmlWriter; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.util.LangUtils; + +/** + * Writes an OPML file into the export directory in the background. + */ +public class OpmlExportWorker extends AsyncTask { + private static final String TAG = "OpmlExportWorker"; + private static final String DEFAULT_OUTPUT_NAME = "antennapod-feeds.opml"; + public static final String EXPORT_DIR = "export/"; + + private Context context; + private File output; + + private ProgressDialog progDialog; + private Exception exception; + + public OpmlExportWorker(Context context, File output) { + this.context = context; + this.output = output; + } + + public OpmlExportWorker(Context context) { + this.context = context; + } + + @Override + protected Void doInBackground(Void... params) { + OpmlWriter opmlWriter = new OpmlWriter(); + if (output == null) { + output = new File( + UserPreferences.getDataFolder(context, EXPORT_DIR), + DEFAULT_OUTPUT_NAME); + if (output.exists()) { + Log.w(TAG, "Overwriting previously exported file."); + output.delete(); + } + } + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); + opmlWriter.writeDocument(DBReader.getFeedList(context), writer); + } catch (IOException e) { + e.printStackTrace(); + exception = e; + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException ioe) { + exception = ioe; + } + } + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + progDialog.dismiss(); + AlertDialog.Builder alert = new AlertDialog.Builder(context) + .setNeutralButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + dialog.dismiss(); + } + }); + if (exception != null) { + alert.setTitle(R.string.export_error_label); + alert.setMessage(exception.getMessage()); + } else { + alert.setTitle(R.string.opml_export_success_title); + alert.setMessage(context + .getString(R.string.opml_export_success_sum) + + output.toString()); + } + alert.create().show(); + } + + @Override + protected void onPreExecute() { + progDialog = new ProgressDialog(context); + progDialog.setMessage(context.getString(R.string.exporting_label)); + progDialog.setIndeterminate(true); + progDialog.show(); + } + + @SuppressLint("NewApi") + public void executeAsync() { + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } else { + execute(); + } + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java new file mode 100644 index 000000000..cb9197b8e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java @@ -0,0 +1,69 @@ +package de.danoeh.antennapod.asynctask; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; +import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.activity.OpmlImportHolder; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.opml.OpmlElement; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; + +import java.util.Arrays; +import java.util.Date; + +/** Queues items for download in the background. */ +public class OpmlFeedQueuer extends AsyncTask { + private Context context; + private ProgressDialog progDialog; + private int[] selection; + + public OpmlFeedQueuer(Context context, int[] selection) { + super(); + this.context = context; + this.selection = Arrays.copyOf(selection, selection.length); + } + + @Override + protected void onPostExecute(Void result) { + progDialog.dismiss(); + } + + @Override + protected void onPreExecute() { + progDialog = new ProgressDialog(context); + progDialog.setMessage(context.getString(R.string.processing_label)); + progDialog.setCancelable(false); + progDialog.setIndeterminate(true); + progDialog.show(); + } + + @Override + protected Void doInBackground(Void... params) { + DownloadRequester requester = DownloadRequester.getInstance(); + for (int idx = 0; idx < selection.length; idx++) { + OpmlElement element = OpmlImportHolder.getReadElements().get( + selection[idx]); + Feed feed = new Feed(element.getXmlUrl(), new Date(), + element.getText()); + try { + requester.downloadFeed(context.getApplicationContext(), feed); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + } + return null; + } + + @SuppressLint("NewApi") + public void executeAsync() { + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } else { + execute(); + } + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java new file mode 100644 index 000000000..cfe0703ca --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -0,0 +1,116 @@ +package de.danoeh.antennapod.asynctask; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.os.AsyncTask; +import android.util.Log; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.opml.OpmlElement; +import de.danoeh.antennapod.core.opml.OpmlReader; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; + +public class OpmlImportWorker extends + AsyncTask> { + private static final String TAG = "OpmlImportWorker"; + + private Context context; + private Exception exception; + + private ProgressDialog progDialog; + + private Reader mReader; + + public OpmlImportWorker(Context context, Reader reader) { + super(); + this.context = context; + this.mReader=reader; + } + + @Override + protected ArrayList doInBackground(Void... params) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Starting background work"); + + if (mReader==null) { + return null; + } + + OpmlReader opmlReader = new OpmlReader(); + try { + ArrayList result = opmlReader.readDocument(mReader); + mReader.close(); + return result; + } catch (XmlPullParserException e) { + e.printStackTrace(); + exception = e; + return null; + } catch (IOException e) { + e.printStackTrace(); + exception = e; + return null; + } + + } + + @Override + protected void onPostExecute(ArrayList result) { + if (mReader != null) { + try { + mReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + progDialog.dismiss(); + if (exception != null) { + if (BuildConfig.DEBUG) + Log.d(TAG, + "An error occured while trying to parse the opml document"); + AlertDialog.Builder alert = new AlertDialog.Builder(context); + alert.setTitle(R.string.error_label); + alert.setMessage(context.getString(R.string.opml_reader_error) + + exception.getMessage()); + alert.setNeutralButton(android.R.string.ok, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + + }); + alert.create().show(); + } + } + + @Override + protected void onPreExecute() { + progDialog = new ProgressDialog(context); + progDialog.setMessage(context.getString(R.string.reading_opml_label)); + progDialog.setIndeterminate(true); + progDialog.setCancelable(false); + progDialog.show(); + } + + public boolean wasSuccessful() { + return exception != null; + } + + @SuppressLint("NewApi") + public void executeAsync() { + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } else { + execute(); + } + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java new file mode 100644 index 000000000..fdbb2139d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/ApplicationCallbacksImpl.java @@ -0,0 +1,23 @@ +package de.danoeh.antennapod.config; + + +import android.app.Application; +import android.content.Context; +import android.content.Intent; + +import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.activity.StorageErrorActivity; +import de.danoeh.antennapod.core.ApplicationCallbacks; + +public class ApplicationCallbacksImpl implements ApplicationCallbacks { + + @Override + public Application getApplicationInstance() { + return PodcastApp.getInstance(); + } + + @Override + public Intent getStorageErrorActivity(Context context) { + return new Intent(context, StorageErrorActivity.class); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java new file mode 100644 index 000000000..5dc3416c6 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -0,0 +1,19 @@ +package de.danoeh.antennapod.config; + +import de.danoeh.antennapod.core.ClientConfig; + +/** + * Configures the ClientConfig class of the core package. + */ +public class ClientConfigurator { + + static { + ClientConfig.USER_AGENT = "AntennaPod/0.9.9.3"; + ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); + ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); + ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl(); + ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); + ClientConfig.storageCallbacks = new StorageCallbacksImpl(); + ClientConfig.flattrCallbacks = new FlattrCallbacksImpl(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java new file mode 100644 index 000000000..0f180e9c5 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -0,0 +1,49 @@ +package de.danoeh.antennapod.config; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import de.danoeh.antennapod.activity.DownloadAuthenticationActivity; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.NavListAdapter; +import de.danoeh.antennapod.core.DownloadServiceCallbacks; +import de.danoeh.antennapod.core.service.download.DownloadRequest; +import de.danoeh.antennapod.fragment.DownloadsFragment; + + +public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { + + @Override + public PendingIntent getNotificationContentIntent(Context context) { + Intent intent = new Intent(context, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS); + Bundle args = new Bundle(); + args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_RUNNING); + intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); + + return PendingIntent.getActivity(context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + @Override + public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) { + final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class); + activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request); + activityIntent.putExtra(DownloadAuthenticationActivity.ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, true); + return PendingIntent.getActivity(context.getApplicationContext(), 0, activityIntent, PendingIntent.FLAG_ONE_SHOT); + } + + @Override + public PendingIntent getReportNotificationContentIntent(Context context) { + Intent intent = new Intent(context, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS); + Bundle args = new Bundle(); + args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); + intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java new file mode 100644 index 000000000..3817db6de --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java @@ -0,0 +1,53 @@ +package de.danoeh.antennapod.config; + + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import org.shredzone.flattr4j.oauth.AccessToken; + +import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.activity.FlattrAuthActivity; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.FlattrCallbacks; + +public class FlattrCallbacksImpl implements FlattrCallbacks { + private static final String TAG = "FlattrCallbacksImpl"; + + @Override + public boolean flattrEnabled() { + return true; + } + + @Override + public Intent getFlattrAuthenticationActivityIntent(Context context) { + return new Intent(context, FlattrAuthActivity.class); + } + + @Override + public PendingIntent getFlattrFailedNotificationContentIntent(Context context) { + return PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); + } + + @Override + public String getFlattrAppKey() { + return BuildConfig.FLATTR_APP_KEY; + } + + @Override + public String getFlattrAppSecret() { + return BuildConfig.FLATTR_APP_SECRET; + } + + @Override + public void handleFlattrAuthenticationSuccess(AccessToken token) { + FlattrAuthActivity instance = FlattrAuthActivity.getInstance(); + if (instance != null) { + instance.handleAuthenticationSuccess(); + } else { + Log.e(TAG, "FlattrAuthActivity instance was null"); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java new file mode 100644 index 000000000..5f8da6894 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/GpodnetCallbacksImpl.java @@ -0,0 +1,22 @@ +package de.danoeh.antennapod.config; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.GpodnetCallbacks; + + +public class GpodnetCallbacksImpl implements GpodnetCallbacks { + @Override + public boolean gpodnetEnabled() { + return true; + } + + @Override + public PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(Context context) { + return PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java new file mode 100644 index 000000000..d1e3a8379 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java @@ -0,0 +1,21 @@ +package de.danoeh.antennapod.config; + +import android.content.Context; +import android.content.Intent; + +import de.danoeh.antennapod.activity.AudioplayerActivity; +import de.danoeh.antennapod.activity.VideoplayerActivity; +import de.danoeh.antennapod.core.PlaybackServiceCallbacks; +import de.danoeh.antennapod.core.feed.MediaType; + + +public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { + @Override + public Intent getPlayerActivityIntent(Context context, MediaType mediaType) { + if (mediaType == MediaType.VIDEO) { + return new Intent(context, VideoplayerActivity.class); + } else { + return new Intent(context, AudioplayerActivity.class); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java new file mode 100644 index 000000000..ec133aed1 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java @@ -0,0 +1,107 @@ +package de.danoeh.antennapod.config; + + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import de.danoeh.antennapod.core.StorageCallbacks; +import de.danoeh.antennapod.core.storage.PodDBAdapter; + +public class StorageCallbacksImpl implements StorageCallbacks { + + @Override + public int getDatabaseVersion() { + return 12; + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " + + newVersion + "."); + if (oldVersion <= 1) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " + + PodDBAdapter.KEY_TYPE + " TEXT"); + } + if (oldVersion <= 2) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + PodDBAdapter.KEY_LINK + " TEXT"); + } + if (oldVersion <= 3) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + PodDBAdapter.KEY_ITEM_IDENTIFIER + " TEXT"); + } + if (oldVersion <= 4) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " + + PodDBAdapter.KEY_FEED_IDENTIFIER + " TEXT"); + } + if (oldVersion <= 5) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG + + " ADD COLUMN " + PodDBAdapter.KEY_REASON_DETAILED + " TEXT"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG + + " ADD COLUMN " + PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE + " TEXT"); + } + if (oldVersion <= 6) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + PodDBAdapter.KEY_CHAPTER_TYPE + " INTEGER"); + } + if (oldVersion <= 7) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE + + " INTEGER"); + } + if (oldVersion <= 8) { + final int KEY_ID_POSITION = 0; + final int KEY_MEDIA_POSITION = 1; + + // Add feeditem column to feedmedia table + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + PodDBAdapter.KEY_FEEDITEM + + " INTEGER"); + Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS, + new String[]{PodDBAdapter.KEY_ID, PodDBAdapter.KEY_MEDIA}, "? > 0", + new String[]{PodDBAdapter.KEY_MEDIA}, null, null, null); + if (feeditemCursor.moveToFirst()) { + db.beginTransaction(); + ContentValues contentValues = new ContentValues(); + do { + long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); + contentValues.put(PodDBAdapter.KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); + db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, PodDBAdapter.KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); + contentValues.clear(); + } while (feeditemCursor.moveToNext()); + db.setTransactionSuccessful(); + db.endTransaction(); + } + feeditemCursor.close(); + } + if (oldVersion <= 9) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + + " INTEGER DEFAULT 1"); + } + if (oldVersion <= 10) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS + + " INTEGER"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS + + " INTEGER"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + PodDBAdapter.KEY_PLAYED_DURATION + + " INTEGER"); + } + if (oldVersion <= 11) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_USERNAME + + " TEXT"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_PASSWORD + + " TEXT"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE + + " INTEGER"); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/core/ClientConfig.java b/app/src/main/java/de/danoeh/antennapod/core/ClientConfig.java deleted file mode 100644 index bf28c17ea..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/ClientConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core; - -/** - * Stores callbacks for core classes like Services, DB classes etc. and other configuration variables. - * Apps using the core module of AntennaPod should register implementations of all interfaces here. - */ -public class ClientConfig { - - /** - * Should be used when setting User-Agent header for HTTP-requests. - */ - public static String USER_AGENT; - - public static DownloadServiceCallbacks downloadServiceCallbacks; - - public static PlaybackServiceCallbacks playbackServiceCallbacks; - - public static GpodnetCallbacks gpodnetCallbacks; - - public static StorageCallbacks storageCallbacks; -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java b/app/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java deleted file mode 100644 index 9e4ed8e2b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.danoeh.antennapod.core; - -import android.app.PendingIntent; - -import de.danoeh.antennapod.core.service.download.DownloadRequest; - -/** - * Callbacks for the DownloadService of the core module - */ -public interface DownloadServiceCallbacks { - - /** - * Returns a PendingIntent for a notification the main notification of the DownloadService. - *

- * The PendingIntent takes the users to a screen where they can observe all currently running - * downloads. - * - * @return A non-null PendingIntent for the notification. - */ - public PendingIntent getNotificationContentIntent(); - - /** - * Returns a PendingIntent for a notification that tells the user to enter a username - * or a password for a requested download. - *

- * The PendingIntent takes users to an Activity that lets the user enter their username - * and password to retry the download. - * - * @return A non-null PendingIntent for the notification. - */ - public PendingIntent getAuthentificationNotificationContentIntent(DownloadRequest request); - - /** - * Returns a PendingIntent for notification that notifies the user about the completion of downloads - * along with information about failed and successful downloads. - *

- * The PendingIntent takes users to an activity where they can look at all successful and failed downloads. - * - * @return A non-null PendingIntent for the notification. - */ - public PendingIntent getReportNotificationContentIntent(); -} - diff --git a/app/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java b/app/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java deleted file mode 100644 index 2dde4d8f3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.danoeh.antennapod.core; - -import android.content.Intent; - -/** - * Callbacks for the flattr integration of the app. - */ -public interface FlattrCallbacks { - - /** - * Returns if true if the flattr integration should be activated, - * false otherwise. - */ - public boolean flattrEnabled(); - - /** - * Returns an intent that starts the activity that is responsible for - * letting users log into their flattr account. - * - * @return The intent that starts the authentication activity or null - * if flattr integration is disabled (i.e. flattrEnabled() == false). - */ - public Intent getFlattrAuthenticationActivityIntent(); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java b/app/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java deleted file mode 100644 index e937bf35c..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.danoeh.antennapod.core; - -import android.app.PendingIntent; - -/** - * Callbacks related to the gpodder.net integration of the core module - */ -public interface GpodnetCallbacks { - - - /** - * Returns if true if the gpodder.net integration should be activated, - * false otherwise. - */ - public boolean gpodnetEnabled(); - - /** - * Returns a PendingIntent for the error notification of the GpodnetSyncService. - *

- * What the PendingIntent does may be implementation-specific. - * - * @return A PendingIntent for the notification or null if gpodder.net integration - * has been disabled (i.e. gpodnetEnabled() == false). - */ - public PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java b/app/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java deleted file mode 100644 index a74c441c4..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.danoeh.antennapod.core; - -import android.content.Intent; - -import de.danoeh.antennapod.core.feed.MediaType; - -/** - * Callbacks for the PlaybackService of the core module - */ -public interface PlaybackServiceCallbacks { - - /** - * Returns an intent which starts an audio- or videoplayer, depending on the - * type of media that is being played. - * - * @param mediaType The type of media that is being played. - * @return A non-null activity intent. - */ - public Intent getPlayerActivityIntent(MediaType mediaType); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java b/app/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java deleted file mode 100644 index 5d1a0fffc..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.danoeh.antennapod.core; - -import android.database.sqlite.SQLiteDatabase; - -/** - * Callbacks for the classes in the storage package of the core module. - */ -public interface StorageCallbacks { - - /** - * Returns the current version of the database. - * - * @return The non-negative version number of the database. - */ - public int getDatabaseVersion(); - - /** - * Upgrades the given database from an old version to a newer version. - * - * @param db The database that is supposed to be upgraded. - * @param oldVersion The old version of the database. - * @param newVersion The version that the database is supposed to be upgraded to. - */ - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java deleted file mode 100644 index 8b3635af8..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java +++ /dev/null @@ -1,177 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.app.Activity; -import android.content.*; -import android.os.Handler; -import android.os.IBinder; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.service.download.Downloader; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Provides access to the DownloadService's list of items that are currently being downloaded. - * The DownloadObserver object should be created in the activity's onCreate() method. resume() and pause() - * should be called in the activity's onResume() and onPause() methods - */ -public class DownloadObserver { - private static final String TAG = "DownloadObserver"; - - /** - * Time period between update notifications. - */ - public static final int WAITING_INTERVAL_MS = 3000; - - private volatile Activity activity; - private final Handler handler; - private final Callback callback; - - private DownloadService downloadService = null; - private AtomicBoolean mIsBound = new AtomicBoolean(false); - - private Thread refresherThread; - private AtomicBoolean refresherThreadRunning = new AtomicBoolean(false); - - - /** - * Creates a new download observer. - * - * @param activity Used for registering receivers - * @param handler All callback methods are executed on this handler. The handler MUST run on the GUI thread. - * @param callback Callback methods for posting content updates - * @throws java.lang.IllegalArgumentException if one of the arguments is null. - */ - public DownloadObserver(Activity activity, Handler handler, Callback callback) { - Validate.notNull(activity); - Validate.notNull(handler); - Validate.notNull(callback); - - this.activity = activity; - this.handler = handler; - this.callback = callback; - } - - public void onResume() { - if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver resumed"); - activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED)); - connectToDownloadService(); - } - - public void onPause() { - if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver paused"); - try { - activity.unregisterReceiver(contentChangedReceiver); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - try { - activity.unbindService(mConnection); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - stopRefresher(); - } - - private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // reconnect to DownloadService if connection has been closed - if (downloadService == null) { - connectToDownloadService(); - } - callback.onContentChanged(); - startRefresher(); - } - }; - - public interface Callback { - void onContentChanged(); - - void onDownloadDataAvailable(List downloaderList); - } - - private void connectToDownloadService() { - activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0); - } - - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceDisconnected(ComponentName className) { - downloadService = null; - mIsBound.set(false); - stopRefresher(); - Log.i(TAG, "Closed connection with DownloadService."); - } - - public void onServiceConnected(ComponentName name, IBinder service) { - downloadService = ((DownloadService.LocalBinder) service) - .getService(); - mIsBound.set(true); - if (BuildConfig.DEBUG) - Log.d(TAG, "Connection to service established"); - List downloaderList = downloadService.getDownloads(); - if (downloaderList != null && !downloaderList.isEmpty()) { - callback.onDownloadDataAvailable(downloaderList); - startRefresher(); - } - } - }; - - private void stopRefresher() { - if (refresherThread != null) { - refresherThread.interrupt(); - } - } - - private void startRefresher() { - if (refresherThread == null || refresherThread.isInterrupted()) { - refresherThread = new Thread(new RefresherThread()); - refresherThread.start(); - } - } - - private class RefresherThread implements Runnable { - - public void run() { - refresherThreadRunning.set(true); - while (!Thread.interrupted()) { - try { - Thread.sleep(WAITING_INTERVAL_MS); - } catch (InterruptedException e) { - Log.d(TAG, "Refresher thread was interrupted"); - } - if (mIsBound.get()) { - postUpdate(); - } - } - refresherThreadRunning.set(false); - } - - private void postUpdate() { - handler.post(new Runnable() { - @Override - public void run() { - callback.onContentChanged(); - if (downloadService != null) { - List downloaderList = downloadService.getDownloads(); - if (downloaderList == null || downloaderList.isEmpty()) { - Thread.currentThread().interrupt(); - } - } - } - }); - } - } - - public void setActivity(Activity activity) { - Validate.notNull(activity); - this.activity = activity; - } - -} - diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java deleted file mode 100644 index 2201dfbe7..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.annotation.SuppressLint; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.os.AsyncTask; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.storage.DBWriter; - -import java.util.concurrent.ExecutionException; - -/** Removes a feed in the background. */ -public class FeedRemover extends AsyncTask { - Context context; - ProgressDialog dialog; - Feed feed; - - public FeedRemover(Context context, Feed feed) { - super(); - this.context = context; - this.feed = feed; - } - - @Override - protected Void doInBackground(Void... params) { - try { - DBWriter.deleteFeed(context, feed.getId()).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void onCancelled() { - dialog.dismiss(); - } - - @Override - protected void onPostExecute(Void result) { - dialog.dismiss(); - } - - @Override - protected void onPreExecute() { - dialog = new ProgressDialog(context); - dialog.setMessage(context.getString(R.string.feed_remover_msg)); - dialog.setOnCancelListener(new OnCancelListener() { - - @Override - public void onCancel(DialogInterface dialog) { - cancel(true); - - } - - }); - dialog.show(); - } - - @SuppressLint("NewApi") - public void executeAsync() { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java deleted file mode 100644 index 44ad91981..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java +++ /dev/null @@ -1,238 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.widget.Toast; - -import org.apache.commons.lang3.Validate; -import org.shredzone.flattr4j.exception.FlattrException; - -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.FlattrAuthActivity; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.flattr.FlattrThing; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; - -/** - * Performs a click action in a background thread. - *

- * When started, the flattr click worker will try to flattr every item that is in the flattr queue. If no network - * connection is available it will shut down immediately. The FlattrClickWorker can also be given one additional - * FlattrThing which will be flattrd immediately. - *

- * The FlattrClickWorker will display a toast notification for every item that has been flattrd. If the FlattrClickWorker failed - * to flattr something, a notification will be displayed. - */ -public class FlattrClickWorker extends AsyncTask { - protected static final String TAG = "FlattrClickWorker"; - - private static final int NOTIFICATION_ID = 4; - - private final Context context; - - public static enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS} - - private volatile int countFailed = 0; - private volatile int countSuccess = 0; - - private volatile FlattrThing extraFlattrThing; - - /** - * Only relevant if just one thing is flattrd - */ - private volatile FlattrException exception; - - /** - * Creates a new FlattrClickWorker which will only flattr all things in the queue. - *

- * The FlattrClickWorker has to be started by calling executeAsync(). - * - * @param context A context for accessing the database and posting notifications. Must not be null. - */ - public FlattrClickWorker(Context context) { - Validate.notNull(context); - this.context = context.getApplicationContext(); - } - - /** - * Creates a new FlattrClickWorker which will flattr all things in the queue and one additional - * FlattrThing. - *

- * The FlattrClickWorker has to be started by calling executeAsync(). - * - * @param context A context for accessing the database and posting notifications. Must not be null. - * @param extraFlattrThing The additional thing to flattr - */ - public FlattrClickWorker(Context context, FlattrThing extraFlattrThing) { - this(context); - this.extraFlattrThing = extraFlattrThing; - } - - - @Override - protected ExitCode doInBackground(Void... params) { - - if (!FlattrUtils.hasToken()) { - return ExitCode.NO_TOKEN; - } - - if (!NetworkUtils.networkAvailable(context)) { - return ExitCode.NO_NETWORK; - } - - final List flattrQueue = DBReader.getFlattrQueue(context); - if (extraFlattrThing != null) { - flattrQueue.add(extraFlattrThing); - } else if (flattrQueue.size() == 1) { - // if only one item is flattrd, the report can specifically mentioned that this item has failed - extraFlattrThing = flattrQueue.get(0); - } - - if (flattrQueue.isEmpty()) { - return ExitCode.NO_THINGS; - } - - List dbFutures = new LinkedList(); - for (FlattrThing thing : flattrQueue) { - if (BuildConfig.DEBUG) Log.d(TAG, "Processing " + thing.getTitle()); - - try { - thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely - FlattrUtils.clickUrl(context, thing.getPaymentLink()); - thing.getFlattrStatus().setFlattred(); - publishProgress(R.string.flattr_click_success); - countSuccess++; - - } catch (FlattrException e) { - e.printStackTrace(); - countFailed++; - if (countFailed == 1) { - exception = e; - } - } - - Future f = DBWriter.setFlattredStatus(context, thing, false); - if (f != null) { - dbFutures.add(f); - } - } - - for (Future f : dbFutures) { - try { - f.get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - - return ExitCode.EXIT_NORMAL; - } - - @Override - protected void onPostExecute(ExitCode exitCode) { - super.onPostExecute(exitCode); - switch (exitCode) { - case EXIT_NORMAL: - if (countFailed > 0) { - postFlattrFailedNotification(); - } - break; - case NO_NETWORK: - postToastNotification(R.string.flattr_click_enqueued); - break; - case NO_TOKEN: - postNoTokenNotification(); - break; - case NO_THINGS: // nothing to notify here - break; - } - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - postToastNotification(values[0]); - } - - private void postToastNotification(int msg) { - Toast.makeText(context, context.getString(msg), Toast.LENGTH_LONG).show(); - } - - private void postNoTokenNotification() { - PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, FlattrAuthActivity.class), 0); - - Notification notification = new NotificationCompat.Builder(context) - .setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg))) - .setContentIntent(contentIntent) - .setContentTitle(context.getString(R.string.no_flattr_token_title)) - .setTicker(context.getString(R.string.no_flattr_token_title)) - .setSmallIcon(R.drawable.stat_notify_sync_error) - .setOngoing(false) - .setAutoCancel(true) - .build(); - ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification); - } - - private void postFlattrFailedNotification() { - if (countFailed == 0) { - return; - } - - PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); - String title; - String subtext; - - if (countFailed == 1) { - title = context.getString(R.string.flattrd_failed_label); - String exceptionMsg = (exception.getMessage() != null) ? exception.getMessage() : ""; - subtext = context.getString(R.string.flattr_click_failure, extraFlattrThing.getTitle()) - + "\n" + exceptionMsg; - } else { - title = context.getString(R.string.flattrd_label); - subtext = context.getString(R.string.flattr_click_success_count, countSuccess) + "\n" - + context.getString(R.string.flattr_click_failure_count, countFailed); - } - - Notification notification = new NotificationCompat.Builder(context) - .setStyle(new NotificationCompat.BigTextStyle().bigText(subtext)) - .setContentIntent(contentIntent) - .setContentTitle(title) - .setTicker(title) - .setSmallIcon(R.drawable.stat_notify_sync_error) - .setOngoing(false) - .setAutoCancel(true) - .build(); - ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification); - } - - - /** - * Starts the FlattrClickWorker as an AsyncTask. - */ - @TargetApi(11) - public void executeAsync() { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java deleted file mode 100644 index ddc4370e6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.content.Context; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import org.shredzone.flattr4j.exception.FlattrException; -import org.shredzone.flattr4j.model.Flattr; - -import java.util.List; -import java.util.concurrent.ExecutionException; - -/** - * Fetch list of flattred things and flattr status in database in a background thread. - */ - -public class FlattrStatusFetcher extends Thread { - protected static final String TAG = "FlattrStatusFetcher"; - protected Context context; - - public FlattrStatusFetcher(Context context) { - super(); - this.context = context; - } - - @Override - public void run() { - if (BuildConfig.DEBUG) Log.d(TAG, "Starting background work: Retrieving Flattr status"); - - Thread.currentThread().setPriority(Thread.MIN_PRIORITY); - - try { - List flattredThings = FlattrUtils.retrieveFlattredThings(); - DBWriter.setFlattredStatus(context, flattredThings).get(); - } catch (FlattrException e) { - e.printStackTrace(); - Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage()); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - - if (BuildConfig.DEBUG) Log.d(TAG, "Finished background work: Retrieved Flattr status"); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java deleted file mode 100644 index 6f8319c7d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java +++ /dev/null @@ -1,95 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - - -import android.annotation.SuppressLint; -import android.app.ProgressDialog; -import android.content.Context; -import android.net.Uri; -import android.os.AsyncTask; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.FlattrAuthActivity; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import org.shredzone.flattr4j.exception.FlattrException; -import org.shredzone.flattr4j.oauth.AccessToken; -import org.shredzone.flattr4j.oauth.AndroidAuthenticator; - -/** Fetches the access token in the background in order to avoid networkOnMainThread exception. */ - -public class FlattrTokenFetcher extends AsyncTask { - private static final String TAG = "FlattrTokenFetcher"; - Context context; - AndroidAuthenticator auth; - AccessToken token; - Uri uri; - ProgressDialog dialog; - FlattrException exception; - - public FlattrTokenFetcher(Context context, AndroidAuthenticator auth, Uri uri) { - super(); - this.context = context; - this.auth = auth; - this.uri = uri; - } - - @Override - protected void onPostExecute(AccessToken result) { - if (result != null) { - FlattrUtils.storeToken(result); - } - dialog.dismiss(); - if (exception == null) { - FlattrAuthActivity instance = FlattrAuthActivity.getInstance(); - if (instance != null) { - instance.handleAuthenticationSuccess(); - } else { - Log.e(TAG, "FlattrAuthActivity instance was null"); - } - } else { - FlattrUtils.showErrorDialog(context, exception.getMessage()); - } - } - - - - @Override - protected void onPreExecute() { - super.onPreExecute(); - dialog = new ProgressDialog(context); - dialog.setMessage(context.getString(R.string.processing_label)); - dialog.setIndeterminate(true); - dialog.setCancelable(false); - dialog.show(); - } - - - - @Override - protected AccessToken doInBackground(Void... params) { - try { - token = auth.fetchAccessToken(uri); - } catch (FlattrException e) { - e.printStackTrace(); - exception = e; - return null; - } - if (token != null) { - if (BuildConfig.DEBUG) Log.d(TAG, "Successfully got token"); - return token; - } else { - Log.w(TAG, "Flattr token was null"); - return null; - } - } - - @SuppressLint("NewApi") - public void executeAsync() { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlExportWorker.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlExportWorker.java deleted file mode 100644 index 9f887bda6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlExportWorker.java +++ /dev/null @@ -1,114 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.util.Log; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.opml.OpmlWriter; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.LangUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; - -/** Writes an OPML file into the export directory in the background. */ -public class OpmlExportWorker extends AsyncTask { - private static final String TAG = "OpmlExportWorker"; - private static final String DEFAULT_OUTPUT_NAME = "antennapod-feeds.opml"; - private Context context; - private File output; - - private ProgressDialog progDialog; - private Exception exception; - - public OpmlExportWorker(Context context, File output) { - this.context = context; - this.output = output; - } - - public OpmlExportWorker(Context context) { - this.context = context; - } - - @Override - protected Void doInBackground(Void... params) { - OpmlWriter opmlWriter = new OpmlWriter(); - if (output == null) { - output = new File( - UserPreferences.getDataFolder(context, PodcastApp.EXPORT_DIR), - DEFAULT_OUTPUT_NAME); - if (output.exists()) { - Log.w(TAG, "Overwriting previously exported file."); - output.delete(); - } - } - OutputStreamWriter writer = null; - try { - writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); - opmlWriter.writeDocument(DBReader.getFeedList(context), writer); - } catch (IOException e) { - e.printStackTrace(); - exception = e; - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException ioe) { - exception = ioe; - } - } - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - progDialog.dismiss(); - AlertDialog.Builder alert = new AlertDialog.Builder(context) - .setNeutralButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - } - }); - if (exception != null) { - alert.setTitle(R.string.export_error_label); - alert.setMessage(exception.getMessage()); - } else { - alert.setTitle(R.string.opml_export_success_title); - alert.setMessage(context - .getString(R.string.opml_export_success_sum) - + output.toString()); - } - alert.create().show(); - } - - @Override - protected void onPreExecute() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.exporting_label)); - progDialog.setIndeterminate(true); - progDialog.show(); - } - - @SuppressLint("NewApi") - public void executeAsync() { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlFeedQueuer.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlFeedQueuer.java deleted file mode 100644 index 13144faa9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlFeedQueuer.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.annotation.SuppressLint; -import android.app.ProgressDialog; -import android.content.Context; -import android.os.AsyncTask; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.OpmlImportHolder; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.opml.OpmlElement; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; - -import java.util.Arrays; -import java.util.Date; - -/** Queues items for download in the background. */ -public class OpmlFeedQueuer extends AsyncTask { - private Context context; - private ProgressDialog progDialog; - private int[] selection; - - public OpmlFeedQueuer(Context context, int[] selection) { - super(); - this.context = context; - this.selection = Arrays.copyOf(selection, selection.length); - } - - @Override - protected void onPostExecute(Void result) { - progDialog.dismiss(); - } - - @Override - protected void onPreExecute() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.processing_label)); - progDialog.setCancelable(false); - progDialog.setIndeterminate(true); - progDialog.show(); - } - - @Override - protected Void doInBackground(Void... params) { - DownloadRequester requester = DownloadRequester.getInstance(); - for (int idx = 0; idx < selection.length; idx++) { - OpmlElement element = OpmlImportHolder.getReadElements().get( - selection[idx]); - Feed feed = new Feed(element.getXmlUrl(), new Date(), - element.getText()); - try { - requester.downloadFeed(context.getApplicationContext(), feed); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } - return null; - } - - @SuppressLint("NewApi") - public void executeAsync() { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlImportWorker.java deleted file mode 100644 index a4308be9b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/OpmlImportWorker.java +++ /dev/null @@ -1,116 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.os.AsyncTask; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.opml.OpmlElement; -import de.danoeh.antennapod.core.opml.OpmlReader; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; - -public class OpmlImportWorker extends - AsyncTask> { - private static final String TAG = "OpmlImportWorker"; - - private Context context; - private Exception exception; - - private ProgressDialog progDialog; - - private Reader mReader; - - public OpmlImportWorker(Context context, Reader reader) { - super(); - this.context = context; - this.mReader=reader; - } - - @Override - protected ArrayList doInBackground(Void... params) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting background work"); - - if (mReader==null) { - return null; - } - - OpmlReader opmlReader = new OpmlReader(); - try { - ArrayList result = opmlReader.readDocument(mReader); - mReader.close(); - return result; - } catch (XmlPullParserException e) { - e.printStackTrace(); - exception = e; - return null; - } catch (IOException e) { - e.printStackTrace(); - exception = e; - return null; - } - - } - - @Override - protected void onPostExecute(ArrayList result) { - if (mReader != null) { - try { - mReader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - progDialog.dismiss(); - if (exception != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "An error occured while trying to parse the opml document"); - AlertDialog.Builder alert = new AlertDialog.Builder(context); - alert.setTitle(R.string.error_label); - alert.setMessage(context.getString(R.string.opml_reader_error) - + exception.getMessage()); - alert.setNeutralButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - - }); - alert.create().show(); - } - } - - @Override - protected void onPreExecute() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.reading_opml_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.show(); - } - - public boolean wasSuccessful() { - return exception != null; - } - - @SuppressLint("NewApi") - public void executeAsync() { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java deleted file mode 100644 index c0d8049db..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.net.Uri; - -/** - * Classes that implement this interface provide access to an image resource that can - * be loaded by the Picasso library. - */ -public interface PicassoImageResource { - - /** - * This scheme should be used by PicassoImageResources to - * indicate that the image Uri points to a file that is not an image - * (e.g. a media file). This workaround is needed so that the Picasso library - * loads these Uri with a Downloader instead of trying to load it directly. - *

- * For example implementations, see FeedMedia or ExternalMedia. - */ - public static final String SCHEME_MEDIA = "media"; - - - /** - * Parameter key for an encoded fallback Uri. This Uri MUST point to a local image file - */ - public static final String PARAM_FALLBACK = "fallback"; - - /** - * Returns a Uri to the image or null if no image is available. - *

- * The Uri can either be an HTTP-URL, a URL pointing to a local image file or - * a non-image file (see SCHEME_MEDIA for more details). - *

- * The Uri can also have an optional fallback-URL if loading the default URL - * failed (see PARAM_FALLBACK). - */ - public Uri getImageUri(); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java deleted file mode 100644 index 6ace92800..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java +++ /dev/null @@ -1,152 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.content.Context; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import com.squareup.picasso.Cache; -import com.squareup.picasso.Downloader; -import com.squareup.picasso.LruCache; -import com.squareup.picasso.OkHttpDownloader; -import com.squareup.picasso.Picasso; - -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Provides access to Picasso instances. - */ -public class PicassoProvider { - private static final String TAG = "PicassoProvider"; - - private static final boolean DEBUG = false; - - private static ExecutorService executorService; - private static Cache memoryCache; - - private static Picasso defaultPicassoInstance; - private static Picasso mediaMetadataPicassoInstance; - - private static synchronized ExecutorService getExecutorService() { - if (executorService == null) { - executorService = Executors.newFixedThreadPool(3); - } - return executorService; - } - - private static synchronized Cache getMemoryCache(Context context) { - if (memoryCache == null) { - memoryCache = new LruCache(context); - } - return memoryCache; - } - - /** - * Returns a Picasso instance that uses an OkHttpDownloader. This instance can only load images - * from image files. - *

- * This instance should be used as long as no images from media files are loaded. - */ - public static synchronized Picasso getDefaultPicassoInstance(Context context) { - Validate.notNull(context); - if (defaultPicassoInstance == null) { - defaultPicassoInstance = new Picasso.Builder(context) - .indicatorsEnabled(DEBUG) - .loggingEnabled(DEBUG) - .downloader(new OkHttpDownloader(context)) - .executor(getExecutorService()) - .memoryCache(getMemoryCache(context)) - .listener(new Picasso.Listener() { - @Override - public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) { - Log.e(TAG, "Failed to load Uri:" + uri.toString()); - e.printStackTrace(); - } - }) - .build(); - } - return defaultPicassoInstance; - } - - /** - * Returns a Picasso instance that uses a MediaMetadataRetriever if the given Uri is a media file - * and a default OkHttpDownloader otherwise. - */ - public static synchronized Picasso getMediaMetadataPicassoInstance(Context context) { - Validate.notNull(context); - if (mediaMetadataPicassoInstance == null) { - mediaMetadataPicassoInstance = new Picasso.Builder(context) - .indicatorsEnabled(DEBUG) - .loggingEnabled(DEBUG) - .downloader(new MediaMetadataDownloader(context)) - .executor(getExecutorService()) - .memoryCache(getMemoryCache(context)) - .listener(new Picasso.Listener() { - @Override - public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) { - Log.e(TAG, "Failed to load Uri:" + uri.toString()); - e.printStackTrace(); - } - }) - .build(); - } - return mediaMetadataPicassoInstance; - } - - private static class MediaMetadataDownloader implements Downloader { - - private static final String TAG = "MediaMetadataDownloader"; - - private final OkHttpDownloader okHttpDownloader; - - public MediaMetadataDownloader(Context context) { - Validate.notNull(context); - okHttpDownloader = new OkHttpDownloader(context); - } - - @Override - public Response load(Uri uri, boolean b) throws IOException { - if (StringUtils.equals(uri.getScheme(), PicassoImageResource.SCHEME_MEDIA)) { - String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(uri.getLastPathSegment())); - if (StringUtils.startsWith(type, "image")) { - File imageFile = new File(uri.toString()); - return new Response(new BufferedInputStream(new FileInputStream(imageFile)), true, imageFile.length()); - } else { - MediaMetadataRetriever mmr = new MediaMetadataRetriever(); - mmr.setDataSource(uri.getPath()); - byte[] data = mmr.getEmbeddedPicture(); - mmr.release(); - - if (data != null) { - return new Response(new ByteArrayInputStream(data), true, data.length); - } else { - - // check for fallback Uri - String fallbackParam = uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK); - - if (fallbackParam != null) { - String fallback = Uri.decode(Uri.parse(fallbackParam).getPath()); - if (fallback != null) { - File imageFile = new File(fallback); - return new Response(new BufferedInputStream(new FileInputStream(imageFile)), true, imageFile.length()); - } - } - return null; - } - } - } - return okHttpDownloader.load(uri, b); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/app/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java deleted file mode 100644 index 72b5066b3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java +++ /dev/null @@ -1,211 +0,0 @@ -package de.danoeh.antennapod.core.backup; - -import android.app.backup.BackupAgentHelper; -import android.app.backup.BackupDataInputStream; -import android.app.backup.BackupDataOutput; -import android.app.backup.BackupHelper; -import android.content.Context; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import de.danoeh.antennapod.BuildConfig; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; -import java.math.BigInteger; -import java.security.DigestInputStream; -import java.security.DigestOutputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; - -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.opml.OpmlElement; -import de.danoeh.antennapod.core.opml.OpmlReader; -import de.danoeh.antennapod.core.opml.OpmlWriter; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.LangUtils; - -public class OpmlBackupAgent extends BackupAgentHelper { - private static final String OPML_BACKUP_KEY = "opml"; - - @Override - public void onCreate() { - addHelper(OPML_BACKUP_KEY, new OpmlBackupHelper(this)); - } - - private static final void LOGD(String tag, String msg) { - if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) { - Log.d(tag, msg); - } - } - - private static final void LOGD(String tag, String msg, Throwable tr) { - if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) { - Log.d(tag, msg, tr); - } - } - - /** Class for backing up and restoring the OPML file. */ - private static class OpmlBackupHelper implements BackupHelper { - private static final String TAG = "OpmlBackupHelper"; - - private static final String OPML_ENTITY_KEY = "antennapod-feeds.opml"; - - private final Context mContext; - - /** Checksum of restored OPML file */ - private byte[] mChecksum; - - public OpmlBackupHelper(Context context) { - mContext = context; - } - - @Override - public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { - Log.d(TAG, "Performing backup"); - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - MessageDigest digester = null; - Writer writer; - - try { - digester = MessageDigest.getInstance("MD5"); - writer = new OutputStreamWriter(new DigestOutputStream(byteStream, digester), - LangUtils.UTF_8); - } catch (NoSuchAlgorithmException e) { - writer = new OutputStreamWriter(byteStream, LangUtils.UTF_8); - } - - try { - // Write OPML - new OpmlWriter().writeDocument(DBReader.getFeedList(mContext), writer); - - // Compare checksum of new and old file to see if we need to perform a backup at all - if (digester != null) { - byte[] newChecksum = digester.digest(); - LOGD(TAG, "New checksum: " + new BigInteger(1, newChecksum).toString(16)); - - // Get the old checksum - if (oldState != null) { - FileInputStream inState = new FileInputStream(oldState.getFileDescriptor()); - int len = inState.read(); - - if (len != -1) { - byte[] oldChecksum = new byte[len]; - inState.read(oldChecksum); - LOGD(TAG, "Old checksum: " + new BigInteger(1, oldChecksum).toString(16)); - - if (Arrays.equals(oldChecksum, newChecksum)) { - LOGD(TAG, "Checksums are the same; won't backup"); - return; - } - } - } - - writeNewStateDescription(newState, newChecksum); - } - - LOGD(TAG, "Backing up OPML"); - byte[] bytes = byteStream.toByteArray(); - data.writeEntityHeader(OPML_ENTITY_KEY, bytes.length); - data.writeEntityData(bytes, bytes.length); - } catch (IOException e) { - Log.e(TAG, "Error during backup", e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - } - } - } - } - - @Override - public void restoreEntity(BackupDataInputStream data) { - LOGD(TAG, "Backup restore"); - - if (!OPML_ENTITY_KEY.equals(data.getKey())) { - LOGD(TAG, "Unknown entity key: " + data.getKey()); - return; - } - - MessageDigest digester = null; - Reader reader; - - try { - digester = MessageDigest.getInstance("MD5"); - reader = new InputStreamReader(new DigestInputStream(data, digester), - LangUtils.UTF_8); - } catch (NoSuchAlgorithmException e) { - reader = new InputStreamReader(data, LangUtils.UTF_8); - } - - try { - ArrayList opmlElements = new OpmlReader().readDocument(reader); - mChecksum = digester == null ? null : digester.digest(); - DownloadRequester downloader = DownloadRequester.getInstance(); - Date lastUpdated = new Date(); - - for (OpmlElement opmlElem : opmlElements) { - Feed feed = new Feed(opmlElem.getXmlUrl(), lastUpdated, opmlElem.getText()); - - try { - downloader.downloadFeed(mContext, feed); - } catch (DownloadRequestException e) { - LOGD(TAG, "Error while restoring/downloading feed", e); - } - } - } catch (XmlPullParserException e) { - Log.e(TAG, "Error while parsing the OPML file", e); - } catch (IOException e) { - Log.e(TAG, "Failed to restore OPML backup", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - } - } - } - } - - @Override - public void writeNewStateDescription(ParcelFileDescriptor newState) { - writeNewStateDescription(newState, mChecksum); - } - - /** - * Writes the new state description, which is the checksum of the OPML file. - * - * @param newState - * @param checksum - */ - private void writeNewStateDescription(ParcelFileDescriptor newState, byte[] checksum) { - if (checksum == null) { - return; - } - - try { - FileOutputStream outState = new FileOutputStream(newState.getFileDescriptor()); - outState.write(checksum.length); - outState.write(checksum); - outState.flush(); - outState.close(); - } catch (IOException e) { - Log.e(TAG, "Failed to write new state description", e); - } - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java b/app/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java deleted file mode 100644 index e51d70708..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.danoeh.antennapod.core.dialog; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; - -/** - * Creates an AlertDialog which asks the user to confirm something. Other - * classes can handle events like confirmation or cancellation. - */ -public abstract class ConfirmationDialog { - private static final String TAG = "ConfirmationDialog"; - - Context context; - int titleId; - int messageId; - - public ConfirmationDialog(Context context, int titleId, int messageId) { - this.context = context; - this.titleId = titleId; - this.messageId = messageId; - } - - public void onCancelButtonPressed(DialogInterface dialog) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Dialog was cancelled"); - dialog.dismiss(); - } - - public abstract void onConfirmButtonPressed(DialogInterface dialog); - - public final AlertDialog createNewDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(titleId); - builder.setMessage(messageId); - builder.setPositiveButton(R.string.confirm_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - onConfirmButtonPressed(dialog); - } - }); - builder.setNegativeButton(R.string.cancel_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - onCancelButtonPressed(dialog); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - - @Override - public void onCancel(DialogInterface dialog) { - onCancelButtonPressed(dialog); - } - }); - return builder.create(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java b/app/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java deleted file mode 100644 index a1c3a4c6a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.danoeh.antennapod.core.dialog; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import de.danoeh.antennapod.R; - -/** Creates Alert Dialogs if a DownloadRequestException has happened. */ -public class DownloadRequestErrorDialogCreator { - private DownloadRequestErrorDialogCreator() { - } - - public static void newRequestErrorDialog(Context context, - String errorMessage) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setNeutralButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setTitle(R.string.download_error_request_error) - .setMessage( - context.getString(R.string.download_request_error_dialog_message_prefix) - + errorMessage); - builder.create().show(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/dialog/TimeDialog.java b/app/src/main/java/de/danoeh/antennapod/core/dialog/TimeDialog.java deleted file mode 100644 index a95e8c6c5..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/dialog/TimeDialog.java +++ /dev/null @@ -1,138 +0,0 @@ -package de.danoeh.antennapod.core.dialog; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.View; -import android.view.Window; -import android.view.inputmethod.InputMethodManager; -import android.widget.*; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; - -import java.util.concurrent.TimeUnit; - -public abstract class TimeDialog extends Dialog { - private static final String TAG = "TimeDialog"; - - private static final int DEFAULT_SPINNER_POSITION = 1; - - private Context context; - - private EditText etxtTime; - private Spinner spTimeUnit; - private Button butConfirm; - private Button butCancel; - - private TimeUnit[] units = {TimeUnit.SECONDS, TimeUnit.MINUTES, - TimeUnit.HOURS}; - - public TimeDialog(Context context, int titleTextId, int leftButtonTextId) { - super(context); - this.context = context; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - String[] spinnerContent = new String[]{context.getString(R.string.time_unit_seconds), - context.getString(R.string.time_unit_minutes), - context.getString(R.string.time_unit_hours)}; - - setContentView(R.layout.time_dialog); - etxtTime = (EditText) findViewById(R.id.etxtTime); - spTimeUnit = (Spinner) findViewById(R.id.spTimeUnit); - butConfirm = (Button) findViewById(R.id.butConfirm); - butCancel = (Button) findViewById(R.id.butCancel); - - butConfirm.setText(R.string.set_sleeptimer_label); - butCancel.setText(R.string.cancel_label); - setTitle(R.string.set_sleeptimer_label); - ArrayAdapter spinnerAdapter = new ArrayAdapter( - this.getContext(), android.R.layout.simple_spinner_item, - spinnerContent); - spinnerAdapter - .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spTimeUnit.setAdapter(spinnerAdapter); - spTimeUnit.setSelection(DEFAULT_SPINNER_POSITION); - butCancel.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - dismiss(); - } - }); - butConfirm.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - try { - long input = readTimeMillis(); - onTimeEntered(input); - dismiss(); - } catch (NumberFormatException e) { - e.printStackTrace(); - Toast toast = Toast.makeText(context, - R.string.time_dialog_invalid_input, - Toast.LENGTH_LONG); - toast.show(); - } - } - }); - etxtTime.addTextChangedListener(new TextWatcher() { - - @Override - public void afterTextChanged(Editable s) { - checkInputLength(s.length()); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - - } - }); - checkInputLength(etxtTime.getText().length()); - etxtTime.postDelayed(new Runnable() { - @Override - public void run() { - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); - } - }, 100); - - - - } - - private void checkInputLength(int length) { - if (length > 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Length is larger than 0, enabling confirm button"); - butConfirm.setEnabled(true); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Length is smaller than 0, disabling confirm button"); - butConfirm.setEnabled(false); - } - } - - public abstract void onTimeEntered(long millis); - - private long readTimeMillis() { - TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()]; - long value = Long.valueOf(etxtTime.getText().toString()); - return selectedUnit.toMillis(value); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java b/app/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java deleted file mode 100644 index ce3352ed6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -public abstract class Chapter extends FeedComponent { - - /** Defines starting point in milliseconds. */ - protected long start; - protected String title; - protected String link; - - public Chapter() { - } - - public Chapter(long start) { - super(); - this.start = start; - } - - public Chapter(long start, String title, FeedItem item, String link) { - super(); - this.start = start; - this.title = title; - this.link = link; - } - - public abstract int getChapterType(); - - public long getStart() { - return start; - } - - public String getTitle() { - return title; - } - - public String getLink() { - return link; - } - - public void setStart(long start) { - this.start = start; - } - - public void setTitle(String title) { - this.title = title; - } - - public void setLink(String link) { - this.link = link; - } - - @Override - public String getHumanReadableIdentifier() { - return title; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java b/app/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java deleted file mode 100644 index 65c55a361..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java +++ /dev/null @@ -1,140 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.os.Handler; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.BuildConfig; - -import java.util.AbstractQueue; -import java.util.Observable; -import java.util.Observer; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * Notifies its observers about changes in the feed database. Observers can - * register by retrieving an instance of this class and registering an - * EventListener. When new events arrive, the EventDistributor will process the - * event queue in a handler that runs on the main thread. The observers will only - * be notified once if the event queue contains multiple elements. - * - * Events can be sent with the send* methods. - */ -public class EventDistributor extends Observable { - private static final String TAG = "EventDistributor"; - - public static final int FEED_LIST_UPDATE = 1; - public static final int UNREAD_ITEMS_UPDATE = 2; - public static final int QUEUE_UPDATE = 4; - public static final int DOWNLOADLOG_UPDATE = 8; - public static final int PLAYBACK_HISTORY_UPDATE = 16; - public static final int DOWNLOAD_QUEUED = 32; - public static final int DOWNLOAD_HANDLED = 64; - - private Handler handler; - private AbstractQueue events; - - private static EventDistributor instance; - - private EventDistributor() { - this.handler = new Handler(); - events = new ConcurrentLinkedQueue(); - } - - public static synchronized EventDistributor getInstance() { - if (instance == null) { - instance = new EventDistributor(); - } - return instance; - } - - public void register(EventListener el) { - addObserver(el); - } - - public void unregister(EventListener el) { - deleteObserver(el); - } - - public void addEvent(Integer i) { - events.offer(i); - handler.post(new Runnable() { - - @Override - public void run() { - processEventQueue(); - } - }); - } - - private void processEventQueue() { - Integer result = 0; - if (BuildConfig.DEBUG) - Log.d(TAG, - "Processing event queue. Number of events: " - + events.size()); - for (Integer current = events.poll(); current != null; current = events - .poll()) { - result |= current; - } - if (result != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Notifying observers. Data: " + result); - setChanged(); - notifyObservers(result); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Event queue didn't contain any new events. Observers will not be notified."); - } - } - - @Override - public void addObserver(Observer observer) { - super.addObserver(observer); - Validate.isInstanceOf(EventListener.class, observer); - } - - public void sendDownloadQueuedBroadcast() { - addEvent(DOWNLOAD_QUEUED); - } - - public void sendUnreadItemsUpdateBroadcast() { - addEvent(UNREAD_ITEMS_UPDATE); - } - - public void sendQueueUpdateBroadcast() { - addEvent(QUEUE_UPDATE); - } - - public void sendFeedUpdateBroadcast() { - addEvent(FEED_LIST_UPDATE); - } - - public void sendPlaybackHistoryUpdateBroadcast() { - addEvent(PLAYBACK_HISTORY_UPDATE); - } - - public void sendDownloadLogUpdateBroadcast() { - addEvent(DOWNLOADLOG_UPDATE); - } - - public void sendDownloadHandledBroadcast() { - addEvent(DOWNLOAD_HANDLED); - } - - public static abstract class EventListener implements Observer { - - @Override - public void update(Observable observable, Object data) { - if (observable instanceof EventDistributor - && data instanceof Integer) { - update((EventDistributor) observable, (Integer) data); - } - } - - public abstract void update(EventDistributor eventDistributor, - Integer arg); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/app/src/main/java/de/danoeh/antennapod/core/feed/Feed.java deleted file mode 100644 index 3f83ab8b6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/Feed.java +++ /dev/null @@ -1,445 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.content.Context; -import android.net.Uri; - -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.EpisodeFilter; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; -import de.danoeh.antennapod.core.util.flattr.FlattrThing; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Data Object for a whole feed - * - * @author daniel - */ -public class Feed extends FeedFile implements FlattrThing, PicassoImageResource { - public static final int FEEDFILETYPE_FEED = 0; - public static final String TYPE_RSS2 = "rss"; - public static final String TYPE_RSS091 = "rss"; - public static final String TYPE_ATOM1 = "atom"; - - private String title; - /** - * Contains 'id'-element in Atom feed. - */ - private String feedIdentifier; - /** - * Link to the website. - */ - private String link; - private String description; - private String language; - /** - * Name of the author - */ - private String author; - private FeedImage image; - private List items; - /** - * Date of last refresh. - */ - private Date lastUpdate; - private FlattrStatus flattrStatus; - private String paymentLink; - /** - * Feed type, for example RSS 2 or Atom - */ - private String type; - - /** - * Feed preferences - */ - private FeedPreferences preferences; - - /** - * This constructor is used for restoring a feed from the database. - */ - public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink, - String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, - String downloadUrl, boolean downloaded, FlattrStatus status) { - super(fileUrl, downloadUrl, downloaded); - this.id = id; - this.title = title; - if (lastUpdate != null) { - this.lastUpdate = (Date) lastUpdate.clone(); - } else { - this.lastUpdate = null; - } - this.link = link; - this.description = description; - this.paymentLink = paymentLink; - this.author = author; - this.language = language; - this.type = type; - this.feedIdentifier = feedIdentifier; - this.image = image; - this.flattrStatus = status; - - items = new ArrayList(); - } - - /** - * This constructor is used for test purposes and uses a default flattr status object. - */ - public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink, - String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, - String downloadUrl, boolean downloaded) { - this(id, lastUpdate, title, link, description, paymentLink, author, language, type, feedIdentifier, image, - fileUrl, downloadUrl, downloaded, new FlattrStatus()); - } - - /** - * This constructor can be used when parsing feed data. Only the 'lastUpdate' and 'items' field are initialized. - */ - public Feed() { - super(); - items = new ArrayList(); - lastUpdate = new Date(); - this.flattrStatus = new FlattrStatus(); - } - - /** - * This constructor is used for requesting a feed download (it must not be used for anything else!). It should NOT be - * used if the title of the feed is already known. - */ - public Feed(String url, Date lastUpdate) { - super(null, url, false); - this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - this.flattrStatus = new FlattrStatus(); - } - - /** - * This constructor is used for requesting a feed download (it must not be used for anything else!). It should be - * used if the title of the feed is already known. - */ - public Feed(String url, Date lastUpdate, String title) { - this(url, lastUpdate); - this.title = title; - this.flattrStatus = new FlattrStatus(); - } - - /** - * This constructor is used for requesting a feed download (it must not be used for anything else!). It should be - * used if the title of the feed is already known. - */ - public Feed(String url, Date lastUpdate, String title, String username, String password) { - this(url, lastUpdate, title); - preferences = new FeedPreferences(0, true, username, password); - } - - /** - * Returns the number of FeedItems where 'read' is false. If the 'display - * only episodes' - preference is set to true, this method will only count - * items with episodes. - */ - public int getNumOfNewItems() { - int count = 0; - for (FeedItem item : items) { - if (item.getState() == FeedItem.State.NEW) { - if (!UserPreferences.isDisplayOnlyEpisodes() - || item.getMedia() != null) { - count++; - } - } - } - return count; - } - - /** - * Returns the number of FeedItems where the media started to play but - * wasn't finished yet. - */ - public int getNumOfStartedItems() { - int count = 0; - - for (FeedItem item : items) { - FeedItem.State state = item.getState(); - if (state == FeedItem.State.IN_PROGRESS - || state == FeedItem.State.PLAYING) { - count++; - } - } - return count; - } - - /** - * Returns true if at least one item in the itemlist is unread. - * - * @param enableEpisodeFilter true if this method should only count items with episodes if - * the 'display only episodes' - preference is set to true by the - * user. - */ - public boolean hasNewItems(boolean enableEpisodeFilter) { - for (FeedItem item : items) { - if (item.getState() == FeedItem.State.NEW) { - if (!(enableEpisodeFilter && UserPreferences - .isDisplayOnlyEpisodes()) || item.getMedia() != null) { - return true; - } - } - } - return false; - } - - /** - * Returns the number of FeedItems. - * - * @param enableEpisodeFilter true if this method should only count items with episodes if - * the 'display only episodes' - preference is set to true by the - * user. - */ - public int getNumOfItems(boolean enableEpisodeFilter) { - if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) { - return EpisodeFilter.countItemsWithEpisodes(items); - } else { - return items.size(); - } - } - - /** - * Returns the item at the specified index. - * - * @param enableEpisodeFilter true if this method should ignore items without episdodes if - * the episodes filter has been enabled by the user. - */ - public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) { - if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) { - return EpisodeFilter.accessEpisodeByIndex(items, position); - } else { - return items.get(position); - } - } - - /** - * Returns the value that uniquely identifies this Feed. If the - * feedIdentifier attribute is not null, it will be returned. Else it will - * try to return the title. If the title is not given, it will use the link - * of the feed. - */ - public String getIdentifyingValue() { - if (feedIdentifier != null && !feedIdentifier.isEmpty()) { - return feedIdentifier; - } else if (download_url != null && !download_url.isEmpty()) { - return download_url; - } else if (title != null && !title.isEmpty()) { - return title; - } else { - return link; - } - } - - @Override - public String getHumanReadableIdentifier() { - if (title != null) { - return title; - } else { - return download_url; - } - } - - public void updateFromOther(Feed other) { - super.updateFromOther(other); - if (other.title != null) { - title = other.title; - } - if (other.feedIdentifier != null) { - feedIdentifier = other.feedIdentifier; - } - if (other.link != null) { - link = other.link; - } - if (other.description != null) { - description = other.description; - } - if (other.language != null) { - language = other.language; - } - if (other.author != null) { - author = other.author; - } - if (other.paymentLink != null) { - paymentLink = other.paymentLink; - } - if (other.flattrStatus != null) { - flattrStatus = other.flattrStatus; - } - } - - public boolean compareWithOther(Feed other) { - if (super.compareWithOther(other)) { - return true; - } - if (!title.equals(other.title)) { - return true; - } - if (other.feedIdentifier != null) { - if (feedIdentifier == null - || !feedIdentifier.equals(other.feedIdentifier)) { - return true; - } - } - if (other.link != null) { - if (link == null || !link.equals(other.link)) { - return true; - } - } - if (other.description != null) { - if (description == null || !description.equals(other.description)) { - return true; - } - } - if (other.language != null) { - if (language == null || !language.equals(other.language)) { - return true; - } - } - if (other.author != null) { - if (author == null || !author.equals(other.author)) { - return true; - } - } - if (other.paymentLink != null) { - if (paymentLink == null || !paymentLink.equals(other.paymentLink)) { - return true; - } - } - return false; - } - - @Override - public int getTypeAsInt() { - return FEEDFILETYPE_FEED; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getLink() { - return link; - } - - public void setLink(String link) { - this.link = link; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public FeedImage getImage() { - return image; - } - - public void setImage(FeedImage image) { - this.image = image; - } - - public List getItems() { - return items; - } - - public void setItems(List list) { - this.items = list; - } - - public Date getLastUpdate() { - return (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - } - - public void setLastUpdate(Date lastUpdate) { - this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - } - - public String getFeedIdentifier() { - return feedIdentifier; - } - - public void setFeedIdentifier(String feedIdentifier) { - this.feedIdentifier = feedIdentifier; - } - - public void setFlattrStatus(FlattrStatus status) { - this.flattrStatus = status; - } - - public FlattrStatus getFlattrStatus() { - return flattrStatus; - } - - public String getPaymentLink() { - return paymentLink; - } - - public void setPaymentLink(String paymentLink) { - this.paymentLink = paymentLink; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public void setPreferences(FeedPreferences preferences) { - this.preferences = preferences; - } - - public FeedPreferences getPreferences() { - return preferences; - } - - public void savePreferences(Context context) { - DBWriter.setFeedPreferences(context, preferences); - } - - @Override - public void setId(long id) { - super.setId(id); - if (preferences != null) { - preferences.setFeedID(id); - } - } - - @Override - public Uri getImageUri() { - if (image != null) { - return image.getImageUri(); - } else { - return null; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java b/app/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java deleted file mode 100644 index 05115c1ea..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java +++ /dev/null @@ -1,66 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -/** - * Represents every possible component of a feed - * - * @author daniel - */ -public abstract class FeedComponent { - - protected long id; - - public FeedComponent() { - super(); - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - /** - * Update this FeedComponent's attributes with the attributes from another - * FeedComponent. This method should only update attributes which where read from - * the feed. - */ - public void updateFromOther(FeedComponent other) { - } - - /** - * Compare's this FeedComponent's attribute values with another FeedComponent's - * attribute values. This method will only compare attributes which were - * read from the feed. - * - * @return true if attribute values are different, false otherwise - */ - public boolean compareWithOther(FeedComponent other) { - return false; - } - - - /** - * Should return a non-null, human-readable String so that the item can be - * identified by the user. Can be title, download-url, etc. - */ - public abstract String getHumanReadableIdentifier(); - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FeedComponent that = (FeedComponent) o; - - if (id != that.id) return false; - - return true; - } - - @Override - public int hashCode() { - return (int) (id ^ (id >>> 32)); - } -} \ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java b/app/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java deleted file mode 100644 index 3dc58654b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java +++ /dev/null @@ -1,105 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import java.io.File; - -/** - * Represents a component of a Feed that has to be downloaded - */ -public abstract class FeedFile extends FeedComponent { - - protected String file_url; - protected String download_url; - protected boolean downloaded; - - /** - * Creates a new FeedFile object. - * - * @param file_url The location of the FeedFile. If this is null, the downloaded-attribute - * will automatically be set to false. - * @param download_url The location where the FeedFile can be downloaded. - * @param downloaded true if the FeedFile has been downloaded, false otherwise. This parameter - * will automatically be interpreted as false if the file_url is null. - */ - public FeedFile(String file_url, String download_url, boolean downloaded) { - super(); - this.file_url = file_url; - this.download_url = download_url; - this.downloaded = (file_url != null) && downloaded; - } - - public FeedFile() { - this(null, null, false); - } - - public abstract int getTypeAsInt(); - - /** - * Update this FeedFile's attributes with the attributes from another - * FeedFile. This method should only update attributes which where read from - * the feed. - */ - public void updateFromOther(FeedFile other) { - super.updateFromOther(other); - this.download_url = other.download_url; - } - - /** - * Compare's this FeedFile's attribute values with another FeedFile's - * attribute values. This method will only compare attributes which were - * read from the feed. - * - * @return true if attribute values are different, false otherwise - */ - public boolean compareWithOther(FeedFile other) { - if (super.compareWithOther(other)) { - return true; - } - if (!download_url.equals(other.download_url)) { - return true; - } - return false; - } - - /** - * Returns true if the file exists at file_url. - */ - public boolean fileExists() { - if (file_url == null) { - return false; - } else { - File f = new File(file_url); - return f.exists(); - } - } - - public String getFile_url() { - return file_url; - } - - /** - * Changes the file_url of this FeedFile. Setting this value to - * null will also set the downloaded-attribute to false. - */ - public void setFile_url(String file_url) { - this.file_url = file_url; - if (file_url == null) { - downloaded = false; - } - } - - public String getDownload_url() { - return download_url; - } - - public void setDownload_url(String download_url) { - this.download_url = download_url; - } - - public boolean isDownloaded() { - return downloaded; - } - - public void setDownloaded(boolean downloaded) { - this.downloaded = downloaded; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java b/app/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java deleted file mode 100644 index 51605691d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.net.Uri; - -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; - -import java.io.File; - - -public class FeedImage extends FeedFile implements PicassoImageResource { - public static final int FEEDFILETYPE_FEEDIMAGE = 1; - - protected String title; - protected FeedComponent owner; - - public FeedImage(String download_url, String title) { - super(null, download_url, false); - this.download_url = download_url; - this.title = title; - } - - public FeedImage(long id, String title, String file_url, - String download_url, boolean downloaded) { - super(file_url, download_url, downloaded); - this.id = id; - this.title = title; - } - - @Override - public String getHumanReadableIdentifier() { - if (owner != null && owner.getHumanReadableIdentifier() != null) { - return owner.getHumanReadableIdentifier(); - } else { - return download_url; - } - } - - @Override - public int getTypeAsInt() { - return FEEDFILETYPE_FEEDIMAGE; - } - - public FeedImage() { - super(); - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public FeedComponent getOwner() { - return owner; - } - - public void setOwner(FeedComponent owner) { - this.owner = owner; - } - - @Override - public Uri getImageUri() { - if (file_url != null && downloaded) { - return Uri.fromFile(new File(file_url)); - } else { - return null; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/app/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java deleted file mode 100644 index 55143b13b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java +++ /dev/null @@ -1,332 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.net.Uri; - -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.ShownotesProvider; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; -import de.danoeh.antennapod.core.util.flattr.FlattrThing; - -import java.util.Date; -import java.util.List; -import java.util.concurrent.Callable; - -/** - * Data Object for a XML message - * - * @author daniel - */ -public class FeedItem extends FeedComponent implements ShownotesProvider, FlattrThing, PicassoImageResource { - - /** - * The id/guid that can be found in the rss/atom feed. Might not be set. - */ - private String itemIdentifier; - private String title; - /** - * The description of a feeditem. - */ - private String description; - /** - * The content of the content-encoded tag of a feeditem. - */ - private String contentEncoded; - - private String link; - private Date pubDate; - private FeedMedia media; - - private Feed feed; - private long feedId; - - private boolean read; - private String paymentLink; - private FlattrStatus flattrStatus; - private List chapters; - private FeedImage image; - - public FeedItem() { - this.read = true; - this.flattrStatus = new FlattrStatus(); - } - - /** - * This constructor should be used for creating test objects. - */ - public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, boolean read, Feed feed) { - this.id = id; - this.title = title; - this.itemIdentifier = itemIdentifier; - this.link = link; - this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null; - this.read = read; - this.feed = feed; - this.flattrStatus = new FlattrStatus(); - } - - public void updateFromOther(FeedItem other) { - super.updateFromOther(other); - if (other.title != null) { - title = other.title; - } - if (other.getDescription() != null) { - description = other.getDescription(); - } - if (other.getContentEncoded() != null) { - contentEncoded = other.contentEncoded; - } - if (other.link != null) { - link = other.link; - } - if (other.pubDate != null && other.pubDate != pubDate) { - pubDate = other.pubDate; - } - if (other.media != null) { - if (media == null) { - setMedia(other.media); - } else if (media.compareWithOther(other)) { - media.updateFromOther(other); - } - } - if (other.paymentLink != null) { - paymentLink = other.paymentLink; - } - if (other.chapters != null) { - if (chapters == null) { - chapters = other.chapters; - } - } - if (image == null) { - image = other.image; - } - } - - /** - * Returns the value that uniquely identifies this FeedItem. If the - * itemIdentifier attribute is not null, it will be returned. Else it will - * try to return the title. If the title is not given, it will use the link - * of the entry. - */ - public String getIdentifyingValue() { - if (itemIdentifier != null && !itemIdentifier.isEmpty()) { - return itemIdentifier; - } else if (title != null && !title.isEmpty()) { - return title; - } else { - return link; - } - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getLink() { - return link; - } - - public void setLink(String link) { - this.link = link; - } - - public Date getPubDate() { - if (pubDate != null) { - return (Date) pubDate.clone(); - } else { - return null; - } - } - - public void setPubDate(Date pubDate) { - if (pubDate != null) { - this.pubDate = (Date) pubDate.clone(); - } else { - this.pubDate = null; - } - } - - public FeedMedia getMedia() { - return media; - } - - /** - * Sets the media object of this FeedItem. If the given - * FeedMedia object is not null, it's 'item'-attribute value - * will also be set to this item. - */ - public void setMedia(FeedMedia media) { - this.media = media; - if (media != null && media.getItem() != this) { - media.setItem(this); - } - } - - public Feed getFeed() { - return feed; - } - - public void setFeed(Feed feed) { - this.feed = feed; - } - - public boolean isRead() { - return read || isInProgress(); - } - - public void setRead(boolean read) { - this.read = read; - } - - private boolean isInProgress() { - return (media != null && media.isInProgress()); - } - - public String getContentEncoded() { - return contentEncoded; - } - - public void setContentEncoded(String contentEncoded) { - this.contentEncoded = contentEncoded; - } - - public void setFlattrStatus(FlattrStatus status) { - this.flattrStatus = status; - } - - public FlattrStatus getFlattrStatus() { - return flattrStatus; - } - - public String getPaymentLink() { - return paymentLink; - } - - public void setPaymentLink(String paymentLink) { - this.paymentLink = paymentLink; - } - - public List getChapters() { - return chapters; - } - - public void setChapters(List chapters) { - this.chapters = chapters; - } - - public String getItemIdentifier() { - return itemIdentifier; - } - - public void setItemIdentifier(String itemIdentifier) { - this.itemIdentifier = itemIdentifier; - } - - public boolean hasMedia() { - return media != null; - } - - private boolean isPlaying() { - if (media != null) { - return media.isPlaying(); - } - return false; - } - - @Override - public Callable loadShownotes() { - return new Callable() { - @Override - public String call() throws Exception { - - if (contentEncoded == null || description == null) { - DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), FeedItem.this); - - } - return (contentEncoded != null) ? contentEncoded : description; - } - }; - } - - @Override - public Uri getImageUri() { - if (hasMedia()) { - return media.getImageUri(); - } else if (feed != null) { - return feed.getImageUri(); - } else { - return null; - } - } - - public enum State { - NEW, IN_PROGRESS, READ, PLAYING - } - - public State getState() { - if (hasMedia()) { - if (isPlaying()) { - return State.PLAYING; - } - if (isInProgress()) { - return State.IN_PROGRESS; - } - } - return (isRead() ? State.READ : State.NEW); - } - - public long getFeedId() { - return feedId; - } - - public void setFeedId(long feedId) { - this.feedId = feedId; - } - - /** - * Returns the image of this item or the image of the feed if this item does - * not have its own image. - */ - public FeedImage getImage() { - return (hasItemImage()) ? image : feed.getImage(); - } - - public void setImage(FeedImage image) { - this.image = image; - if (image != null) { - image.setOwner(this); - } - } - - /** - * Returns true if this FeedItem has its own image, false otherwise. - */ - public boolean hasItemImage() { - return image != null; - } - - /** - * Returns true if this FeedItem has its own image and the image has been downloaded. - */ - public boolean hasItemImageDownloaded() { - return image != null && image.isDownloaded(); - } - - @Override - public String getHumanReadableIdentifier() { - return title; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/app/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java deleted file mode 100644 index ab87e822d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ /dev/null @@ -1,408 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Date; -import java.util.List; -import java.util.concurrent.Callable; - -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.ChapterUtils; -import de.danoeh.antennapod.core.util.playback.Playable; - -public class FeedMedia extends FeedFile implements Playable { - private static final String TAG = "FeedMedia"; - - public static final int FEEDFILETYPE_FEEDMEDIA = 2; - public static final int PLAYABLE_TYPE_FEEDMEDIA = 1; - - public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId"; - public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId"; - - private int duration; - private int position; // Current position in file - private int played_duration; // How many ms of this file have been played (for autoflattring) - private long size; // File size in Byte - private String mime_type; - private volatile FeedItem item; - private Date playbackCompletionDate; - - /* Used for loading item when restoring from parcel. */ - private long itemID; - - public FeedMedia(FeedItem i, String download_url, long size, - String mime_type) { - super(null, download_url, false); - this.item = i; - this.size = size; - this.mime_type = mime_type; - } - - public FeedMedia(long id, FeedItem item, int duration, int position, - long size, String mime_type, String file_url, String download_url, - boolean downloaded, Date playbackCompletionDate, int played_duration) { - super(file_url, download_url, downloaded); - this.id = id; - this.item = item; - this.duration = duration; - this.position = position; - this.played_duration = played_duration; - this.size = size; - this.mime_type = mime_type; - this.playbackCompletionDate = playbackCompletionDate == null - ? null : (Date) playbackCompletionDate.clone(); - } - - public FeedMedia(long id, FeedItem item) { - super(); - this.id = id; - this.item = item; - } - - @Override - public String getHumanReadableIdentifier() { - if (item != null && item.getTitle() != null) { - return item.getTitle(); - } else { - return download_url; - } - } - - /** - * Uses mimetype to determine the type of media. - */ - public MediaType getMediaType() { - if (mime_type == null || mime_type.isEmpty()) { - return MediaType.UNKNOWN; - } else { - if (mime_type.startsWith("audio")) { - return MediaType.AUDIO; - } else if (mime_type.startsWith("video")) { - return MediaType.VIDEO; - } else if (mime_type.equals("application/ogg")) { - return MediaType.AUDIO; - } - } - return MediaType.UNKNOWN; - } - - public void updateFromOther(FeedMedia other) { - super.updateFromOther(other); - if (other.size > 0) { - size = other.size; - } - if (other.mime_type != null) { - mime_type = other.mime_type; - } - } - - public boolean compareWithOther(FeedMedia other) { - if (super.compareWithOther(other)) { - return true; - } - if (other.mime_type != null) { - if (mime_type == null || !mime_type.equals(other.mime_type)) { - return true; - } - } - if (other.size > 0 && other.size != size) { - return true; - } - return false; - } - - /** - * Reads playback preferences to determine whether this FeedMedia object is - * currently being played. - */ - public boolean isPlaying() { - return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA - && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id; - } - - @Override - public int getTypeAsInt() { - return FEEDFILETYPE_FEEDMEDIA; - } - - public int getDuration() { - return duration; - } - - public void setDuration(int duration) { - this.duration = duration; - } - - public int getPlayedDuration() { - return played_duration; - } - - public void setPlayedDuration(int played_duration) { - this.played_duration = played_duration; - } - - public int getPosition() { - return position; - } - - public void setPosition(int position) { - this.position = position; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public String getMime_type() { - return mime_type; - } - - public void setMime_type(String mime_type) { - this.mime_type = mime_type; - } - - public FeedItem getItem() { - return item; - } - - /** - * Sets the item object of this FeedMedia. If the given - * FeedItem object is not null, it's 'media'-attribute value - * will also be set to this media object. - */ - public void setItem(FeedItem item) { - this.item = item; - if (item != null && item.getMedia() != this) { - item.setMedia(this); - } - } - - public Date getPlaybackCompletionDate() { - return playbackCompletionDate == null - ? null : (Date) playbackCompletionDate.clone(); - } - - public void setPlaybackCompletionDate(Date playbackCompletionDate) { - this.playbackCompletionDate = playbackCompletionDate == null - ? null : (Date) playbackCompletionDate.clone(); - } - - public boolean isInProgress() { - return (this.position > 0); - } - - public FeedImage getImage() { - if (item != null) { - return (item.hasItemImageDownloaded()) ? item.getImage() : item.getFeed().getImage(); - } - return null; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(id); - dest.writeLong(item.getId()); - - dest.writeInt(duration); - dest.writeInt(position); - dest.writeLong(size); - dest.writeString(mime_type); - dest.writeString(file_url); - dest.writeString(download_url); - dest.writeByte((byte) ((downloaded) ? 1 : 0)); - dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0); - dest.writeInt(played_duration); - } - - @Override - public void writeToPreferences(Editor prefEditor) { - prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId()); - prefEditor.putLong(PREF_MEDIA_ID, id); - } - - @Override - public void loadMetadata() throws PlayableException { - if (item == null && itemID != 0) { - item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID); - } - } - - @Override - public void loadChapterMarks() { - if (getChapters() == null && !localFileAvailable()) { - ChapterUtils.loadChaptersFromStreamUrl(this); - if (getChapters() != null && item != null) { - DBWriter.setFeedItem(PodcastApp.getInstance(), - item); - } - } - - } - - @Override - public String getEpisodeTitle() { - if (item == null) { - return null; - } - if (getItem().getTitle() != null) { - return getItem().getTitle(); - } else { - return getItem().getIdentifyingValue(); - } - } - - @Override - public List getChapters() { - if (item == null) { - return null; - } - return getItem().getChapters(); - } - - @Override - public String getWebsiteLink() { - if (item == null) { - return null; - } - return getItem().getLink(); - } - - @Override - public String getFeedTitle() { - if (item == null) { - return null; - } - return getItem().getFeed().getTitle(); - } - - @Override - public Object getIdentifier() { - return id; - } - - @Override - public String getLocalMediaUrl() { - return file_url; - } - - @Override - public String getStreamUrl() { - return download_url; - } - - @Override - public String getPaymentLink() { - if (item == null) { - return null; - } - return getItem().getPaymentLink(); - } - - @Override - public boolean localFileAvailable() { - return isDownloaded() && file_url != null; - } - - @Override - public boolean streamAvailable() { - return download_url != null; - } - - @Override - public void saveCurrentPosition(SharedPreferences pref, int newPosition) { - setPosition(newPosition); - DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this); - } - - @Override - public void onPlaybackStart() { - } - - @Override - public void onPlaybackCompleted() { - - } - - @Override - public int getPlayableType() { - return PLAYABLE_TYPE_FEEDMEDIA; - } - - @Override - public void setChapters(List chapters) { - getItem().setChapters(chapters); - } - - @Override - public Callable loadShownotes() { - return new Callable() { - @Override - public String call() throws Exception { - if (item == null) { - item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID); - } - if (item.getContentEncoded() == null || item.getDescription() == null) { - DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), item); - - } - return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription(); - } - }; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public FeedMedia createFromParcel(Parcel in) { - final long id = in.readLong(); - final long itemID = in.readLong(); - FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(), - in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt()); - result.itemID = itemID; - return result; - } - - public FeedMedia[] newArray(int size) { - return new FeedMedia[size]; - } - }; - - @Override - public Uri getImageUri() { - final Uri feedImgUri = getFeedImageUri(); - - if (localFileAvailable()) { - Uri.Builder builder = new Uri.Builder(); - builder.scheme(SCHEME_MEDIA) - .encodedPath(getLocalMediaUrl()); - if (feedImgUri != null) { - builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString()); - } - return builder.build(); - } else { - return feedImgUri; - } - } - - private Uri getFeedImageUri() { - if (item != null && item.getFeed() != null) { - return item.getFeed().getImageUri(); - } else { - return null; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/app/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java deleted file mode 100644 index 2f0304182..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java +++ /dev/null @@ -1,89 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.content.Context; -import de.danoeh.antennapod.core.storage.DBWriter; -import org.apache.commons.lang3.StringUtils; - -/** - * Contains preferences for a single feed. - */ -public class FeedPreferences { - - private long feedID; - private boolean autoDownload; - private String username; - private String password; - - public FeedPreferences(long feedID, boolean autoDownload, String username, String password) { - this.feedID = feedID; - this.autoDownload = autoDownload; - this.username = username; - this.password = password; - } - - - /** - * Compare another FeedPreferences with this one. The feedID and autoDownload attribute are excluded from the - * comparison. - * - * @return True if the two objects are different. - */ - public boolean compareWithOther(FeedPreferences other) { - if (other == null) - return true; - if (!StringUtils.equals(username, other.username)) { - return true; - } - if (!StringUtils.equals(password, other.password)) { - return true; - } - return false; - } - - /** - * Update this FeedPreferences object from another one. The feedID and autoDownload attributes are excluded - * from the update. - */ - public void updateFromOther(FeedPreferences other) { - if (other == null) - return; - this.username = other.username; - this.password = other.password; - } - - public long getFeedID() { - return feedID; - } - - public void setFeedID(long feedID) { - this.feedID = feedID; - } - - public boolean getAutoDownload() { - return autoDownload; - } - - public void setAutoDownload(boolean autoDownload) { - this.autoDownload = autoDownload; - } - - public void save(Context context) { - DBWriter.setFeedPreferences(context, this); - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/ID3Chapter.java b/app/src/main/java/de/danoeh/antennapod/core/feed/ID3Chapter.java deleted file mode 100644 index f0ff03a93..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/ID3Chapter.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -public class ID3Chapter extends Chapter { - public static final int CHAPTERTYPE_ID3CHAPTER = 2; - - /** - * Identifies the chapter in its ID3 tag. This attribute does not have to be - * store in the DB and is only used for parsing. - */ - private String id3ID; - - public ID3Chapter(String id3ID, long start) { - super(start); - this.id3ID = id3ID; - } - - public ID3Chapter(long start, String title, FeedItem item, String link) { - super(start, title, item, link); - } - - @Override - public String toString() { - return "ID3Chapter [id3ID=" + id3ID + ", title=" + title + ", start=" - + start + ", url=" + link + "]"; - } - - @Override - public int getChapterType() { - return CHAPTERTYPE_ID3CHAPTER; - } - - public String getId3ID() { - return id3ID; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java b/app/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java deleted file mode 100644 index 7b3cb829d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -public enum MediaType { - AUDIO, VIDEO, UNKNOWN -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java b/app/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java deleted file mode 100644 index 9aa8d3170..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -public class SearchResult { - private FeedComponent component; - /** Additional information (e.g. where it was found) */ - private String subtitle; - /** Higher value means more importance */ - private int value; - - public SearchResult(FeedComponent component, int value, String subtitle) { - super(); - this.component = component; - this.value = value; - this.subtitle = subtitle; - } - - public FeedComponent getComponent() { - return component; - } - - public String getSubtitle() { - return subtitle; - } - - public void setSubtitle(String subtitle) { - this.subtitle = subtitle; - } - - public int getValue() { - return value; - } - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/SimpleChapter.java b/app/src/main/java/de/danoeh/antennapod/core/feed/SimpleChapter.java deleted file mode 100644 index 2dadd3ec8..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/SimpleChapter.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -public class SimpleChapter extends Chapter { - public static final int CHAPTERTYPE_SIMPLECHAPTER = 0; - - public SimpleChapter(long start, String title, FeedItem item, String link) { - super(start, title, item, link); - } - - @Override - public int getChapterType() { - return CHAPTERTYPE_SIMPLECHAPTER; - } - - public void updateFromOther(SimpleChapter other) { - super.updateFromOther(other); - start = other.start; - if (other.title != null) { - title = other.title; - } - if (other.link != null) { - link = other.link; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java b/app/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java deleted file mode 100644 index 5b54a2d59..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java +++ /dev/null @@ -1,109 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException; - -import java.util.concurrent.TimeUnit; - -public class VorbisCommentChapter extends Chapter { - public static final int CHAPTERTYPE_VORBISCOMMENT_CHAPTER = 3; - - private static final int CHAPTERXXX_LENGTH = "chapterxxx".length(); - - private int vorbisCommentId; - - public VorbisCommentChapter(int vorbisCommentId) { - this.vorbisCommentId = vorbisCommentId; - } - - public VorbisCommentChapter(long start, String title, FeedItem item, - String link) { - super(start, title, item, link); - } - - @Override - public String toString() { - return "VorbisCommentChapter [id=" + id + ", title=" + title - + ", link=" + link + ", start=" + start + "]"; - } - - public static long getStartTimeFromValue(String value) - throws VorbisCommentReaderException { - String[] parts = value.split(":"); - if (parts.length >= 3) { - try { - long hours = TimeUnit.MILLISECONDS.convert( - Long.parseLong(parts[0]), TimeUnit.HOURS); - long minutes = TimeUnit.MILLISECONDS.convert( - Long.parseLong(parts[1]), TimeUnit.MINUTES); - if (parts[2].contains("-->")) { - parts[2] = parts[2].substring(0, parts[2].indexOf("-->")); - } - long seconds = TimeUnit.MILLISECONDS.convert( - ((long) Float.parseFloat(parts[2])), TimeUnit.SECONDS); - return hours + minutes + seconds; - } catch (NumberFormatException e) { - throw new VorbisCommentReaderException(e); - } - } else { - throw new VorbisCommentReaderException("Invalid time string"); - } - } - - /** - * Return the id of a vorbiscomment chapter from a string like CHAPTERxxx* - * - * @return the id of the chapter key or -1 if the id couldn't be read. - * @throws VorbisCommentReaderException - * */ - public static int getIDFromKey(String key) - throws VorbisCommentReaderException { - if (key.length() >= CHAPTERXXX_LENGTH) { // >= CHAPTERxxx - try { - String strId = key.substring(8, 10); - return Integer.parseInt(strId); - } catch (NumberFormatException e) { - throw new VorbisCommentReaderException(e); - } - } - throw new VorbisCommentReaderException("key is too short (" + key + ")"); - } - - /** - * Get the string that comes after 'CHAPTERxxx', for example 'name' or - * 'url'. - */ - public static String getAttributeTypeFromKey(String key) { - if (key.length() > CHAPTERXXX_LENGTH) { - return key.substring(CHAPTERXXX_LENGTH, key.length()); - } - return null; - } - - @Override - public int getChapterType() { - return CHAPTERTYPE_VORBISCOMMENT_CHAPTER; - } - - public void setTitle(String title) { - this.title = title; - } - - public void setLink(String link) { - this.link = link; - } - - public void setStart(long start) { - this.start = start; - } - - public int getVorbisCommentId() { - return vorbisCommentId; - } - - public void setVorbisCommentId(int vorbisCommentId) { - this.vorbisCommentId = vorbisCommentId; - } - - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java deleted file mode 100644 index 117cbf96b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java +++ /dev/null @@ -1,718 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet; - -import org.apache.commons.lang3.Validate; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.auth.AuthenticationException; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.BasicScheme; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; - -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; - -/** - * Communicates with the gpodder.net service. - */ -public class GpodnetService { - - private static final String BASE_SCHEME = "https"; - - public static final String DEFAULT_BASE_HOST = "gpodder.net"; - private final String BASE_HOST; - - private final HttpClient httpClient; - - public GpodnetService() { - httpClient = AntennapodHttpClient.getHttpClient(); - BASE_HOST = GpodnetPreferences.getHostname(); - } - - /** - * Returns the [count] most used tags. - */ - public List getTopTags(int count) - throws GpodnetServiceException { - URI uri; - try { - uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/api/2/tags/%d.json", count), null); - } catch (URISyntaxException e1) { - e1.printStackTrace(); - throw new IllegalStateException(e1); - } - - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - try { - JSONArray jsonTagList = new JSONArray(response); - List tagList = new ArrayList( - jsonTagList.length()); - for (int i = 0; i < jsonTagList.length(); i++) { - JSONObject jObj = jsonTagList.getJSONObject(i); - String name = jObj.getString("tag"); - int usage = jObj.getInt("usage"); - tagList.add(new GpodnetTag(name, usage)); - } - return tagList; - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - } - - /** - * Returns the [count] most subscribed podcasts for the given tag. - * - * @throws IllegalArgumentException if tag is null - */ - public List getPodcastsForTag(GpodnetTag tag, int count) - throws GpodnetServiceException { - Validate.notNull(tag); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/api/2/tag/%s/%d.json", tag.getName(), count), null); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - - JSONArray jsonArray = new JSONArray(response); - return readPodcastListFromJSONArray(jsonArray); - - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - - } - } - - /** - * Returns the toplist of podcast. - * - * @param count of elements that should be returned. Must be in range 1..100. - * @throws IllegalArgumentException if count is out of range. - */ - public List getPodcastToplist(int count) - throws GpodnetServiceException { - Validate.isTrue(count >= 1 && count <= 100, "Count must be in range 1..100"); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/toplist/%d.json", count), null); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - - JSONArray jsonArray = new JSONArray(response); - return readPodcastListFromJSONArray(jsonArray); - - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - - } - } - - /** - * Returns a list of suggested podcasts for the user that is currently - * logged in. - *

- * This method requires authentication. - * - * @param count The - * number of elements that should be returned. Must be in range - * 1..100. - * @throws IllegalArgumentException if count is out of range. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public List getSuggestions(int count) throws GpodnetServiceException { - Validate.isTrue(count >= 1 && count <= 100, "Count must be in range 1..100"); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/suggestions/%d.json", count), null); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - - JSONArray jsonArray = new JSONArray(response); - return readPodcastListFromJSONArray(jsonArray); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - } - - /** - * Searches the podcast directory for a given string. - * - * @param query The search query - * @param scaledLogoSize The size of the logos that are returned by the search query. - * Must be in range 1..256. If the value is out of range, the - * default value defined by the gpodder.net API will be used. - */ - public List searchPodcasts(String query, int scaledLogoSize) - throws GpodnetServiceException { - String parameters = (scaledLogoSize > 0 && scaledLogoSize <= 256) ? String - .format("q=%s&scale_logo=%d", query, scaledLogoSize) : String - .format("q=%s", query); - try { - URI uri = new URI(BASE_SCHEME, null, BASE_HOST, -1, "/search.json", - parameters, null); - System.out.println(uri.toASCIIString()); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - - JSONArray jsonArray = new JSONArray(response); - return readPodcastListFromJSONArray(jsonArray); - - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - - } - } - - /** - * Returns all devices of a given user. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @throws IllegalArgumentException If username is null. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public List getDevices(String username) - throws GpodnetServiceException { - Validate.notNull(username); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/api/2/devices/%s.json", username), null); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - JSONArray devicesArray = new JSONArray(response); - List result = readDeviceListFromJSONArray(devicesArray); - - return result; - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - } - - /** - * Configures the device of a given user. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @param deviceId The ID of the device that should be configured. - * @throws IllegalArgumentException If username or deviceId is null. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public void configureDevice(String username, String deviceId, - String caption, GpodnetDevice.DeviceType type) - throws GpodnetServiceException { - Validate.notNull(username); - Validate.notNull(deviceId); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/api/2/devices/%s/%s.json", username, deviceId), null); - HttpPost request = new HttpPost(uri); - if (caption != null || type != null) { - JSONObject jsonContent = new JSONObject(); - if (caption != null) { - jsonContent.put("caption", caption); - } - if (type != null) { - jsonContent.put("type", type.toString()); - } - StringEntity strEntity = new StringEntity( - jsonContent.toString(), "UTF-8"); - strEntity.setContentType("application/json"); - request.setEntity(strEntity); - } - executeRequest(request); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalArgumentException(e); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - } - - /** - * Returns the subscriptions of a specific device. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @param deviceId The ID of the device whose subscriptions should be returned. - * @return A list of subscriptions in OPML format. - * @throws IllegalArgumentException If username or deviceId is null. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public String getSubscriptionsOfDevice(String username, String deviceId) - throws GpodnetServiceException { - Validate.notNull(username); - Validate.notNull(deviceId); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/subscriptions/%s/%s.opml", username, deviceId), null); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - return response; - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalArgumentException(e); - } - } - - /** - * Returns all subscriptions of a specific user. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @return A list of subscriptions in OPML format. - * @throws IllegalArgumentException If username is null. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public String getSubscriptionsOfUser(String username) - throws GpodnetServiceException { - Validate.notNull(username); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/subscriptions/%s.opml", username), null); - HttpGet request = new HttpGet(uri); - String response = executeRequest(request); - return response; - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalArgumentException(e); - } - } - - /** - * Uploads the subscriptions of a specific device. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @param deviceId The ID of the device whose subscriptions should be updated. - * @param subscriptions A list of feed URLs containing all subscriptions of the - * device. - * @throws IllegalArgumentException If username, deviceId or subscriptions is null. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public void uploadSubscriptions(String username, String deviceId, - List subscriptions) throws GpodnetServiceException { - if (username == null || deviceId == null || subscriptions == null) { - throw new IllegalArgumentException( - "Username, device ID and subscriptions must not be null"); - } - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/subscriptions/%s/%s.txt", username, deviceId), null); - HttpPut request = new HttpPut(uri); - StringBuilder builder = new StringBuilder(); - for (String s : subscriptions) { - builder.append(s); - builder.append("\n"); - } - StringEntity entity = new StringEntity(builder.toString(), "UTF-8"); - request.setEntity(entity); - - executeRequest(request); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } - } - - /** - * Updates the subscription list of a specific device. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @param deviceId The ID of the device whose subscriptions should be updated. - * @param added Collection of feed URLs of added feeds. This Collection MUST NOT contain any duplicates - * @param removed Collection of feed URLs of removed feeds. This Collection MUST NOT contain any duplicates - * @return a GpodnetUploadChangesResponse. See {@link de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse} - * for details. - * @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null. - * @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there - * is an authentication error. - */ - public GpodnetUploadChangesResponse uploadChanges(String username, String deviceId, Collection added, - Collection removed) throws GpodnetServiceException { - Validate.notNull(username); - Validate.notNull(deviceId); - Validate.notNull(added); - Validate.notNull(removed); - - try { - URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/api/2/subscriptions/%s/%s.json", username, deviceId), null); - - final JSONObject requestObject = new JSONObject(); - requestObject.put("add", new JSONArray(added)); - requestObject.put("remove", new JSONArray(removed)); - - HttpPost request = new HttpPost(uri); - StringEntity entity = new StringEntity(requestObject.toString(), "UTF-8"); - request.setEntity(entity); - - final String response = executeRequest(request); - return GpodnetUploadChangesResponse.fromJSONObject(response); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } - - } - - /** - * Returns all subscription changes of a specific device. - *

- * This method requires authentication. - * - * @param username The username. Must be the same user as the one which is - * currently logged in. - * @param deviceId The ID of the device whose subscription changes should be - * downloaded. - * @param timestamp A timestamp that can be used to receive all changes since a - * specific point in time. - * @throws IllegalArgumentException If username or deviceId is null. - * @throws GpodnetServiceAuthenticationException If there is an authentication error. - */ - public GpodnetSubscriptionChange getSubscriptionChanges(String username, - String deviceId, long timestamp) throws GpodnetServiceException { - Validate.notNull(username); - Validate.notNull(deviceId); - - String params = String.format("since=%d", timestamp); - String path = String.format("/api/2/subscriptions/%s/%s.json", - username, deviceId); - try { - URI uri = new URI(BASE_SCHEME, null, BASE_HOST, -1, path, params, - null); - HttpGet request = new HttpGet(uri); - - String response = executeRequest(request); - JSONObject changes = new JSONObject(response); - return readSubscriptionChangesFromJSONObject(changes); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } catch (JSONException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - - } - - /** - * Logs in a specific user. This method must be called if any of the methods - * that require authentication is used. - * - * @throws IllegalArgumentException If username or password is null. - */ - public void authenticate(String username, String password) - throws GpodnetServiceException { - Validate.notNull(username); - Validate.notNull(password); - - URI uri; - try { - uri = new URI(BASE_SCHEME, BASE_HOST, String.format( - "/api/2/auth/%s/login.json", username), null); - } catch (URISyntaxException e) { - e.printStackTrace(); - throw new GpodnetServiceException(); - } - HttpPost request = new HttpPost(uri); - executeRequestWithAuthentication(request, username, password); - } - - /** - * Shuts down the GpodnetService's HTTP client. The service will be shut down in a separate thread to avoid - * NetworkOnMainThreadExceptions. - */ - public void shutdown() { - new Thread() { - @Override - public void run() { - AntennapodHttpClient.cleanup(); - } - }.start(); - } - - private String executeRequest(HttpRequestBase request) - throws GpodnetServiceException { - Validate.notNull(request); - - String responseString = null; - HttpResponse response = null; - try { - response = httpClient.execute(request); - checkStatusCode(response); - responseString = getStringFromEntity(response.getEntity()); - } catch (ClientProtocolException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (IOException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } finally { - if (response != null) { - try { - response.getEntity().consumeContent(); - } catch (IOException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - } - - } - return responseString; - } - - private String executeRequestWithAuthentication(HttpRequestBase request, - String username, String password) throws GpodnetServiceException { - if (request == null || username == null || password == null) { - throw new IllegalArgumentException( - "request and credentials must not be null"); - } - String result = null; - HttpResponse response = null; - try { - Header auth = new BasicScheme().authenticate( - new UsernamePasswordCredentials(username, password), - request); - request.addHeader(auth); - response = httpClient.execute(request); - checkStatusCode(response); - result = getStringFromEntity(response.getEntity()); - } catch (ClientProtocolException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (IOException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } catch (AuthenticationException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } finally { - if (response != null) { - try { - response.getEntity().consumeContent(); - } catch (IOException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - } - } - return result; - } - - private String getStringFromEntity(HttpEntity entity) - throws GpodnetServiceException { - Validate.notNull(entity); - - ByteArrayOutputStream outputStream; - int contentLength = (int) entity.getContentLength(); - if (contentLength > 0) { - outputStream = new ByteArrayOutputStream(contentLength); - } else { - outputStream = new ByteArrayOutputStream(); - } - try { - byte[] buffer = new byte[8 * 1024]; - InputStream in = entity.getContent(); - int count; - while ((count = in.read(buffer)) > 0) { - outputStream.write(buffer, 0, count); - } - } catch (IOException e) { - e.printStackTrace(); - throw new GpodnetServiceException(e); - } - // System.out.println(outputStream.toString()); - return outputStream.toString(); - } - - private void checkStatusCode(HttpResponse response) - throws GpodnetServiceException { - Validate.notNull(response); - int responseCode = response.getStatusLine().getStatusCode(); - if (responseCode != HttpStatus.SC_OK) { - if (responseCode == HttpStatus.SC_UNAUTHORIZED) { - throw new GpodnetServiceAuthenticationException("Wrong username or password"); - } else { - throw new GpodnetServiceBadStatusCodeException( - "Bad response code: " + responseCode, responseCode); - } - } - } - - private List readPodcastListFromJSONArray(JSONArray array) - throws JSONException { - Validate.notNull(array); - - List result = new ArrayList( - array.length()); - for (int i = 0; i < array.length(); i++) { - result.add(readPodcastFromJSONObject(array.getJSONObject(i))); - } - return result; - - } - - private GpodnetPodcast readPodcastFromJSONObject(JSONObject object) - throws JSONException { - String url = object.getString("url"); - - String title; - Object titleObj = object.opt("title"); - if (titleObj != null && titleObj instanceof String) { - title = (String) titleObj; - } else { - title = url; - } - - String description; - Object descriptionObj = object.opt("description"); - if (descriptionObj != null && descriptionObj instanceof String) { - description = (String) descriptionObj; - } else { - description = ""; - } - - int subscribers = object.getInt("subscribers"); - - Object logoUrlObj = object.opt("logo_url"); - String logoUrl = (logoUrlObj instanceof String) ? (String) logoUrlObj - : null; - if (logoUrl == null) { - Object scaledLogoUrl = object.opt("scaled_logo_url"); - if (scaledLogoUrl != null && scaledLogoUrl instanceof String) { - logoUrl = (String) scaledLogoUrl; - } - } - - String website = null; - Object websiteObj = object.opt("website"); - if (websiteObj != null && websiteObj instanceof String) { - website = (String) websiteObj; - } - String mygpoLink = object.getString("mygpo_link"); - return new GpodnetPodcast(url, title, description, subscribers, - logoUrl, website, mygpoLink); - } - - private List readDeviceListFromJSONArray(JSONArray array) - throws JSONException { - Validate.notNull(array); - - List result = new ArrayList( - array.length()); - for (int i = 0; i < array.length(); i++) { - result.add(readDeviceFromJSONObject(array.getJSONObject(i))); - } - return result; - } - - private GpodnetDevice readDeviceFromJSONObject(JSONObject object) - throws JSONException { - String id = object.getString("id"); - String caption = object.getString("caption"); - String type = object.getString("type"); - int subscriptions = object.getInt("subscriptions"); - return new GpodnetDevice(id, caption, type, subscriptions); - } - - private GpodnetSubscriptionChange readSubscriptionChangesFromJSONObject( - JSONObject object) throws JSONException { - Validate.notNull(object); - - List added = new LinkedList(); - JSONArray jsonAdded = object.getJSONArray("add"); - for (int i = 0; i < jsonAdded.length(); i++) { - added.add(jsonAdded.getString(i)); - } - - List removed = new LinkedList(); - JSONArray jsonRemoved = object.getJSONArray("remove"); - for (int i = 0; i < jsonRemoved.length(); i++) { - removed.add(jsonRemoved.getString(i)); - } - - long timestamp = object.getLong("timestamp"); - return new GpodnetSubscriptionChange(added, removed, timestamp); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java deleted file mode 100644 index 8bd56218c..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceAuthenticationException.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet; - -public class GpodnetServiceAuthenticationException extends GpodnetServiceException { - - public GpodnetServiceAuthenticationException() { - super(); - } - - public GpodnetServiceAuthenticationException(String message, Throwable cause) { - super(message, cause); - } - - public GpodnetServiceAuthenticationException(String message) { - super(message); - } - - public GpodnetServiceAuthenticationException(Throwable cause) { - super(cause); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java deleted file mode 100644 index 16f01f0f4..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet; - -public class GpodnetServiceBadStatusCodeException extends GpodnetServiceException { - int statusCode; - - public GpodnetServiceBadStatusCodeException(String message, int statusCode) { - super(message); - this.statusCode = statusCode; - } - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java deleted file mode 100644 index ce704f7e3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet; - -public class GpodnetServiceException extends Exception { - - public GpodnetServiceException() { - } - - public GpodnetServiceException(String message) { - super(message); - } - - public GpodnetServiceException(Throwable cause) { - super(cause); - } - - public GpodnetServiceException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java deleted file mode 100644 index 4885a243a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet.model; - -import org.apache.commons.lang3.Validate; - -public class GpodnetDevice { - - private String id; - private String caption; - private DeviceType type; - private int subscriptions; - - public GpodnetDevice(String id, String caption, String type, - int subscriptions) { - Validate.notNull(id); - - this.id = id; - this.caption = caption; - this.type = DeviceType.fromString(type); - this.subscriptions = subscriptions; - } - - @Override - public String toString() { - return "GpodnetDevice [id=" + id + ", caption=" + caption + ", type=" - + type + ", subscriptions=" + subscriptions + "]"; - } - - public static enum DeviceType { - DESKTOP, LAPTOP, MOBILE, SERVER, OTHER; - - static DeviceType fromString(String s) { - if (s == null) { - return OTHER; - } - - if (s.equals("desktop")) { - return DESKTOP; - } else if (s.equals("laptop")) { - return LAPTOP; - } else if (s.equals("mobile")) { - return MOBILE; - } else if (s.equals("server")) { - return SERVER; - } else { - return OTHER; - } - } - - @Override - public String toString() { - return super.toString().toLowerCase(); - } - - } - - public String getId() { - return id; - } - - public String getCaption() { - return caption; - } - - public DeviceType getType() { - return type; - } - - public int getSubscriptions() { - return subscriptions; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java deleted file mode 100644 index afebf66ac..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet.model; - -import org.apache.commons.lang3.Validate; - -public class GpodnetPodcast { - private String url; - private String title; - private String description; - private int subscribers; - private String logoUrl; - private String website; - private String mygpoLink; - - public GpodnetPodcast(String url, String title, String description, - int subscribers, String logoUrl, String website, String mygpoLink) { - Validate.notNull(url); - Validate.notNull(title); - Validate.notNull(description); - - this.url = url; - this.title = title; - this.description = description; - this.subscribers = subscribers; - this.logoUrl = logoUrl; - this.website = website; - this.mygpoLink = mygpoLink; - } - - @Override - public String toString() { - return "GpodnetPodcast [url=" + url + ", title=" + title - + ", description=" + description + ", subscribers=" - + subscribers + ", logoUrl=" + logoUrl + ", website=" + website - + ", mygpoLink=" + mygpoLink + "]"; - } - - public String getUrl() { - return url; - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - public int getSubscribers() { - return subscribers; - } - - public String getLogoUrl() { - return logoUrl; - } - - public String getWebsite() { - return website; - } - - public String getMygpoLink() { - return mygpoLink; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java deleted file mode 100644 index a5cb8c0f0..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet.model; - -import org.apache.commons.lang3.Validate; - -import java.util.List; - -public class GpodnetSubscriptionChange { - private List added; - private List removed; - private long timestamp; - - public GpodnetSubscriptionChange(List added, List removed, - long timestamp) { - Validate.notNull(added); - Validate.notNull(removed); - - this.added = added; - this.removed = removed; - this.timestamp = timestamp; - } - - @Override - public String toString() { - return "GpodnetSubscriptionChange [added=" + added.toString() - + ", removed=" + removed.toString() + ", timestamp=" - + timestamp + "]"; - } - - public List getAdded() { - return added; - } - - public List getRemoved() { - return removed; - } - - public long getTimestamp() { - return timestamp; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java deleted file mode 100644 index 7178f4be5..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet.model; - -import org.apache.commons.lang3.Validate; - -import java.util.Comparator; - -public class GpodnetTag { - - private String name; - private int usage; - - public GpodnetTag(String name, int usage) { - Validate.notNull(name); - - this.name = name; - this.usage = usage; - } - - public GpodnetTag(String name) { - super(); - this.name = name; - } - - @Override - public String toString() { - return "GpodnetTag [name=" + name + ", usage=" + usage + "]"; - } - - public String getName() { - return name; - } - - public int getUsage() { - return usage; - } - - public static class UsageComparator implements Comparator { - - @Override - public int compare(GpodnetTag o1, GpodnetTag o2) { - return o1.usage - o2.usage; - } - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java b/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java deleted file mode 100644 index 5a37efa5e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java +++ /dev/null @@ -1,56 +0,0 @@ -package de.danoeh.antennapod.core.gpoddernet.model; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Map; - -/** - * Object returned by {@link de.danoeh.antennapod.core.gpoddernet.GpodnetService} in uploadChanges method. - */ -public class GpodnetUploadChangesResponse { - - /** - * timestamp/ID that can be used for requesting changes since this upload. - */ - public final long timestamp; - - /** - * URLs that should be updated. The key of the map is the original URL, the value of the map - * is the sanitized URL. - */ - public final Map updatedUrls; - - public GpodnetUploadChangesResponse(long timestamp, Map updatedUrls) { - this.timestamp = timestamp; - this.updatedUrls = updatedUrls; - } - - /** - * Creates a new GpodnetUploadChangesResponse-object from a JSON object that was - * returned by an uploadChanges call. - * - * @throws org.json.JSONException If the method could not parse the JSONObject. - */ - public static GpodnetUploadChangesResponse fromJSONObject(String objectString) throws JSONException { - final JSONObject object = new JSONObject(objectString); - final long timestamp = object.getLong("timestamp"); - Map updatedUrls = new HashMap(); - JSONArray urls = object.getJSONArray("update_urls"); - for (int i = 0; i < urls.length(); i++) { - JSONArray urlPair = urls.getJSONArray(i); - updatedUrls.put(urlPair.getString(0), urlPair.getString(1)); - } - return new GpodnetUploadChangesResponse(timestamp, updatedUrls); - } - - @Override - public String toString() { - return "GpodnetUploadChangesResponse{" + - "timestamp=" + timestamp + - ", updatedUrls=" + updatedUrls + - '}'; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlElement.java b/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlElement.java deleted file mode 100644 index 8d0a4a842..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlElement.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.danoeh.antennapod.core.opml; - -/** Represents a single feed in an OPML file. */ -public class OpmlElement { - private String text; - private String xmlUrl; - private String htmlUrl; - private String type; - - public OpmlElement() { - - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public String getXmlUrl() { - return xmlUrl; - } - - public void setXmlUrl(String xmlUrl) { - this.xmlUrl = xmlUrl; - } - - public String getHtmlUrl() { - return htmlUrl; - } - - public void setHtmlUrl(String htmlUrl) { - this.htmlUrl = htmlUrl; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java b/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java deleted file mode 100644 index aa484954d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java +++ /dev/null @@ -1,87 +0,0 @@ -package de.danoeh.antennapod.core.opml; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; - -/** Reads OPML documents. */ -public class OpmlReader { - private static final String TAG = "OpmlReader"; - - // ATTRIBUTES - private boolean isInOpml = false; - private ArrayList elementList; - - /** - * Reads an Opml document and returns a list of all OPML elements it can - * find - * - * @throws IOException - * @throws XmlPullParserException - */ - public ArrayList readDocument(Reader reader) - throws XmlPullParserException, IOException { - elementList = new ArrayList(); - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(reader); - int eventType = xpp.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - switch (eventType) { - case XmlPullParser.START_DOCUMENT: - if (BuildConfig.DEBUG) - Log.d(TAG, "Reached beginning of document"); - break; - case XmlPullParser.START_TAG: - if (xpp.getName().equals(OpmlSymbols.OPML)) { - isInOpml = true; - if (BuildConfig.DEBUG) - Log.d(TAG, "Reached beginning of OPML tree."); - } else if (isInOpml && xpp.getName().equals(OpmlSymbols.OUTLINE)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Found new Opml element"); - OpmlElement element = new OpmlElement(); - - final String title = xpp.getAttributeValue(null, OpmlSymbols.TITLE); - if (title != null) { - Log.i(TAG, "Using title: " + title); - element.setText(title); - } else { - Log.i(TAG, "Title not found, using text"); - element.setText(xpp.getAttributeValue(null, OpmlSymbols.TEXT)); - } - element.setXmlUrl(xpp.getAttributeValue(null, OpmlSymbols.XMLURL)); - element.setHtmlUrl(xpp.getAttributeValue(null, OpmlSymbols.HTMLURL)); - element.setType(xpp.getAttributeValue(null, OpmlSymbols.TYPE)); - if (element.getXmlUrl() != null) { - if (element.getText() == null) { - Log.i(TAG, "Opml element has no text attribute."); - element.setText(element.getXmlUrl()); - } - elementList.add(element); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Skipping element because of missing xml url"); - } - } - break; - } - eventType = xpp.next(); - } - - if (BuildConfig.DEBUG) - Log.d(TAG, "Parsing finished."); - - return elementList; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java b/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java deleted file mode 100644 index 2b831ca2a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core.opml; - -/** Contains symbols for reading and writing OPML documents. */ -public final class OpmlSymbols { - - public static final String OPML = "opml"; - public static final String BODY = "body"; - public static final String OUTLINE = "outline"; - public static final String TEXT = "text"; - public static final String XMLURL = "xmlUrl"; - public static final String HTMLURL = "htmlUrl"; - public static final String TYPE = "type"; - public static final String VERSION = "version"; - public static final String HEAD = "head"; - public static final String TITLE = "title"; - - private OpmlSymbols() { - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java b/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java deleted file mode 100644 index fe14b4954..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.danoeh.antennapod.core.opml; - -import android.util.Log; -import android.util.Xml; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Feed; -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.io.Writer; -import java.util.List; - -/** Writes OPML documents. */ -public class OpmlWriter { - private static final String TAG = "OpmlWriter"; - private static final String ENCODING = "UTF-8"; - private static final String OPML_VERSION = "2.0"; - private static final String OPML_TITLE = "AntennaPod Subscriptions"; - - /** - * Takes a list of feeds and a writer and writes those into an OPML - * document. - * - * @throws IOException - * @throws IllegalStateException - * @throws IllegalArgumentException - */ - public void writeDocument(List feeds, Writer writer) - throws IllegalArgumentException, IllegalStateException, IOException { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting to write document"); - XmlSerializer xs = Xml.newSerializer(); - xs.setOutput(writer); - - xs.startDocument(ENCODING, false); - xs.startTag(null, OpmlSymbols.OPML); - xs.attribute(null, OpmlSymbols.VERSION, OPML_VERSION); - - xs.startTag(null, OpmlSymbols.HEAD); - xs.startTag(null, OpmlSymbols.TITLE); - xs.text(OPML_TITLE); - xs.endTag(null, OpmlSymbols.TITLE); - xs.endTag(null, OpmlSymbols.HEAD); - - xs.startTag(null, OpmlSymbols.BODY); - for (Feed feed : feeds) { - xs.startTag(null, OpmlSymbols.OUTLINE); - xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle()); - xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle()); - if (feed.getType() != null) { - xs.attribute(null, OpmlSymbols.TYPE, feed.getType()); - } - xs.attribute(null, OpmlSymbols.XMLURL, feed.getDownload_url()); - if (feed.getLink() != null) { - xs.attribute(null, OpmlSymbols.HTMLURL, feed.getLink()); - } - xs.endTag(null, OpmlSymbols.OUTLINE); - } - xs.endTag(null, OpmlSymbols.BODY); - xs.endTag(null, OpmlSymbols.OPML); - xs.endDocument(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Finished writing document"); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/app/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java deleted file mode 100644 index 716a74f53..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java +++ /dev/null @@ -1,246 +0,0 @@ -package de.danoeh.antennapod.core.preferences; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.service.GpodnetSyncService; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Manages preferences for accessing gpodder.net service - */ -public class GpodnetPreferences { - - private static final String TAG = "GpodnetPreferences"; - - private static final String PREF_NAME = "gpodder.net"; - public static final String PREF_GPODNET_USERNAME = "de.danoeh.antennapod.preferences.gpoddernet.username"; - public static final String PREF_GPODNET_PASSWORD = "de.danoeh.antennapod.preferences.gpoddernet.password"; - public static final String PREF_GPODNET_DEVICEID = "de.danoeh.antennapod.preferences.gpoddernet.deviceID"; - public static final String PREF_GPODNET_HOSTNAME = "prefGpodnetHostname"; - - - public static final String PREF_LAST_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp"; - public static final String PREF_SYNC_ADDED = "de.danoeh.antennapod.preferences.gpoddernet.sync_added"; - public static final String PREF_SYNC_REMOVED = "de.danoeh.antennapod.preferences.gpoddernet.sync_removed"; - - private static String username; - private static String password; - private static String deviceID; - private static String hostname; - - private static ReentrantLock feedListLock = new ReentrantLock(); - private static Set addedFeeds; - private static Set removedFeeds; - - /** - * Last value returned by getSubscriptionChanges call. Will be used for all subsequent calls of getSubscriptionChanges. - */ - private static long lastSyncTimestamp; - - private static boolean preferencesLoaded = false; - - private static SharedPreferences getPreferences() { - return PodcastApp.getInstance().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - } - - private static synchronized void ensurePreferencesLoaded() { - if (!preferencesLoaded) { - SharedPreferences prefs = getPreferences(); - username = prefs.getString(PREF_GPODNET_USERNAME, null); - password = prefs.getString(PREF_GPODNET_PASSWORD, null); - deviceID = prefs.getString(PREF_GPODNET_DEVICEID, null); - lastSyncTimestamp = prefs.getLong(PREF_LAST_SYNC_TIMESTAMP, 0); - addedFeeds = readListFromString(prefs.getString(PREF_SYNC_ADDED, "")); - removedFeeds = readListFromString(prefs.getString(PREF_SYNC_REMOVED, "")); - hostname = checkGpodnetHostname(prefs.getString(PREF_GPODNET_HOSTNAME, GpodnetService.DEFAULT_BASE_HOST)); - - preferencesLoaded = true; - } - } - - private static void writePreference(String key, String value) { - SharedPreferences.Editor editor = getPreferences().edit(); - editor.putString(key, value); - editor.commit(); - } - - private static void writePreference(String key, long value) { - SharedPreferences.Editor editor = getPreferences().edit(); - editor.putLong(key, value); - editor.commit(); - } - - private static void writePreference(String key, Collection value) { - SharedPreferences.Editor editor = getPreferences().edit(); - editor.putString(key, writeListToString(value)); - editor.commit(); - } - - public static String getUsername() { - ensurePreferencesLoaded(); - return username; - } - - public static void setUsername(String username) { - GpodnetPreferences.username = username; - writePreference(PREF_GPODNET_USERNAME, username); - } - - public static String getPassword() { - ensurePreferencesLoaded(); - return password; - } - - public static void setPassword(String password) { - GpodnetPreferences.password = password; - writePreference(PREF_GPODNET_PASSWORD, password); - } - - public static String getDeviceID() { - ensurePreferencesLoaded(); - return deviceID; - } - - public static void setDeviceID(String deviceID) { - GpodnetPreferences.deviceID = deviceID; - writePreference(PREF_GPODNET_DEVICEID, deviceID); - } - - public static long getLastSyncTimestamp() { - ensurePreferencesLoaded(); - return lastSyncTimestamp; - } - - public static void setLastSyncTimestamp(long lastSyncTimestamp) { - GpodnetPreferences.lastSyncTimestamp = lastSyncTimestamp; - writePreference(PREF_LAST_SYNC_TIMESTAMP, lastSyncTimestamp); - } - - public static String getHostname() { - ensurePreferencesLoaded(); - return hostname; - } - - public static void setHostname(String value) { - value = checkGpodnetHostname(value); - if (!value.equals(hostname)) { - logout(); - writePreference(PREF_GPODNET_HOSTNAME, value); - hostname = value; - } - } - - public static void addAddedFeed(String feed) { - ensurePreferencesLoaded(); - feedListLock.lock(); - if (addedFeeds.add(feed)) { - writePreference(PREF_SYNC_ADDED, addedFeeds); - } - if (removedFeeds.remove(feed)) { - writePreference(PREF_SYNC_REMOVED, removedFeeds); - } - feedListLock.unlock(); - GpodnetSyncService.sendSyncIntent(PodcastApp.getInstance()); - } - - public static void addRemovedFeed(String feed) { - ensurePreferencesLoaded(); - feedListLock.lock(); - if (removedFeeds.add(feed)) { - writePreference(PREF_SYNC_REMOVED, removedFeeds); - } - if (addedFeeds.remove(feed)) { - writePreference(PREF_SYNC_ADDED, addedFeeds); - } - feedListLock.unlock(); - GpodnetSyncService.sendSyncIntent(PodcastApp.getInstance()); - } - - public static Set getAddedFeedsCopy() { - ensurePreferencesLoaded(); - Set copy = new HashSet(); - feedListLock.lock(); - copy.addAll(addedFeeds); - feedListLock.unlock(); - return copy; - } - - public static void removeAddedFeeds(Collection removed) { - ensurePreferencesLoaded(); - feedListLock.lock(); - addedFeeds.removeAll(removed); - writePreference(PREF_SYNC_ADDED, addedFeeds); - feedListLock.unlock(); - } - - public static Set getRemovedFeedsCopy() { - ensurePreferencesLoaded(); - Set copy = new HashSet(); - feedListLock.lock(); - copy.addAll(removedFeeds); - feedListLock.unlock(); - return copy; - } - - public static void removeRemovedFeeds(Collection removed) { - ensurePreferencesLoaded(); - removedFeeds.removeAll(removed); - writePreference(PREF_SYNC_REMOVED, removedFeeds); - - } - - /** - * Returns true if device ID, username and password have a non-null value - */ - public static boolean loggedIn() { - ensurePreferencesLoaded(); - return deviceID != null && username != null && password != null; - } - - public static synchronized void logout() { - if (BuildConfig.DEBUG) Log.d(TAG, "Logout: Clearing preferences"); - setUsername(null); - setPassword(null); - setDeviceID(null); - addedFeeds.clear(); - writePreference(PREF_SYNC_ADDED, addedFeeds); - removedFeeds.clear(); - writePreference(PREF_SYNC_REMOVED, removedFeeds); - setLastSyncTimestamp(0); - } - - private static Set readListFromString(String s) { - Set result = new HashSet(); - for (String item : s.split(" ")) { - result.add(item); - } - return result; - } - - private static String writeListToString(Collection c) { - StringBuilder result = new StringBuilder(); - for (String item : c) { - result.append(item); - result.append(" "); - } - return result.toString().trim(); - } - - private static String checkGpodnetHostname(String value) { - int startIndex = 0; - if (value.startsWith("http://")) { - startIndex = "http://".length(); - } else if (value.startsWith("https://")) { - startIndex = "https://".length(); - } - return value.substring(startIndex); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java b/app/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java deleted file mode 100644 index 756b4067c..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java +++ /dev/null @@ -1,146 +0,0 @@ -package de.danoeh.antennapod.core.preferences; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.BuildConfig; - -/** - * Provides access to preferences set by the playback service. A private - * instance of this class must first be instantiated via createInstance() or - * otherwise every public method will throw an Exception when called. - */ -public class PlaybackPreferences implements - SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = "PlaybackPreferences"; - - /** - * Contains the feed id of the currently playing item if it is a FeedMedia - * object. - */ - public static final String PREF_CURRENTLY_PLAYING_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; - - /** - * Contains the id of the currently playing FeedMedia object or - * NO_MEDIA_PLAYING if the currently playing media is no FeedMedia object. - */ - public static final String PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedMediaId"; - - /** - * Type of the media object that is currently being played. This preference - * is set to NO_MEDIA_PLAYING after playback has been completed and is set - * as soon as the 'play' button is pressed. - */ - public static final String PREF_CURRENTLY_PLAYING_MEDIA = "de.danoeh.antennapod.preferences.currentlyPlayingMedia"; - - /** True if last played media was streamed. */ - public static final String PREF_CURRENT_EPISODE_IS_STREAM = "de.danoeh.antennapod.preferences.lastIsStream"; - - /** True if last played media was a video. */ - public static final String PREF_CURRENT_EPISODE_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo"; - - /** Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. */ - public static final long NO_MEDIA_PLAYING = -1; - - private long currentlyPlayingFeedId; - private long currentlyPlayingFeedMediaId; - private long currentlyPlayingMedia; - private boolean currentEpisodeIsStream; - private boolean currentEpisodeIsVideo; - - private static PlaybackPreferences instance; - private Context context; - - private PlaybackPreferences(Context context) { - this.context = context; - loadPreferences(); - } - - /** - * Sets up the UserPreferences class. - * - * @throws IllegalArgumentException - * if context is null - * */ - public static void createInstance(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating new instance of UserPreferences"); - Validate.notNull(context); - - instance = new PlaybackPreferences(context); - - PreferenceManager.getDefaultSharedPreferences(context) - .registerOnSharedPreferenceChangeListener(instance); - } - - private void loadPreferences() { - SharedPreferences sp = PreferenceManager - .getDefaultSharedPreferences(context); - currentlyPlayingFeedId = sp.getLong(PREF_CURRENTLY_PLAYING_FEED_ID, -1); - currentlyPlayingFeedMediaId = sp.getLong( - PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); - currentlyPlayingMedia = sp.getLong(PREF_CURRENTLY_PLAYING_MEDIA, - NO_MEDIA_PLAYING); - currentEpisodeIsStream = sp.getBoolean(PREF_CURRENT_EPISODE_IS_STREAM, true); - currentEpisodeIsVideo = sp.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sp, String key) { - if (key.equals(PREF_CURRENTLY_PLAYING_FEED_ID)) { - currentlyPlayingFeedId = sp.getLong(PREF_CURRENTLY_PLAYING_FEED_ID, - -1); - - } else if (key.equals(PREF_CURRENTLY_PLAYING_MEDIA)) { - currentlyPlayingMedia = sp - .getLong(PREF_CURRENTLY_PLAYING_MEDIA, -1); - - } else if (key.equals(PREF_CURRENT_EPISODE_IS_STREAM)) { - currentEpisodeIsStream = sp.getBoolean(PREF_CURRENT_EPISODE_IS_STREAM, true); - - } else if (key.equals(PREF_CURRENT_EPISODE_IS_VIDEO)) { - currentEpisodeIsVideo = sp.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false); - - } else if (key.equals(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID)) { - currentlyPlayingFeedMediaId = sp.getLong( - PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); - } - } - - private static void instanceAvailable() { - if (instance == null) { - throw new IllegalStateException( - "UserPreferences was used before being set up"); - } - } - - - public static long getLastPlayedFeedId() { - instanceAvailable(); - return instance.currentlyPlayingFeedId; - } - - public static long getCurrentlyPlayingMedia() { - instanceAvailable(); - return instance.currentlyPlayingMedia; - } - - public static long getCurrentlyPlayingFeedMediaId() { - return instance.currentlyPlayingFeedMediaId; - } - - public static boolean getCurrentEpisodeIsStream() { - instanceAvailable(); - return instance.currentEpisodeIsStream; - } - - public static boolean getCurrentEpisodeIsVideo() { - instanceAvailable(); - return instance.currentEpisodeIsVideo; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/app/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java deleted file mode 100644 index 1669fc601..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ /dev/null @@ -1,577 +0,0 @@ -package de.danoeh.antennapod.core.preferences; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.json.JSONArray; -import org.json.JSONException; - -import java.io.File; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver; - -/** - * Provides access to preferences set by the user in the settings screen. A - * private instance of this class must first be instantiated via - * createInstance() or otherwise every public method will throw an Exception - * when called. - */ -public class UserPreferences implements - SharedPreferences.OnSharedPreferenceChangeListener { - public static final String IMPORT_DIR = "import/"; - private static final String TAG = "UserPreferences"; - - public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect"; - public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue"; - public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly"; - public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall"; - public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; - public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes"; - public static final String PREF_AUTO_DELETE = "prefAutoDelete"; - public static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; - public static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold"; - public static final String PREF_THEME = "prefTheme"; - public static final String PREF_DATA_FOLDER = "prefDataFolder"; - public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl"; - public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter"; - private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; - public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; - private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed"; - private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; - public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; - private static final String PREF_SEEK_DELTA_SECS = "prefSeekDeltaSecs"; - - // TODO: Make this value configurable - private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f; - - private static int EPISODE_CACHE_SIZE_UNLIMITED = -1; - - private static UserPreferences instance; - private final Context context; - - // Preferences - private boolean pauseOnHeadsetDisconnect; - private boolean followQueue; - private boolean downloadMediaOnWifiOnly; - private long updateInterval; - private boolean allowMobileUpdate; - private boolean displayOnlyEpisodes; - private boolean autoDelete; - private boolean autoFlattr; - private float autoFlattrPlayedDurationThreshold; - private int theme; - private boolean enableAutodownload; - private boolean enableAutodownloadWifiFilter; - private String[] autodownloadSelectedNetworks; - private int episodeCacheSize; - private String playbackSpeed; - private String[] playbackSpeedArray; - private boolean pauseForFocusLoss; - private int seekDeltaSecs; - private boolean isFreshInstall; - - private UserPreferences(Context context) { - this.context = context; - loadPreferences(); - } - - /** - * Sets up the UserPreferences class. - * - * @throws IllegalArgumentException if context is null - */ - public static void createInstance(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating new instance of UserPreferences"); - Validate.notNull(context); - - instance = new UserPreferences(context); - - createImportDirectory(); - createNoMediaFile(); - PreferenceManager.getDefaultSharedPreferences(context) - .registerOnSharedPreferenceChangeListener(instance); - - } - - private void loadPreferences() { - SharedPreferences sp = PreferenceManager - .getDefaultSharedPreferences(context); - EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger( - R.integer.episode_cache_size_unlimited); - pauseOnHeadsetDisconnect = sp.getBoolean( - PREF_PAUSE_ON_HEADSET_DISCONNECT, true); - followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); - downloadMediaOnWifiOnly = sp.getBoolean( - PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true); - updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, - "0")); - allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); - displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); - autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); - autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); - autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, - PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); - theme = readThemeValue(sp.getString(PREF_THEME, "0")); - enableAutodownloadWifiFilter = sp.getBoolean( - PREF_ENABLE_AUTODL_WIFI_FILTER, false); - autodownloadSelectedNetworks = StringUtils.split( - sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); - episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString( - PREF_EPISODE_CACHE_SIZE, "20")); - enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); - playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); - playbackSpeedArray = readPlaybackSpeedArray(sp.getString( - PREF_PLAYBACK_SPEED_ARRAY, null)); - pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); - seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30")); - } - - private int readThemeValue(String valueFromPrefs) { - switch (Integer.parseInt(valueFromPrefs)) { - case 0: - return R.style.Theme_AntennaPod_Light; - case 1: - return R.style.Theme_AntennaPod_Dark; - default: - return R.style.Theme_AntennaPod_Light; - } - } - - private long readUpdateInterval(String valueFromPrefs) { - int hours = Integer.parseInt(valueFromPrefs); - return TimeUnit.HOURS.toMillis(hours); - } - - private int readEpisodeCacheSizeInternal(String valueFromPrefs) { - if (valueFromPrefs.equals(context - .getString(R.string.pref_episode_cache_unlimited))) { - return EPISODE_CACHE_SIZE_UNLIMITED; - } else { - return Integer.valueOf(valueFromPrefs); - } - } - - private String[] readPlaybackSpeedArray(String valueFromPrefs) { - String[] selectedSpeeds = null; - // If this preference hasn't been set yet, return the default options - if (valueFromPrefs == null) { - String[] allSpeeds = context.getResources().getStringArray( - R.array.playback_speed_values); - List speedList = new LinkedList(); - for (String speedStr : allSpeeds) { - float speed = Float.parseFloat(speedStr); - if (speed < 2.0001 && speed * 10 % 1 == 0) { - speedList.add(speedStr); - } - } - selectedSpeeds = speedList.toArray(new String[speedList.size()]); - } else { - try { - JSONArray jsonArray = new JSONArray(valueFromPrefs); - selectedSpeeds = new String[jsonArray.length()]; - for (int i = 0; i < jsonArray.length(); i++) { - selectedSpeeds[i] = jsonArray.getString(i); - } - } catch (JSONException e) { - Log.e(TAG, - "Got JSON error when trying to get speeds from JSONArray"); - e.printStackTrace(); - } - } - return selectedSpeeds; - } - - private static void instanceAvailable() { - if (instance == null) { - throw new IllegalStateException( - "UserPreferences was used before being set up"); - } - } - - public static boolean isPauseOnHeadsetDisconnect() { - instanceAvailable(); - return instance.pauseOnHeadsetDisconnect; - } - - public static boolean isFollowQueue() { - instanceAvailable(); - return instance.followQueue; - } - - public static boolean isDownloadMediaOnWifiOnly() { - instanceAvailable(); - return instance.downloadMediaOnWifiOnly; - } - - public static long getUpdateInterval() { - instanceAvailable(); - return instance.updateInterval; - } - - public static boolean isAllowMobileUpdate() { - instanceAvailable(); - return instance.allowMobileUpdate; - } - - public static boolean isDisplayOnlyEpisodes() { - instanceAvailable(); - //return instance.displayOnlyEpisodes; - return false; - } - - public static boolean isAutoDelete() { - instanceAvailable(); - return instance.autoDelete; - } - - public static boolean isAutoFlattr() { - instanceAvailable(); - return instance.autoFlattr; - } - - /** - * Returns the time after which an episode should be auto-flattr'd in percent of the episode's - * duration. - */ - public static float getAutoFlattrPlayedDurationThreshold() { - instanceAvailable(); - return instance.autoFlattrPlayedDurationThreshold; - } - - public static int getTheme() { - instanceAvailable(); - return instance.theme; - } - - public static boolean isEnableAutodownloadWifiFilter() { - instanceAvailable(); - return instance.enableAutodownloadWifiFilter; - } - - public static String[] getAutodownloadSelectedNetworks() { - instanceAvailable(); - return instance.autodownloadSelectedNetworks; - } - - public static int getEpisodeCacheSizeUnlimited() { - return EPISODE_CACHE_SIZE_UNLIMITED; - } - - public static String getPlaybackSpeed() { - instanceAvailable(); - return instance.playbackSpeed; - } - - public static String[] getPlaybackSpeedArray() { - instanceAvailable(); - return instance.playbackSpeedArray; - } - - public static int getSeekDeltaMs() { - instanceAvailable(); - return 1000 * instance.seekDeltaSecs; - } - - /** - * Returns the capacity of the episode cache. This method will return the - * negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to - * 'unlimited'. - */ - public static int getEpisodeCacheSize() { - instanceAvailable(); - return instance.episodeCacheSize; - } - - public static boolean isEnableAutodownload() { - instanceAvailable(); - return instance.enableAutodownload; - } - - public static boolean shouldPauseForFocusLoss() { - instanceAvailable(); - return instance.pauseForFocusLoss; - } - - public static boolean isFreshInstall() { - instanceAvailable(); - return instance.isFreshInstall; - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sp, String key) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Registered change of user preferences. Key: " + key); - - if (key.equals(PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY)) { - downloadMediaOnWifiOnly = sp.getBoolean( - PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true); - - } else if (key.equals(PREF_MOBILE_UPDATE)) { - allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); - - } else if (key.equals(PREF_FOLLOW_QUEUE)) { - followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); - - } else if (key.equals(PREF_UPDATE_INTERVAL)) { - updateInterval = readUpdateInterval(sp.getString( - PREF_UPDATE_INTERVAL, "0")); - restartUpdateAlarm(updateInterval); - - } else if (key.equals(PREF_AUTO_DELETE)) { - autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); - - } else if (key.equals(PREF_AUTO_FLATTR)) { - autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); - } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) { - displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, - false); - } else if (key.equals(PREF_THEME)) { - theme = readThemeValue(sp.getString(PREF_THEME, "")); - } else if (key.equals(PREF_ENABLE_AUTODL_WIFI_FILTER)) { - enableAutodownloadWifiFilter = sp.getBoolean( - PREF_ENABLE_AUTODL_WIFI_FILTER, false); - } else if (key.equals(PREF_AUTODL_SELECTED_NETWORKS)) { - autodownloadSelectedNetworks = StringUtils.split( - sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); - } else if (key.equals(PREF_EPISODE_CACHE_SIZE)) { - episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString( - PREF_EPISODE_CACHE_SIZE, "20")); - } else if (key.equals(PREF_ENABLE_AUTODL)) { - enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); - } else if (key.equals(PREF_PLAYBACK_SPEED)) { - playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); - } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) { - playbackSpeedArray = readPlaybackSpeedArray(sp.getString( - PREF_PLAYBACK_SPEED_ARRAY, null)); - } else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) { - pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); - } else if (key.equals(PREF_SEEK_DELTA_SECS)) { - seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30")); - } else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) { - pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true); - } else if (key.equals(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD)) { - autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, - PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); - } - } - - public static void setPlaybackSpeed(String speed) { - PreferenceManager.getDefaultSharedPreferences(instance.context).edit() - .putString(PREF_PLAYBACK_SPEED, speed).apply(); - } - - public static void setPlaybackSpeedArray(String[] speeds) { - JSONArray jsonArray = new JSONArray(); - for (String speed : speeds) { - jsonArray.put(speed); - } - PreferenceManager.getDefaultSharedPreferences(instance.context).edit() - .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString()) - .apply(); - } - - public static void setAutodownloadSelectedNetworks(Context context, - String[] value) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(context.getApplicationContext()) - .edit(); - editor.putString(PREF_AUTODL_SELECTED_NETWORKS, - StringUtils.join(value, ',')); - editor.commit(); - } - - /** - * Sets the update interval value. Should only be used for testing purposes! - */ - public static void setUpdateInterval(Context context, long newValue) { - instanceAvailable(); - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(context.getApplicationContext()) - .edit(); - editor.putString(PREF_UPDATE_INTERVAL, - String.valueOf(newValue)); - editor.commit(); - instance.updateInterval = newValue; - } - - /** - * Change the auto-flattr settings - * - * @param context For accessing the shared preferences - * @param enabled Whether automatic flattring should be enabled at all - * @param autoFlattrThreshold The percentage of playback time after which an episode should be - * flattrd. Must be a value between 0 and 1 (inclusive) - * */ - public static void setAutoFlattrSettings(Context context, boolean enabled, float autoFlattrThreshold) { - instanceAvailable(); - Validate.inclusiveBetween(0.0, 1.0, autoFlattrThreshold); - PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()) - .edit() - .putBoolean(PREF_AUTO_FLATTR, enabled) - .putFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, autoFlattrThreshold) - .commit(); - instance.autoFlattr = enabled; - instance.autoFlattrPlayedDurationThreshold = autoFlattrThreshold; - } - - /** - * Return the folder where the app stores all of its data. This method will - * return the standard data folder if none has been set by the user. - * - * @param type The name of the folder inside the data folder. May be null - * when accessing the root of the data folder. - * @return The data folder that has been requested or null if the folder - * could not be created. - */ - public static File getDataFolder(Context context, String type) { - instanceAvailable(); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context.getApplicationContext()); - String strDir = prefs.getString(PREF_DATA_FOLDER, null); - if (strDir == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Using default data folder"); - return context.getExternalFilesDir(type); - } else { - File dataDir = new File(strDir); - if (!dataDir.exists()) { - if (!dataDir.mkdir()) { - Log.w(TAG, "Could not create data folder"); - return null; - } - } - - if (type == null) { - return dataDir; - } else { - // handle path separators - String[] dirs = type.split("/"); - for (int i = 0; i < dirs.length; i++) { - if (dirs.length > 0) { - if (i < dirs.length - 1) { - dataDir = getDataFolder(context, dirs[i]); - if (dataDir == null) { - return null; - } - } - type = dirs[i]; - } - } - File typeDir = new File(dataDir, type); - if (!typeDir.exists()) { - if (dataDir.canWrite()) { - if (!typeDir.mkdir()) { - Log.e(TAG, "Could not create data folder named " - + type); - return null; - } - } - } - return typeDir; - } - - } - } - - public static void setDataFolder(String dir) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Result from DirectoryChooser: " + dir); - instanceAvailable(); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(instance.context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(PREF_DATA_FOLDER, dir); - editor.commit(); - createImportDirectory(); - } - - /** - * Create a .nomedia file to prevent scanning by the media scanner. - */ - private static void createNoMediaFile() { - File f = new File(instance.context.getExternalFilesDir(null), - ".nomedia"); - if (!f.exists()) { - try { - f.createNewFile(); - } catch (IOException e) { - Log.e(TAG, "Could not create .nomedia file"); - e.printStackTrace(); - } - if (BuildConfig.DEBUG) - Log.d(TAG, ".nomedia file created"); - } - } - - /** - * Creates the import directory if it doesn't exist and if storage is - * available - */ - private static void createImportDirectory() { - File importDir = getDataFolder(instance.context, - IMPORT_DIR); - if (importDir != null) { - if (importDir.exists()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Import directory already exists"); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating import directory"); - importDir.mkdir(); - } - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Could not access external storage."); - } - } - - /** - * Updates alarm registered with the AlarmManager service or deactivates it. - * - * @param millis new value to register with AlarmManager. If millis is 0, the - * alarm is deactivated. - */ - public static void restartUpdateAlarm(long millis) { - instanceAvailable(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Restarting update alarm. New value: " + millis); - AlarmManager alarmManager = (AlarmManager) instance.context - .getSystemService(Context.ALARM_SERVICE); - PendingIntent updateIntent = PendingIntent.getBroadcast( - instance.context, 0, new Intent( - FeedUpdateReceiver.ACTION_REFRESH_FEEDS), 0 - ); - alarmManager.cancel(updateIntent); - if (millis != 0) { - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, millis, millis, - updateIntent); - if (BuildConfig.DEBUG) - Log.d(TAG, "Changed alarm to new interval"); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Automatic update was deactivated"); - } - } - - /** - * Reads episode cache size as it is saved in the episode_cache_size_values array. - */ - public static int readEpisodeCacheSize(String valueFromPrefs) { - instanceAvailable(); - return instance.readEpisodeCacheSizeInternal(valueFromPrefs); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java b/app/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java deleted file mode 100644 index 2057b0881..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.danoeh.antennapod.core.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.preferences.UserPreferences; - -/** Listens for events that make it necessary to reset the update alarm. */ -public class AlarmUpdateReceiver extends BroadcastReceiver { - private static final String TAG = "AlarmUpdateReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received intent"); - if (StringUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Resetting update alarm after reboot"); - } else if (StringUtils.equals(intent.getAction(), Intent.ACTION_PACKAGE_REPLACED)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Resetting update alarm after app upgrade"); - } - - UserPreferences.restartUpdateAlarm(UserPreferences.getUpdateInterval()); - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/core/receiver/ConnectivityActionReceiver.java deleted file mode 100644 index e6b1a1b49..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/receiver/ConnectivityActionReceiver.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.danoeh.antennapod.core.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.NetworkUtils; - -public class ConnectivityActionReceiver extends BroadcastReceiver { - private static final String TAG = "ConnectivityActionReceiver"; - - @Override - public void onReceive(final Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received intent"); - - if (NetworkUtils.autodownloadNetworkAvailable(context)) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "auto-dl network available, starting auto-download"); - DBTasks.autodownloadUndownloadedItems(context); - } else { // if new network is Wi-Fi, finish ongoing downloads, - // otherwise cancel all downloads - ConnectivityManager cm = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo ni = cm.getActiveNetworkInfo(); - if (ni == null || ni.getType() != ConnectivityManager.TYPE_WIFI) { - if (BuildConfig.DEBUG) - Log.i(TAG, - "Device is no longer connected to Wi-Fi. Cancelling ongoing downloads"); - DownloadRequester.getInstance().cancelAllDownloads(context); - } - - } - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/app/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java deleted file mode 100644 index ec63bc2ae..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.danoeh.antennapod.core.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBTasks; - -/** Refreshes all feeds when it receives an intent */ -public class FeedUpdateReceiver extends BroadcastReceiver { - private static final String TAG = "FeedUpdateReceiver"; - public static final String ACTION_REFRESH_FEEDS = "de.danoeh.antennapod.feedupdatereceiver.refreshFeeds"; - - @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ACTION_REFRESH_FEEDS)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received intent"); - boolean mobileUpdate = UserPreferences.isAllowMobileUpdate(); - if (mobileUpdate || connectedToWifi(context)) { - DBTasks.refreshExpiredFeeds(context); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Blocking automatic update: no wifi available / no mobile updates allowed"); - } - } - } - - private boolean connectedToWifi(Context context) { - ConnectivityManager connManager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo mWifi = connManager - .getNetworkInfo(ConnectivityManager.TYPE_WIFI); - - return mWifi.isConnected(); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/app/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java deleted file mode 100644 index be54148cf..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java +++ /dev/null @@ -1,32 +0,0 @@ -package de.danoeh.antennapod.core.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.view.KeyEvent; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.service.playback.PlaybackService; - -/** Receives media button events. */ -public class MediaButtonReceiver extends BroadcastReceiver { - private static final String TAG = "MediaButtonReceiver"; - public static final String EXTRA_KEYCODE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.KEYCODE"; - - public static final String NOTIFY_BUTTON_RECEIVER = "de.danoeh.antennapod.NOTIFY_BUTTON_RECEIVER"; - - @Override - public void onReceive(Context context, Intent intent) { - if (BuildConfig.DEBUG) Log.d(TAG, "Received intent"); - KeyEvent event = (KeyEvent) intent.getExtras().get( - Intent.EXTRA_KEY_EVENT); - if (event.getAction() == KeyEvent.ACTION_DOWN) { - Intent serviceIntent = new Intent(context, PlaybackService.class); - int keycode = event.getKeyCode(); - serviceIntent.putExtra(EXTRA_KEYCODE, keycode); - context.startService(serviceIntent); - } - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java b/app/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java deleted file mode 100644 index 3dcfecdbd..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.danoeh.antennapod.core.receiver; - -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.service.playback.PlayerWidgetService; - -public class PlayerWidget extends AppWidgetProvider { - private static final String TAG = "PlayerWidget"; - public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE"; - public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE"; - - @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), FORCE_WIDGET_UPDATE)) { - startUpdate(context); - } else if (StringUtils.equals(intent.getAction(), STOP_WIDGET_UPDATE)) { - stopUpdate(context); - } - - } - - @Override - public void onEnabled(Context context) { - super.onEnabled(context); - if (BuildConfig.DEBUG) - Log.d(TAG, "Widget enabled"); - } - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, - int[] appWidgetIds) { - startUpdate(context); - } - - private void startUpdate(Context context) { - context.startService(new Intent(context, PlayerWidgetService.class)); - } - - private void stopUpdate(Context context) { - context.stopService(new Intent(context, PlayerWidgetService.class)); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/app/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java deleted file mode 100644 index 8a2659029..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java +++ /dev/null @@ -1,245 +0,0 @@ -package de.danoeh.antennapod.core.service; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException; -import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.NetworkUtils; - -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -/** - * Synchronizes local subscriptions with gpodder.net service. The service should be started with ACTION_SYNC as an action argument. - * This class also provides static methods for starting the GpodnetSyncService. - */ -public class GpodnetSyncService extends Service { - private static final String TAG = "GpodnetSyncService"; - - private static final long WAIT_INTERVAL = 5000L; - - public static final String ARG_ACTION = "action"; - - public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync"; - - private GpodnetService service; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null; - if (action != null && action.equals(ACTION_SYNC)) { - Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL)); - syncWaiterThread.restart(); - } else { - Log.e(TAG, "Received invalid intent: action argument is null or invalid"); - } - return START_FLAG_REDELIVERY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (BuildConfig.DEBUG) Log.d(TAG, "onDestroy"); - syncWaiterThread.interrupt(); - - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - private synchronized GpodnetService tryLogin() throws GpodnetServiceException { - if (service == null) { - service = new GpodnetService(); - service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); - } - return service; - } - - private synchronized void syncChanges() { - if (GpodnetPreferences.loggedIn() && NetworkUtils.networkAvailable(this)) { - final long timestamp = GpodnetPreferences.getLastSyncTimestamp(); - try { - final List localSubscriptions = DBReader.getFeedListDownloadUrls(this); - GpodnetService service = tryLogin(); - - if (timestamp == 0) { - // first sync: download all subscriptions... - GpodnetSubscriptionChange changes = - service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), 0); - if (BuildConfig.DEBUG) Log.d(TAG, "Downloaded subscription changes: " + changes); - processSubscriptionChanges(localSubscriptions, changes); - - // ... then upload all local subscriptions - if (BuildConfig.DEBUG) Log.d(TAG, "Uploading subscription list: " + localSubscriptions); - GpodnetUploadChangesResponse uploadChangesResponse = - service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), localSubscriptions, new LinkedList()); - if (BuildConfig.DEBUG) Log.d(TAG, "Uploading changes response: " + uploadChangesResponse); - GpodnetPreferences.removeAddedFeeds(localSubscriptions); - GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy()); - GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp); - } else { - Set added = GpodnetPreferences.getAddedFeedsCopy(); - Set removed = GpodnetPreferences.getRemovedFeedsCopy(); - - // download remote changes first... - GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), timestamp); - if (BuildConfig.DEBUG) Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges); - processSubscriptionChanges(localSubscriptions, subscriptionChanges); - - // ... then upload changes local changes - if (BuildConfig.DEBUG) Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s", - added.toString(), removed)); - GpodnetUploadChangesResponse uploadChangesResponse = service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), added, removed); - if (BuildConfig.DEBUG) Log.d(TAG, "Upload subscriptions response: " + uploadChangesResponse); - - GpodnetPreferences.removeAddedFeeds(added); - GpodnetPreferences.removeRemovedFeeds(removed); - GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp); - } - clearErrorNotifications(); - } catch (GpodnetServiceException e) { - e.printStackTrace(); - updateErrorNotification(e); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } - stopSelf(); - } - - private synchronized void processSubscriptionChanges(List localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException { - for (String downloadUrl : changes.getAdded()) { - if (!localSubscriptions.contains(downloadUrl)) { - Feed feed = new Feed(downloadUrl, new Date()); - DownloadRequester.getInstance().downloadFeed(this, feed); - } - } - for (String downloadUrl : changes.getRemoved()) { - DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl); - } - } - - private void clearErrorNotifications() { - NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - nm.cancel(R.id.notification_gpodnet_sync_error); - nm.cancel(R.id.notification_gpodnet_sync_autherror); - } - - private void updateErrorNotification(GpodnetServiceException exception) { - if (BuildConfig.DEBUG) Log.d(TAG, "Posting error notification"); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(this); - final String title; - final String description; - final int id; - if (exception instanceof GpodnetServiceAuthenticationException) { - title = getString(R.string.gpodnetsync_auth_error_title); - description = getString(R.string.gpodnetsync_auth_error_descr); - id = R.id.notification_gpodnet_sync_autherror; - } else { - title = getString(R.string.gpodnetsync_error_title); - description = getString(R.string.gpodnetsync_error_descr) + exception.getMessage(); - id = R.id.notification_gpodnet_sync_error; - } - - PendingIntent activityIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); -// TODO getGpodnetSyncServiceErrorNotificationPendingIntent - Notification notification = builder.setContentTitle(title) - .setContentText(description) - .setContentIntent(activityIntent) - .setSmallIcon(R.drawable.stat_notify_sync_error) - .setAutoCancel(true) - .build(); - NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify(id, notification); - } - - private WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) { - @Override - public void onWaitCompleted() { - syncChanges(); - } - }; - - private abstract class WaiterThread { - private long waitInterval; - private Thread thread; - - private WaiterThread(long waitInterval) { - this.waitInterval = waitInterval; - reinit(); - } - - public abstract void onWaitCompleted(); - - public void exec() { - if (!thread.isAlive()) { - thread.start(); - } - } - - private void reinit() { - if (thread != null && thread.isAlive()) { - Log.d(TAG, "Interrupting waiter thread"); - thread.interrupt(); - } - thread = new Thread() { - @Override - public void run() { - try { - Thread.sleep(waitInterval); - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (!isInterrupted()) { - synchronized (this) { - onWaitCompleted(); - } - } - } - }; - } - - public void restart() { - reinit(); - exec(); - } - - public void interrupt() { - if (thread != null && thread.isAlive()) { - thread.interrupt(); - } - } - } - - public static void sendSyncIntent(Context context) { - if (GpodnetPreferences.loggedIn()) { - Intent intent = new Intent(context, GpodnetSyncService.class); - intent.putExtra(ARG_ACTION, ACTION_SYNC); - context.startService(intent); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java b/app/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java deleted file mode 100644 index 1c62eaa77..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.impl.client.DefaultRedirectHandler; -import org.apache.http.protocol.HttpContext; - -import java.net.URI; - -public class APRedirectHandler extends DefaultRedirectHandler { - // Identifier for logger - private static final String TAG = "APRedirectHandler"; - // Header field, which has to be potentially fixed - private static final String LOC = "Location"; - // Regular expressions for character strings, which should not appear in URLs - private static final String CHi[] = { "\\{", "\\}", "\\|", "\\\\", "\\^", "~", "\\[", "\\]", "\\`"}; - private static final String CHo[] = { "%7B", "%7D", "%7C", "%5C", "%5E", "%7E", "%5B", "%5D", "%60"}; - - /** - * Workaround for broken URLs in redirection. - * Proper solution involves LaxRedirectStrategy() which is not available in - * current API yet. - */ - @Override - public URI getLocationURI(HttpResponse response, HttpContext context) - throws org.apache.http.ProtocolException { - - Header h[] = response.getHeaders(LOC); - if (h.length>0) { - String s = h[0].getValue(); - - // Fix broken URL - for(int i=0; i 0); - if (in.dataAvail() > 0) { - username = in.readString(); - } else { - username = null; - } - if (in.dataAvail() > 0) { - password = in.readString(); - } else { - password = null; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(destination); - dest.writeString(source); - dest.writeString(title); - dest.writeLong(feedfileId); - dest.writeInt(feedfileType); - dest.writeByte((deleteOnFailure) ? (byte) 1 : 0); - if (username != null) { - dest.writeString(username); - } - if (password != null) { - dest.writeString(password); - } - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public DownloadRequest createFromParcel(Parcel in) { - return new DownloadRequest(in); - } - - public DownloadRequest[] newArray(int size) { - return new DownloadRequest[size]; - } - }; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - DownloadRequest that = (DownloadRequest) o; - - if (deleteOnFailure != that.deleteOnFailure) return false; - if (feedfileId != that.feedfileId) return false; - if (feedfileType != that.feedfileType) return false; - if (progressPercent != that.progressPercent) return false; - if (size != that.size) return false; - if (soFar != that.soFar) return false; - if (statusMsg != that.statusMsg) return false; - if (destination != null ? !destination.equals(that.destination) : that.destination != null) - return false; - if (password != null ? !password.equals(that.password) : that.password != null) - return false; - if (source != null ? !source.equals(that.source) : that.source != null) return false; - if (title != null ? !title.equals(that.title) : that.title != null) return false; - if (username != null ? !username.equals(that.username) : that.username != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = destination != null ? destination.hashCode() : 0; - result = 31 * result + (source != null ? source.hashCode() : 0); - result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + (username != null ? username.hashCode() : 0); - result = 31 * result + (password != null ? password.hashCode() : 0); - result = 31 * result + (deleteOnFailure ? 1 : 0); - result = 31 * result + (int) (feedfileId ^ (feedfileId >>> 32)); - result = 31 * result + feedfileType; - result = 31 * result + progressPercent; - result = 31 * result + (int) (soFar ^ (soFar >>> 32)); - result = 31 * result + (int) (size ^ (size >>> 32)); - result = 31 * result + statusMsg; - return result; - } - - public String getDestination() { - return destination; - } - - public String getSource() { - return source; - } - - public String getTitle() { - return title; - } - - public long getFeedfileId() { - return feedfileId; - } - - public int getFeedfileType() { - return feedfileType; - } - - public int getProgressPercent() { - return progressPercent; - } - - public void setProgressPercent(int progressPercent) { - this.progressPercent = progressPercent; - } - - public long getSoFar() { - return soFar; - } - - public void setSoFar(long soFar) { - this.soFar = soFar; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public int getStatusMsg() { - return statusMsg; - } - - public void setStatusMsg(int statusMsg) { - this.statusMsg = statusMsg; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public boolean isDeleteOnFailure() { - return deleteOnFailure; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java deleted file mode 100644 index e9381d509..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ /dev/null @@ -1,1230 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -import android.annotation.SuppressLint; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaMetadataRetriever; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.webkit.URLUtil; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.apache.http.HttpStatus; -import org.xml.sax.SAXException; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.xml.parsers.ParserConfigurationException; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.DownloadAuthenticationActivity; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.NavListAdapter; -import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.FeedPreferences; -import de.danoeh.antennapod.fragment.DownloadsFragment; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.syndication.handler.FeedHandler; -import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException; -import de.danoeh.antennapod.core.util.ChapterUtils; -import de.danoeh.antennapod.core.util.DownloadError; -import de.danoeh.antennapod.core.util.InvalidFeedException; - -/** - * Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent. - * The argument of the intent is an instance of DownloadRequest in the EXTRA_REQUEST field of - * the intent. - * After the downloads have finished, the downloaded object will be passed on to a specific handler, depending on the - * type of the feedfile. - */ -public class DownloadService extends Service { - private static final String TAG = "DownloadService"; - - /** - * Cancels one download. The intent MUST have an EXTRA_DOWNLOAD_URL extra that contains the download URL of the - * object whose download should be cancelled. - */ - public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.core.service.cancelDownload"; - - /** - * Cancels all running downloads. - */ - public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.core.service.cancelAllDownloads"; - - /** - * Extra for ACTION_CANCEL_DOWNLOAD - */ - public static final String EXTRA_DOWNLOAD_URL = "downloadUrl"; - - /** - * Sent by the DownloadService when the content of the downloads list - * changes. - */ - public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.core.service.downloadsContentChanged"; - - /** - * Extra for ACTION_ENQUEUE_DOWNLOAD intent. - */ - public static final String EXTRA_REQUEST = "request"; - - /** - * Stores new media files that will be queued for auto-download if possible. - */ - private List newMediaFiles; - - /** - * Contains all completed downloads that have not been included in the report yet. - */ - private List reportQueue; - - private ExecutorService syncExecutor; - private CompletionService downloadExecutor; - private FeedSyncThread feedSyncThread; - - /** - * Number of threads of downloadExecutor. - */ - private static final int NUM_PARALLEL_DOWNLOADS = 6; - - private DownloadRequester requester; - - - private NotificationCompat.Builder notificationCompatBuilder; - private Notification.BigTextStyle notificationBuilder; - private int NOTIFICATION_ID = 2; - private int REPORT_ID = 3; - - /** - * Currently running downloads. - */ - private List downloads; - - /** - * Number of running downloads. - */ - private AtomicInteger numberOfDownloads; - - /** - * True if service is running. - */ - public static boolean isRunning = false; - - private Handler handler; - - private NotificationUpdater notificationUpdater; - private ScheduledFuture notificationUpdaterFuture; - private static final int SCHED_EX_POOL_SIZE = 1; - private ScheduledThreadPoolExecutor schedExecutor; - - private final IBinder mBinder = new LocalBinder(); - - public class LocalBinder extends Binder { - public DownloadService getService() { - return DownloadService.this; - } - } - - private Thread downloadCompletionThread = new Thread() { - private static final String TAG = "downloadCompletionThread"; - - @Override - public void run() { - if (BuildConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started"); - while (!isInterrupted()) { - try { - Downloader downloader = downloadExecutor.take().get(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Received 'Download Complete' - message."); - removeDownload(downloader); - DownloadStatus status = downloader.getResult(); - boolean successful = status.isSuccessful(); - - final int type = status.getFeedfileType(); - if (successful) { - if (type == Feed.FEEDFILETYPE_FEED) { - handleCompletedFeedDownload(downloader - .getDownloadRequest()); - } else if (type == FeedImage.FEEDFILETYPE_FEEDIMAGE) { - handleCompletedImageDownload(status, downloader.getDownloadRequest()); - } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest()); - } - } else { - numberOfDownloads.decrementAndGet(); - if (!status.isCancelled()) { - if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { - postAuthenticationNotification(downloader.getDownloadRequest()); - } else if (status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR - && Integer.valueOf(status.getReasonDetailed()) == HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE) { - - Log.d(TAG, "Requested invalid range, restarting download from the beginning"); - FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination())); - DownloadRequester.getInstance().download(DownloadService.this, downloader.getDownloadRequest()); - } else { - Log.e(TAG, "Download failed"); - saveDownloadStatus(status); - handleFailedDownload(status, downloader.getDownloadRequest()); - } - } - sendDownloadHandledIntent(); - queryDownloadsAsync(); - } - } catch (InterruptedException e) { - if (BuildConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted"); - } catch (ExecutionException e) { - e.printStackTrace(); - numberOfDownloads.decrementAndGet(); - } - } - if (BuildConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread"); - } - }; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent.getParcelableExtra(EXTRA_REQUEST) != null) { - onDownloadQueued(intent); - } else if (numberOfDownloads.get() == 0) { - stopSelf(); - } - return Service.START_NOT_STICKY; - } - - @SuppressLint("NewApi") - @Override - public void onCreate() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Service started"); - isRunning = true; - handler = new Handler(); - newMediaFiles = Collections.synchronizedList(new ArrayList()); - reportQueue = Collections.synchronizedList(new ArrayList()); - downloads = new ArrayList(); - numberOfDownloads = new AtomicInteger(0); - - IntentFilter cancelDownloadReceiverFilter = new IntentFilter(); - cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS); - cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD); - registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter); - syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); - downloadExecutor = new ExecutorCompletionService( - Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS, - new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - } - ) - ); - schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, - new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }, new RejectedExecutionHandler() { - - @Override - public void rejectedExecution(Runnable r, - ThreadPoolExecutor executor) { - Log.w(TAG, "SchedEx rejected submission of new task"); - } - } - ); - downloadCompletionThread.start(); - feedSyncThread = new FeedSyncThread(); - feedSyncThread.start(); - - setupNotificationBuilders(); - requester = DownloadRequester.getInstance(); - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public void onDestroy() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Service shutting down"); - isRunning = false; - updateReport(); - - stopForeground(true); - NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - nm.cancel(NOTIFICATION_ID); - - downloadCompletionThread.interrupt(); - syncExecutor.shutdown(); - schedExecutor.shutdown(); - feedSyncThread.shutdown(); - cancelNotificationUpdater(); - unregisterReceiver(cancelDownloadReceiver); - - if (!newMediaFiles.isEmpty()) { - DBTasks.autodownloadUndownloadedItems(getApplicationContext(), - ArrayUtils.toPrimitive(newMediaFiles.toArray(new Long[newMediaFiles.size()]))); - } - } - - @SuppressLint("NewApi") - private void setupNotificationBuilders() { // TODO getNotificationContentIntent - Intent intent = new Intent(this, MainActivity.class); - intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); - intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS); - Bundle args = new Bundle(); - args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_RUNNING); - intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); - - PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT - ); - - - Bitmap icon = BitmapFactory.decodeResource(getResources(), - R.drawable.stat_notify_sync); - - if (android.os.Build.VERSION.SDK_INT >= 16) { - notificationBuilder = new Notification.BigTextStyle( - new Notification.Builder(this).setOngoing(true) - .setContentIntent(pIntent).setLargeIcon(icon) - .setSmallIcon(R.drawable.stat_notify_sync) - ); - } else { - notificationCompatBuilder = new NotificationCompat.Builder(this) - .setOngoing(true).setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(R.drawable.stat_notify_sync); - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Notification set up"); - } - - /** - * Updates the contents of the service's notifications. Should be called - * before setupNotificationBuilders. - */ - @SuppressLint("NewApi") - private Notification updateNotifications() { - String contentTitle = getString(R.string.download_notification_title); - int numDownloads = requester.getNumberOfDownloads(); - String downloadsLeft; - if (numDownloads > 0) { - downloadsLeft = requester.getNumberOfDownloads() - + getString(R.string.downloads_left); - } else { - downloadsLeft = getString(R.string.downloads_processing); - } - if (android.os.Build.VERSION.SDK_INT >= 16) { - - if (notificationBuilder != null) { - - StringBuilder bigText = new StringBuilder(""); - for (int i = 0; i < downloads.size(); i++) { - Downloader downloader = downloads.get(i); - final DownloadRequest request = downloader - .getDownloadRequest(); - if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { - if (request.getTitle() != null) { - if (i > 0) { - bigText.append("\n"); - } - bigText.append("\u2022 " + request.getTitle()); - } - } else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - if (request.getTitle() != null) { - if (i > 0) { - bigText.append("\n"); - } - bigText.append("\u2022 " + request.getTitle() - + " (" + request.getProgressPercent() - + "%)"); - } - } - - } - notificationBuilder.setSummaryText(downloadsLeft); - notificationBuilder.setBigContentTitle(contentTitle); - if (bigText != null) { - notificationBuilder.bigText(bigText.toString()); - } - return notificationBuilder.build(); - } - } else { - if (notificationCompatBuilder != null) { - notificationCompatBuilder.setContentTitle(contentTitle); - notificationCompatBuilder.setContentText(downloadsLeft); - return notificationCompatBuilder.build(); - } - } - return null; - } - - private Downloader getDownloader(String downloadUrl) { - for (Downloader downloader : downloads) { - if (downloader.getDownloadRequest().getSource().equals(downloadUrl)) { - return downloader; - } - } - return null; - } - - private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_DOWNLOAD)) { - String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL); - Validate.notNull(url, "ACTION_CANCEL_DOWNLOAD intent needs download url extra"); - - if (BuildConfig.DEBUG) - Log.d(TAG, "Cancelling download with url " + url); - Downloader d = getDownloader(url); - if (d != null) { - d.cancel(); - } else { - Log.e(TAG, "Could not cancel download with url " + url); - } - - } else if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) { - for (Downloader d : downloads) { - d.cancel(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Cancelled all downloads"); - } - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); - - } - queryDownloads(); - } - - }; - - private void onDownloadQueued(Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received enqueue request"); - DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST); - if (request == null) { - throw new IllegalArgumentException( - "ACTION_ENQUEUE_DOWNLOAD intent needs request extra"); - } - - Downloader downloader = getDownloader(request); - if (downloader != null) { - numberOfDownloads.incrementAndGet(); - downloads.add(downloader); - downloadExecutor.submit(downloader); - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); - } - - queryDownloads(); - } - - private Downloader getDownloader(DownloadRequest request) { - if (URLUtil.isHttpUrl(request.getSource()) - || URLUtil.isHttpsUrl(request.getSource())) { - return new HttpDownloader(request); - } - Log.e(TAG, - "Could not find appropriate downloader for " - + request.getSource() - ); - return null; - } - - /** - * Remove download from the DownloadRequester list and from the - * DownloadService list. - */ - private void removeDownload(final Downloader d) { - handler.post(new Runnable() { - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Removing downloader: " - + d.getDownloadRequest().getSource()); - boolean rc = downloads.remove(d); - if (BuildConfig.DEBUG) - Log.d(TAG, "Result of downloads.remove: " + rc); - DownloadRequester.getInstance().removeDownload(d.getDownloadRequest()); - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); - } - }); - } - - /** - * Adds a new DownloadStatus object to the list of completed downloads and - * saves it in the database - * - * @param status the download that is going to be saved - */ - private void saveDownloadStatus(DownloadStatus status) { - reportQueue.add(status); - DBWriter.addDownloadStatus(this, status); - } - - private void sendDownloadHandledIntent() { - EventDistributor.getInstance().sendDownloadHandledBroadcast(); - } - - /** - * Creates a notification at the end of the service lifecycle to notify the - * user about the number of completed downloads. A report will only be - * created if the number of successfully downloaded feeds is bigger than 1 - * or if there is at least one failed download which is not an image or if - * there is at least one downloaded media file. - */ - private void updateReport() { - // check if report should be created - boolean createReport = false; - int successfulDownloads = 0; - int failedDownloads = 0; - - // a download report is created if at least one download has failed - // (excluding failed image downloads) - for (DownloadStatus status : reportQueue) { - if (status.isSuccessful()) { - successfulDownloads++; - } else if (!status.isCancelled()) { - if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) { - createReport = true; - } - failedDownloads++; - } - } - - if (createReport) { // TODO getReportNotificationContentIntent - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating report"); - Intent intent = new Intent(this, MainActivity.class); - intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); - intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS); - Bundle args = new Bundle(); - args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); - intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); - - // create notification object - Notification notification = new NotificationCompat.Builder(this) - .setTicker( - getString(de.danoeh.antennapod.R.string.download_report_title)) - .setContentTitle( - getString(de.danoeh.antennapod.R.string.download_report_title)) - .setContentText( - String.format( - getString(R.string.download_report_content), - successfulDownloads, failedDownloads) - ) - .setSmallIcon(R.drawable.stat_notify_sync) - .setLargeIcon( - BitmapFactory.decodeResource(getResources(), - R.drawable.stat_notify_sync) - ) - .setContentIntent( - PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) - ) - .setAutoCancel(true).build(); - NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify(REPORT_ID, notification); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "No report is created"); - } - reportQueue.clear(); - } - - /** - * Calls query downloads on the services main thread. This method should be used instead of queryDownloads if it is - * used from a thread other than the main thread. - */ - void queryDownloadsAsync() { - handler.post(new Runnable() { - public void run() { - queryDownloads(); - ; - } - }); - } - - /** - * Check if there's something else to download, otherwise stop - */ - void queryDownloads() { - if (BuildConfig.DEBUG) { - Log.d(TAG, numberOfDownloads.get() + " downloads left"); - } - - if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown"); - stopSelf(); - } else { - setupNotificationUpdater(); - startForeground(NOTIFICATION_ID, updateNotifications()); - } - } - - private void postAuthenticationNotification(final DownloadRequest downloadRequest) { - handler.post(new Runnable() { - @Override - public void run() { - final String resourceTitle = (downloadRequest.getTitle() != null) - ? downloadRequest.getTitle() : downloadRequest.getSource(); - - // TODO getAuthentificationNotificationContentIntent - final Intent activityIntent = new Intent(getApplicationContext(), DownloadAuthenticationActivity.class); - activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, downloadRequest); - activityIntent.putExtra(DownloadAuthenticationActivity.ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, true); - final PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, activityIntent, PendingIntent.FLAG_ONE_SHOT); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this); - builder.setTicker(getText(R.string.authentication_notification_title)) - .setContentTitle(getText(R.string.authentication_notification_title)) - .setContentText(getText(R.string.authentication_notification_msg)) - .setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.authentication_notification_msg) - + ": " + resourceTitle)) - .setSmallIcon(R.drawable.ic_stat_authentication) - .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_stat_authentication)) - .setAutoCancel(true) - .setContentIntent(contentIntent); - Notification n = builder.build(); - NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify(downloadRequest.getSource().hashCode(), n); - } - }); - } - - /** - * Is called whenever a Feed is downloaded - */ - private void handleCompletedFeedDownload(DownloadRequest request) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Handling completed Feed Download"); - feedSyncThread.submitCompletedDownload(request); - - } - - /** - * Is called whenever a Feed-Image is downloaded - */ - private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Handling completed Image Download"); - syncExecutor.execute(new ImageHandlerThread(status, request)); - } - - /** - * Is called whenever a FeedMedia is downloaded. - */ - private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Handling completed FeedMedia Download"); - syncExecutor.execute(new MediaHandlerThread(status, request)); - } - - private void handleFailedDownload(DownloadStatus status, DownloadRequest request) { - if (BuildConfig.DEBUG) Log.d(TAG, "Handling failed download"); - syncExecutor.execute(new FailedDownloadHandler(status, request)); - } - - /** - * Takes a single Feed, parses the corresponding file and refreshes - * information in the manager - */ - class FeedSyncThread extends Thread { - private static final String TAG = "FeedSyncThread"; - - private BlockingQueue completedRequests = new LinkedBlockingDeque(); - private CompletionService parserService = new ExecutorCompletionService(Executors.newSingleThreadExecutor()); - private ExecutorService dbService = Executors.newSingleThreadExecutor(); - private Future dbUpdateFuture; - private volatile boolean isActive = true; - private volatile boolean isCollectingRequests = false; - - private final long WAIT_TIMEOUT = 3000; - - - /** - * Waits for completed requests. Once the first request has been taken, the method will wait WAIT_TIMEOUT ms longer to - * collect more completed requests. - * - * @return Collected feeds or null if the method has been interrupted during the first waiting period. - */ - private List collectCompletedRequests() { - List results = new LinkedList(); - DownloadRequester requester = DownloadRequester.getInstance(); - int tasks = 0; - - try { - DownloadRequest request = completedRequests.take(); - parserService.submit(new FeedParserTask(request)); - tasks++; - } catch (InterruptedException e) { - return null; - } - - tasks += pollCompletedDownloads(); - - isCollectingRequests = true; - - if (requester.isDownloadingFeeds()) { - // wait for completion of more downloads - long startTime = System.currentTimeMillis(); - long currentTime = startTime; - while (requester.isDownloadingFeeds() && (currentTime - startTime) < WAIT_TIMEOUT) { - try { - if (BuildConfig.DEBUG) - Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms"); - sleep(startTime + WAIT_TIMEOUT - currentTime); - } catch (InterruptedException e) { - if (BuildConfig.DEBUG) - Log.d(TAG, "interrupted while waiting for more downloads"); - tasks += pollCompletedDownloads(); - } finally { - currentTime = System.currentTimeMillis(); - } - } - - tasks += pollCompletedDownloads(); - - } - - isCollectingRequests = false; - - for (int i = 0; i < tasks; i++) { - try { - Feed f = parserService.take().get(); - if (f != null) { - results.add(f); - } - } catch (InterruptedException e) { - e.printStackTrace(); - - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - - return results; - } - - private int pollCompletedDownloads() { - int tasks = 0; - for (int i = 0; i < completedRequests.size(); i++) { - parserService.submit(new FeedParserTask(completedRequests.poll())); - tasks++; - } - return tasks; - } - - @Override - public void run() { - while (isActive) { - final List feeds = collectCompletedRequests(); - - if (feeds == null) { - continue; - } - - if (BuildConfig.DEBUG) Log.d(TAG, "Bundling " + feeds.size() + " feeds"); - - for (Feed feed : feeds) { - removeDuplicateImages(feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet. - } - - // Save information of feed in DB - if (dbUpdateFuture != null) { - try { - dbUpdateFuture.get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - - dbUpdateFuture = dbService.submit(new Runnable() { - @Override - public void run() { - Feed[] savedFeeds = DBTasks.updateFeed(DownloadService.this, feeds.toArray(new Feed[feeds.size()])); - - for (Feed savedFeed : savedFeeds) { - // Download Feed Image if provided and not downloaded - if (savedFeed.getImage() != null - && savedFeed.getImage().isDownloaded() == false) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed has image; Downloading...."); - savedFeed.getImage().setOwner(savedFeed); - final Feed savedFeedRef = savedFeed; - try { - requester.downloadImage(DownloadService.this, - savedFeedRef.getImage()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - DownloadService.this, - new DownloadStatus( - savedFeedRef.getImage(), - savedFeedRef - .getImage() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage() - ) - ); - } - } - - // queue new media files for automatic download - for (FeedItem item : savedFeed.getItems()) { - if (!item.isRead() && item.hasMedia() && !item.getMedia().isDownloaded()) { - newMediaFiles.add(item.getMedia().getId()); - } - } - - numberOfDownloads.decrementAndGet(); - } - - sendDownloadHandledIntent(); - - queryDownloadsAsync(); - } - }); - - } - - if (dbUpdateFuture != null) { - try { - dbUpdateFuture.get(); - } catch (InterruptedException e) { - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - - if (BuildConfig.DEBUG) Log.d(TAG, "Shutting down"); - - } - - private class FeedParserTask implements Callable { - - private DownloadRequest request; - - private FeedParserTask(DownloadRequest request) { - this.request = request; - } - - @Override - public Feed call() throws Exception { - return parseFeed(request); - } - } - - private Feed parseFeed(DownloadRequest request) { - Feed savedFeed = null; - - Feed feed = new Feed(request.getSource(), new Date()); - feed.setFile_url(request.getDestination()); - feed.setId(request.getFeedfileId()); - feed.setDownloaded(true); - feed.setPreferences(new FeedPreferences(0, true, request.getUsername(), request.getPassword())); - - DownloadError reason = null; - String reasonDetailed = null; - boolean successful = true; - FeedHandler feedHandler = new FeedHandler(); - - try { - feed = feedHandler.parseFeed(feed).feed; - if (BuildConfig.DEBUG) - Log.d(TAG, feed.getTitle() + " parsed"); - if (checkFeedData(feed) == false) { - throw new InvalidFeedException(); - } - - } catch (SAXException e) { - successful = false; - e.printStackTrace(); - reason = DownloadError.ERROR_PARSER_EXCEPTION; - reasonDetailed = e.getMessage(); - } catch (IOException e) { - successful = false; - e.printStackTrace(); - reason = DownloadError.ERROR_PARSER_EXCEPTION; - reasonDetailed = e.getMessage(); - } catch (ParserConfigurationException e) { - successful = false; - e.printStackTrace(); - reason = DownloadError.ERROR_PARSER_EXCEPTION; - reasonDetailed = e.getMessage(); - } catch (UnsupportedFeedtypeException e) { - e.printStackTrace(); - successful = false; - reason = DownloadError.ERROR_UNSUPPORTED_TYPE; - reasonDetailed = e.getMessage(); - } catch (InvalidFeedException e) { - e.printStackTrace(); - successful = false; - reason = DownloadError.ERROR_PARSER_EXCEPTION; - reasonDetailed = e.getMessage(); - } - - // cleanup(); - if (savedFeed == null) { - savedFeed = feed; - } - - - if (successful) { - return savedFeed; - } else { - numberOfDownloads.decrementAndGet(); - saveDownloadStatus(new DownloadStatus(savedFeed, - savedFeed.getHumanReadableIdentifier(), reason, successful, - reasonDetailed)); - return null; - } - } - - - /** - * Checks if the feed was parsed correctly. - */ - private boolean checkFeedData(Feed feed) { - if (feed.getTitle() == null) { - Log.e(TAG, "Feed has no title."); - return false; - } - if (!hasValidFeedItems(feed)) { - Log.e(TAG, "Feed has invalid items"); - return false; - } - return true; - } - - /** - * Checks if the FeedItems of this feed have images that point - * to the same URL. If two FeedItems have an image that points to - * the same URL, the reference of the second item is removed, so that every image - * reference is unique. - */ - private void removeDuplicateImages(Feed feed) { - for (int x = 0; x < feed.getItems().size(); x++) { - for (int y = x + 1; y < feed.getItems().size(); y++) { - FeedItem item1 = feed.getItems().get(x); - FeedItem item2 = feed.getItems().get(y); - if (item1.hasItemImage() && item2.hasItemImage()) { - if (StringUtils.equals(item1.getImage().getDownload_url(), item2.getImage().getDownload_url())) { - item2.setImage(null); - } - } - } - } - } - - private boolean hasValidFeedItems(Feed feed) { - for (FeedItem item : feed.getItems()) { - if (item.getTitle() == null) { - Log.e(TAG, "Item has no title"); - return false; - } - if (item.getPubDate() == null) { - Log.e(TAG, - "Item has no pubDate. Using current time as pubDate"); - if (item.getTitle() != null) { - Log.e(TAG, "Title of invalid item: " + item.getTitle()); - } - item.setPubDate(new Date()); - } - } - return true; - } - - /** - * Delete files that aren't needed anymore - */ - private void cleanup(Feed feed) { - if (feed.getFile_url() != null) { - if (new File(feed.getFile_url()).delete()) - if (BuildConfig.DEBUG) - Log.d(TAG, "Successfully deleted cache file."); - else - Log.e(TAG, "Failed to delete cache file."); - feed.setFile_url(null); - } else if (BuildConfig.DEBUG) { - Log.d(TAG, "Didn't delete cache file: File url is not set."); - } - } - - public void shutdown() { - isActive = false; - if (isCollectingRequests) { - interrupt(); - } - } - - public void submitCompletedDownload(DownloadRequest request) { - completedRequests.offer(request); - if (isCollectingRequests) { - interrupt(); - } - } - - } - - /** - * Handles failed downloads. - *

- * If the file has been partially downloaded, this handler will set the file_url of the FeedFile to the location - * of the downloaded file. - *

- * Currently, this handler only handles FeedMedia objects, because Feeds and FeedImages are deleted if the download fails. - */ - class FailedDownloadHandler implements Runnable { - - private DownloadRequest request; - private DownloadStatus status; - - FailedDownloadHandler(DownloadStatus status, DownloadRequest request) { - this.request = request; - this.status = status; - } - - @Override - public void run() { - if (request.isDeleteOnFailure()) { - if (BuildConfig.DEBUG) Log.d(TAG, "Ignoring failed download, deleteOnFailure=true"); - } else { - File dest = new File(request.getDestination()); - if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - Log.d(TAG, "File has been partially downloaded. Writing file url"); - FeedMedia media = DBReader.getFeedMedia(DownloadService.this, request.getFeedfileId()); - media.setFile_url(request.getDestination()); - try { - DBWriter.setFeedMedia(DownloadService.this, media).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - } - } - } - - /** - * Handles a completed image download. - */ - class ImageHandlerThread implements Runnable { - - private DownloadRequest request; - private DownloadStatus status; - - public ImageHandlerThread(DownloadStatus status, DownloadRequest request) { - Validate.notNull(status); - Validate.notNull(request); - - this.status = status; - this.request = request; - } - - @Override - public void run() { - FeedImage image = DBReader.getFeedImage(DownloadService.this, request.getFeedfileId()); - if (image == null) { - throw new IllegalStateException("Could not find downloaded image in database"); - } - - image.setFile_url(request.getDestination()); - image.setDownloaded(true); - - saveDownloadStatus(status); - sendDownloadHandledIntent(); - DBWriter.setFeedImage(DownloadService.this, image); - numberOfDownloads.decrementAndGet(); - queryDownloadsAsync(); - } - } - - /** - * Handles a completed media download. - */ - class MediaHandlerThread implements Runnable { - - private DownloadRequest request; - private DownloadStatus status; - - public MediaHandlerThread(DownloadStatus status, DownloadRequest request) { - Validate.notNull(status); - Validate.notNull(request); - - this.status = status; - this.request = request; - } - - @Override - public void run() { - FeedMedia media = DBReader.getFeedMedia(DownloadService.this, - request.getFeedfileId()); - if (media == null) { - throw new IllegalStateException( - "Could not find downloaded media object in database"); - } - boolean chaptersRead = false; - media.setDownloaded(true); - media.setFile_url(request.getDestination()); - - // Get duration - MediaMetadataRetriever mmr = null; - try { - mmr = new MediaMetadataRetriever(); - mmr.setDataSource(media.getFile_url()); - String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - media.setDuration(Integer.parseInt(durationStr)); - if (BuildConfig.DEBUG) - Log.d(TAG, "Duration of file is " + media.getDuration()); - } catch (NumberFormatException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } finally { - if (mmr != null) { - mmr.release(); - } - } - - if (media.getItem().getChapters() == null) { - ChapterUtils.loadChaptersFromFileUrl(media); - if (media.getItem().getChapters() != null) { - chaptersRead = true; - } - } - - try { - if (chaptersRead) { - DBWriter.setFeedItem(DownloadService.this, media.getItem()).get(); - } - DBWriter.setFeedMedia(DownloadService.this, media).get(); - if (!DBTasks.isInQueue(DownloadService.this, media.getItem().getId())) { - DBWriter.addQueueItem(DownloadService.this, media.getItem().getId()).get(); - } - } catch (ExecutionException e) { - e.printStackTrace(); - status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage()); - } catch (InterruptedException e) { - e.printStackTrace(); - status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage()); - } - - saveDownloadStatus(status); - sendDownloadHandledIntent(); - - numberOfDownloads.decrementAndGet(); - queryDownloadsAsync(); - } - } - - /** - * Schedules the notification updater task if it hasn't been scheduled yet. - */ - private void setupNotificationUpdater() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting up notification updater"); - if (notificationUpdater == null) { - notificationUpdater = new NotificationUpdater(); - notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate( - notificationUpdater, 5L, 5L, TimeUnit.SECONDS); - } - } - - private void cancelNotificationUpdater() { - boolean result = false; - if (notificationUpdaterFuture != null) { - result = notificationUpdaterFuture.cancel(true); - } - notificationUpdater = null; - notificationUpdaterFuture = null; - Log.d(TAG, "NotificationUpdater cancelled. Result: " + result); - } - - private class NotificationUpdater implements Runnable { - public void run() { - handler.post(new Runnable() { - @Override - public void run() { - Notification n = updateNotifications(); - if (n != null) { - NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify(NOTIFICATION_ID, n); - } - } - }); - } - } - - public List getDownloads() { - return downloads; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java b/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java deleted file mode 100644 index d05650d10..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java +++ /dev/null @@ -1,181 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.core.feed.FeedFile; -import de.danoeh.antennapod.core.util.DownloadError; - -import java.util.Date; - -/** Contains status attributes for one download */ -public class DownloadStatus { - /** - * Downloaders should use this constant for the size attribute if necessary - * so that the listadapters etc. can react properly. - */ - public static final int SIZE_UNKNOWN = -1; - - // ----------------------------------- ATTRIBUTES STORED IN DB - /** Unique id for storing the object in database. */ - protected long id; - /** - * A human-readable string which is shown to the user so that he can - * identify the download. Should be the title of the item/feed/media or the - * URL if the download has no other title. - */ - protected String title; - protected DownloadError reason; - /** - * A message which can be presented to the user to give more information. - * Should be null if Download was successful. - */ - protected String reasonDetailed; - protected boolean successful; - protected Date completionDate; - protected long feedfileId; - /** - * Is used to determine the type of the feedfile even if the feedfile does - * not exist anymore. The value should be FEEDFILETYPE_FEED, - * FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA - */ - protected int feedfileType; - - // ------------------------------------ NOT STORED IN DB - protected boolean done; - protected boolean cancelled; - - /** Constructor for restoring Download status entries from DB. */ - public DownloadStatus(long id, String title, long feedfileId, - int feedfileType, boolean successful, DownloadError reason, - Date completionDate, String reasonDetailed) { - this.id = id; - this.title = title; - this.done = true; - this.feedfileId = feedfileId; - this.reason = reason; - this.successful = successful; - this.completionDate = (Date) completionDate.clone(); - this.reasonDetailed = reasonDetailed; - this.feedfileType = feedfileType; - } - - public DownloadStatus(DownloadRequest request, DownloadError reason, - boolean successful, boolean cancelled, String reasonDetailed) { - Validate.notNull(request); - - this.title = request.getTitle(); - this.feedfileId = request.getFeedfileId(); - this.feedfileType = request.getFeedfileType(); - this.reason = reason; - this.successful = successful; - this.cancelled = cancelled; - this.reasonDetailed = reasonDetailed; - this.completionDate = new Date(); - } - - /** Constructor for creating new completed downloads. */ - public DownloadStatus(FeedFile feedfile, String title, DownloadError reason, - boolean successful, String reasonDetailed) { - Validate.notNull(feedfile); - - this.title = title; - this.done = true; - this.feedfileId = feedfile.getId(); - this.feedfileType = feedfile.getTypeAsInt(); - this.reason = reason; - this.successful = successful; - this.completionDate = new Date(); - this.reasonDetailed = reasonDetailed; - } - - /** Constructor for creating new completed downloads. */ - public DownloadStatus(long feedfileId, int feedfileType, String title, - DownloadError reason, boolean successful, String reasonDetailed) { - this.title = title; - this.done = true; - this.feedfileId = feedfileId; - this.feedfileType = feedfileType; - this.reason = reason; - this.successful = successful; - this.completionDate = new Date(); - this.reasonDetailed = reasonDetailed; - } - - @Override - public String toString() { - return "DownloadStatus [id=" + id + ", title=" + title + ", reason=" - + reason + ", reasonDetailed=" + reasonDetailed - + ", successful=" + successful + ", completionDate=" - + completionDate + ", feedfileId=" + feedfileId - + ", feedfileType=" + feedfileType + ", done=" + done - + ", cancelled=" + cancelled + "]"; - } - - public long getId() { - return id; - } - - public String getTitle() { - return title; - } - - public DownloadError getReason() { - return reason; - } - - public String getReasonDetailed() { - return reasonDetailed; - } - - public boolean isSuccessful() { - return successful; - } - - public Date getCompletionDate() { - return (Date) completionDate.clone(); - } - - public long getFeedfileId() { - return feedfileId; - } - - public int getFeedfileType() { - return feedfileType; - } - - public boolean isDone() { - return done; - } - - public boolean isCancelled() { - return cancelled; - } - - public void setSuccessful() { - this.successful = true; - this.reason = DownloadError.SUCCESS; - this.done = true; - } - - public void setFailed(DownloadError reason, String reasonDetailed) { - this.successful = false; - this.reason = reason; - this.reasonDetailed = reasonDetailed; - this.done = true; - } - - public void setCancelled() { - this.successful = false; - this.reason = DownloadError.ERROR_DOWNLOAD_CANCELLED; - this.done = true; - this.cancelled = true; - } - - public void setCompletionDate(Date completionDate) { - this.completionDate = (Date) completionDate.clone(); - } - - public void setId(long id) { - this.id = id; - } -} \ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java b/app/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java deleted file mode 100644 index 5af9c2d05..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -import android.content.Context; -import android.net.wifi.WifiManager; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.R; - -import java.util.concurrent.Callable; - -/** Downloads files */ -public abstract class Downloader implements Callable { - private static final String TAG = "Downloader"; - - protected volatile boolean finished; - - protected volatile boolean cancelled; - - protected DownloadRequest request; - protected DownloadStatus result; - - public Downloader(DownloadRequest request) { - super(); - this.request = request; - this.request.setStatusMsg(R.string.download_pending); - this.cancelled = false; - this.result = new DownloadStatus(request, null, false, false, null); - } - - protected abstract void download(); - - public final Downloader call() { - WifiManager wifiManager = (WifiManager) PodcastApp.getInstance().getSystemService(Context.WIFI_SERVICE); - WifiManager.WifiLock wifiLock = null; - if (wifiManager != null) { - wifiLock = wifiManager.createWifiLock(TAG); - wifiLock.acquire(); - } - - download(); - - if (wifiLock != null) { - wifiLock.release(); - } - - if (result == null) { - throw new IllegalStateException( - "Downloader hasn't created DownloadStatus object"); - } - finished = true; - return this; - } - - public DownloadRequest getDownloadRequest() { - return request; - } - - public DownloadStatus getResult() { - return result; - } - - public boolean isFinished() { - return finished; - } - - public void cancel() { - cancelled = true; - } - -} \ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java b/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java deleted file mode 100644 index 2d9347b0a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -/** - * Callback used by the Downloader-classes to notify the requester that the - * download has completed. - */ -public interface DownloaderCallback { - - public void onDownloadCompleted(Downloader downloader); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/app/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java deleted file mode 100644 index cba59be01..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java +++ /dev/null @@ -1,246 +0,0 @@ -package de.danoeh.antennapod.core.service.download; - -import android.net.http.AndroidHttpClient; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.util.DownloadError; -import de.danoeh.antennapod.core.util.StorageUtils; -import de.danoeh.antennapod.core.util.URIUtil; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.message.BasicHeader; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -public class HttpDownloader extends Downloader { - private static final String TAG = "HttpDownloader"; - - private static final int BUFFER_SIZE = 8 * 1024; - - public HttpDownloader(DownloadRequest request) { - super(request); - } - - @Override - protected void download() { - File destination = new File(request.getDestination()); - final boolean fileExists = destination.exists(); - - if (request.isDeleteOnFailure() && fileExists) { - Log.w(TAG, "File already exists"); - if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) { - onFail(DownloadError.ERROR_FILE_EXISTS, null); - return; - } else { - onSuccess(); - return; - } - } - - HttpClient httpClient = AntennapodHttpClient.getHttpClient(); - RandomAccessFile out = null; - InputStream connection = null; - try { - HttpGet httpGet = new HttpGet(URIUtil.getURIFromRequestUrl(request.getSource())); - - // add authentication information - String userInfo = httpGet.getURI().getUserInfo(); - if (userInfo != null) { - String[] parts = userInfo.split(":"); - if (parts.length == 2) { - httpGet.addHeader(BasicScheme.authenticate( - new UsernamePasswordCredentials(parts[0], parts[1]), - "UTF-8", false)); - } - } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) { - httpGet.addHeader(BasicScheme.authenticate(new UsernamePasswordCredentials(request.getUsername(), - request.getPassword()), "UTF-8", false)); - } - - // add range header if necessary - if (fileExists) { - request.setSoFar(destination.length()); - httpGet.addHeader(new BasicHeader("Range", - "bytes=" + request.getSoFar() + "-")); - if (BuildConfig.DEBUG) Log.d(TAG, "Adding range header: " + request.getSoFar()); - } - - HttpResponse response = httpClient.execute(httpGet); - HttpEntity httpEntity = response.getEntity(); - int responseCode = response.getStatusLine().getStatusCode(); - Header contentEncodingHeader = response.getFirstHeader("Content-Encoding"); - - final boolean isGzip = contentEncodingHeader != null && - contentEncodingHeader.getValue().equalsIgnoreCase("gzip"); - - if (BuildConfig.DEBUG) - Log.d(TAG, "Response code is " + responseCode); - - if (responseCode / 100 != 2 || httpEntity == null) { - final DownloadError error; - final String details; - if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { - error = DownloadError.ERROR_UNAUTHORIZED; - details = String.valueOf(responseCode); - } else { - error = DownloadError.ERROR_HTTP_DATA_ERROR; - details = String.valueOf(responseCode); - } - onFail(error, details); - return; - } - - if (!StorageUtils.storageAvailable(PodcastApp.getInstance())) { - onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null); - return; - } - - connection = new BufferedInputStream(AndroidHttpClient - .getUngzippedContent(httpEntity)); - - Header[] contentRangeHeaders = (fileExists) ? response.getHeaders("Content-Range") : null; - - if (fileExists && responseCode == HttpStatus.SC_PARTIAL_CONTENT - && contentRangeHeaders != null && contentRangeHeaders.length > 0) { - String start = contentRangeHeaders[0].getValue().substring("bytes ".length(), - contentRangeHeaders[0].getValue().indexOf("-")); - request.setSoFar(Long.valueOf(start)); - Log.d(TAG, "Starting download at position " + request.getSoFar()); - - out = new RandomAccessFile(destination, "rw"); - out.seek(request.getSoFar()); - } else { - destination.delete(); - destination.createNewFile(); - out = new RandomAccessFile(destination, "rw"); - } - - - byte[] buffer = new byte[BUFFER_SIZE]; - int count = 0; - request.setStatusMsg(R.string.download_running); - if (BuildConfig.DEBUG) - Log.d(TAG, "Getting size of download"); - request.setSize(httpEntity.getContentLength() + request.getSoFar()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Size is " + request.getSize()); - if (request.getSize() < 0) { - request.setSize(DownloadStatus.SIZE_UNKNOWN); - } - - long freeSpace = StorageUtils.getFreeSpaceAvailable(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Free space is " + freeSpace); - - if (request.getSize() != DownloadStatus.SIZE_UNKNOWN - && request.getSize() > freeSpace) { - onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null); - return; - } - - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting download"); - while (!cancelled - && (count = connection.read(buffer)) != -1) { - out.write(buffer, 0, count); - request.setSoFar(request.getSoFar() + count); - request.setProgressPercent((int) (((double) request - .getSoFar() / (double) request - .getSize()) * 100)); - } - if (cancelled) { - onCancelled(); - } else { - // check if size specified in the response header is the same as the size of the - // written file. This check cannot be made if compression was used - if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN && - request.getSoFar() != request.getSize()) { - onFail(DownloadError.ERROR_IO_ERROR, - "Download completed but size: " + - request.getSoFar() + - " does not equal expected size " + - request.getSize() - ); - return; - } - onSuccess(); - } - - } catch (IllegalArgumentException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage()); - } catch (SocketTimeoutException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage()); - } catch (UnknownHostException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage()); - } catch (IOException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_IO_ERROR, e.getMessage()); - } catch (NullPointerException e) { - // might be thrown by connection.getInputStream() - e.printStackTrace(); - onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource()); - } finally { - IOUtils.closeQuietly(out); - AntennapodHttpClient.cleanup(); - } - } - - private void onSuccess() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Download was successful"); - result.setSuccessful(); - } - - private void onFail(DownloadError reason, String reasonDetailed) { - if (BuildConfig.DEBUG) { - Log.d(TAG, "Download failed"); - } - result.setFailed(reason, reasonDetailed); - if (request.isDeleteOnFailure()) { - cleanup(); - } - } - - private void onCancelled() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Download was cancelled"); - result.setCancelled(); - cleanup(); - } - - /** - * Deletes unfinished downloads. - */ - private void cleanup() { - if (request.getDestination() != null) { - File dest = new File(request.getDestination()); - if (dest.exists()) { - boolean rc = dest.delete(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Deleted file " + dest.getName() + "; Result: " - + rc); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "cleanup() didn't delete file: does not exist."); - } - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java deleted file mode 100644 index c191c9521..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ /dev/null @@ -1,1080 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.annotation.SuppressLint; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.AudioManager; -import android.media.MediaMetadataRetriever; -import android.media.MediaPlayer; -import android.media.RemoteControlClient; -import android.media.RemoteControlClient.MetadataEditor; -import android.os.AsyncTask; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.util.Pair; -import android.view.KeyEvent; -import android.view.SurfaceHolder; -import android.widget.Toast; - -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.util.List; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AudioplayerActivity; -import de.danoeh.antennapod.activity.VideoplayerActivity; -import de.danoeh.antennapod.core.asynctask.PicassoProvider; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; -import de.danoeh.antennapod.core.receiver.PlayerWidget; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import de.danoeh.antennapod.core.util.playback.Playable; - -/** - * Controls the MediaPlayer that plays a FeedMedia-file - */ -public class PlaybackService extends Service { - /** - * Logging tag - */ - private static final String TAG = "PlaybackService"; - - /** - * Parcelable of type Playable. - */ - public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra"; - /** - * True if media should be streamed. - */ - public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.core.service.shouldStream"; - /** - * True if playback should be started immediately after media has been - * prepared. - */ - public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.core.service.startWhenPrepared"; - - public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.core.service.prepareImmediately"; - - public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.core.service.playerStatusChanged"; - private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged"; - private static final String AVRCP_ACTION_META_CHANGED = "com.android.music.metachanged"; - - public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.core.service.playerNotification"; - public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.core.service.notificationCode"; - public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.core.service.notificationType"; - - /** - * If the PlaybackService receives this action, it will stop playback and - * try to shutdown. - */ - public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.core.service.actionShutdownPlaybackService"; - - /** - * If the PlaybackService receives this action, it will end playback of the - * current episode and load the next episode if there is one available. - */ - public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.skipCurrentEpisode"; - - /** - * Used in NOTIFICATION_TYPE_RELOAD. - */ - public static final int EXTRA_CODE_AUDIO = 1; - public static final int EXTRA_CODE_VIDEO = 2; - - public static final int NOTIFICATION_TYPE_ERROR = 0; - public static final int NOTIFICATION_TYPE_INFO = 1; - public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2; - - /** - * Receivers of this intent should update their information about the curently playing media - */ - public static final int NOTIFICATION_TYPE_RELOAD = 3; - /** - * The state of the sleeptimer changed. - */ - public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4; - public static final int NOTIFICATION_TYPE_BUFFER_START = 5; - public static final int NOTIFICATION_TYPE_BUFFER_END = 6; - /** - * No more episodes are going to be played. - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7; - - /** - * Playback speed has changed - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8; - - /** - * Returned by getPositionSafe() or getDurationSafe() if the playbackService - * is in an invalid state. - */ - public static final int INVALID_TIME = -1; - - /** - * Is true if service is running. - */ - public static boolean isRunning = false; - /** - * Is true if service has received a valid start command. - */ - public static boolean started = false; - - private static final int NOTIFICATION_ID = 1; - - private RemoteControlClient remoteControlClient; - private PlaybackServiceMediaPlayer mediaPlayer; - private PlaybackServiceTaskManager taskManager; - - private static volatile MediaType currentMediaType = MediaType.UNKNOWN; - - private final IBinder mBinder = new LocalBinder(); - - public class LocalBinder extends Binder { - public PlaybackService getService() { - return PlaybackService.this; - } - } - - @Override - public boolean onUnbind(Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received onUnbind event"); - return super.onUnbind(intent); - } - - /** - * Returns an intent which starts an audio- or videoplayer, depending on the - * type of media that is being played. If the playbackservice is not - * running, the type of the last played media will be looked up. - */ - public static Intent getPlayerActivityIntent(Context context) { - if (isRunning) { // TODO getPlayerActivityIntent - if (currentMediaType == MediaType.VIDEO) { - return new Intent(context, VideoplayerActivity.class); - } else { - return new Intent(context, AudioplayerActivity.class); - } - } else { - if (PlaybackPreferences.getCurrentEpisodeIsVideo()) { - return new Intent(context, VideoplayerActivity.class); - } else { - return new Intent(context, AudioplayerActivity.class); - } - } - } - - /** - * Same as getPlayerActivityIntent(context), but here the type of activity - * depends on the FeedMedia that is provided as an argument. - */ - public static Intent getPlayerActivityIntent(Context context, Playable media) { - MediaType mt = media.getMediaType(); - if (mt == MediaType.VIDEO) { - return new Intent(context, VideoplayerActivity.class); - } else { - return new Intent(context, AudioplayerActivity.class); - } - } - - @SuppressLint("NewApi") - @Override - public void onCreate() { - super.onCreate(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Service created."); - isRunning = true; - - registerReceiver(headsetDisconnected, new IntentFilter( - Intent.ACTION_HEADSET_PLUG)); - registerReceiver(shutdownReceiver, new IntentFilter( - ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - registerReceiver(audioBecomingNoisy, new IntentFilter( - AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter( - ACTION_SKIP_CURRENT_EPISODE)); - remoteControlClient = setupRemoteControlClient(); - taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); - mediaPlayer = new PlaybackServiceMediaPlayer(this, mediaPlayerCallback); - - } - - @SuppressLint("NewApi") - @Override - public void onDestroy() { - super.onDestroy(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Service is about to be destroyed"); - isRunning = false; - started = false; - currentMediaType = MediaType.UNKNOWN; - - unregisterReceiver(headsetDisconnected); - unregisterReceiver(shutdownReceiver); - unregisterReceiver(audioBecomingNoisy); - unregisterReceiver(skipCurrentEpisodeReceiver); - mediaPlayer.shutdown(); - taskManager.shutdown(); - } - - @Override - public IBinder onBind(Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received onBind event"); - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); - - if (BuildConfig.DEBUG) - Log.d(TAG, "OnStartCommand called"); - final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1); - final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); - if (keycode == -1 && playable == null) { - Log.e(TAG, "PlaybackService was started with no arguments"); - stopSelf(); - } - - if ((flags & Service.START_FLAG_REDELIVERY) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now."); - stopForeground(true); - } else { - - if (keycode != -1) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received media button event"); - handleKeycode(keycode); - } else { - started = true; - boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, - true); - boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false); - boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately); - } - } - - return Service.START_REDELIVER_INTENT; - } - - /** - * Handles media button events - */ - private void handleKeycode(int keycode) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Handling keycode: " + keycode); - - final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - final PlayerStatus status = info.playerStatus; - switch (keycode) { - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, true); - } else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - mediaPlayer.resume(); - } else if (status == PlayerStatus.PREPARING) { - mediaPlayer.setStartWhenPrepared(!mediaPlayer.isStartWhenPrepared()); - } else if (status == PlayerStatus.INITIALIZED) { - mediaPlayer.setStartWhenPrepared(true); - mediaPlayer.prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - mediaPlayer.resume(); - } else if (status == PlayerStatus.INITIALIZED) { - mediaPlayer.setStartWhenPrepared(true); - mediaPlayer.prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, true); - } - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - mediaPlayer.seekDelta(UserPreferences.getSeekDeltaMs()); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - mediaPlayer.seekDelta(-UserPreferences.getSeekDeltaMs()); - break; - default: - if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something - String message = String.format(getResources().getString(R.string.unknown_media_key), keycode); - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - break; - } - } - - /** - * Called by a mediaplayer Activity as soon as it has prepared its - * mediaplayer. - */ - public void setVideoSurface(SurfaceHolder sh) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting display"); - mediaPlayer.setVideoSurface(sh); - } - - /** - * Called when the surface holder of the mediaplayer has to be changed. - */ - private void resetVideoSurface() { - taskManager.cancelPositionSaver(); - mediaPlayer.resetVideoSurface(); - } - - public void notifyVideoSurfaceAbandoned() { - stopForeground(true); - mediaPlayer.resetVideoSurface(); - } - - private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() { - @Override - public void positionSaverTick() { - saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL); - } - - @Override - public void onSleepTimerExpired() { - mediaPlayer.pause(true, true); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - - @Override - public void onWidgetUpdaterTick() { - updateWidget(); - } - - @Override - public void onChapterLoaded(Playable media) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } - }; - - private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() { - @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { - currentMediaType = mediaPlayer.getCurrentMediaType(); - switch (newInfo.playerStatus) { - case INITIALIZED: - writePlaybackPreferences(); - break; - - case PREPARED: - taskManager.startChapterLoader(newInfo.playable); - break; - - case PAUSED: - taskManager.cancelPositionSaver(); - saveCurrentPosition(false, 0); - taskManager.cancelWidgetUpdater(); - stopForeground(true); - break; - - case STOPPED: - //setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - //stopSelf(); - break; - - case PLAYING: - if (BuildConfig.DEBUG) - Log.d(TAG, "Audiofocus successfully requested"); - if (BuildConfig.DEBUG) - Log.d(TAG, "Resuming/Starting playback"); - - taskManager.startPositionSaver(); - taskManager.startWidgetUpdater(); - setupNotification(newInfo); - break; - case ERROR: - writePlaybackPreferencesNoMediaPlaying(); - break; - - } - - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - updateWidget(); - refreshRemoteControlClientState(newInfo); - bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED); - bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED); - } - - @Override - public void shouldStop() { - stopSelf(); - } - - @Override - public void playbackSpeedChanged(float s) { - sendNotificationBroadcast( - NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); - } - - @Override - public void onBufferingUpdate(int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); - } - - @Override - public boolean onMediaPlayerInfo(int code) { - switch (code) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - return true; - default: - return false; - } - } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - final String TAG = "PlaybackService.onErrorListener"; - Log.w(TAG, "An error has occured: " + what + " " + extra); - if (mediaPlayer.getPSMPInfo().playerStatus == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, false); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - writePlaybackPreferencesNoMediaPlaying(); - stopSelf(); - return true; - } - - @Override - public boolean endPlayback(boolean playNextEpisode) { - PlaybackService.this.endPlayback(true); - return true; - } - - @Override - public RemoteControlClient getRemoteControlClient() { - return remoteControlClient; - } - }; - - private void endPlayback(boolean playNextEpisode) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Playback ended"); - - final Playable media = mediaPlayer.getPSMPInfo().playable; - if (media == null) { - Log.e(TAG, "Cannot end playback: media was null"); - return; - } - - taskManager.cancelPositionSaver(); - - boolean isInQueue = false; - FeedItem nextItem = null; - - if (media instanceof FeedMedia) { - FeedItem item = ((FeedMedia) media).getItem(); - DBWriter.markItemRead(PlaybackService.this, item, true, true); - - try { - final List queue = taskManager.getQueue(); - isInQueue = QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId()); - nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue); - } catch (InterruptedException e) { - e.printStackTrace(); - // isInQueue remains false - } - if (isInQueue) { - DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true); - } - DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media); - - // auto-flattr if enabled - if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) { - DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item); - } - } - - // Load next episode if previous episode was in the queue and if there - // is an episode in the queue left. - // Start playback immediately if continuous playback is enabled - Playable nextMedia = null; - boolean loadNextItem = isInQueue && nextItem != null; - playNextEpisode = playNextEpisode && loadNextItem - && UserPreferences.isFollowQueue(); - if (loadNextItem) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading next item in queue"); - nextMedia = nextItem.getMedia(); - } - final boolean prepareImmediately; - final boolean startWhenPrepared; - final boolean stream; - - if (playNextEpisode) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Playback of next episode will start immediately."); - prepareImmediately = startWhenPrepared = true; - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "No more episodes available to play"); - - prepareImmediately = startWhenPrepared = false; - stopForeground(true); - stopWidgetUpdater(); - } - - writePlaybackPreferencesNoMediaPlaying(); - if (nextMedia != null) { - stream = !media.localFileAvailable(); - mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - (nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO); - } else { - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); - mediaPlayer.stop(); - //stopSelf(); - } - } - - public void setSleepTimer(long waitingTime) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) - + " milliseconds"); - taskManager.setSleepTimer(waitingTime); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public void disableSleepTimer() { - taskManager.disableSleepTimer(); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - private void writePlaybackPreferencesNoMediaPlaying() { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.commit(); - } - - - private void writePlaybackPreferences() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Writing playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - MediaType mediaType = mediaPlayer.getCurrentMediaType(); - boolean stream = mediaPlayer.isStreaming(); - - if (info.playable != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - info.playable.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - stream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - mediaType == MediaType.VIDEO); - if (info.playable instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) info.playable; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - info.playable.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - - editor.commit(); - } - - /** - * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. - */ - private void postStatusUpdateIntent() { - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - } - - private void sendNotificationBroadcast(int type, int code) { - Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); - intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); - intent.putExtra(EXTRA_NOTIFICATION_CODE, code); - sendBroadcast(intent); - } - - /** - * Used by setupNotification to load notification data in another thread. - */ - private AsyncTask notificationSetupTask; - - /** - * Prepares notification and starts the service in the foreground. - */ - @SuppressLint("NewApi") - private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) { - final PendingIntent pIntent = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - - if (notificationSetupTask != null) { - notificationSetupTask.cancel(true); - } - notificationSetupTask = new AsyncTask() { - Bitmap icon = null; - - @Override - protected Void doInBackground(Void... params) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting background work"); - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (info.playable != null) { - try { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - icon = PicassoProvider.getMediaMetadataPicassoInstance(PlaybackService.this) - .load(info.playable.getImageUri()) - .resize(iconSize, iconSize) - .get(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - } - if (icon == null) { - icon = BitmapFactory.decodeResource(getResources(), - R.drawable.ic_stat_antenna); - } - - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (!isCancelled() && info.playerStatus == PlayerStatus.PLAYING - && info.playable != null) { - String contentText = info.playable.getFeedTitle(); - String contentTitle = info.playable.getEpisodeTitle(); - Notification notification = null; - if (android.os.Build.VERSION.SDK_INT >= 16) { - Intent pauseButtonIntent = new Intent( - PlaybackService.this, PlaybackService.class); - pauseButtonIntent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - KeyEvent.KEYCODE_MEDIA_PAUSE); - PendingIntent pauseButtonPendingIntent = PendingIntent - .getService(PlaybackService.this, 0, - pauseButtonIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - Notification.Builder notificationBuilder = new Notification.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setOngoing(true) - .setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna) - .addAction(android.R.drawable.ic_media_pause, - getString(R.string.pause_label), - pauseButtonPendingIntent); - notification = notificationBuilder.build(); - } else { - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText).setOngoing(true) - .setContentIntent(pIntent).setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna); - notification = notificationBuilder.getNotification(); - } - startForeground(NOTIFICATION_ID, notification); - if (BuildConfig.DEBUG) - Log.d(TAG, "Notification set up"); - } - } - - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - notificationSetupTask - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - notificationSetupTask.execute(); - } - - } - - /** - * Saves the current position of the media file to the DB - * - * @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects - * @param deltaPlayedDuration value by which played_duration should be increased. - */ - private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) { - int position = getCurrentPosition(); - int duration = getDuration(); - float playbackSpeed = getCurrentPlaybackSpeed(); - final Playable playable = mediaPlayer.getPSMPInfo().playable; - if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Saving current position to " + position); - if (updatePlayedDuration && playable instanceof FeedMedia) { - FeedMedia m = (FeedMedia) playable; - FeedItem item = m.getItem(); - m.setPlayedDuration(m.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed))); - // Auto flattr - if (isAutoFlattrable(m) && - (m.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) { - - if (BuildConfig.DEBUG) - Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(m.getPlayedDuration()) - + " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration)); - DBTasks.flattrItemIfLoggedIn(this, item); - } - } - playable.saveCurrentPosition(PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()), - position - ); - } - } - - private void stopWidgetUpdater() { - taskManager.cancelWidgetUpdater(); - sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); - } - - private void updateWidget() { - PlaybackService.this.sendBroadcast(new Intent( - PlayerWidget.FORCE_WIDGET_UPDATE)); - } - - public boolean sleepTimerActive() { - return taskManager.isSleepTimerActive(); - } - - public long getSleepTimerTimeLeft() { - return taskManager.getSleepTimerTimeLeft(); - } - - @SuppressLint("NewApi") - private RemoteControlClient setupRemoteControlClient() { - if (Build.VERSION.SDK_INT < 14) { - return null; - } - - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(new ComponentName(getPackageName(), - MediaButtonReceiver.class.getName())); - PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( - getApplicationContext(), 0, mediaButtonIntent, 0); - remoteControlClient = new RemoteControlClient(mediaPendingIntent); - int controlFlags; - if (android.os.Build.VERSION.SDK_INT < 16) { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE - | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - } else { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - } - remoteControlClient.setTransportControlFlags(controlFlags); - return remoteControlClient; - } - - /** - * Refresh player status and metadata. - */ - @SuppressLint("NewApi") - private void refreshRemoteControlClientState(PlaybackServiceMediaPlayer.PSMPInfo info) { - if (android.os.Build.VERSION.SDK_INT >= 14) { - if (remoteControlClient != null) { - switch (info.playerStatus) { - case PLAYING: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); - break; - case PAUSED: - case INITIALIZED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); - break; - case STOPPED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); - break; - case ERROR: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); - break; - default: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); - } - if (info.playable != null) { - MetadataEditor editor = remoteControlClient - .editMetadata(false); - editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - info.playable.getEpisodeTitle()); - - editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, - info.playable.getFeedTitle()); - - editor.apply(); - } - if (BuildConfig.DEBUG) - Log.d(TAG, "RemoteControlClient state was refreshed"); - } - } - } - - private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) { - boolean isPlaying = false; - - if (info.playerStatus == PlayerStatus.PLAYING) { - isPlaying = true; - } - - if (info.playable != null) { - Intent i = new Intent(whatChanged); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", info.playable.getFeedTitle()); - i.putExtra("track", info.playable.getEpisodeTitle()); - i.putExtra("playing", isPlaying); - final List queue = taskManager.getQueueIfLoaded(); - if (queue != null) { - i.putExtra("ListSize", queue.size()); - } - i.putExtra("duration", info.playable.getDuration()); - i.putExtra("position", info.playable.getPosition()); - sendBroadcast(i); - } - } - - /** - * Pauses playback when the headset is disconnected and the preference is - * set - */ - private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { - private static final String TAG = "headsetDisconnected"; - private static final int UNPLUGGED = 0; - - @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", -1); - if (state != -1) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Headset plug event. State is " + state); - if (state == UNPLUGGED) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Headset was unplugged during playback."); - pauseIfPauseOnDisconnect(); - } - } else { - Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); - } - } - } - }; - - private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - // sound is about to change, eg. bluetooth -> speaker - if (BuildConfig.DEBUG) - Log.d(TAG, "Pausing playback because audio is becoming noisy"); - pauseIfPauseOnDisconnect(); - } - // android.media.AUDIO_BECOMING_NOISY - }; - - /** - * Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. - */ - private void pauseIfPauseOnDisconnect() { - if (UserPreferences.isPauseOnHeadsetDisconnect()) { - mediaPlayer.pause(true, true); - } - } - - private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - stopSelf(); - } - } - - }; - - private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (StringUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); - mediaPlayer.endPlayback(); - } - } - }; - - public static MediaType getCurrentMediaType() { - return currentMediaType; - } - - public void resume() { - mediaPlayer.resume(); - } - - public void prepare() { - mediaPlayer.prepare(); - } - - public void pause(boolean abandonAudioFocus, boolean reinit) { - mediaPlayer.pause(abandonAudioFocus, reinit); - } - - public void reinit() { - mediaPlayer.reinit(); - } - - public PlaybackServiceMediaPlayer.PSMPInfo getPSMPInfo() { - return mediaPlayer.getPSMPInfo(); - } - - public PlayerStatus getStatus() { - return mediaPlayer.getPSMPInfo().playerStatus; - } - - public Playable getPlayable() { - return mediaPlayer.getPSMPInfo().playable; - } - - public void setSpeed(float speed) { - mediaPlayer.setSpeed(speed); - } - - public boolean canSetSpeed() { - return mediaPlayer.canSetSpeed(); - } - - public float getCurrentPlaybackSpeed() { - return mediaPlayer.getPlaybackSpeed(); - } - - public boolean isStartWhenPrepared() { - return mediaPlayer.isStartWhenPrepared(); - } - - public void setStartWhenPrepared(boolean s) { - mediaPlayer.setStartWhenPrepared(s); - } - - - public void seekTo(final int t) { - mediaPlayer.seekTo(t); - } - - - public void seekDelta(final int d) { - mediaPlayer.seekDelta(d); - } - - /** - * @see de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer#seekToChapter(de.danoeh.antennapod.core.feed.Chapter) - */ - public void seekToChapter(Chapter c) { - mediaPlayer.seekToChapter(c); - } - - /** - * call getDuration() on mediaplayer or return INVALID_TIME if player is in - * an invalid state. - */ - public int getDuration() { - return mediaPlayer.getDuration(); - } - - /** - * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player - * is in an invalid state. - */ - public int getCurrentPosition() { - return mediaPlayer.getPosition(); - } - - public boolean isStreaming() { - return mediaPlayer.isStreaming(); - } - - public Pair getVideoSize() { - return mediaPlayer.getVideoSize(); - } - - private boolean isAutoFlattrable(Playable p) { - if (p != null && p instanceof FeedMedia) { - FeedMedia media = (FeedMedia) p; - FeedItem item = ((FeedMedia) p).getItem(); - return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred(); - } else { - return false; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java deleted file mode 100644 index 62ad59166..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ /dev/null @@ -1,979 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.content.ComponentName; -import android.content.Context; -import android.media.AudioManager; -import android.media.RemoteControlClient; -import android.net.wifi.WifiManager; -import android.os.PowerManager; -import android.telephony.TelephonyManager; -import android.util.Log; -import android.util.Pair; -import android.view.SurfaceHolder; - -import org.apache.commons.lang3.Validate; - -import java.io.IOException; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantLock; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; -import de.danoeh.antennapod.core.util.playback.AudioPlayer; -import de.danoeh.antennapod.core.util.playback.IPlayer; -import de.danoeh.antennapod.core.util.playback.Playable; -import de.danoeh.antennapod.core.util.playback.VideoPlayer; - -/** - * Manages the MediaPlayer object of the PlaybackService. - */ -public class PlaybackServiceMediaPlayer { - public static final String TAG = "PlaybackServiceMediaPlayer"; - - /** - * Return value of some PSMP methods if the method call failed. - */ - public static final int INVALID_TIME = -1; - - private final AudioManager audioManager; - - private volatile PlayerStatus playerStatus; - private volatile PlayerStatus statusBeforeSeeking; - private volatile IPlayer mediaPlayer; - private volatile Playable media; - - private volatile boolean stream; - private volatile MediaType mediaType; - private volatile AtomicBoolean startWhenPrepared; - private volatile boolean pausedBecauseOfTransientAudiofocusLoss; - private volatile Pair videoSize; - - /** - * Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads - * have to wait until these operations have finished. - */ - private final ReentrantLock playerLock; - - private final PSMPCallback callback; - private final Context context; - - private final ThreadPoolExecutor executor; - - /** - * A wifi-lock that is acquired if the media file is being streamed. - */ - private WifiManager.WifiLock wifiLock; - - public PlaybackServiceMediaPlayer(Context context, PSMPCallback callback) { - Validate.notNull(context); - Validate.notNull(callback); - - this.context = context; - this.callback = callback; - this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - this.playerLock = new ReentrantLock(); - this.startWhenPrepared = new AtomicBoolean(false); - executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque(), - new RejectedExecutionHandler() { - @Override - public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { - if (BuildConfig.DEBUG) Log.d(TAG, "Rejected execution of runnable"); - } - } - ); - - mediaPlayer = null; - statusBeforeSeeking = null; - pausedBecauseOfTransientAudiofocusLoss = false; - mediaType = MediaType.UNKNOWN; - playerStatus = PlayerStatus.STOPPED; - videoSize = null; - } - - /** - * Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing - * episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will - * not do anything. - * Whether playback starts immediately depends on the given parameters. See below for more details. - *

- * States: - * During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters. - *

- * If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If - * 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state. - *

- * If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object - * will enter the ERROR state. - *

- * This method is executed on an internal executor service. - * - * @param playable The Playable object that is supposed to be played. This parameter must not be null. - * @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via - * getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by - * the Android MediaPlayer via getStreamUrl. - * @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the - * episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared - * for playback immediately (see 'prepareImmediately' parameter for more details) - * @param prepareImmediately Set to true if the method should also prepare the episode for playback. - */ - public void playMediaObject(final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { - Validate.notNull(playable); - - if (BuildConfig.DEBUG) Log.d(TAG, "Play media object."); - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - try { - playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately); - } catch (RuntimeException e) { - e.printStackTrace(); - throw e; - } finally { - playerLock.unlock(); - } - } - }); - } - - /** - * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if - * the given playable parameter is the same object as the currently playing media. - *

- * This method requires the playerLock and is executed on the caller's thread. - * - * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean) - */ - private void playMediaObject(final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { - Validate.notNull(playable); - if (!playerLock.isHeldByCurrentThread()) - throw new IllegalStateException("method requires playerLock"); - - - if (media != null) { - if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) { - // episode is already playing -> ignore method call - if (BuildConfig.DEBUG) - Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing."); - return; - } else { - // stop playback of this episode - if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) { - mediaPlayer.stop(); - } - setPlayerStatus(PlayerStatus.INDETERMINATE, null); - } - } - - this.media = playable; - this.stream = stream; - this.mediaType = media.getMediaType(); - this.videoSize = null; - createMediaPlayer(); - PlaybackServiceMediaPlayer.this.startWhenPrepared.set(startWhenPrepared); - setPlayerStatus(PlayerStatus.INITIALIZING, media); - try { - media.loadMetadata(); - if (stream) { - mediaPlayer.setDataSource(media.getStreamUrl()); - } else { - mediaPlayer.setDataSource(media.getLocalMediaUrl()); - } - setPlayerStatus(PlayerStatus.INITIALIZED, media); - - if (mediaType == MediaType.VIDEO) { - VideoPlayer vp = (VideoPlayer) mediaPlayer; - // vp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT); - } - - if (prepareImmediately) { - setPlayerStatus(PlayerStatus.PREPARING, media); - mediaPlayer.prepare(); - onPrepared(startWhenPrepared); - } - - } catch (Playable.PlayableException e) { - e.printStackTrace(); - setPlayerStatus(PlayerStatus.ERROR, null); - } catch (IOException e) { - e.printStackTrace(); - setPlayerStatus(PlayerStatus.ERROR, null); - } catch (IllegalStateException e) { - e.printStackTrace(); - setPlayerStatus(PlayerStatus.ERROR, null); - } - } - - - /** - * Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state. - * nothing will happen. - *

- * This method is executed on an internal executor service. - */ - public void resume() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - resumeSync(); - playerLock.unlock(); - } - }); - } - - private void resumeSync() { - if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) { - int focusGained = audioManager.requestAudioFocus( - audioFocusChangeListener, AudioManager.STREAM_MUSIC, - AudioManager.AUDIOFOCUS_GAIN); - if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - acquireWifiLockIfNecessary(); - setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed())); - mediaPlayer.start(); - if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) { - mediaPlayer.seekTo(media.getPosition()); - } - - setPlayerStatus(PlayerStatus.PLAYING, media); - pausedBecauseOfTransientAudiofocusLoss = false; - if (android.os.Build.VERSION.SDK_INT >= 14) { - RemoteControlClient remoteControlClient = callback.getRemoteControlClient(); - if (remoteControlClient != null) { - audioManager - .registerRemoteControlClient(remoteControlClient); - } - } - audioManager - .registerMediaButtonEventReceiver(new ComponentName(context.getPackageName(), - MediaButtonReceiver.class.getName())); - media.onPlaybackStart(); - - } else { - if (BuildConfig.DEBUG) Log.e(TAG, "Failed to request audio focus"); - } - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus); - } - } - - - /** - * Saves the current position and pauses playback. Note that, if audiofocus - * is abandoned, the lockscreen controls will also disapear. - *

- * This method is executed on an internal executor service. - * - * @param abandonFocus is true if the service should release audio focus - * @param reinit is true if service should reinit after pausing if the media - * file is being streamed - */ - public void pause(final boolean abandonFocus, final boolean reinit) { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - releaseWifiLockIfNecessary(); - if (playerStatus == PlayerStatus.PLAYING) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Pausing playback."); - mediaPlayer.pause(); - setPlayerStatus(PlayerStatus.PAUSED, media); - - if (abandonFocus) { - audioManager.abandonAudioFocus(audioFocusChangeListener); - pausedBecauseOfTransientAudiofocusLoss = false; - } - if (stream && reinit) { - reinit(); - } - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state"); - } - - playerLock.unlock(); - } - }); - } - - /** - * Prepared media player for playback if the service is in the INITALIZED - * state. - *

- * This method is executed on an internal executor service. - */ - public void prepare() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - - if (playerStatus == PlayerStatus.INITIALIZED) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Preparing media player"); - setPlayerStatus(PlayerStatus.PREPARING, media); - try { - mediaPlayer.prepare(); - onPrepared(startWhenPrepared.get()); - } catch (IOException e) { - e.printStackTrace(); - setPlayerStatus(PlayerStatus.ERROR, null); - } - } - playerLock.unlock(); - - } - }); - } - - /** - * Called after media player has been prepared. This method is executed on the caller's thread. - */ - void onPrepared(final boolean startWhenPrepared) { - playerLock.lock(); - - if (playerStatus != PlayerStatus.PREPARING) { - playerLock.unlock(); - throw new IllegalStateException("Player is not in PREPARING state"); - } - - if (BuildConfig.DEBUG) - Log.d(TAG, "Resource prepared"); - - if (mediaType == MediaType.VIDEO) { - VideoPlayer vp = (VideoPlayer) mediaPlayer; - videoSize = new Pair(vp.getVideoWidth(), vp.getVideoHeight()); - } - - if (media.getPosition() > 0) { - mediaPlayer.seekTo(media.getPosition()); - } - - if (media.getDuration() == 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting duration of media"); - media.setDuration(mediaPlayer.getDuration()); - } - setPlayerStatus(PlayerStatus.PREPARED, media); - - if (startWhenPrepared) { - resumeSync(); - } - - playerLock.unlock(); - } - - /** - * Resets the media player and moves it into INITIALIZED state. - *

- * This method is executed on an internal executor service. - */ - public void reinit() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - releaseWifiLockIfNecessary(); - if (media != null) { - playMediaObject(media, true, stream, startWhenPrepared.get(), false); - } else if (mediaPlayer != null) { - mediaPlayer.reset(); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null"); - } - playerLock.unlock(); - } - }); - } - - - /** - * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing. - * - * @param t The position to seek to in milliseconds. t < 0 will be interpreted as t = 0 - *

- * This method is executed on the caller's thread. - */ - private void seekToSync(int t) { - if (t < 0) { - t = 0; - } - playerLock.lock(); - - if (playerStatus == PlayerStatus.PLAYING - || playerStatus == PlayerStatus.PAUSED - || playerStatus == PlayerStatus.PREPARED) { - if (stream) { - // statusBeforeSeeking = playerStatus; - // setPlayerStatus(PlayerStatus.SEEKING, media); - } - mediaPlayer.seekTo(t); - - } else if (playerStatus == PlayerStatus.INITIALIZED) { - media.setPosition(t); - startWhenPrepared.set(true); - prepare(); - } - playerLock.unlock(); - } - - /** - * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing. - * Invalid time values (< 0) will be ignored. - *

- * This method is executed on an internal executor service. - */ - public void seekTo(final int t) { - executor.submit(new Runnable() { - @Override - public void run() { - seekToSync(t); - } - }); - } - - /** - * Seek a specific position from the current position - * - * @param d offset from current position (positive or negative) - */ - public void seekDelta(final int d) { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - int currentPosition = getPosition(); - if (currentPosition != INVALID_TIME) { - seekToSync(currentPosition + d); - } else { - Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta"); - } - - playerLock.unlock(); - } - }); - } - - /** - * Seek to the start of the specified chapter. - */ - public void seekToChapter(Chapter c) { - Validate.notNull(c); - - seekTo((int) c.getStart()); - } - - /** - * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved. - */ - public int getDuration() { - if (!playerLock.tryLock()) { - return INVALID_TIME; - } - - int retVal = INVALID_TIME; - if (playerStatus == PlayerStatus.PLAYING - || playerStatus == PlayerStatus.PAUSED - || playerStatus == PlayerStatus.PREPARED) { - retVal = mediaPlayer.getDuration(); - } else if (media != null && media.getDuration() > 0) { - retVal = media.getDuration(); - } - - playerLock.unlock(); - return retVal; - } - - /** - * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved. - */ - public int getPosition() { - if (!playerLock.tryLock()) { - return INVALID_TIME; - } - - int retVal = INVALID_TIME; - if (playerStatus == PlayerStatus.PLAYING - || playerStatus == PlayerStatus.PAUSED - || playerStatus == PlayerStatus.PREPARED) { - retVal = mediaPlayer.getCurrentPosition(); - } else if (media != null && media.getPosition() > 0) { - retVal = media.getPosition(); - } - - playerLock.unlock(); - return retVal; - } - - public boolean isStartWhenPrepared() { - return startWhenPrepared.get(); - } - - public void setStartWhenPrepared(boolean startWhenPrepared) { - this.startWhenPrepared.set(startWhenPrepared); - } - - /** - * Returns true if the playback speed can be adjusted. - */ - public boolean canSetSpeed() { - boolean retVal = false; - if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) { - retVal = (mediaPlayer).canSetSpeed(); - } - return retVal; - } - - /** - * Sets the playback speed. - * This method is executed on the caller's thread. - */ - private void setSpeedSync(float speed) { - playerLock.lock(); - if (media != null && media.getMediaType() == MediaType.AUDIO) { - if (mediaPlayer.canSetSpeed()) { - mediaPlayer.setPlaybackSpeed((float) speed); - if (BuildConfig.DEBUG) - Log.d(TAG, "Playback speed was set to " + speed); - callback.playbackSpeedChanged(speed); - } - } - playerLock.unlock(); - } - - /** - * Sets the playback speed. - * This method is executed on an internal executor service. - */ - public void setSpeed(final float speed) { - executor.submit(new Runnable() { - @Override - public void run() { - setSpeedSync(speed); - } - }); - } - - /** - * Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned. - */ - public float getPlaybackSpeed() { - if (!playerLock.tryLock()) { - return 1; - } - - float retVal = 1; - if ((playerStatus == PlayerStatus.PLAYING - || playerStatus == PlayerStatus.PAUSED - || playerStatus == PlayerStatus.PREPARED) && mediaPlayer.canSetSpeed()) { - retVal = mediaPlayer.getCurrentSpeedMultiplier(); - } - playerLock.unlock(); - return retVal; - } - - public MediaType getCurrentMediaType() { - return mediaType; - } - - public boolean isStreaming() { - return stream; - } - - - /** - * Releases internally used resources. This method should only be called when the object is not used anymore. - */ - public void shutdown() { - executor.shutdown(); - if (mediaPlayer != null) { - mediaPlayer.release(); - } - releaseWifiLockIfNecessary(); - } - - public void setVideoSurface(final SurfaceHolder surface) { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - if (mediaPlayer != null) { - mediaPlayer.setDisplay(surface); - } - playerLock.unlock(); - } - }); - } - - public void resetVideoSurface() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Resetting video surface"); - mediaPlayer.setDisplay(null); - reinit(); - playerLock.unlock(); - } - }); - } - - /** - * Return width and height of the currently playing video as a pair. - * - * @return Width and height as a Pair or null if the video size could not be determined. The method might still - * return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return - * invalid values. - */ - public Pair getVideoSize() { - if (!playerLock.tryLock()) { - // use cached value if lock can't be aquired - return videoSize; - } - Pair res; - if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) { - res = null; - } else { - VideoPlayer vp = (VideoPlayer) mediaPlayer; - videoSize = new Pair(vp.getVideoWidth(), vp.getVideoHeight()); - res = videoSize; - } - playerLock.unlock(); - return res; - } - - /** - * Returns a PSMInfo object that contains information about the current state of the PSMP object. - * - * @return The PSMPInfo object. - */ - public synchronized PSMPInfo getPSMPInfo() { - return new PSMPInfo(playerStatus, media); - } - - /** - * Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time - * so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null). - *

- * This method will notify the callback about the change of the player status (even if the new status is the same - * as the old one). - * - * @param newStatus The new PlayerStatus. This must not be null. - * @param newMedia The new playable object of the PSMP object. This can be null. - */ - private synchronized void setPlayerStatus(PlayerStatus newStatus, Playable newMedia) { - Validate.notNull(newStatus); - - if (BuildConfig.DEBUG) Log.d(TAG, "Setting player status to " + newStatus); - - this.playerStatus = newStatus; - this.media = newMedia; - callback.statusChanged(new PSMPInfo(playerStatus, media)); - } - - private IPlayer createMediaPlayer() { - if (mediaPlayer != null) { - mediaPlayer.release(); - } - if (media == null || media.getMediaType() == MediaType.VIDEO) { - mediaPlayer = new VideoPlayer(); - } else { - mediaPlayer = new AudioPlayer(context); - } - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - return setMediaPlayerListeners(mediaPlayer); - } - - private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { - - @Override - public void onAudioFocusChange(final int focusChange) { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - - // If there is an incoming call, playback should be paused permanently - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - final int callState = (tm != null) ? tm.getCallState() : 0; - if (BuildConfig.DEBUG) Log.d(TAG, "Call state: " + callState); - Log.i(TAG, "Call state:" + callState); - - if (focusChange == AudioManager.AUDIOFOCUS_LOSS || callState != TelephonyManager.CALL_STATE_IDLE) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Lost audio focus"); - pause(true, false); - callback.shouldStop(); - } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Gained audio focus"); - if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now - resume(); - } else { // we ducked => raise audio level back - audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, - AudioManager.ADJUST_RAISE, 0); - } - } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { - if (playerStatus == PlayerStatus.PLAYING) { - if (!UserPreferences.shouldPauseForFocusLoss()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Lost audio focus temporarily. Ducking..."); - audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, - AudioManager.ADJUST_LOWER, 0); - pausedBecauseOfTransientAudiofocusLoss = false; - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing..."); - pause(false, false); - pausedBecauseOfTransientAudiofocusLoss = true; - } - } - } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { - if (playerStatus == PlayerStatus.PLAYING) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Lost audio focus temporarily. Pausing..."); - pause(false, false); - pausedBecauseOfTransientAudiofocusLoss = true; - } - playerLock.unlock(); - } - } - }); - } - }; - - - public void endPlayback() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - releaseWifiLockIfNecessary(); - - if (playerStatus != PlayerStatus.INDETERMINATE) { - setPlayerStatus(PlayerStatus.INDETERMINATE, media); - } - if (mediaPlayer != null) { - mediaPlayer.reset(); - - } - audioManager.abandonAudioFocus(audioFocusChangeListener); - callback.endPlayback(true); - - playerLock.unlock(); - } - }); - } - - /** - * Moves the PlaybackServiceMediaPlayer into STOPPED state. This call is only valid if the player is currently in - * INDETERMINATE state, for example after a call to endPlayback. - * This method will only take care of changing the PlayerStatus of this object! Other tasks like - * abandoning audio focus have to be done with other methods. - */ - public void stop() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - releaseWifiLockIfNecessary(); - - if (playerStatus == PlayerStatus.INDETERMINATE) { - setPlayerStatus(PlayerStatus.STOPPED, null); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus); - } - playerLock.unlock(); - - } - }); - } - - private synchronized void acquireWifiLockIfNecessary() { - if (stream) { - if (wifiLock == null) { - wifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - wifiLock.setReferenceCounted(false); - } - wifiLock.acquire(); - } - } - - private synchronized void releaseWifiLockIfNecessary() { - if (wifiLock != null && wifiLock.isHeld()) { - wifiLock.release(); - } - } - - /** - * Holds information about a PSMP object. - */ - public class PSMPInfo { - public PlayerStatus playerStatus; - public Playable playable; - - public PSMPInfo(PlayerStatus playerStatus, Playable playable) { - this.playerStatus = playerStatus; - this.playable = playable; - } - } - - public static interface PSMPCallback { - public void statusChanged(PSMPInfo newInfo); - - public void shouldStop(); - - public void playbackSpeedChanged(float s); - - public void onBufferingUpdate(int percent); - - public boolean onMediaPlayerInfo(int code); - - public boolean onMediaPlayerError(Object inObj, int what, int extra); - - public boolean endPlayback(boolean playNextEpisode); - - public RemoteControlClient getRemoteControlClient(); - } - - private IPlayer setMediaPlayerListeners(IPlayer mp) { - if (mp != null && media != null) { - if (media.getMediaType() == MediaType.AUDIO) { - ((AudioPlayer) mp) - .setOnCompletionListener(audioCompletionListener); - ((AudioPlayer) mp) - .setOnSeekCompleteListener(audioSeekCompleteListener); - ((AudioPlayer) mp).setOnErrorListener(audioErrorListener); - ((AudioPlayer) mp) - .setOnBufferingUpdateListener(audioBufferingUpdateListener); - ((AudioPlayer) mp).setOnInfoListener(audioInfoListener); - } else { - ((VideoPlayer) mp) - .setOnCompletionListener(videoCompletionListener); - ((VideoPlayer) mp) - .setOnSeekCompleteListener(videoSeekCompleteListener); - ((VideoPlayer) mp).setOnErrorListener(videoErrorListener); - ((VideoPlayer) mp) - .setOnBufferingUpdateListener(videoBufferingUpdateListener); - ((VideoPlayer) mp).setOnInfoListener(videoInfoListener); - } - } - return mp; - } - - private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(com.aocate.media.MediaPlayer mp) { - genericOnCompletion(); - } - }; - - private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(android.media.MediaPlayer mp) { - genericOnCompletion(); - } - }; - - private void genericOnCompletion() { - endPlayback(); - } - - private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() { - @Override - public void onBufferingUpdate(com.aocate.media.MediaPlayer mp, - int percent) { - genericOnBufferingUpdate(percent); - } - }; - - private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() { - @Override - public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) { - genericOnBufferingUpdate(percent); - } - }; - - private void genericOnBufferingUpdate(int percent) { - callback.onBufferingUpdate(percent); - } - - private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() { - @Override - public boolean onInfo(com.aocate.media.MediaPlayer mp, int what, - int extra) { - return genericInfoListener(what); - } - }; - - private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() { - @Override - public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) { - return genericInfoListener(what); - } - }; - - private boolean genericInfoListener(int what) { - return callback.onMediaPlayerInfo(what); - } - - private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() { - @Override - public boolean onError(com.aocate.media.MediaPlayer mp, int what, - int extra) { - return genericOnError(mp, what, extra); - } - }; - - private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() { - @Override - public boolean onError(android.media.MediaPlayer mp, int what, int extra) { - return genericOnError(mp, what, extra); - } - }; - - private boolean genericOnError(Object inObj, int what, int extra) { - return callback.onMediaPlayerError(inObj, what, extra); - } - - private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() { - @Override - public void onSeekComplete(com.aocate.media.MediaPlayer mp) { - genericSeekCompleteListener(); - } - }; - - private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() { - @Override - public void onSeekComplete(android.media.MediaPlayer mp) { - genericSeekCompleteListener(); - } - }; - - private final void genericSeekCompleteListener() { - executor.submit(new Runnable() { - @Override - public void run() { - playerLock.lock(); - if (playerStatus == PlayerStatus.SEEKING) { - setPlayerStatus(statusBeforeSeeking, media); - } - playerLock.unlock(); - } - }); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java deleted file mode 100644 index 1b33e8667..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java +++ /dev/null @@ -1,384 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.content.Context; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.playback.Playable; - -import java.util.List; -import java.util.concurrent.*; - -/** - * Manages the background tasks of PlaybackSerivce, i.e. - * the sleep timer, the position saver, the widget updater and - * the queue loader. - *

- * The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback) - * to notify the PlaybackService about updates from the running tasks. - */ -public class PlaybackServiceTaskManager { - private static final String TAG = "PlaybackServiceTaskManager"; - - /** - * Update interval of position saver in milliseconds. - */ - public static final int POSITION_SAVER_WAITING_INTERVAL = 5000; - /** - * Notification interval of widget updater in milliseconds. - */ - public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500; - - private static final int SCHED_EX_POOL_SIZE = 2; - private final ScheduledThreadPoolExecutor schedExecutor; - - private ScheduledFuture positionSaverFuture; - private ScheduledFuture widgetUpdaterFuture; - private ScheduledFuture sleepTimerFuture; - private volatile Future> queueFuture; - private volatile Future chapterLoaderFuture; - - private SleepTimer sleepTimer; - - private final Context context; - private final PSTMCallback callback; - - /** - * Sets up a new PSTM. This method will also start the queue loader task. - * - * @param context - * @param callback A PSTMCallback object for notifying the user about updates. Must not be null. - */ - public PlaybackServiceTaskManager(Context context, PSTMCallback callback) { - Validate.notNull(context); - Validate.notNull(callback); - - this.context = context; - this.callback = callback; - schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); - loadQueue(); - EventDistributor.getInstance().register(eventDistributorListener); - } - - private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EventDistributor.QUEUE_UPDATE & arg) != 0) { - cancelQueueLoader(); - loadQueue(); - } - } - }; - - private synchronized boolean isQueueLoaderActive() { - return queueFuture != null && !queueFuture.isDone(); - } - - private synchronized void cancelQueueLoader() { - if (isQueueLoaderActive()) { - queueFuture.cancel(true); - } - } - - private synchronized void loadQueue() { - if (!isQueueLoaderActive()) { - queueFuture = schedExecutor.submit(new Callable>() { - @Override - public List call() throws Exception { - return DBReader.getQueue(context); - } - }); - } - } - - /** - * Returns the queue if it is already loaded or null if it hasn't been loaded yet. - * In order to wait until the queue has been loaded, use getQueue() - */ - public synchronized List getQueueIfLoaded() { - if (queueFuture.isDone()) { - try { - return queueFuture.get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - return null; - } - - /** - * Returns the queue or waits until the PSTM has loaded the queue from the database. - */ - public synchronized List getQueue() throws InterruptedException { - try { - return queueFuture.get(); - } catch (ExecutionException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Starts the position saver task. If the position saver is already active, nothing will happen. - */ - public synchronized void startPositionSaver() { - if (!isPositionSaverActive()) { - Runnable positionSaver = new Runnable() { - @Override - public void run() { - callback.positionSaverTick(); - } - }; - positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL, - POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS); - - if (BuildConfig.DEBUG) Log.d(TAG, "Started PositionSaver"); - } else { - if (BuildConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored."); - } - } - - /** - * Returns true if the position saver is currently running. - */ - public synchronized boolean isPositionSaverActive() { - return positionSaverFuture != null && !positionSaverFuture.isCancelled() && !positionSaverFuture.isDone(); - } - - /** - * Cancels the position saver. If the position saver is not running, nothing will happen. - */ - public synchronized void cancelPositionSaver() { - if (isPositionSaverActive()) { - positionSaverFuture.cancel(false); - if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver"); - } - } - - /** - * Starts the widget updater task. If the widget updater is already active, nothing will happen. - */ - public synchronized void startWidgetUpdater() { - if (!isWidgetUpdaterActive()) { - Runnable widgetUpdater = new Runnable() { - @Override - public void run() { - callback.onWidgetUpdaterTick(); - } - }; - widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL, - WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS); - - if (BuildConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater"); - } else { - if (BuildConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored."); - } - } - - /** - * Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be - * cancelled first. - * After waitingTime has elapsed, onSleepTimerExpired() will be called. - * - * @throws java.lang.IllegalArgumentException if waitingTime <= 0 - */ - public synchronized void setSleepTimer(long waitingTime) { - Validate.isTrue(waitingTime > 0, "Waiting time <= 0"); - - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) - + " milliseconds"); - if (isSleepTimerActive()) { - sleepTimerFuture.cancel(true); - } - sleepTimer = new SleepTimer(waitingTime); - sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS); - } - - /** - * Returns true if the sleep timer is currently active. - */ - public synchronized boolean isSleepTimerActive() { - return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone() && sleepTimer.isWaiting; - } - - /** - * Disables the sleep timer. If the sleep timer is not active, nothing will happen. - */ - public synchronized void disableSleepTimer() { - if (isSleepTimerActive()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Disabling sleep timer"); - sleepTimerFuture.cancel(true); - } - } - - /** - * Returns the current sleep timer time or 0 if the sleep timer is not active. - */ - public synchronized long getSleepTimerTimeLeft() { - if (isSleepTimerActive()) { - return sleepTimer.getWaitingTime(); - } else { - return 0; - } - } - - - /** - * Returns true if the widget updater is currently running. - */ - public synchronized boolean isWidgetUpdaterActive() { - return widgetUpdaterFuture != null && !widgetUpdaterFuture.isCancelled() && !widgetUpdaterFuture.isDone(); - } - - /** - * Cancels the widget updater. If the widget updater is not running, nothing will happen. - */ - public synchronized void cancelWidgetUpdater() { - if (isWidgetUpdaterActive()) { - widgetUpdaterFuture.cancel(false); - if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater"); - } - } - - private synchronized void cancelChapterLoader() { - if (isChapterLoaderActive()) { - chapterLoaderFuture.cancel(true); - } - } - - private synchronized boolean isChapterLoaderActive() { - return chapterLoaderFuture != null && !chapterLoaderFuture.isDone(); - } - - /** - * Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active, - * it will be cancelled first. - * On completion, the callback's onChapterLoaded method will be called. - */ - public synchronized void startChapterLoader(final Playable media) { - Validate.notNull(media); - - if (isChapterLoaderActive()) { - cancelChapterLoader(); - } - - Runnable chapterLoader = new Runnable() { - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Chapter loader started"); - if (media.getChapters() == null) { - media.loadChapterMarks(); - if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) { - callback.onChapterLoaded(media); - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Chapter loader stopped"); - } - }; - chapterLoaderFuture = schedExecutor.submit(chapterLoader); - } - - - /** - * Cancels all tasks. The PSTM will be in the initial state after execution of this method. - */ - public synchronized void cancelAllTasks() { - cancelPositionSaver(); - cancelWidgetUpdater(); - disableSleepTimer(); - cancelQueueLoader(); - cancelChapterLoader(); - } - - /** - * Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after - * execution of this method. - */ - public synchronized void shutdown() { - EventDistributor.getInstance().unregister(eventDistributorListener); - cancelAllTasks(); - schedExecutor.shutdown(); - } - - /** - * Sleeps for a given time and then pauses playback. - */ - private class SleepTimer implements Runnable { - private static final String TAG = "SleepTimer"; - private static final long UPDATE_INTERVALL = 1000L; - private volatile long waitingTime; - private volatile boolean isWaiting; - - public SleepTimer(long waitingTime) { - super(); - this.waitingTime = waitingTime; - isWaiting = true; - } - - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting"); - while (waitingTime > 0) { - try { - Thread.sleep(UPDATE_INTERVALL); - waitingTime -= UPDATE_INTERVALL; - - if (waitingTime <= 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Waiting completed"); - postExecute(); - if (!Thread.currentThread().isInterrupted()) { - callback.onSleepTimerExpired(); - } - - } - } catch (InterruptedException e) { - Log.d(TAG, "Thread was interrupted while waiting"); - break; - } - } - postExecute(); - } - - protected void postExecute() { - isWaiting = false; - } - - public long getWaitingTime() { - return waitingTime; - } - - public boolean isWaiting() { - return isWaiting; - } - - } - - public static interface PSTMCallback { - void positionSaverTick(); - - void onSleepTimerExpired(); - - void onWidgetUpdaterTick(); - - void onChapterLoaded(Playable media); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java b/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java deleted file mode 100644 index 1ad0c25d9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -public enum PlayerStatus { - INDETERMINATE, // player is currently changing its state, listeners should wait until the player has left this state. - ERROR, - PREPARING, - PAUSED, - PLAYING, - STOPPED, - PREPARED, - SEEKING, - INITIALIZING, // playback service is loading the Playable's metadata - INITIALIZED // playback service was started, data source of media player was set. -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerWidgetService.java b/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerWidgetService.java deleted file mode 100644 index 495e2c0f2..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerWidgetService.java +++ /dev/null @@ -1,190 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.app.PendingIntent; -import android.app.Service; -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Build; -import android.os.IBinder; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.widget.RemoteViews; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; -import de.danoeh.antennapod.core.receiver.PlayerWidget; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.playback.Playable; - -/** Updates the state of the player widget */ -public class PlayerWidgetService extends Service { - private static final String TAG = "PlayerWidgetService"; - - private PlaybackService playbackService; - /** True while service is updating the widget */ - private volatile boolean isUpdating; - - public PlayerWidgetService() { - } - - @Override - public void onCreate() { - super.onCreate(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Service created"); - isUpdating = false; - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Service is about to be destroyed"); - try { - unbindService(mConnection); - } catch (IllegalArgumentException e) { - Log.w(TAG, "IllegalArgumentException when trying to unbind service"); - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (!isUpdating) { - if (playbackService == null && PlaybackService.isRunning) { - bindService(new Intent(this, PlaybackService.class), - mConnection, 0); - } else { - startViewUpdaterIfNotRunning(); - } - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Service was called while updating. Ignoring update request"); - } - return Service.START_NOT_STICKY; - } - - private void updateViews() { - if (playbackService == null) { - return; - } - isUpdating = true; - - ComponentName playerWidget = new ComponentName(this, PlayerWidget.class); - AppWidgetManager manager = AppWidgetManager.getInstance(this); - RemoteViews views = new RemoteViews(getPackageName(), - R.layout.player_widget); - PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), 0); - - views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); - final Playable media = playbackService.getPlayable(); - if (playbackService != null && media != null) { - PlayerStatus status = playbackService.getStatus(); - - views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); - - if (status == PlayerStatus.PLAYING) { - String progressString = getProgressString(playbackService); - if (progressString != null) { - views.setTextViewText(R.id.txtvProgress, progressString); - } - views.setImageViewResource(R.id.butPlay, R.drawable.av_pause_dark); - if (Build.VERSION.SDK_INT >= 15) { - views.setContentDescription(R.id.butPlay, getString(R.string.pause_label)); - } - } else { - views.setImageViewResource(R.id.butPlay, R.drawable.av_play_dark); - if (Build.VERSION.SDK_INT >= 15) { - views.setContentDescription(R.id.butPlay, getString(R.string.play_label)); - } - } - views.setOnClickPendingIntent(R.id.butPlay, - createMediaButtonIntent()); - } else { - views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE); - views.setTextViewText(R.id.txtvTitle, - this.getString(R.string.no_media_playing_label)); - views.setImageViewResource(R.id.butPlay, R.drawable.av_play); - - } - - manager.updateAppWidget(playerWidget, views); - isUpdating = false; - } - - /** Creates an intent which fakes a mediabutton press */ - private PendingIntent createMediaButtonIntent() { - KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); - Intent startingIntent = new Intent( - MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); - startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - - return PendingIntent.getBroadcast(this, 0, startingIntent, 0); - } - - private String getProgressString(PlaybackService ps) { - int position = ps.getCurrentPosition(); - int duration = ps.getDuration(); - if (position != PlaybackService.INVALID_TIME - && duration != PlaybackService.INVALID_TIME) { - return Converter.getDurationStringLong(position) + " / " - + Converter.getDurationStringLong(duration); - } else { - return null; - } - } - - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Connection to service established"); - playbackService = ((PlaybackService.LocalBinder) service) - .getService(); - startViewUpdaterIfNotRunning(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - playbackService = null; - if (BuildConfig.DEBUG) - Log.d(TAG, "Disconnected from service"); - } - - }; - - private void startViewUpdaterIfNotRunning() { - if (!isUpdating) { - ViewUpdater updateThread = new ViewUpdater(this); - updateThread.start(); - } - } - - static class ViewUpdater extends Thread { - private static final String THREAD_NAME = "ViewUpdater"; - private PlayerWidgetService service; - - public ViewUpdater(PlayerWidgetService service) { - super(); - setName(THREAD_NAME); - this.service = service; - - } - - @Override - public void run() { - service.updateViews(); - } - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/app/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java deleted file mode 100644 index 1b93e6ea2..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ /dev/null @@ -1,908 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.*; -import de.danoeh.antennapod.core.service.download.DownloadStatus; -import de.danoeh.antennapod.core.util.DownloadError; -import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator; -import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.core.util.comparator.PlaybackCompletionDateComparator; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; -import de.danoeh.antennapod.core.util.flattr.FlattrThing; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -/** - * Provides methods for reading data from the AntennaPod database. - * In general, all database calls in DBReader-methods are executed on the caller's thread. - * This means that the caller should make sure that DBReader-methods are not executed on the GUI-thread. - * This class will use the {@link de.danoeh.antennapod.core.feed.EventDistributor} to notify listeners about changes in the database. - */ -public final class DBReader { - private static final String TAG = "DBReader"; - - /** - * Maximum size of the list returned by {@link #getPlaybackHistory(android.content.Context)}. - */ - public static final int PLAYBACK_HISTORY_SIZE = 50; - - /** - * Maximum size of the list returned by {@link #getDownloadLog(android.content.Context)}. - */ - public static final int DOWNLOAD_LOG_SIZE = 200; - - - private DBReader() { - } - - /** - * Returns a list of Feeds, sorted alphabetically by their title. - * - * @param context A context that is used for opening a database connection. - * @return A list of Feeds, sorted alphabetically by their title. A Feed-object - * of the returned list does NOT have its list of FeedItems yet. The FeedItem-list - * can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.core.feed.Feed)}. - */ - public static List getFeedList(final Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feedlist"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List result = getFeedList(adapter); - adapter.close(); - return result; - } - - private static List getFeedList(PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feedlist"); - - Cursor feedlistCursor = adapter.getAllFeedsCursor(); - List feeds = new ArrayList(feedlistCursor.getCount()); - - if (feedlistCursor.moveToFirst()) { - do { - Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); - feeds.add(feed); - } while (feedlistCursor.moveToNext()); - } - feedlistCursor.close(); - return feeds; - } - - /** - * Returns a list with the download URLs of all feeds. - * - * @param context A context that is used for opening the database connection. - * @return A list of Strings with the download URLs of all feeds. - */ - public static List getFeedListDownloadUrls(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - List result = new ArrayList(); - adapter.open(); - Cursor feeds = adapter.getFeedCursorDownloadUrls(); - if (feeds.moveToFirst()) { - do { - result.add(feeds.getString(1)); - } while (feeds.moveToNext()); - } - feeds.close(); - adapter.close(); - - return result; - } - - /** - * Returns a list of 'expired Feeds', i.e. Feeds that have not been updated for a certain amount of time. - * - * @param context A context that is used for opening a database connection. - * @param expirationTime Time that is used for determining whether a feed is outdated or not. - * A Feed is considered expired if 'lastUpdate < (currentTime - expirationTime)' evaluates to true. - * @return A list of Feeds, sorted alphabetically by their title. A Feed-object - * of the returned list does NOT have its list of FeedItems yet. The FeedItem-list - * can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.core.feed.Feed)}. - */ - public static List getExpiredFeedsList(final Context context, final long expirationTime) { - if (BuildConfig.DEBUG) - Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime)); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime); - List feeds = new ArrayList(feedlistCursor.getCount()); - - if (feedlistCursor.moveToFirst()) { - do { - Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor); - feeds.add(feed); - } while (feedlistCursor.moveToNext()); - } - feedlistCursor.close(); - return feeds; - } - - /** - * Takes a list of FeedItems and loads their corresponding Feed-objects from the database. - * The feedID-attribute of a FeedItem must be set to the ID of its feed or the method will - * not find the correct feed of an item. - * - * @param context A context that is used for opening a database connection. - * @param items The FeedItems whose Feed-objects should be loaded. - */ - public static void loadFeedDataOfFeedItemlist(Context context, - List items) { - List feeds = getFeedList(context); - for (FeedItem item : items) { - for (Feed feed : feeds) { - if (feed.getId() == item.getFeedId()) { - item.setFeed(feed); - break; - } - } - if (item.getFeed() == null) { - Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId()); - } - } - } - - /** - * Loads the list of FeedItems for a certain Feed-object. This method should NOT be used if the FeedItems are not - * used. In order to get information ABOUT the list of FeedItems, consider using {@link #getFeedStatisticsList(android.content.Context)} instead. - * - * @param context A context that is used for opening a database connection. - * @param feed The Feed whose items should be loaded - * @return A list with the FeedItems of the Feed. The Feed-attribute of the FeedItems will already be set correctly. - * The method does NOT change the items-attribute of the feed. - */ - public static List getFeedItemList(Context context, - final Feed feed) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - Collections.sort(items, new FeedItemPubdateComparator()); - - adapter.close(); - - for (FeedItem item : items) { - item.setFeed(feed); - } - - return items; - } - - static List extractItemlistFromCursor(Context context, Cursor itemlistCursor) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List result = extractItemlistFromCursor(adapter, itemlistCursor); - adapter.close(); - return result; - } - - private static List extractItemlistFromCursor( - PodDBAdapter adapter, Cursor itemlistCursor) { - ArrayList itemIds = new ArrayList(); - List items = new ArrayList( - itemlistCursor.getCount()); - - if (itemlistCursor.moveToFirst()) { - do { - FeedItem item = new FeedItem(); - - item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID)); - item.setTitle(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_TITLE)); - item.setLink(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_LINK)); - item.setPubDate(new Date(itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE))); - item.setPaymentLink(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK)); - item.setFeedId(itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_FEED)); - itemIds.add(String.valueOf(item.getId())); - - item.setRead((itemlistCursor - .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0)); - item.setItemIdentifier(itemlistCursor - .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); - item.setFlattrStatus(new FlattrStatus(itemlistCursor - .getLong(PodDBAdapter.IDX_FI_SMALL_FLATTR_STATUS))); - - long imageIndex = itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_IMAGE); - if (imageIndex != 0) { - item.setImage(getFeedImage(adapter, imageIndex)); - } - - // extract chapters - boolean hasSimpleChapters = itemlistCursor - .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0; - if (hasSimpleChapters) { - Cursor chapterCursor = adapter - .getSimpleChaptersOfFeedItemCursor(item); - if (chapterCursor.moveToFirst()) { - item.setChapters(new ArrayList()); - do { - int chapterType = chapterCursor - .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX); - Chapter chapter = null; - long start = chapterCursor - .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX); - String title = chapterCursor - .getString(PodDBAdapter.KEY_TITLE_INDEX); - String link = chapterCursor - .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX); - - switch (chapterType) { - case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER: - chapter = new SimpleChapter(start, title, item, - link); - break; - case ID3Chapter.CHAPTERTYPE_ID3CHAPTER: - chapter = new ID3Chapter(start, title, item, - link); - break; - case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER: - chapter = new VorbisCommentChapter(start, - title, item, link); - break; - } - if (chapter != null) { - chapter.setId(chapterCursor - .getLong(PodDBAdapter.KEY_ID_INDEX)); - item.getChapters().add(chapter); - } - } while (chapterCursor.moveToNext()); - } - chapterCursor.close(); - } - items.add(item); - } while (itemlistCursor.moveToNext()); - } - - extractMediafromItemlist(adapter, items, itemIds); - return items; - } - - private static void extractMediafromItemlist(PodDBAdapter adapter, - List items, ArrayList itemIds) { - - List itemsCopy = new ArrayList(items); - Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds - .toArray(new String[itemIds.size()])); - if (cursor.moveToFirst()) { - do { - long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX); - // find matching feed item - FeedItem item = getMatchingItemForMedia(itemId, itemsCopy); - if (item != null) { - item.setMedia(extractFeedMediaFromCursorRow(cursor)); - item.getMedia().setItem(item); - } - } while (cursor.moveToNext()); - cursor.close(); - } - } - - private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) { - long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); - Date playbackCompletionDate = null; - long playbackCompletionTime = cursor - .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX); - if (playbackCompletionTime > 0) { - playbackCompletionDate = new Date( - playbackCompletionTime); - } - - return new FeedMedia( - mediaId, - null, - cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX), - cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX), - cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX), - cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX), - cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), - cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), - cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, - playbackCompletionDate, - cursor.getInt(PodDBAdapter.KEY_PLAYED_DURATION_INDEX)); - } - - private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, - Cursor cursor) { - Date lastUpdate = new Date( - cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_LASTUPDATE)); - - final FeedImage image; - long imageIndex = cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_IMAGE); - if (imageIndex != 0) { - image = getFeedImage(adapter, imageIndex); - } else { - image = null; - } - Feed feed = new Feed(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_ID), - lastUpdate, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_TITLE), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_LINK), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_DESCRIPTION), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_PAYMENT_LINK), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_AUTHOR), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_LANGUAGE), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_TYPE), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_FEED_IDENTIFIER), - image, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_FILE_URL), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOAD_URL), - cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0, - new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS))); - - if (image != null) { - image.setOwner(feed); - } - - FeedPreferences preferences = new FeedPreferences(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_ID), - cursor.getInt(PodDBAdapter.IDX_FEED_SEL_PREFERENCES_AUTO_DOWNLOAD) > 0, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_PREFERENCES_USERNAME), - cursor.getString(PodDBAdapter.IDX_FEED_SEL_PREFERENCES_PASSWORD)); - - feed.setPreferences(preferences); - return feed; - } - - private static FeedItem getMatchingItemForMedia(long itemId, - List items) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return item; - } - } - return null; - } - - static List getQueue(Context context, PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting queue"); - - Cursor itemlistCursor = adapter.getQueueCursor(); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); - - return items; - } - - /** - * Loads the IDs of the FeedItems in the queue. This method should be preferred over - * {@link #getQueue(android.content.Context)} if the FeedItems of the queue are not needed. - * - * @param context A context that is used for opening a database connection. - * @return A list of IDs sorted by the same order as the queue. The caller can wrap the returned - * list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties. - */ - public static List getQueueIDList(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - - adapter.open(); - List result = getQueueIDList(adapter); - adapter.close(); - - return result; - } - - static List getQueueIDList(PodDBAdapter adapter) { - adapter.open(); - Cursor queueCursor = adapter.getQueueIDCursor(); - - List queueIds = new ArrayList(queueCursor.getCount()); - if (queueCursor.moveToFirst()) { - do { - queueIds.add(queueCursor.getLong(0)); - } while (queueCursor.moveToNext()); - } - return queueIds; - } - - - /** - * Loads a list of the FeedItems in the queue. If the FeedItems of the queue are not used directly, consider using - * {@link #getQueueIDList(android.content.Context)} instead. - * - * @param context A context that is used for opening a database connection. - * @return A list of FeedItems sorted by the same order as the queue. The caller can wrap the returned - * list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties. - */ - public static List getQueue(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting queue"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List items = getQueue(context, adapter); - adapter.close(); - return items; - } - - /** - * Loads a list of FeedItems whose episode has been downloaded. - * - * @param context A context that is used for opening a database connection. - * @return A list of FeedItems whose episdoe has been downloaded. - */ - public static List getDownloadedItems(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting downloaded items"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getDownloadedItemsCursor(); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - loadFeedDataOfFeedItemlist(context, items); - Collections.sort(items, new FeedItemPubdateComparator()); - - adapter.close(); - return items; - - } - - /** - * Loads a list of FeedItems whose 'read'-attribute is set to false. - * - * @param context A context that is used for opening a database connection. - * @return A list of FeedItems whose 'read'-attribute it set to false. If the FeedItems in the list are not used, - * consider using {@link #getUnreadItemIds(android.content.Context)} instead. - */ - public static List getUnreadItemsList(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting unread items list"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getUnreadItemsCursor(); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - loadFeedDataOfFeedItemlist(context, items); - - adapter.close(); - - return items; - } - - /** - * Loads the IDs of the FeedItems whose 'read'-attribute is set to false. - * - * @param context A context that is used for opening a database connection. - * @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred - * over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used. - */ - public static long[] getUnreadItemIds(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor cursor = adapter.getUnreadItemIdsCursor(); - long[] itemIds = new long[cursor.getCount()]; - int i = 0; - if (cursor.moveToFirst()) { - do { - itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); - i++; - } while (cursor.moveToNext()); - } - return itemIds; - } - - - /** - * Loads a list of FeedItems sorted by pubDate in descending order. - * - * @param context A context that is used for opening a database connection. - * @param limit The maximum number of episodes that should be loaded. - */ - public static List getRecentlyPublishedEpisodes(Context context, int limit) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting recently published items list"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor itemlistCursor = adapter.getRecentlyPublishedItemsCursor(limit); - List items = extractItemlistFromCursor(adapter, - itemlistCursor); - itemlistCursor.close(); - - loadFeedDataOfFeedItemlist(context, items); - - adapter.close(); - - return items; - } - - /** - * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode - * has been completed at least once. - * - * @param context A context that is used for opening a database connection. - * @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order. - * The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}. - */ - public static List getPlaybackHistory(final Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading playback history"); - final int PLAYBACK_HISTORY_SIZE = 50; - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE); - String[] itemIds = new String[mediaCursor.getCount()]; - for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) { - itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX)); - } - mediaCursor.close(); - Cursor itemCursor = adapter.getFeedItemCursor(itemIds); - List items = extractItemlistFromCursor(adapter, itemCursor); - loadFeedDataOfFeedItemlist(context, items); - itemCursor.close(); - adapter.close(); - - Collections.sort(items, new PlaybackCompletionDateComparator()); - return items; - } - - /** - * Loads the download log from the database. - * - * @param context A context that is used for opening a database connection. - * @return A list with DownloadStatus objects that represent the download log. - * The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}. - */ - public static List getDownloadLog(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting DownloadLog"); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE); - List downloadLog = new ArrayList( - logCursor.getCount()); - - if (logCursor.moveToFirst()) { - do { - long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - - long feedfileId = logCursor - .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX); - int feedfileType = logCursor - .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX); - boolean successful = logCursor - .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0; - int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX); - String reasonDetailed = logCursor - .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX); - String title = logCursor - .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX); - Date completionDate = new Date( - logCursor - .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX) - ); - downloadLog.add(new DownloadStatus(id, title, feedfileId, - feedfileType, successful, DownloadError.fromCode(reason), completionDate, - reasonDetailed)); - - } while (logCursor.moveToNext()); - } - logCursor.close(); - Collections.sort(downloadLog, new DownloadStatusComparator()); - return downloadLog; - } - - /** - * Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over - * {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.core.feed.Feed)} if only metadata about - * the FeedItems is needed. - * - * @param context A context that is used for opening a database connection. - * @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title. - */ - public static List getFeedStatisticsList(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List result = new ArrayList(); - Cursor cursor = adapter.getFeedStatisticsCursor(); - if (cursor.moveToFirst()) { - do { - result.add(new FeedItemStatistics(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_FEED), - cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NUM_ITEMS), - cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NEW_ITEMS), - cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES), - new Date(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_LATEST_EPISODE)))); - } while (cursor.moveToNext()); - } - - cursor.close(); - adapter.close(); - return result; - } - - /** - * Loads a specific Feed from the database. - * - * @param context A context that is used for opening a database connection. - * @param feedId The ID of the Feed - * @return The Feed or null if the Feed could not be found. The Feeds FeedItems will also be loaded from the - * database and the items-attribute will be set correctly. - */ - public static Feed getFeed(final Context context, final long feedId) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Feed result = getFeed(context, feedId, adapter); - adapter.close(); - return result; - } - - static Feed getFeed(final Context context, final long feedId, PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading feed with id " + feedId); - Feed feed = null; - - Cursor feedCursor = adapter.getFeedCursor(feedId); - if (feedCursor.moveToFirst()) { - feed = extractFeedFromCursorRow(adapter, feedCursor); - feed.setItems(getFeedItemList(context, feed)); - } else { - Log.e(TAG, "getFeed could not find feed with id " + feedId); - } - feedCursor.close(); - return feed; - } - - static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading feeditem with id " + itemId); - FeedItem item = null; - - Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId)); - if (itemCursor.moveToFirst()) { - List list = extractItemlistFromCursor(adapter, itemCursor); - if (list.size() > 0) { - item = list.get(0); - loadFeedDataOfFeedItemlist(context, list); - } - } - return item; - - } - - /** - * Loads a specific FeedItem from the database. - * - * @param context A context that is used for opening a database connection. - * @param itemId The ID of the FeedItem - * @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes of the FeedItem will - * also be loaded from the database. - */ - public static FeedItem getFeedItem(final Context context, final long itemId) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading feeditem with id " + itemId); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - FeedItem item = getFeedItem(context, itemId, adapter); - adapter.close(); - return item; - - } - - /** - * Loads additional information about a FeedItem, e.g. shownotes - * - * @param context A context that is used for opening a database connection. - * @param item The FeedItem - */ - public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor extraCursor = adapter.getExtraInformationOfItem(item); - if (extraCursor.moveToFirst()) { - String description = extraCursor - .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION); - String contentEncoded = extraCursor - .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED); - item.setDescription(description); - item.setContentEncoded(contentEncoded); - } - adapter.close(); - } - - /** - * Returns the number of downloaded episodes. - * - * @param context A context that is used for opening a database connection. - * @return The number of downloaded episodes. - */ - public static int getNumberOfDownloadedEpisodes(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final int result = adapter.getNumberOfDownloadedEpisodes(); - adapter.close(); - return result; - } - - /** - * Returns the number of unread items. - * - * @param context A context that is used for opening a database connection. - * @return The number of unread items. - */ - public static int getNumberOfUnreadItems(final Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final int result = adapter.getNumberOfUnreadItems(); - adapter.close(); - return result; - } - - /** - * Searches the DB for a FeedImage of the given id. - * - * @param context A context that is used for opening a database connection. - * @param imageId The id of the object - * @return The found object - */ - public static FeedImage getFeedImage(final Context context, final long imageId) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - FeedImage result = getFeedImage(adapter, imageId); - adapter.close(); - return result; - } - - /** - * Searches the DB for a FeedImage of the given id. - * - * @param id The id of the object - * @return The found object - */ - static FeedImage getFeedImage(PodDBAdapter adapter, final long id) { - Cursor cursor = adapter.getImageCursor(id); - if ((cursor.getCount() == 0) || !cursor.moveToFirst()) { - throw new SQLException("No FeedImage found at index: " + id); - } - FeedImage image = new FeedImage(id, cursor.getString(cursor - .getColumnIndex(PodDBAdapter.KEY_TITLE)), - cursor.getString(cursor - .getColumnIndex(PodDBAdapter.KEY_FILE_URL)), - cursor.getString(cursor - .getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)), - cursor.getInt(cursor - .getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0 - ); - cursor.close(); - return image; - } - - /** - * Searches the DB for a FeedMedia of the given id. - * - * @param context A context that is used for opening a database connection. - * @param mediaId The id of the object - * @return The found object - */ - public static FeedMedia getFeedMedia(final Context context, final long mediaId) { - PodDBAdapter adapter = new PodDBAdapter(context); - - adapter.open(); - Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId); - - FeedMedia media = null; - if (mediaCursor.moveToFirst()) { - final long itemId = mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX); - media = extractFeedMediaFromCursorRow(mediaCursor); - FeedItem item = getFeedItem(context, itemId); - if (media != null && item != null) { - media.setItem(item); - item.setMedia(media); - } - } - - mediaCursor.close(); - adapter.close(); - - return media; - } - - /** - * Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems. - * - * @param context A context that is used for opening a database connection. - * @return The flattr queue as a List. - */ - public static List getFlattrQueue(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List result = new ArrayList(); - - // load feeds - Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor(); - if (feedCursor.moveToFirst()) { - do { - result.add(extractFeedFromCursorRow(adapter, feedCursor)); - } while (feedCursor.moveToNext()); - } - feedCursor.close(); - - //load feed items - Cursor feedItemCursor = adapter.getFeedItemsInFlattrQueueCursor(); - result.addAll(extractItemlistFromCursor(adapter, feedItemCursor)); - feedItemCursor.close(); - - adapter.close(); - Log.d(TAG, "Returning flattrQueueIterator for queue with " + result.size() + " items."); - return result; - } - - - /** - * Returns true if the flattr queue is empty. - * - * @param context A context that is used for opening a database connection. - */ - public static boolean getFlattrQueueEmpty(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - boolean empty = adapter.getFlattrQueueSize() == 0; - adapter.close(); - return empty; - } - - /** - * Returns data necessary for displaying the navigation drawer. This includes - * the list of subscriptions, the number of items in the queue and the number of unread - * items. - * - * @param context A context that is used for opening a database connection. - */ - public static NavDrawerData getNavDrawerData(Context context) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - List feeds = getFeedList(adapter); - int queueSize = adapter.getQueueSize(); - int numUnreadItems = adapter.getNumberOfUnreadItems(); - NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems); - adapter.close(); - return result; - } - - public static class NavDrawerData { - public List feeds; - public int queueSize; - public int numUnreadItems; - - public NavDrawerData(List feeds, int queueSize, int numUnreadItems) { - this.feeds = feeds; - this.queueSize = queueSize; - this.numUnreadItems = numUnreadItems; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/app/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java deleted file mode 100644 index 28cab29b9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ /dev/null @@ -1,895 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.asynctask.FlattrClickWorker; -import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher; -import de.danoeh.antennapod.core.feed.*; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.GpodnetSyncService; -import de.danoeh.antennapod.core.service.download.DownloadStatus; -import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.util.DownloadError; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; - -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Provides methods for doing common tasks that use DBReader and DBWriter. - */ -public final class DBTasks { - private static final String TAG = "DBTasks"; - - /** - * Executor service used by the autodownloadUndownloadedEpisodes method. - */ - private static ExecutorService autodownloadExec; - - static { - autodownloadExec = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); - } - - private DBTasks() { - } - - /** - * Removes the feed with the given download url. This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the db - * @param downloadUrl URL of the feed. - */ - public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor cursor = adapter.getFeedCursorDownloadUrls(); - long feedID = 0; - if (cursor.moveToFirst()) { - do { - if (cursor.getString(1).equals(downloadUrl)) { - feedID = cursor.getLong(0); - } - } while (cursor.moveToNext()); - } - cursor.close(); - adapter.close(); - - if (feedID != 0) { - try { - DBWriter.deleteFeed(context, feedID).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } else { - Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: " + downloadUrl); - } - } - - /** - * Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to - * start the {@link PlaybackService}. - * - * @param context Used for sending starting Services and Activities. - * @param media The FeedMedia object. - * @param showPlayer If true, starts the appropriate player activity ({@link de.danoeh.antennapod.activity.AudioplayerActivity} - * or {@link de.danoeh.antennapod.activity.VideoplayerActivity} - * @param startWhenPrepared Parameter for the {@link PlaybackService} start intent. If true, playback will start as - * soon as the PlaybackService has finished loading the FeedMedia object's file. - * @param shouldStream Parameter for the {@link PlaybackService} start intent. If true, the FeedMedia object's file - * will be streamed, otherwise the downloaded file will be used. If the downloaded file cannot be - * found, the PlaybackService will shutdown and the database entry of the FeedMedia object will be - * corrected. - */ - public static void playMedia(final Context context, final FeedMedia media, - boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { - try { - if (!shouldStream) { - if (media.fileExists() == false) { - throw new MediaFileNotFoundException( - "No episode was found at " + media.getFile_url(), - media); - } - } - // Start playback Service - Intent launchIntent = new Intent(context, PlaybackService.class); - launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); - launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, - startWhenPrepared); - launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, - shouldStream); - launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, - true); - context.startService(launchIntent); - if (showPlayer) { - // Launch media player - context.startActivity(PlaybackService.getPlayerActivityIntent( - context, media)); - } - DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false); - } catch (MediaFileNotFoundException e) { - e.printStackTrace(); - if (media.isPlaying()) { - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - notifyMissingFeedMediaFile(context, media); - } - } - - private static AtomicBoolean isRefreshing = new AtomicBoolean(false); - - /** - * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still - * enqueuing Feeds for download from a previous call - * - * @param context Might be used for accessing the database - * @param feeds List of Feeds that should be refreshed. - */ - public static void refreshAllFeeds(final Context context, - final List feeds) { - if (isRefreshing.compareAndSet(false, true)) { - new Thread() { - public void run() { - if (feeds != null) { - refreshFeeds(context, feeds); - } else { - refreshFeeds(context, DBReader.getFeedList(context)); - } - isRefreshing.set(false); - - if (FlattrUtils.hasToken()) { - if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things."); - new FlattrClickWorker(context).executeAsync(); // flattr pending things - - if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status."); - new FlattrStatusFetcher(context).start(); - - } - GpodnetSyncService.sendSyncIntent(context); - autodownloadUndownloadedItems(context); - } - }.start(); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Ignoring request to refresh all feeds: Refresh lock is locked"); - } - } - - /** - * Used by refreshExpiredFeeds to determine which feeds should be refreshed. - * This method will use the value specified in the UserPreferences as the - * expiration time. - * - * @param context Used for DB access. - * @return A list of expired feeds. An empty list will be returned if there - * are no expired feeds. - */ - public static List getExpiredFeeds(final Context context) { - long millis = UserPreferences.getUpdateInterval(); - - if (millis > 0) { - - List feedList = DBReader.getExpiredFeedsList(context, - millis); - if (feedList.size() > 0) { - refreshFeeds(context, feedList); - } - return feedList; - } else { - return new ArrayList(); - } - } - - /** - * Refreshes expired Feeds in the list returned by the getExpiredFeedsList(Context, long) method in DBReader. - * The expiration date parameter is determined by the update interval specified in {@link UserPreferences}. - * - * @param context Used for DB access. - */ - public static void refreshExpiredFeeds(final Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Refreshing expired feeds"); - - new Thread() { - public void run() { - refreshFeeds(context, getExpiredFeeds(context)); - } - }.start(); - } - - private static void refreshFeeds(final Context context, - final List feedList) { - - for (Feed feed : feedList) { - try { - refreshFeed(context, feed); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - context, - new DownloadStatus(feed, feed - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, false, e - .getMessage() - ) - ); - } - } - - } - - /** - * Updates a specific Feed. - * - * @param context Used for requesting the download. - * @param feed The Feed object. - */ - public static void refreshFeed(Context context, Feed feed) - throws DownloadRequestException { - Feed f; - if (feed.getPreferences() == null) { - f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle()); - } else { - f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle(), - feed.getPreferences().getUsername(), feed.getPreferences().getPassword()); - } - f.setId(feed.getId()); - DownloadRequester.getInstance().downloadFeed(context, f); - } - - /** - * Notifies the database about a missing FeedImage file. This method will attempt to re-download the file. - * - * @param context Used for requesting the download. - * @param image The FeedImage object. - */ - public static void notifyInvalidImageFile(final Context context, - final FeedImage image) { - Log.i(TAG, - "The DB was notified about an invalid image download. It will now try to re-download the image file"); - try { - DownloadRequester.getInstance().downloadImage(context, image); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Log.w(TAG, "Failed to download invalid feed image"); - } - } - - /** - * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the - * DB and send a FeedUpdateBroadcast. - */ - public static void notifyMissingFeedMediaFile(final Context context, - final FeedMedia media) { - Log.i(TAG, - "The feedmanager was notified about a missing episode. It will update its database now."); - media.setDownloaded(false); - media.setFile_url(null); - DBWriter.setFeedMedia(context, media); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - } - - /** - * Request the download of all objects in the queue. from a separate Thread. - * - * @param context Used for requesting the download an accessing the database. - */ - public static void downloadAllItemsInQueue(final Context context) { - new Thread() { - public void run() { - List queue = DBReader.getQueue(context); - if (!queue.isEmpty()) { - try { - downloadFeedItems(context, - queue.toArray(new FeedItem[queue.size()])); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } - } - }.start(); - } - - /** - * Requests the download of a list of FeedItem objects. - * - * @param context Used for requesting the download and accessing the DB. - * @param items The FeedItem objects. - */ - public static void downloadFeedItems(final Context context, - FeedItem... items) throws DownloadRequestException { - downloadFeedItems(true, context, items); - } - - private static void downloadFeedItems(boolean performAutoCleanup, - final Context context, final FeedItem... items) - throws DownloadRequestException { - final DownloadRequester requester = DownloadRequester.getInstance(); - - if (performAutoCleanup) { - new Thread() { - - @Override - public void run() { - performAutoCleanup(context, - getPerformAutoCleanupArgs(context, items.length)); - } - - }.start(); - } - for (FeedItem item : items) { - if (item.getMedia() != null - && !requester.isDownloadingFile(item.getMedia()) - && !item.getMedia().isDownloaded()) { - if (items.length > 1) { - try { - requester.downloadMedia(context, item.getMedia()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus(context, - new DownloadStatus(item.getMedia(), item - .getMedia() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage() - ) - ); - } - } else { - requester.downloadMedia(context, item.getMedia()); - } - } - } - } - - private static int getNumberOfUndownloadedEpisodes( - final List queue, final List unreadItems) { - int counter = 0; - for (FeedItem item : queue) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying() - && item.getFeed().getPreferences().getAutoDownload()) { - counter++; - } - } - for (FeedItem item : unreadItems) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && item.getFeed().getPreferences().getAutoDownload()) { - counter++; - } - } - return counter; - } - - /** - * Looks for undownloaded episodes in the queue or list of unread items and request a download if - * 1. Network is available - * 2. There is free space in the episode cache - * This method is executed on an internal single thread executor. - * - * @param context Used for accessing the DB. - * @param mediaIds If this list is not empty, the method will only download a candidate for automatic downloading if - * its media ID is in the mediaIds list. - * @return A Future that can be used for waiting for the methods completion. - */ - public static Future autodownloadUndownloadedItems(final Context context, final long... mediaIds) { - return autodownloadExec.submit(new Runnable() { - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Performing auto-dl of undownloaded episodes"); - if (NetworkUtils.autodownloadNetworkAvailable(context) - && UserPreferences.isEnableAutodownload()) { - final List queue = DBReader.getQueue(context); - final List unreadItems = DBReader - .getUnreadItemsList(context); - - int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue, - unreadItems); - int downloadedEpisodes = DBReader - .getNumberOfDownloadedEpisodes(context); - int deletedEpisodes = performAutoCleanup(context, - getPerformAutoCleanupArgs(context, undownloadedEpisodes)); - int episodeSpaceLeft = undownloadedEpisodes; - boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences - .getEpisodeCacheSizeUnlimited(); - - if (!cacheIsUnlimited - && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes - + undownloadedEpisodes) { - episodeSpaceLeft = UserPreferences.getEpisodeCacheSize() - - (downloadedEpisodes - deletedEpisodes); - } - - Arrays.sort(mediaIds); // sort for binary search - final boolean ignoreMediaIds = mediaIds.length == 0; - List itemsToDownload = new ArrayList(); - - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (int i = 0; i < queue.size(); i++) { // ignore playing item - FeedItem item = queue.get(i); - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1; - if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0) - && item.hasMedia() - && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying() - && item.getFeed().getPreferences().getAutoDownload()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (FeedItem item : unreadItems) { - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1; - if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0) - && item.hasMedia() - && !item.getMedia().isDownloaded() - && item.getFeed().getPreferences().getAutoDownload()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Enqueueing " + itemsToDownload.size() - + " items for download"); - - try { - downloadFeedItems(false, context, - itemsToDownload.toArray(new FeedItem[itemsToDownload - .size()]) - ); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - - } - } - }); - - } - - private static int getPerformAutoCleanupArgs(Context context, - final int episodeNumber) { - if (episodeNumber >= 0 - && UserPreferences.getEpisodeCacheSize() != UserPreferences - .getEpisodeCacheSizeUnlimited()) { - int downloadedEpisodes = DBReader - .getNumberOfDownloadedEpisodes(context); - if (downloadedEpisodes + episodeNumber >= UserPreferences - .getEpisodeCacheSize()) { - - return downloadedEpisodes + episodeNumber - - UserPreferences.getEpisodeCacheSize(); - } - } - return 0; - } - - /** - * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller - * 'playbackCompletionDate'-value will be deleted first. - *

- * This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the DB. - */ - public static void performAutoCleanup(final Context context) { - performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0)); - } - - private static int performAutoCleanup(final Context context, - final int episodeNumber) { - List candidates = new ArrayList(); - List downloadedItems = DBReader.getDownloadedItems(context); - QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context)); - List delete; - for (FeedItem item : downloadedItems) { - if (item.hasMedia() && item.getMedia().isDownloaded() - && !queue.contains(item.getId()) && item.isRead()) { - candidates.add(item); - } - - } - - Collections.sort(candidates, new Comparator() { - @Override - public int compare(FeedItem lhs, FeedItem rhs) { - Date l = lhs.getMedia().getPlaybackCompletionDate(); - Date r = rhs.getMedia().getPlaybackCompletionDate(); - - if (l == null) { - l = new Date(0); - } - if (r == null) { - r = new Date(0); - } - return l.compareTo(r); - } - }); - - if (candidates.size() > episodeNumber) { - delete = candidates.subList(0, episodeNumber); - } else { - delete = candidates; - } - - for (FeedItem item : delete) { - try { - DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - - int counter = delete.size(); - - if (BuildConfig.DEBUG) - Log.d(TAG, String.format( - "Auto-delete deleted %d episodes (%d requested)", counter, - episodeNumber)); - - return counter; - } - - /** - * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread. - */ - public static void enqueueAllNewItems(final Context context) { - long[] unreadItems = DBReader.getUnreadItemIds(context); - DBWriter.addQueueItem(context, unreadItems); - } - - /** - * Returns the successor of a FeedItem in the queue. - * - * @param context Used for accessing the DB. - * @param itemId ID of the FeedItem - * @param queue Used for determining the successor of the item. If this parameter is null, the method will load - * the queue from the database in the same thread. - * @return Successor of the FeedItem or null if the FeedItem is not in the queue or has no successor. - */ - public static FeedItem getQueueSuccessorOfItem(Context context, - final long itemId, List queue) { - FeedItem result = null; - if (queue == null) { - queue = DBReader.getQueue(context); - } - if (queue != null) { - Iterator iterator = queue.iterator(); - while (iterator.hasNext()) { - FeedItem item = iterator.next(); - if (item.getId() == itemId) { - if (iterator.hasNext()) { - result = iterator.next(); - } - break; - } - } - } - return result; - } - - /** - * Loads the queue from the database and checks if the specified FeedItem is in the queue. - * This method should NOT be executed in the GUI thread. - * - * @param context Used for accessing the DB. - * @param feedItemId ID of the FeedItem - */ - public static boolean isInQueue(Context context, final long feedItemId) { - List queue = DBReader.getQueueIDList(context); - return QueueAccess.IDListAccess(queue).contains(feedItemId); - } - - private static Feed searchFeedByIdentifyingValueOrID(Context context, PodDBAdapter adapter, - Feed feed) { - if (feed.getId() != 0) { - return DBReader.getFeed(context, feed.getId(), adapter); - } else { - List feeds = DBReader.getFeedList(context); - for (Feed f : feeds) { - if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) { - f.setItems(DBReader.getFeedItemList(context, f)); - return f; - } - } - } - return null; - } - - /** - * Get a FeedItem by its identifying value. - */ - private static FeedItem searchFeedItemByIdentifyingValue(Feed feed, - String identifier) { - for (FeedItem item : feed.getItems()) { - if (item.getIdentifyingValue().equals(identifier)) { - return item; - } - } - return null; - } - - /** - * Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same - * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed. - * These FeedItems will be marked as unread. - *

- * This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior. - *

- * This method should NOT be executed on the GUI thread. - * - * @param context Used for accessing the DB. - * @param newFeeds The new Feed objects. - * @return The updated Feeds from the database if it already existed, or the new Feed from the parameters otherwise. - */ - public static synchronized Feed[] updateFeed(final Context context, - final Feed... newFeeds) { - List newFeedsList = new ArrayList(); - List updatedFeedsList = new ArrayList(); - Feed[] resultFeeds = new Feed[newFeeds.length]; - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - - for (int feedIdx = 0; feedIdx < newFeeds.length; feedIdx++) { - - final Feed newFeed = newFeeds[feedIdx]; - - // Look up feed in the feedslist - final Feed savedFeed = searchFeedByIdentifyingValueOrID(context, adapter, - newFeed); - if (savedFeed == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Found no existing Feed with title " - + newFeed.getTitle() + ". Adding as new one." - ); - // Add a new Feed - newFeedsList.add(newFeed); - resultFeeds[feedIdx] = newFeed; - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed with title " + newFeed.getTitle() - + " already exists. Syncing new with existing one."); - - Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator()); - if (savedFeed.compareWithOther(newFeed)) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Feed has updated attribute values. Updating old feed's attributes"); - savedFeed.updateFromOther(newFeed); - } - if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences"); - savedFeed.getPreferences().updateFromOther(newFeed.getPreferences()); - } - // Look for new or updated Items - for (int idx = 0; idx < newFeed.getItems().size(); idx++) { - final FeedItem item = newFeed.getItems().get(idx); - FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed, - item.getIdentifyingValue()); - if (oldItem == null) { - // item is new - final int i = idx; - item.setFeed(savedFeed); - savedFeed.getItems().add(i, item); - item.setRead(false); - } else { - oldItem.updateFromOther(item); - } - } - // update attributes - savedFeed.setLastUpdate(newFeed.getLastUpdate()); - savedFeed.setType(newFeed.getType()); - - updatedFeedsList.add(savedFeed); - resultFeeds[feedIdx] = savedFeed; - } - } - - adapter.close(); - - try { - DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[newFeedsList.size()])).get(); - DBWriter.setCompleteFeed(context, updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - - return resultFeeds; - } - - /** - * Searches the titles of FeedItems of a specific Feed for a given - * string. - * - * @param context Used for accessing the DB. - * @param feedID The id of the feed whose items should be searched. - * @param query The search string. - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask> searchFeedItemTitle(final Context context, - final long feedID, final String query) { - return new FutureTask>(new QueryTask>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemTitles(feedID, - query); - List items = DBReader.extractItemlistFromCursor(context, searchResult); - DBReader.loadFeedDataOfFeedItemlist(context, items); - setResult(items); - searchResult.close(); - } - }); - } - - /** - * Searches the descriptions of FeedItems of a specific Feed for a given - * string. - * - * @param context Used for accessing the DB. - * @param feedID The id of the feed whose items should be searched. - * @param query The search string - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask> searchFeedItemDescription(final Context context, - final long feedID, final String query) { - return new FutureTask>(new QueryTask>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemDescriptions(feedID, - query); - List items = DBReader.extractItemlistFromCursor(context, searchResult); - DBReader.loadFeedDataOfFeedItemlist(context, items); - setResult(items); - searchResult.close(); - } - }); - } - - /** - * Searches the contentEncoded-value of FeedItems of a specific Feed for a given - * string. - * - * @param context Used for accessing the DB. - * @param feedID The id of the feed whose items should be searched. - * @param query The search string - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask> searchFeedItemContentEncoded(final Context context, - final long feedID, final String query) { - return new FutureTask>(new QueryTask>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemContentEncoded(feedID, - query); - List items = DBReader.extractItemlistFromCursor(context, searchResult); - DBReader.loadFeedDataOfFeedItemlist(context, items); - setResult(items); - searchResult.close(); - } - }); - } - - /** - * Searches chapters of the FeedItems of a specific Feed for a given string. - * - * @param context Used for accessing the DB. - * @param feedID The id of the feed whose items should be searched. - * @param query The search string - * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems. - */ - public static FutureTask> searchFeedItemChapters(final Context context, - final long feedID, final String query) { - return new FutureTask>(new QueryTask>(context) { - @Override - public void execute(PodDBAdapter adapter) { - Cursor searchResult = adapter.searchItemChapters(feedID, - query); - List items = DBReader.extractItemlistFromCursor(context, searchResult); - DBReader.loadFeedDataOfFeedItemlist(context, items); - setResult(items); - searchResult.close(); - } - }); - } - - /** - * A runnable which should be used for database queries. The onCompletion - * method is executed on the database executor to handle Cursors correctly. - * This class automatically creates a PodDBAdapter object and closes it when - * it is no longer in use. - */ - static abstract class QueryTask implements Callable { - private T result; - private Context context; - - public QueryTask(Context context) { - this.context = context; - } - - @Override - public T call() throws Exception { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - execute(adapter); - adapter.close(); - return result; - } - - public abstract void execute(PodDBAdapter adapter); - - protected void setResult(T result) { - this.result = result; - } - } - - /** - * Adds the given FeedItem to the flattr queue if the user is logged in. Otherwise, a dialog - * will be opened that lets the user go either to the login screen or the website of the flattr thing. - * - * @param context - * @param item - */ - public static void flattrItemIfLoggedIn(Context context, FeedItem item) { - if (FlattrUtils.hasToken()) { - item.getFlattrStatus().setFlattrQueue(); - DBWriter.setFlattredStatus(context, item, true); - } else { - FlattrUtils.showNoTokenDialogOrRedirect(context, item.getPaymentLink()); - } - } - - /** - * Adds the given Feed to the flattr queue if the user is logged in. Otherwise, a dialog - * will be opened that lets the user go either to the login screen or the website of the flattr thing. - * - * @param context - * @param feed - */ - public static void flattrFeedIfLoggedIn(Context context, Feed feed) { - if (FlattrUtils.hasToken()) { - feed.getFlattrStatus().setFlattrQueue(); - DBWriter.setFlattredStatus(context, feed, true); - } else { - FlattrUtils.showNoTokenDialogOrRedirect(context, feed.getPaymentLink()); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/app/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java deleted file mode 100644 index 225f74c96..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ /dev/null @@ -1,974 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import android.app.backup.BackupManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.preference.PreferenceManager; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.asynctask.FlattrClickWorker; -import de.danoeh.antennapod.core.feed.*; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.service.download.DownloadStatus; -import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; -import de.danoeh.antennapod.core.util.flattr.FlattrThing; -import de.danoeh.antennapod.core.util.flattr.SimpleFlattrThing; -import org.shredzone.flattr4j.model.Flattr; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; - -/** - * Provides methods for writing data to AntennaPod's database. - * In general, DBWriter-methods will be executed on an internal ExecutorService. - * Some methods return a Future-object which the caller can use for waiting for the method's completion. The returned Future's - * will NOT contain any results. - * The caller can also use the {@link EventDistributor} in order to be notified about the method's completion asynchronously. - * This class will use the {@link EventDistributor} to notify listeners about changes in the database. - */ -public class DBWriter { - private static final String TAG = "DBWriter"; - - private static final ExecutorService dbExec; - - static { - dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); - } - - private DBWriter() { - } - - /** - * Deletes a downloaded FeedMedia file from the storage device. - * - * @param context A context that is used for opening a database connection. - * @param mediaId ID of the FeedMedia object whose downloaded file should be deleted. - */ - public static Future deleteFeedMediaOfItem(final Context context, - final long mediaId) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - - final FeedMedia media = DBReader.getFeedMedia(context, mediaId); - if (media != null) { - Log.i(TAG, String.format("Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s", - media.getId(), media.getEpisodeTitle(), String.valueOf(media.isDownloaded()))); - boolean result = false; - if (media.isDownloaded()) { - // delete downloaded media file - File mediaFile = new File(media.getFile_url()); - if (mediaFile.exists()) { - result = mediaFile.delete(); - } - media.setDownloaded(false); - media.setFile_url(null); - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setMedia(media); - adapter.close(); - - // If media is currently being played, change playback - // type to 'stream' and shutdown playback service - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) { - if (media.getId() == PlaybackPreferences - .getCurrentlyPlayingFeedMediaId()) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - true); - editor.commit(); - } - if (PlaybackPreferences - .getCurrentlyPlayingFeedMediaId() == media - .getId()) { - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Deleting File. Result: " + result); - EventDistributor.getInstance().sendQueueUpdateBroadcast(); - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - } - }); - } - - /** - * Deletes a Feed and all downloaded files of its components like images and downloaded episodes. - * - * @param context A context that is used for opening a database connection. - * @param feedId ID of the Feed that should be deleted. - */ - public static Future deleteFeed(final Context context, final long feedId) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - DownloadRequester requester = DownloadRequester.getInstance(); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context - .getApplicationContext()); - final Feed feed = DBReader.getFeed(context, feedId); - if (feed != null) { - if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA - && PlaybackPreferences.getLastPlayedFeedId() == feed - .getId()) { - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - -1); - editor.commit(); - } - - // delete image file - if (feed.getImage() != null) { - if (feed.getImage().isDownloaded() - && feed.getImage().getFile_url() != null) { - File imageFile = new File(feed.getImage() - .getFile_url()); - imageFile.delete(); - } else if (requester.isDownloadingFile(feed.getImage())) { - requester.cancelDownload(context, feed.getImage()); - } - } - // delete stored media files and mark them as read - List queue = DBReader.getQueue(context); - boolean queueWasModified = false; - if (feed.getItems() == null) { - DBReader.getFeedItemList(context, feed); - } - - for (FeedItem item : feed.getItems()) { - queueWasModified |= queue.remove(item); - if (item.getMedia() != null - && item.getMedia().isDownloaded()) { - File mediaFile = new File(item.getMedia() - .getFile_url()); - mediaFile.delete(); - } else if (item.getMedia() != null - && requester.isDownloadingFile(item.getMedia())) { - requester.cancelDownload(context, item.getMedia()); - } - - if (item.hasItemImage()) { - FeedImage image = item.getImage(); - if (image.isDownloaded() && image.getFile_url() != null) { - File imgFile = new File(image.getFile_url()); - imgFile.delete(); - } else if (requester.isDownloadingFile(image)) { - requester.cancelDownload(context, item.getImage()); - } - } - } - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - if (queueWasModified) { - adapter.setQueue(queue); - } - adapter.removeFeed(feed); - adapter.close(); - - GpodnetPreferences.addRemovedFeed(feed.getDownload_url()); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - - BackupManager backupManager = new BackupManager(context); - backupManager.dataChanged(); - } - } - }); - } - - /** - * Deletes the entire playback history. - * - * @param context A context that is used for opening a database connection. - */ - public static Future clearPlaybackHistory(final Context context) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.clearPlaybackHistory(); - adapter.close(); - EventDistributor.getInstance() - .sendPlaybackHistoryUpdateBroadcast(); - } - }); - } - - /** - * Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if - * its playback completion date is set to a non-null value. This method will set the playback completion date to the - * current date regardless of the current value. - * - * @param context A context that is used for opening a database connection. - * @param media FeedMedia that should be added to the playback history. - */ - public static Future addItemToPlaybackHistory(final Context context, - final FeedMedia media) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Adding new item to playback history"); - media.setPlaybackCompletionDate(new Date()); - // reset played_duration to 0 so that it behaves correctly when the episode is played again - media.setPlayedDuration(0); - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedMediaPlaybackCompletionDate(media); - adapter.close(); - EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast(); - - } - }); - } - - private static void cleanupDownloadLog(final PodDBAdapter adapter) { - final long logSize = adapter.getDownloadLogSize(); - if (logSize > DBReader.DOWNLOAD_LOG_SIZE) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Cleaning up download log"); - adapter.removeDownloadLogItems(logSize - DBReader.DOWNLOAD_LOG_SIZE); - } - } - - /** - * Adds a Download status object to the download log. - * - * @param context A context that is used for opening a database connection. - * @param status The DownloadStatus object. - */ - public static Future addDownloadStatus(final Context context, - final DownloadStatus status) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setDownloadStatus(status); - adapter.close(); - EventDistributor.getInstance().sendDownloadLogUpdateBroadcast(); - } - }); - - } - - /** - * Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to - * true. If the FeedItem is already in the queue, the queue will not be modified. - * - * @param context A context that is used for opening a database connection. - * @param itemId ID of the FeedItem that should be added to the queue. - * @param index Destination index. Must be in range 0..queue.size() - * @param performAutoDownload True if an auto-download process should be started after the operation - * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size() - */ - public static Future addQueueItemAt(final Context context, final long itemId, - final int index, final boolean performAutoDownload) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader - .getQueue(context, adapter); - FeedItem item = null; - - if (queue != null) { - boolean queueModified = false; - boolean unreadItemsModified = false; - - if (!itemListContains(queue, itemId)) { - item = DBReader.getFeedItem(context, itemId); - if (item != null) { - queue.add(index, item); - queueModified = true; - if (!item.isRead()) { - item.setRead(true); - unreadItemsModified = true; - } - } - } - if (queueModified) { - adapter.setQueue(queue); - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } - if (unreadItemsModified && item != null) { - adapter.setSingleFeedItem(item); - EventDistributor.getInstance() - .sendUnreadItemsUpdateBroadcast(); - } - } - adapter.close(); - if (performAutoDownload) { - DBTasks.autodownloadUndownloadedItems(context); - } - - } - }); - - } - - /** - * Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true. - * If a FeedItem is already in the queue, the FeedItem will not change its position in the queue. - * - * @param context A context that is used for opening a database connection. - * @param itemIds IDs of the FeedItem objects that should be added to the queue. - */ - public static Future addQueueItem(final Context context, - final long... itemIds) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - if (itemIds.length > 0) { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader.getQueue(context, - adapter); - - if (queue != null) { - boolean queueModified = false; - boolean unreadItemsModified = false; - List itemsToSave = new LinkedList(); - for (int i = 0; i < itemIds.length; i++) { - if (!itemListContains(queue, itemIds[i])) { - final FeedItem item = DBReader.getFeedItem( - context, itemIds[i]); - - if (item != null) { - queue.add(item); - queueModified = true; - if (!item.isRead()) { - item.setRead(true); - itemsToSave.add(item); - unreadItemsModified = true; - } - } - } - } - if (queueModified) { - adapter.setQueue(queue); - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } - if (unreadItemsModified) { - adapter.setFeedItemlist(itemsToSave); - EventDistributor.getInstance() - .sendUnreadItemsUpdateBroadcast(); - } - } - adapter.close(); - DBTasks.autodownloadUndownloadedItems(context); - } - } - }); - - } - - /** - * Removes all FeedItem objects from the queue. - * - * @param context A context that is used for opening a database connection. - */ - public static Future clearQueue(final Context context) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.clearQueue(); - adapter.close(); - - EventDistributor.getInstance().sendQueueUpdateBroadcast(); - } - }); - } - - /** - * Removes a FeedItem object from the queue. - * - * @param context A context that is used for opening a database connection. - * @param itemId ID of the FeedItem that should be removed. - * @param performAutoDownload true if an auto-download process should be started after the operation. - */ - public static Future removeQueueItem(final Context context, - final long itemId, final boolean performAutoDownload) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader - .getQueue(context, adapter); - FeedItem item = null; - - if (queue != null) { - boolean queueModified = false; - QueueAccess queueAccess = QueueAccess.ItemListAccess(queue); - if (queueAccess.contains(itemId)) { - item = DBReader.getFeedItem(context, itemId); - if (item != null) { - queueModified = queueAccess.remove(itemId); - } - } - if (queueModified) { - adapter.setQueue(queue); - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } else { - Log.w(TAG, "Queue was not modified by call to removeQueueItem"); - } - } else { - Log.e(TAG, "removeQueueItem: Could not load queue"); - } - adapter.close(); - if (performAutoDownload) { - DBTasks.autodownloadUndownloadedItems(context); - } - } - }); - - } - - /** - * Moves the specified item to the top of the queue. - * - * @param context A context that is used for opening a database connection. - * @param itemId The item to move to the top of the queue - * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to - * false if the caller wants to avoid unexpected updates of the GUI. - */ - public static Future moveQueueItemToTop(final Context context, final long itemId, final boolean broadcastUpdate) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - List queueIdList = DBReader.getQueueIDList(context); - int currentLocation = 0; - for (long id : queueIdList) { - if (id == itemId) { - moveQueueItemHelper(context, currentLocation, 0, broadcastUpdate); - return; - } - currentLocation++; - } - Log.e(TAG, "moveQueueItemToTop: item not found"); - } - }); - } - - /** - * Moves the specified item to the bottom of the queue. - * - * @param context A context that is used for opening a database connection. - * @param itemId The item to move to the bottom of the queue - * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to - * false if the caller wants to avoid unexpected updates of the GUI. - */ - public static Future moveQueueItemToBottom(final Context context, final long itemId, - final boolean broadcastUpdate) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - List queueIdList = DBReader.getQueueIDList(context); - int currentLocation = 0; - for (long id : queueIdList) { - if (id == itemId) { - moveQueueItemHelper(context, currentLocation, queueIdList.size() - 1, - broadcastUpdate); - return; - } - currentLocation++; - } - Log.e(TAG, "moveQueueItemToBottom: item not found"); - } - }); - } - - /** - * Changes the position of a FeedItem in the queue. - * - * @param context A context that is used for opening a database connection. - * @param from Source index. Must be in range 0..queue.size()-1. - * @param to Destination index. Must be in range 0..queue.size()-1. - * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to - * false if the caller wants to avoid unexpected updates of the GUI. - * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) - */ - public static Future moveQueueItem(final Context context, final int from, - final int to, final boolean broadcastUpdate) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - moveQueueItemHelper(context, from, to, broadcastUpdate); - } - }); - } - - /** - * Changes the position of a FeedItem in the queue. - *

- * This function must be run using the ExecutorService (dbExec). - * - * @param context A context that is used for opening a database connection. - * @param from Source index. Must be in range 0..queue.size()-1. - * @param to Destination index. Must be in range 0..queue.size()-1. - * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to - * false if the caller wants to avoid unexpected updates of the GUI. - * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) - */ - private static void moveQueueItemHelper(final Context context, final int from, - final int to, final boolean broadcastUpdate) { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - final List queue = DBReader - .getQueue(context, adapter); - - if (queue != null) { - if (from >= 0 && from < queue.size() && to >= 0 - && to < queue.size()) { - - final FeedItem item = queue.remove(from); - queue.add(to, item); - - adapter.setQueue(queue); - if (broadcastUpdate) { - EventDistributor.getInstance() - .sendQueueUpdateBroadcast(); - } - - } - } else { - Log.e(TAG, "moveQueueItemHelper: Could not load queue"); - } - adapter.close(); - } - - /** - * Sets the 'read'-attribute of a FeedItem to the specified value. - * - * @param context A context that is used for opening a database connection. - * @param item The FeedItem object - * @param read New value of the 'read'-attribute - * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object. - * If the FeedItem has no FeedMedia object, this parameter will be ignored. - */ - public static Future markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) { - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0; - return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition); - } - - /** - * Sets the 'read'-attribute of a FeedItem to the specified value. - * - * @param context A context that is used for opening a database connection. - * @param itemId ID of the FeedItem - * @param read New value of the 'read'-attribute - */ - public static Future markItemRead(final Context context, final long itemId, - final boolean read) { - return markItemRead(context, itemId, read, 0, false); - } - - private static Future markItemRead(final Context context, final long itemId, - final boolean read, final long mediaId, - final boolean resetMediaPosition) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedItemRead(read, itemId, mediaId, - resetMediaPosition); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - } - - /** - * Sets the 'read'-attribute of all FeedItems of a specific Feed to true. - * - * @param context A context that is used for opening a database connection. - * @param feedId ID of the Feed. - */ - public static Future markFeedRead(final Context context, final long feedId) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId); - long[] itemIds = new long[itemCursor.getCount()]; - itemCursor.moveToFirst(); - for (int i = 0; i < itemIds.length; i++) { - itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - itemCursor.moveToNext(); - } - itemCursor.close(); - adapter.setFeedItemRead(true, itemIds); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - - } - - /** - * Sets the 'read'-attribute of all FeedItems to true. - * - * @param context A context that is used for opening a database connection. - */ - public static Future markAllItemsRead(final Context context) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - Cursor itemCursor = adapter.getUnreadItemsCursor(); - long[] itemIds = new long[itemCursor.getCount()]; - itemCursor.moveToFirst(); - for (int i = 0; i < itemIds.length; i++) { - itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - itemCursor.moveToNext(); - } - itemCursor.close(); - adapter.setFeedItemRead(true, itemIds); - adapter.close(); - - EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); - } - }); - - } - - static Future addNewFeed(final Context context, final Feed... feeds) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - final PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setCompleteFeed(feeds); - adapter.close(); - - for (Feed feed : feeds) { - GpodnetPreferences.addAddedFeed(feed.getDownload_url()); - } - - BackupManager backupManager = new BackupManager(context); - backupManager.dataChanged(); - } - }); - } - - static Future setCompleteFeed(final Context context, final Feed... feeds) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setCompleteFeed(feeds); - adapter.close(); - - } - }); - - } - - /** - * Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The - * contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved. - * - * @param context A context that is used for opening a database connection. - * @param media The FeedMedia object. - */ - public static Future setFeedMedia(final Context context, - final FeedMedia media) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setMedia(media); - adapter.close(); - } - }); - } - - /** - * Saves the 'position' and 'duration' attributes of a FeedMedia object - * - * @param context A context that is used for opening a database connection. - * @param media The FeedMedia object. - */ - public static Future setFeedMediaPlaybackInformation(final Context context, final FeedMedia media) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedMediaPlaybackInformation(media); - adapter.close(); - } - }); - } - - /** - * Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including - * the content of FeedComponent-attributes. - * - * @param context A context that is used for opening a database connection. - * @param item The FeedItem object. - */ - public static Future setFeedItem(final Context context, - final FeedItem item) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setSingleFeedItem(item); - adapter.close(); - } - }); - } - - /** - * Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The - * contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved. - * - * @param context A context that is used for opening a database connection. - * @param image The FeedImage object. - */ - public static Future setFeedImage(final Context context, - final FeedImage image) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setImage(image); - adapter.close(); - } - }); - } - - /** - * Updates download URLs of feeds from a given Map. The key of the Map is the original URL of the feed - * and the value is the updated URL - */ - public static Future updateFeedDownloadURLs(final Context context, final Map urls) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - for (String key : urls.keySet()) { - if (BuildConfig.DEBUG) Log.d(TAG, "Replacing URL " + key + " with url " + urls.get(key)); - - adapter.setFeedDownloadUrl(key, urls.get(key)); - } - adapter.close(); - } - }); - } - - /** - * Saves a FeedPreferences object in the database. The Feed ID of the FeedPreferences-object MUST NOT be 0. - * - * @param context Used for opening a database connection. - * @param preferences The FeedPreferences object. - */ - public static Future setFeedPreferences(final Context context, final FeedPreferences preferences) { - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedPreferences(preferences); - adapter.close(); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); - } - }); - } - - private static boolean itemListContains(List items, long itemId) { - for (FeedItem item : items) { - if (item.getId() == itemId) { - return true; - } - } - return false; - } - - /** - * Saves the FlattrStatus of a FeedItem object in the database. - * - * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved - */ - public static Future setFeedItemFlattrStatus(final Context context, - final FeedItem item, - final boolean startFlattrClickWorker) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedItemFlattrStatus(item); - adapter.close(); - if (startFlattrClickWorker) { - new FlattrClickWorker(context).executeAsync(); - } - } - }); - } - - /** - * Saves the FlattrStatus of a Feed object in the database. - * - * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved - */ - private static Future setFeedFlattrStatus(final Context context, - final Feed feed, - final boolean startFlattrClickWorker) { - return dbExec.submit(new Runnable() { - - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.setFeedFlattrStatus(feed); - adapter.close(); - if (startFlattrClickWorker) { - new FlattrClickWorker(context).executeAsync(); - } - } - }); - } - - /** - * format an url for querying the database - * (postfix a / and apply percent-encoding) - */ - private static String formatURIForQuery(String uri) { - try { - return URLEncoder.encode(uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri, "UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, e.getMessage()); - return ""; - } - } - - - /** - * Set flattr status of the passed thing (either a FeedItem or a Feed) - * - * @param context - * @param thing - * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved - * @return - */ - public static Future setFlattredStatus(Context context, FlattrThing thing, boolean startFlattrClickWorker) { - // must propagate this to back db - if (thing instanceof FeedItem) - return setFeedItemFlattrStatus(context, (FeedItem) thing, startFlattrClickWorker); - else if (thing instanceof Feed) - return setFeedFlattrStatus(context, (Feed) thing, startFlattrClickWorker); - else if (thing instanceof SimpleFlattrThing) { - } // SimpleFlattrThings are generated on the fly and do not have DB backing - else - Log.e(TAG, "flattrQueue processing - thing is neither FeedItem nor Feed nor SimpleFlattrThing"); - - return null; - } - - /** - * Reset flattr status to unflattrd for all items - */ - public static Future clearAllFlattrStatus(final Context context) { - Log.d(TAG, "clearAllFlattrStatus()"); - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - adapter.clearAllFlattrStatus(); - adapter.close(); - } - }); - } - - /** - * Set flattr status of the feeds/feeditems in flattrList to flattred at the given timestamp, - * where the information has been retrieved from the flattr API - */ - public static Future setFlattredStatus(final Context context, final List flattrList) { - Log.d(TAG, "setFlattredStatus to status retrieved from flattr api running with " + flattrList.size() + " items"); - // clear flattr status in db - clearAllFlattrStatus(context); - - // submit list with flattred things having normalized URLs to db - return dbExec.submit(new Runnable() { - @Override - public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); - for (Flattr flattr : flattrList) { - adapter.setItemFlattrStatus(formatURIForQuery(flattr.getThing().getUrl()), new FlattrStatus(flattr.getCreated().getTime())); - } - adapter.close(); - } - }); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequestException.java b/app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequestException.java deleted file mode 100644 index c85559e20..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequestException.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -/** - * Thrown by the DownloadRequester if a download request contains invalid data - * or something went wrong while processing the request. - */ -public class DownloadRequestException extends Exception { - - public DownloadRequestException() { - super(); - } - - public DownloadRequestException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - - public DownloadRequestException(String detailMessage) { - super(detailMessage); - } - - public DownloadRequestException(Throwable throwable) { - super(throwable); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java deleted file mode 100644 index c313055a5..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java +++ /dev/null @@ -1,366 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.webkit.URLUtil; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.*; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.download.DownloadRequest; -import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.util.FileNameGenerator; -import de.danoeh.antennapod.core.util.URLChecker; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; - -import java.io.File; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - - -/** - * Sends download requests to the DownloadService. This class should always be used for starting downloads, - * otherwise they won't work correctly. - */ -public class DownloadRequester { - private static final String TAG = "DownloadRequester"; - - public static final String IMAGE_DOWNLOADPATH = "images/"; - public static final String FEED_DOWNLOADPATH = "cache/"; - public static final String MEDIA_DOWNLOADPATH = "media/"; - - private static DownloadRequester downloader; - - Map downloads; - - private DownloadRequester() { - downloads = new ConcurrentHashMap(); - } - - public static synchronized DownloadRequester getInstance() { - if (downloader == null) { - downloader = new DownloadRequester(); - } - return downloader; - } - - /** - * Starts a new download with the given DownloadRequest. This method should only - * be used from outside classes if the DownloadRequest was created by the DownloadService to - * ensure that the data is valid. Use downloadFeed(), downloadImage() or downloadMedia() instead. - * - * @param context Context object for starting the DownloadService - * @param request The DownloadRequest. If another DownloadRequest with the same source URL is already stored, this method - * call will return false. - * @return True if the download request was accepted, false otherwise. - */ - public boolean download(Context context, DownloadRequest request) { - Validate.notNull(context); - Validate.notNull(request); - - if (downloads.containsKey(request.getSource())) { - if (BuildConfig.DEBUG) Log.i(TAG, "DownloadRequest is already stored."); - return false; - } - downloads.put(request.getSource(), request); - - Intent launchIntent = new Intent(context, DownloadService.class); - launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request); - context.startService(launchIntent); - EventDistributor.getInstance().sendDownloadQueuedBroadcast(); - return true; - } - - private void download(Context context, FeedFile item, File dest, - boolean overwriteIfExists, String username, String password, boolean deleteOnFailure) { - if (!isDownloadingFile(item)) { - if (!isFilenameAvailable(dest.toString()) || (deleteOnFailure && dest.exists())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Filename already used."); - if (isFilenameAvailable(dest.toString()) && overwriteIfExists) { - boolean result = dest.delete(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Deleting file. Result: " + result); - } else { - // find different name - File newDest = null; - for (int i = 1; i < Integer.MAX_VALUE; i++) { - String newName = FilenameUtils.getBaseName(dest - .getName()) - + "-" - + i - + FilenameUtils.EXTENSION_SEPARATOR - + FilenameUtils.getExtension(dest.getName()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Testing filename " + newName); - newDest = new File(dest.getParent(), newName); - if (!newDest.exists() - && isFilenameAvailable(newDest.toString())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "File doesn't exist yet. Using " - + newName); - break; - } - } - if (newDest != null) { - dest = newDest; - } - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, - "Requesting download of url " + item.getDownload_url()); - item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); - - DownloadRequest request = new DownloadRequest(dest.toString(), - URLChecker.prepareURL(item.getDownload_url()), item.getHumanReadableIdentifier(), - item.getId(), item.getTypeAsInt(), username, password, deleteOnFailure); - - download(context, request); - } else { - Log.e(TAG, "URL " + item.getDownload_url() - + " is already being downloaded"); - } - } - - /** - * Returns true if a filename is available and false if it has already been - * taken by another requested download. - */ - private boolean isFilenameAvailable(String path) { - for (String key : downloads.keySet()) { - DownloadRequest r = downloads.get(key); - if (StringUtils.equals(r.getDestination(), path)) { - if (BuildConfig.DEBUG) - Log.d(TAG, path - + " is already used by another requested download"); - return false; - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, path + " is available as a download destination"); - return true; - } - - public void downloadFeed(Context context, Feed feed) - throws DownloadRequestException { - if (feedFileValid(feed)) { - String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null; - String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null; - - download(context, feed, new File(getFeedfilePath(context), - getFeedfileName(feed)), true, username, password, true); - } - } - - public void downloadImage(Context context, FeedImage image) - throws DownloadRequestException { - if (feedFileValid(image)) { - download(context, image, new File(getImagefilePath(context), - getImagefileName(image)), false, null, null, false); - } - } - - public void downloadMedia(Context context, FeedMedia feedmedia) - throws DownloadRequestException { - if (feedFileValid(feedmedia)) { - Feed feed = feedmedia.getItem().getFeed(); - String username; - String password; - if (feed != null && feed.getPreferences() != null) { - username = feed.getPreferences().getUsername(); - password = feed.getPreferences().getPassword(); - } else { - username = null; - password = null; - } - - File dest; - if (feedmedia.getFile_url() != null) { - dest = new File(feedmedia.getFile_url()); - } else { - dest = new File(getMediafilePath(context, feedmedia), - getMediafilename(feedmedia)); - } - download(context, feedmedia, - dest, false, username, password, false - ); - } - } - - /** - * Throws a DownloadRequestException if the feedfile or the download url of - * the feedfile is null. - * - * @throws DownloadRequestException - */ - private boolean feedFileValid(FeedFile f) throws DownloadRequestException { - if (f == null) { - throw new DownloadRequestException("Feedfile was null"); - } else if (f.getDownload_url() == null) { - throw new DownloadRequestException("File has no download URL"); - } else { - return true; - } - } - - /** - * Cancels a running download. - */ - public void cancelDownload(final Context context, final FeedFile f) { - cancelDownload(context, f.getDownload_url()); - } - - /** - * Cancels a running download. - */ - public void cancelDownload(final Context context, final String downloadUrl) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Cancelling download with url " + downloadUrl); - Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD); - cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl); - context.sendBroadcast(cancelIntent); - } - - /** - * Cancels all running downloads - */ - public void cancelAllDownloads(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Cancelling all running downloads"); - context.sendBroadcast(new Intent( - DownloadService.ACTION_CANCEL_ALL_DOWNLOADS)); - } - - /** - * Returns true if there is at least one Feed in the downloads queue. - */ - public boolean isDownloadingFeeds() { - for (DownloadRequest r : downloads.values()) { - if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { - return true; - } - } - return false; - } - - /** - * Checks if feedfile is in the downloads list - */ - public boolean isDownloadingFile(FeedFile item) { - if (item.getDownload_url() != null) { - return downloads.containsKey(item.getDownload_url()); - } - return false; - } - - public DownloadRequest getDownload(String downloadUrl) { - return downloads.get(downloadUrl); - } - - /** - * Checks if feedfile with the given download url is in the downloads list - */ - public boolean isDownloadingFile(String downloadUrl) { - return downloads.get(downloadUrl) != null; - } - - public boolean hasNoDownloads() { - return downloads.isEmpty(); - } - - /** - * Remove an object from the downloads-list of the requester. - */ - public void removeDownload(DownloadRequest r) { - if (downloads.remove(r.getSource()) == null) { - Log.e(TAG, - "Could not remove object with url " + r.getSource()); - } - } - - /** - * Get the number of uncompleted Downloads - */ - public int getNumberOfDownloads() { - return downloads.size(); - } - - public String getFeedfilePath(Context context) - throws DownloadRequestException { - return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH) - .toString() + "/"; - } - - public String getFeedfileName(Feed feed) { - String filename = feed.getDownload_url(); - if (feed.getTitle() != null && !feed.getTitle().isEmpty()) { - filename = feed.getTitle(); - } - return "feed-" + FileNameGenerator.generateFileName(filename); - } - - public String getImagefilePath(Context context) - throws DownloadRequestException { - return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH) - .toString() + "/"; - } - - public String getImagefileName(FeedImage image) { - String filename = image.getDownload_url(); - if (image.getOwner() != null && image.getOwner().getHumanReadableIdentifier() != null) { - filename = image.getOwner().getHumanReadableIdentifier(); - } - return "image-" + FileNameGenerator.generateFileName(filename); - } - - public String getMediafilePath(Context context, FeedMedia media) - throws DownloadRequestException { - File externalStorage = getExternalFilesDirOrThrowException( - context, - MEDIA_DOWNLOADPATH - + FileNameGenerator.generateFileName(media.getItem() - .getFeed().getTitle()) + "/" - ); - return externalStorage.toString(); - } - - private File getExternalFilesDirOrThrowException(Context context, - String type) throws DownloadRequestException { - File result = UserPreferences.getDataFolder(context, type); - if (result == null) { - throw new DownloadRequestException( - "Failed to access external storage"); - } - return result; - } - - public String getMediafilename(FeedMedia media) { - String filename; - String titleBaseFilename = ""; - - // Try to generate the filename by the item title - if (media.getItem() != null && media.getItem().getTitle() != null) { - String title = media.getItem().getTitle(); - // Delete reserved characters - titleBaseFilename = title.replaceAll("[\\\\/%\\?\\*:|<>\"\\p{Cntrl}]", ""); - titleBaseFilename = titleBaseFilename.trim(); - } - - String URLBaseFilename = URLUtil.guessFileName(media.getDownload_url(), - null, media.getMime_type()); - ; - - if (titleBaseFilename != "") { - // Append extension - filename = titleBaseFilename + FilenameUtils.EXTENSION_SEPARATOR + - FilenameUtils.getExtension(URLBaseFilename); - } else { - // Fall back on URL file name - filename = URLBaseFilename; - } - return filename; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java b/app/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java deleted file mode 100644 index f6a59836b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java +++ /dev/null @@ -1,70 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import java.util.Date; - -/** - * Contains information about a feed's items. - */ -public class FeedItemStatistics { - private long feedID; - private int numberOfItems; - private int numberOfNewItems; - private int numberOfInProgressItems; - private Date lastUpdate; - private static final Date UNKNOWN_DATE = new Date(0); - - - /** - * Creates new FeedItemStatistics object. - * - * @param feedID ID of the feed. - * @param numberOfItems Number of items that this feed has. - * @param numberOfNewItems Number of unread items this feed has. - * @param numberOfInProgressItems Number of items that the user has started listening to. - * @param lastUpdate pubDate of the latest episode. A lastUpdate value of 0 will be interpreted as DATE_UNKOWN if - * numberOfItems is 0. - */ - public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) { - this.feedID = feedID; - this.numberOfItems = numberOfItems; - this.numberOfNewItems = numberOfNewItems; - this.numberOfInProgressItems = numberOfInProgressItems; - if (numberOfItems > 0) { - this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - } else { - this.lastUpdate = UNKNOWN_DATE; - } - } - - public long getFeedID() { - return feedID; - } - - public int getNumberOfItems() { - return numberOfItems; - } - - public int getNumberOfNewItems() { - return numberOfNewItems; - } - - public int getNumberOfInProgressItems() { - return numberOfInProgressItems; - } - - /** - * Returns the pubDate of the latest item in the feed. Users of this method - * should check if this value is unkown or not by calling lastUpdateKnown() first. - */ - public Date getLastUpdate() { - return (lastUpdate != null) ? (Date) lastUpdate.clone() : null; - } - - /** - * Returns true if the lastUpdate value is known. The lastUpdate value is unkown if the - * feed has no items. - */ - public boolean lastUpdateKnown() { - return lastUpdate != UNKNOWN_DATE; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/app/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java deleted file mode 100644 index 41b379471..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import android.content.Context; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.SearchResult; -import de.danoeh.antennapod.core.util.comparator.SearchResultValueComparator; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -/** - * Performs search on Feeds and FeedItems - */ -public class FeedSearcher { - private static final String TAG = "FeedSearcher"; - - - /** - * Performs a search in all feeds or one specific feed. - */ - public static List performSearch(final Context context, - final String query, final long selectedFeed) { - final int values[] = {0, 0, 1, 2}; - final String[] subtitles = {context.getString(R.string.found_in_shownotes_label), - context.getString(R.string.found_in_shownotes_label), - context.getString(R.string.found_in_chapters_label), - context.getString(R.string.found_in_title_label)}; - - List result = new ArrayList(); - - FutureTask>[] tasks = new FutureTask[4]; - (tasks[0] = DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query)).run(); - (tasks[1] = DBTasks.searchFeedItemDescription(context, selectedFeed, query)).run(); - (tasks[2] = DBTasks.searchFeedItemChapters(context, selectedFeed, query)).run(); - (tasks[3] = DBTasks.searchFeedItemTitle(context, selectedFeed, query)).run(); - try { - for (int i = 0; i < tasks.length; i++) { - FutureTask task = tasks[i]; - List items = (List) task.get(); - for (FeedItem item : items) { - result.add(new SearchResult(item, values[i], subtitles[i])); - } - - } - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - Collections.sort(result, new SearchResultValueComparator()); - return result; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/app/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java deleted file mode 100644 index eb6592510..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ /dev/null @@ -1,1391 +0,0 @@ -package de.danoeh.antennapod.core.storage; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.MergeCursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import org.apache.commons.lang3.Validate; - -import java.util.Arrays; -import java.util.List; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedComponent; -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.FeedPreferences; -import de.danoeh.antennapod.core.service.download.DownloadStatus; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; - -// TODO Remove media column from feeditem table - -/** - * Implements methods for accessing the database - */ -public class PodDBAdapter { - private static final String TAG = "PodDBAdapter"; - private static final int DATABASE_VERSION = 12; - public static final String DATABASE_NAME = "Antennapod.db"; - - /** - * Maximum number of arguments for IN-operator. - */ - public static final int IN_OPERATOR_MAXIMUM = 800; - - /** - * Maximum number of entries per search request. - */ - public static final int SEARCH_LIMIT = 30; - - // ----------- Column indices - // ----------- General indices - public static final int KEY_ID_INDEX = 0; - public static final int KEY_TITLE_INDEX = 1; - public static final int KEY_FILE_URL_INDEX = 2; - public static final int KEY_DOWNLOAD_URL_INDEX = 3; - public static final int KEY_DOWNLOADED_INDEX = 4; - public static final int KEY_LINK_INDEX = 5; - public static final int KEY_DESCRIPTION_INDEX = 6; - public static final int KEY_PAYMENT_LINK_INDEX = 7; - // ----------- Feed indices - public static final int KEY_LAST_UPDATE_INDEX = 8; - public static final int KEY_LANGUAGE_INDEX = 9; - public static final int KEY_AUTHOR_INDEX = 10; - public static final int KEY_IMAGE_INDEX = 11; - public static final int KEY_TYPE_INDEX = 12; - public static final int KEY_FEED_IDENTIFIER_INDEX = 13; - public static final int KEY_FEED_FLATTR_STATUS_INDEX = 14; - public static final int KEY_FEED_USERNAME_INDEX = 15; - public static final int KEY_FEED_PASSWORD_INDEX = 16; - // ----------- FeedItem indices - public static final int KEY_CONTENT_ENCODED_INDEX = 2; - public static final int KEY_PUBDATE_INDEX = 3; - public static final int KEY_READ_INDEX = 4; - public static final int KEY_MEDIA_INDEX = 8; - public static final int KEY_FEED_INDEX = 9; - public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10; - public static final int KEY_ITEM_IDENTIFIER_INDEX = 11; - public static final int KEY_ITEM_FLATTR_STATUS_INDEX = 12; - // ---------- FeedMedia indices - public static final int KEY_DURATION_INDEX = 1; - public static final int KEY_POSITION_INDEX = 5; - public static final int KEY_SIZE_INDEX = 6; - public static final int KEY_MIME_TYPE_INDEX = 7; - public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8; - public static final int KEY_MEDIA_FEEDITEM_INDEX = 9; - public static final int KEY_PLAYED_DURATION_INDEX = 10; - // --------- Download log indices - public static final int KEY_FEEDFILE_INDEX = 1; - public static final int KEY_FEEDFILETYPE_INDEX = 2; - public static final int KEY_REASON_INDEX = 3; - public static final int KEY_SUCCESSFUL_INDEX = 4; - public static final int KEY_COMPLETION_DATE_INDEX = 5; - public static final int KEY_REASON_DETAILED_INDEX = 6; - public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7; - // --------- Queue indices - public static final int KEY_FEEDITEM_INDEX = 1; - public static final int KEY_QUEUE_FEED_INDEX = 2; - // --------- Chapters indices - public static final int KEY_CHAPTER_START_INDEX = 2; - public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3; - public static final int KEY_CHAPTER_LINK_INDEX = 4; - public static final int KEY_CHAPTER_TYPE_INDEX = 5; - - // Key-constants - public static final String KEY_ID = "id"; - public static final String KEY_TITLE = "title"; - public static final String KEY_NAME = "name"; - public static final String KEY_LINK = "link"; - public static final String KEY_DESCRIPTION = "description"; - public static final String KEY_FILE_URL = "file_url"; - public static final String KEY_DOWNLOAD_URL = "download_url"; - public static final String KEY_PUBDATE = "pubDate"; - public static final String KEY_READ = "read"; - public static final String KEY_DURATION = "duration"; - public static final String KEY_POSITION = "position"; - public static final String KEY_SIZE = "filesize"; - public static final String KEY_MIME_TYPE = "mime_type"; - public static final String KEY_IMAGE = "image"; - public static final String KEY_FEED = "feed"; - public static final String KEY_MEDIA = "media"; - public static final String KEY_DOWNLOADED = "downloaded"; - public static final String KEY_LASTUPDATE = "last_update"; - public static final String KEY_FEEDFILE = "feedfile"; - public static final String KEY_REASON = "reason"; - public static final String KEY_SUCCESSFUL = "successful"; - public static final String KEY_FEEDFILETYPE = "feedfile_type"; - public static final String KEY_COMPLETION_DATE = "completion_date"; - public static final String KEY_FEEDITEM = "feeditem"; - public static final String KEY_CONTENT_ENCODED = "content_encoded"; - public static final String KEY_PAYMENT_LINK = "payment_link"; - public static final String KEY_START = "start"; - public static final String KEY_LANGUAGE = "language"; - public static final String KEY_AUTHOR = "author"; - public static final String KEY_HAS_CHAPTERS = "has_simple_chapters"; - public static final String KEY_TYPE = "type"; - public static final String KEY_ITEM_IDENTIFIER = "item_identifier"; - public static final String KEY_FLATTR_STATUS = "flattr_status"; - public static final String KEY_FEED_IDENTIFIER = "feed_identifier"; - public static final String KEY_REASON_DETAILED = "reason_detailed"; - public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; - public static final String KEY_CHAPTER_TYPE = "type"; - public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; - public static final String KEY_AUTO_DOWNLOAD = "auto_download"; - public static final String KEY_PLAYED_DURATION = "played_duration"; - public static final String KEY_USERNAME = "username"; - public static final String KEY_PASSWORD = "password"; - - // Table names - public static final String TABLE_NAME_FEEDS = "Feeds"; - public static final String TABLE_NAME_FEED_ITEMS = "FeedItems"; - public static final String TABLE_NAME_FEED_IMAGES = "FeedImages"; - public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia"; - public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog"; - public static final String TABLE_NAME_QUEUE = "Queue"; - public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters"; - - // SQL Statements for creating new tables - private static final String TABLE_PRIMARY_KEY = KEY_ID - + " INTEGER PRIMARY KEY AUTOINCREMENT ,"; - - private static final String CREATE_TABLE_FEEDS = "CREATE TABLE " - + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," - + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT," - + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," - + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR - + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT," - + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1," - + KEY_FLATTR_STATUS + " INTEGER," - + KEY_USERNAME + " TEXT," - + KEY_PASSWORD + " TEXT)"; - - private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " - + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE - + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT," - + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," - + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," - + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," - + KEY_FLATTR_STATUS + " INTEGER," - + KEY_IMAGE + " INTEGER)"; - - private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " - + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT," - + KEY_DOWNLOADED + " INTEGER)"; - - private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " - + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION - + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL - + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION - + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," - + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," - + KEY_FEEDITEM + " INTEGER," - + KEY_PLAYED_DURATION + " INTEGER)"; - - private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " - + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE - + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON - + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE - + " INTEGER," + KEY_REASON_DETAILED + " TEXT," - + KEY_DOWNLOADSTATUS_TITLE + " TEXT)"; - - private static final String CREATE_TABLE_QUEUE = "CREATE TABLE " - + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY," - + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)"; - - private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE " - + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE - + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER," - + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)"; - - private SQLiteDatabase db; - private final Context context; - private PodDBHelper helper; - - /** - * Select all columns from the feed-table - */ - private static final String[] FEED_SEL_STD = { - TABLE_NAME_FEEDS + "." + KEY_ID, - TABLE_NAME_FEEDS + "." + KEY_TITLE, - TABLE_NAME_FEEDS + "." + KEY_FILE_URL, - TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL, - TABLE_NAME_FEEDS + "." + KEY_DOWNLOADED, - TABLE_NAME_FEEDS + "." + KEY_LINK, - TABLE_NAME_FEEDS + "." + KEY_DESCRIPTION, - TABLE_NAME_FEEDS + "." + KEY_PAYMENT_LINK, - TABLE_NAME_FEEDS + "." + KEY_LASTUPDATE, - TABLE_NAME_FEEDS + "." + KEY_LANGUAGE, - TABLE_NAME_FEEDS + "." + KEY_AUTHOR, - TABLE_NAME_FEEDS + "." + KEY_IMAGE, - TABLE_NAME_FEEDS + "." + KEY_TYPE, - TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER, - TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD, - TABLE_NAME_FEEDS + "." + KEY_FLATTR_STATUS, - TABLE_NAME_FEEDS + "." + KEY_USERNAME, - TABLE_NAME_FEEDS + "." + KEY_PASSWORD - }; - - // column indices for FEED_SEL_STD - public static final int IDX_FEED_SEL_STD_ID = 0; - public static final int IDX_FEED_SEL_STD_TITLE = 1; - public static final int IDX_FEED_SEL_STD_FILE_URL = 2; - public static final int IDX_FEED_SEL_STD_DOWNLOAD_URL = 3; - public static final int IDX_FEED_SEL_STD_DOWNLOADED = 4; - public static final int IDX_FEED_SEL_STD_LINK = 5; - public static final int IDX_FEED_SEL_STD_DESCRIPTION = 6; - public static final int IDX_FEED_SEL_STD_PAYMENT_LINK = 7; - public static final int IDX_FEED_SEL_STD_LASTUPDATE = 8; - public static final int IDX_FEED_SEL_STD_LANGUAGE = 9; - public static final int IDX_FEED_SEL_STD_AUTHOR = 10; - public static final int IDX_FEED_SEL_STD_IMAGE = 11; - public static final int IDX_FEED_SEL_STD_TYPE = 12; - public static final int IDX_FEED_SEL_STD_FEED_IDENTIFIER = 13; - public static final int IDX_FEED_SEL_PREFERENCES_AUTO_DOWNLOAD = 14; - public static final int IDX_FEED_SEL_STD_FLATTR_STATUS = 15; - public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 16; - public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 17; - - - /** - * Select all columns from the feeditems-table except description and - * content-encoded. - */ - private static final String[] FEEDITEM_SEL_FI_SMALL = { - TABLE_NAME_FEED_ITEMS + "." + KEY_ID, - TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE, - TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE, - TABLE_NAME_FEED_ITEMS + "." + KEY_READ, - TABLE_NAME_FEED_ITEMS + "." + KEY_LINK, - TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA, - TABLE_NAME_FEED_ITEMS + "." + KEY_FEED, - TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, - TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER, - TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS, - TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE}; - - /** - * Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries. - */ - private static final String SEL_FI_SMALL_STR; - - static { - String selFiSmall = Arrays.toString(FEEDITEM_SEL_FI_SMALL); - SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1); - } - - // column indices for FEEDITEM_SEL_FI_SMALL - - public static final int IDX_FI_SMALL_ID = 0; - public static final int IDX_FI_SMALL_TITLE = 1; - public static final int IDX_FI_SMALL_PUBDATE = 2; - public static final int IDX_FI_SMALL_READ = 3; - public static final int IDX_FI_SMALL_LINK = 4; - public static final int IDX_FI_SMALL_PAYMENT_LINK = 5; - public static final int IDX_FI_SMALL_MEDIA = 6; - public static final int IDX_FI_SMALL_FEED = 7; - public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8; - public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9; - public static final int IDX_FI_SMALL_FLATTR_STATUS = 10; - public static final int IDX_FI_SMALL_IMAGE = 11; - - /** - * Select id, description and content-encoded column from feeditems. - */ - private static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION, - KEY_CONTENT_ENCODED, KEY_FEED}; - - // column indices for SEL_FI_EXTRA - - public static final int IDX_FI_EXTRA_ID = 0; - public static final int IDX_FI_EXTRA_DESCRIPTION = 1; - public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2; - public static final int IDX_FI_EXTRA_FEED = 3; - - static PodDBHelper dbHelperSingleton; - - private static synchronized PodDBHelper getDbHelperSingleton(Context appContext) { - if (dbHelperSingleton == null) { - dbHelperSingleton = new PodDBHelper(appContext, DATABASE_NAME, null, DATABASE_VERSION); - } - return dbHelperSingleton; - } - - public PodDBAdapter(Context c) { - this.context = c; - helper = getDbHelperSingleton(c.getApplicationContext()); - } - - public PodDBAdapter open() { - if (db == null || !db.isOpen() || db.isReadOnly()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Opening DB"); - try { - db = helper.getWritableDatabase(); - } catch (SQLException ex) { - ex.printStackTrace(); - db = helper.getReadableDatabase(); - } - } - return this; - } - - public void close() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Closing DB"); - //db.close(); - } - - public static boolean deleteDatabase(Context context) { - Log.w(TAG, "Deleting database"); - dbHelperSingleton.close(); - dbHelperSingleton = null; - return context.deleteDatabase(DATABASE_NAME); - } - - /** - * Inserts or updates a feed entry - * - * @return the id of the entry - */ - public long setFeed(Feed feed) { - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, feed.getTitle()); - values.put(KEY_LINK, feed.getLink()); - values.put(KEY_DESCRIPTION, feed.getDescription()); - values.put(KEY_PAYMENT_LINK, feed.getPaymentLink()); - values.put(KEY_AUTHOR, feed.getAuthor()); - values.put(KEY_LANGUAGE, feed.getLanguage()); - if (feed.getImage() != null) { - if (feed.getImage().getId() == 0) { - setImage(feed.getImage()); - } - values.put(KEY_IMAGE, feed.getImage().getId()); - } - - values.put(KEY_FILE_URL, feed.getFile_url()); - values.put(KEY_DOWNLOAD_URL, feed.getDownload_url()); - values.put(KEY_DOWNLOADED, feed.isDownloaded()); - values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime()); - values.put(KEY_TYPE, feed.getType()); - values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); - - Log.d(TAG, "Setting feed with flattr status " + feed.getTitle() + ": " + feed.getFlattrStatus().toLong()); - - values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); - if (feed.getId() == 0) { - // Create new entry - if (BuildConfig.DEBUG) - Log.d(this.toString(), "Inserting new Feed into db"); - feed.setId(db.insert(TABLE_NAME_FEEDS, null, values)); - } else { - if (BuildConfig.DEBUG) - Log.d(this.toString(), "Updating existing Feed in db"); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", - new String[]{String.valueOf(feed.getId())}); - - } - return feed.getId(); - } - - public void setFeedPreferences(FeedPreferences prefs) { - if (prefs.getFeedID() == 0) { - throw new IllegalArgumentException("Feed ID of preference must not be null"); - } - ContentValues values = new ContentValues(); - values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload()); - values.put(KEY_USERNAME, prefs.getUsername()); - values.put(KEY_PASSWORD, prefs.getPassword()); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())}); - } - - /** - * Inserts or updates an image entry - * - * @return the id of the entry - */ - public long setImage(FeedImage image) { - db.beginTransaction(); - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, image.getTitle()); - values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); - values.put(KEY_DOWNLOADED, image.isDownloaded()); - values.put(KEY_FILE_URL, image.getFile_url()); - if (image.getId() == 0) { - image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values)); - } else { - db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", - new String[]{String.valueOf(image.getId())}); - } - - final FeedComponent owner = image.getOwner(); - if (owner != null && owner.getId() != 0) { - values.clear(); - values.put(KEY_IMAGE, image.getId()); - if (owner instanceof Feed) { - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())}); - } - } - db.setTransactionSuccessful(); - db.endTransaction(); - return image.getId(); - } - - /** - * Inserts or updates an image entry - * - * @return the id of the entry - */ - public long setMedia(FeedMedia media) { - ContentValues values = new ContentValues(); - values.put(KEY_DURATION, media.getDuration()); - values.put(KEY_POSITION, media.getPosition()); - values.put(KEY_SIZE, media.getSize()); - values.put(KEY_MIME_TYPE, media.getMime_type()); - values.put(KEY_DOWNLOAD_URL, media.getDownload_url()); - values.put(KEY_DOWNLOADED, media.isDownloaded()); - values.put(KEY_FILE_URL, media.getFile_url()); - - if (media.getPlaybackCompletionDate() != null) { - values.put(KEY_PLAYBACK_COMPLETION_DATE, media - .getPlaybackCompletionDate().getTime()); - } else { - values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); - } - if (media.getItem() != null) { - values.put(KEY_FEEDITEM, media.getItem().getId()); - } - if (media.getId() == 0) { - media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values)); - } else { - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[]{String.valueOf(media.getId())}); - } - return media.getId(); - } - - public void setFeedMediaPlaybackInformation(FeedMedia media) { - if (media.getId() != 0) { - ContentValues values = new ContentValues(); - values.put(KEY_POSITION, media.getPosition()); - values.put(KEY_DURATION, media.getDuration()); - values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[]{String.valueOf(media.getId())}); - } else { - Log.e(TAG, "setFeedMediaPlaybackInformation: ID of media was 0"); - } - } - - public void setFeedMediaPlaybackCompletionDate(FeedMedia media) { - if (media.getId() != 0) { - ContentValues values = new ContentValues(); - values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime()); - values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", - new String[]{String.valueOf(media.getId())}); - } else { - Log.e(TAG, "setFeedMediaPlaybackCompletionDate: ID of media was 0"); - } - } - - /** - * Insert all FeedItems of a feed and the feed object itself in a single - * transaction - */ - public void setCompleteFeed(Feed... feeds) { - db.beginTransaction(); - for (Feed feed : feeds) { - setFeed(feed); - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - setFeedItem(item, false); - } - } - if (feed.getPreferences() != null) { - setFeedPreferences(feed.getPreferences()); - } - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - /** - * Update the flattr status of a feed - */ - public void setFeedFlattrStatus(Feed feed) { - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); - } - - /** - * Get all feeds in the flattr queue. - */ - public Cursor getFeedsInFlattrQueueCursor() { - return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_FLATTR_STATUS + "=?", - new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)}, null, null, null); - } - - /** - * Get all feed items in the flattr queue. - */ - public Cursor getFeedItemsInFlattrQueueCursor() { - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FLATTR_STATUS + "=?", - new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)}, null, null, null); - } - - /** - * Counts feeds and feed items in the flattr queue - */ - public int getFlattrQueueSize() { - int res = 0; - Cursor c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s", - TABLE_NAME_FEEDS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null); - if (c.moveToFirst()) { - res = c.getInt(0); - c.close(); - } else { - Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feeds"); - } - c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s", - TABLE_NAME_FEED_ITEMS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null); - if (c.moveToFirst()) { - res += c.getInt(0); - c.close(); - } else { - Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feed items"); - } - - return res; - } - - /** - * Updates the download URL of a Feed. - */ - public void setFeedDownloadUrl(String original, String updated) { - ContentValues values = new ContentValues(); - values.put(KEY_DOWNLOAD_URL, updated); - db.update(TABLE_NAME_FEEDS, values, KEY_DOWNLOAD_URL + "=?", new String[]{original}); - } - - public void setFeedItemlist(List items) { - db.beginTransaction(); - for (FeedItem item : items) { - setFeedItem(item, true); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public long setSingleFeedItem(FeedItem item) { - db.beginTransaction(); - long result = setFeedItem(item, true); - db.setTransactionSuccessful(); - db.endTransaction(); - return result; - } - - /** - * Update the flattr status of a FeedItem - */ - public void setFeedItemFlattrStatus(FeedItem feedItem) { - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, feedItem.getFlattrStatus().toLong()); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(feedItem.getId())}); - } - - /** - * Update the flattr status of a feed or feed item specified by its payment link - * and the new flattr status to use - */ - public void setItemFlattrStatus(String url, FlattrStatus status) { - //Log.d(TAG, "setItemFlattrStatus(" + url + ") = " + status.toString()); - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, status.toLong()); - - // regexps in sqlite would be neat! - String[] query_urls = new String[]{ - "*" + url + "&*", - "*" + url + "%2F&*", - "*" + url + "", - "*" + url + "%2F" - }; - - if (db.update(TABLE_NAME_FEEDS, values, - KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls - ) > 0) { - Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in Feeds table"); - return; - } - if (db.update(TABLE_NAME_FEED_ITEMS, values, - KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?" - + " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls - ) > 0) { - Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in FeedsItems table"); - } - } - - /** - * Reset flattr status to unflattrd for all items - */ - public void clearAllFlattrStatus() { - ContentValues values = new ContentValues(); - values.put(KEY_FLATTR_STATUS, 0); - db.update(TABLE_NAME_FEEDS, values, null, null); - db.update(TABLE_NAME_FEED_ITEMS, values, null, null); - } - - /** - * Inserts or updates a feeditem entry - * - * @param item The FeedItem - * @param saveFeed true if the Feed of the item should also be saved. This should be set to - * false if the method is executed on a list of FeedItems of the same Feed. - * @return the id of the entry - */ - private long setFeedItem(FeedItem item, boolean saveFeed) { - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, item.getTitle()); - values.put(KEY_LINK, item.getLink()); - if (item.getDescription() != null) { - values.put(KEY_DESCRIPTION, item.getDescription()); - } - if (item.getContentEncoded() != null) { - values.put(KEY_CONTENT_ENCODED, item.getContentEncoded()); - } - values.put(KEY_PUBDATE, item.getPubDate().getTime()); - values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); - if (saveFeed && item.getFeed() != null) { - setFeed(item.getFeed()); - } - values.put(KEY_FEED, item.getFeed().getId()); - values.put(KEY_READ, item.isRead()); - values.put(KEY_HAS_CHAPTERS, item.getChapters() != null); - values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); - values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong()); - if (item.hasItemImage()) { - if (item.getImage().getId() == 0) { - setImage(item.getImage()); - } - values.put(KEY_IMAGE, item.getImage().getId()); - } - - if (item.getId() == 0) { - item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); - } else { - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", - new String[]{String.valueOf(item.getId())}); - } - if (item.getMedia() != null) { - setMedia(item.getMedia()); - } - if (item.getChapters() != null) { - setChapters(item); - } - return item.getId(); - } - - public void setFeedItemRead(boolean read, long itemId, long mediaId, - boolean resetMediaPosition) { - db.beginTransaction(); - ContentValues values = new ContentValues(); - - values.put(KEY_READ, read); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)}); - - if (resetMediaPosition) { - values.clear(); - values.put(KEY_POSITION, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); - } - - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void setFeedItemRead(boolean read, long... itemIds) { - db.beginTransaction(); - ContentValues values = new ContentValues(); - for (long id : itemIds) { - values.clear(); - values.put(KEY_READ, read); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)}); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void setChapters(FeedItem item) { - ContentValues values = new ContentValues(); - for (Chapter chapter : item.getChapters()) { - values.put(KEY_TITLE, chapter.getTitle()); - values.put(KEY_START, chapter.getStart()); - values.put(KEY_FEEDITEM, item.getId()); - values.put(KEY_LINK, chapter.getLink()); - values.put(KEY_CHAPTER_TYPE, chapter.getChapterType()); - if (chapter.getId() == 0) { - chapter.setId(db - .insert(TABLE_NAME_SIMPLECHAPTERS, null, values)); - } else { - db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?", - new String[]{String.valueOf(chapter.getId())}); - } - } - } - - /** - * Inserts or updates a download status. - */ - public long setDownloadStatus(DownloadStatus status) { - ContentValues values = new ContentValues(); - values.put(KEY_FEEDFILE, status.getFeedfileId()); - values.put(KEY_FEEDFILETYPE, status.getFeedfileType()); - values.put(KEY_REASON, status.getReason().getCode()); - values.put(KEY_SUCCESSFUL, status.isSuccessful()); - values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime()); - values.put(KEY_REASON_DETAILED, status.getReasonDetailed()); - values.put(KEY_DOWNLOADSTATUS_TITLE, status.getTitle()); - if (status.getId() == 0) { - status.setId(db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values)); - } else { - db.update(TABLE_NAME_DOWNLOAD_LOG, values, KEY_ID + "=?", - new String[]{String.valueOf(status.getId())}); - } - return status.getId(); - } - - public long getDownloadLogSize() { - final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_DOWNLOAD_LOG); - Cursor result = db.rawQuery(query, null); - long count = 0; - if (result.moveToFirst()) { - count = result.getLong(0); - } - result.close(); - return count; - } - - public void removeDownloadLogItems(long count) { - if (count > 0) { - final String sql = String.format("DELETE FROM %s WHERE %s in (SELECT %s from %s ORDER BY %s ASC LIMIT %d)", - TABLE_NAME_DOWNLOAD_LOG, KEY_ID, KEY_ID, TABLE_NAME_DOWNLOAD_LOG, KEY_COMPLETION_DATE, count); - db.execSQL(sql, null); - } - } - - public void setQueue(List queue) { - ContentValues values = new ContentValues(); - db.beginTransaction(); - db.delete(TABLE_NAME_QUEUE, null, null); - for (int i = 0; i < queue.size(); i++) { - FeedItem item = queue.get(i); - values.put(KEY_ID, i); - values.put(KEY_FEEDITEM, item.getId()); - values.put(KEY_FEED, item.getFeed().getId()); - db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, - SQLiteDatabase.CONFLICT_REPLACE); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void clearQueue() { - db.delete(TABLE_NAME_QUEUE, null, null); - } - - public void removeFeedMedia(FeedMedia media) { - db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?", - new String[]{String.valueOf(media.getId())}); - } - - public void removeChaptersOfItem(FeedItem item) { - db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?", - new String[]{String.valueOf(item.getId())}); - } - - public void removeFeedImage(FeedImage image) { - db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?", - new String[]{String.valueOf(image.getId())}); - } - - /** - * Remove a FeedItem and its FeedMedia entry. - */ - public void removeFeedItem(FeedItem item) { - if (item.getMedia() != null) { - removeFeedMedia(item.getMedia()); - } - if (item.getChapters() != null) { - removeChaptersOfItem(item); - } - if (item.hasItemImage()) { - removeFeedImage(item.getImage()); - } - db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?", - new String[]{String.valueOf(item.getId())}); - } - - /** - * Remove a feed with all its FeedItems and Media entries. - */ - public void removeFeed(Feed feed) { - db.beginTransaction(); - if (feed.getImage() != null) { - removeFeedImage(feed.getImage()); - } - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - removeFeedItem(item); - } - } - - db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", - new String[]{String.valueOf(feed.getId())}); - db.setTransactionSuccessful(); - db.endTransaction(); - } - - public void removeDownloadStatus(DownloadStatus remove) { - db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?", - new String[]{String.valueOf(remove.getId())}); - } - - public void clearPlaybackHistory() { - ContentValues values = new ContentValues(); - values.put(KEY_PLAYBACK_COMPLETION_DATE, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, null, null); - } - - /** - * Get all Feeds from the Feed Table. - * - * @return The cursor of the query - */ - public final Cursor getAllFeedsCursor() { - Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, null, null, null, null, - KEY_TITLE + " COLLATE NOCASE ASC"); - return c; - } - - public final Cursor getFeedCursorDownloadUrls() { - return db.query(TABLE_NAME_FEEDS, new String[]{KEY_ID, KEY_DOWNLOAD_URL}, null, null, null, null, null); - } - - public final Cursor getExpiredFeedsCursor(long expirationTime) { - Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_LASTUPDATE + " < " + String.valueOf(System.currentTimeMillis() - expirationTime), - null, null, null, - null); - return c; - } - - /** - * Returns a cursor with all FeedItems of a Feed. Uses FEEDITEM_SEL_FI_SMALL - * - * @param feed The feed you want to get the FeedItems from. - * @return The cursor of the query - */ - public final Cursor getAllItemsOfFeedCursor(final Feed feed) { - return getAllItemsOfFeedCursor(feed.getId()); - } - - public final Cursor getAllItemsOfFeedCursor(final long feedId) { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED - + "=?", new String[]{String.valueOf(feedId)}, null, null, - null - ); - return c; - } - - /** - * Return a cursor with the SEL_FI_EXTRA selection of a single feeditem. - */ - public final Cursor getExtraInformationOfItem(final FeedItem item) { - Cursor c = db - .query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?", - new String[]{String.valueOf(item.getId())}, null, - null, null); - return c; - } - - /** - * Returns a cursor for a DB query in the FeedMedia table for a given ID. - * - * @param item The item you want to get the FeedMedia from - * @return The cursor of the query - */ - public final Cursor getFeedMediaOfItemCursor(final FeedItem item) { - Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", - new String[]{String.valueOf(item.getMedia().getId())}, null, - null, null); - return c; - } - - /** - * Returns a cursor for a DB query in the FeedImages table for a given ID. - * - * @param id ID of the FeedImage - * @return The cursor of the query - */ - public final Cursor getImageCursor(final long id) { - Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?", - new String[]{String.valueOf(id)}, null, null, null); - return c; - } - - public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) { - Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM - + "=?", new String[]{String.valueOf(item.getId())}, null, - null, null - ); - return c; - } - - public final Cursor getDownloadLogCursor(final int limit) { - Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null, - null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit); - return c; - } - - /** - * Returns a cursor which contains all feed items in the queue. The returned - * cursor uses the FEEDITEM_SEL_FI_SMALL selection. - */ - public final Cursor getQueueCursor() { - Object[] args = (Object[]) new String[]{ - SEL_FI_SMALL_STR + "," + TABLE_NAME_QUEUE + "." + KEY_ID, - TABLE_NAME_FEED_ITEMS, TABLE_NAME_QUEUE, - TABLE_NAME_FEED_ITEMS + "." + KEY_ID, - TABLE_NAME_QUEUE + "." + KEY_FEEDITEM, - TABLE_NAME_QUEUE + "." + KEY_ID}; - String query = String.format( - "SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s", args); - Cursor c = db.rawQuery(query, null); - /* - * Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, - * "INNER JOIN ? ON ?=?", new String[] { TABLE_NAME_QUEUE, - * TABLE_NAME_FEED_ITEMS + "." + KEY_ID, TABLE_NAME_QUEUE + "." + - * KEY_FEEDITEM }, null, null, TABLE_NAME_QUEUE + "." + KEY_FEEDITEM); - */ - return c; - } - - public Cursor getQueueIDCursor() { - Cursor c = db.query(TABLE_NAME_QUEUE, new String[]{KEY_FEEDITEM}, null, null, null, null, KEY_ID + " ASC", null); - return c; - } - - /** - * Returns a cursor which contains all feed items in the unread items list. - * The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection. - */ - public final Cursor getUnreadItemsCursor() { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_READ - + "=0", null, null, null, KEY_PUBDATE + " DESC"); - return c; - } - - public final Cursor getUnreadItemIdsCursor() { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID}, - KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC"); - return c; - - } - - public final Cursor getRecentlyPublishedItemsCursor(int limit) { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, null, null, null, null, KEY_PUBDATE + " DESC LIMIT " + limit); - return c; - } - - public Cursor getDownloadedItemsCursor() { - final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS - + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " - + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" - + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + " WHERE " - + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0"; - Cursor c = db.rawQuery(query, null); - return c; - } - - /** - * Returns a cursor which contains feed media objects with a playback - * completion date in ascending order. - * - * @param limit The maximum row count of the returned cursor. Must be an - * integer >= 0. - * @throws IllegalArgumentException if limit < 0 - */ - public final Cursor getCompletedMediaCursor(int limit) { - Validate.isTrue(limit >= 0, "Limit must be >= 0"); - - Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, - KEY_PLAYBACK_COMPLETION_DATE + " > 0 LIMIT " + limit, null, null, - null, null); - return c; - } - - public final Cursor getSingleFeedMediaCursor(long id) { - return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[]{String.valueOf(id)}, null, null, null); - } - - public final Cursor getFeedMediaCursorByItemID(String... mediaIds) { - int length = mediaIds.length; - if (length > IN_OPERATOR_MAXIMUM) { - Log.w(TAG, "Length of id array is larger than " - + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors"); - int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1; - Cursor[] cursors = new Cursor[numCursors]; - for (int i = 0; i < numCursors; i++) { - int neededLength = 0; - String[] parts = null; - final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM; - - if (elementsLeft >= IN_OPERATOR_MAXIMUM) { - neededLength = IN_OPERATOR_MAXIMUM; - parts = Arrays.copyOfRange(mediaIds, i - * IN_OPERATOR_MAXIMUM, (i + 1) - * IN_OPERATOR_MAXIMUM); - } else { - neededLength = elementsLeft; - parts = Arrays.copyOfRange(mediaIds, i - * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM) - + neededLength); - } - - cursors[i] = db.rawQuery("SELECT * FROM " - + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_FEEDITEM + " IN " - + buildInOperator(neededLength), parts); - } - return new MergeCursor(cursors); - } else { - return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN " - + buildInOperator(length), mediaIds, null, null, null); - } - } - - /** - * Builds an IN-operator argument depending on the number of items. - */ - private String buildInOperator(int size) { - if (size == 1) { - return "(?)"; - } - StringBuffer buffer = new StringBuffer("("); - for (int i = 0; i < size - 1; i++) { - buffer.append("?,"); - } - buffer.append("?)"); - return buffer.toString(); - } - - public final Cursor getFeedCursor(final long id) { - Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_ID + "=" + id, null, - null, null, null); - return c; - } - - public final Cursor getFeedItemCursor(final String... ids) { - if (ids.length > IN_OPERATOR_MAXIMUM) { - throw new IllegalArgumentException( - "number of IDs must not be larger than " - + IN_OPERATOR_MAXIMUM - ); - } - - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_ID + " IN " - + buildInOperator(ids.length), ids, null, null, null); - - } - - public int getQueueSize() { - final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE); - Cursor c = db.rawQuery(query, null); - int result = 0; - if (c.moveToFirst()) { - result = c.getInt(0); - } - c.close(); - return result; - } - - public final int getNumberOfUnreadItems() { - final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS + - " WHERE " + KEY_READ + " = 0"; - Cursor c = db.rawQuery(query, null); - int result = 0; - if (c.moveToFirst()) { - result = c.getInt(0); - } - c.close(); - return result; - } - - public final int getNumberOfDownloadedEpisodes() { - final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA + - " WHERE " + KEY_DOWNLOADED + " > 0"; - - Cursor c = db.rawQuery(query, null); - int result = 0; - if (c.moveToFirst()) { - result = c.getInt(0); - } - c.close(); - return result; - } - - /** - * Uses DatabaseUtils to escape a search query and removes ' at the - * beginning and the end of the string returned by the escape method. - */ - private String prepareSearchQuery(String query) { - StringBuilder builder = new StringBuilder(); - DatabaseUtils.appendEscapedSQLString(builder, query); - builder.deleteCharAt(0); - builder.deleteCharAt(builder.length() - 1); - return builder.toString(); - } - - /** - * Searches for the given query in the description of all items or the items - * of a specified feed. - * - * @return A cursor with all search results in SEL_FI_EXTRA selection. - */ - public Cursor searchItemDescriptions(long feedID, String query) { - if (feedID != 0) { - // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED - + "=? AND " + KEY_DESCRIPTION + " LIKE '%" - + prepareSearchQuery(query) + "%'", - new String[]{String.valueOf(feedID)}, null, null, - null - ); - } else { - // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, - KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query) - + "%'", null, null, null, null - ); - } - } - - /** - * Searches for the given query in the content-encoded field of all items or - * the items of a specified feed. - * - * @return A cursor with all search results in SEL_FI_EXTRA selection. - */ - public Cursor searchItemContentEncoded(long feedID, String query) { - if (feedID != 0) { - // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED - + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%" - + prepareSearchQuery(query) + "%'", - new String[]{String.valueOf(feedID)}, null, null, - null - ); - } else { - // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, - KEY_CONTENT_ENCODED + " LIKE '%" - + prepareSearchQuery(query) + "%'", null, null, - null, null - ); - } - } - - public Cursor searchItemTitles(long feedID, String query) { - if (feedID != 0) { - // search items in specific feed - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED - + "=? AND " + KEY_TITLE + " LIKE '%" - + prepareSearchQuery(query) + "%'", - new String[]{String.valueOf(feedID)}, null, null, - null - ); - } else { - // search through all items - return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, - KEY_TITLE + " LIKE '%" - + prepareSearchQuery(query) + "%'", null, null, - null, null - ); - } - } - - public Cursor searchItemChapters(long feedID, String searchQuery) { - final String query; - if (feedID != 0) { - query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + - TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + - TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + - feedID + " AND " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" - + prepareSearchQuery(searchQuery) + "%'"; - } else { - query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + - TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" + - TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%" - + prepareSearchQuery(searchQuery) + "%'"; - } - return db.rawQuery(query, null); - } - - - public static final int IDX_FEEDSTATISTICS_FEED = 0; - public static final int IDX_FEEDSTATISTICS_NUM_ITEMS = 1; - public static final int IDX_FEEDSTATISTICS_NEW_ITEMS = 2; - public static final int IDX_FEEDSTATISTICS_LATEST_EPISODE = 3; - public static final int IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES = 4; - - /** - * Select number of items, new items, the date of the latest episode and the number of episodes in progress. The result - * is sorted by the title of the feed. - */ - private static final String FEED_STATISTICS_QUERY = "SELECT Feeds.id, num_items, new_items, latest_episode, in_progress FROM " + - " Feeds LEFT JOIN " + - "(SELECT feed,count(*) AS num_items," + - " COUNT(CASE WHEN read=0 THEN 1 END) AS new_items," + - " MAX(pubDate) AS latest_episode," + - " COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," + - " COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " + - " FROM FeedItems LEFT JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" + - " ON Feeds.id = feed ORDER BY Feeds.title COLLATE NOCASE ASC;"; - - public Cursor getFeedStatisticsCursor() { - return db.rawQuery(FEED_STATISTICS_QUERY, null); - } - - /** - * Helper class for opening the Antennapod database. - */ - private static class PodDBHelper extends SQLiteOpenHelper { - /** - * Constructor. - * - * @param context Context to use - * @param name Name of the database - * @param factory to use for creating cursor objects - * @param version number of the database - */ - public PodDBHelper(final Context context, final String name, - final CursorFactory factory, final int version) { - super(context, name, factory, version); - } - - @Override - public void onCreate(final SQLiteDatabase db) { - db.execSQL(CREATE_TABLE_FEEDS); - db.execSQL(CREATE_TABLE_FEED_ITEMS); - db.execSQL(CREATE_TABLE_FEED_IMAGES); - db.execSQL(CREATE_TABLE_FEED_MEDIA); - db.execSQL(CREATE_TABLE_DOWNLOAD_LOG); - db.execSQL(CREATE_TABLE_QUEUE); - db.execSQL(CREATE_TABLE_SIMPLECHAPTERS); - } - - @Override - public void onUpgrade(final SQLiteDatabase db, final int oldVersion, - final int newVersion) { // TODO onUpgrade - Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " - + newVersion + "."); - if (oldVersion <= 1) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " - + KEY_TYPE + " TEXT"); - } - if (oldVersion <= 2) { - db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + KEY_LINK + " TEXT"); - } - if (oldVersion <= 3) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 4) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN " - + KEY_FEED_IDENTIFIER + " TEXT"); - } - if (oldVersion <= 5) { - db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG - + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT"); - } - if (oldVersion <= 6) { - db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS - + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER"); - } - if (oldVersion <= 7) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE - + " INTEGER"); - } - if (oldVersion <= 8) { - final int KEY_ID_POSITION = 0; - final int KEY_MEDIA_POSITION = 1; - - // Add feeditem column to feedmedia table - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_FEEDITEM - + " INTEGER"); - Cursor feeditemCursor = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID, KEY_MEDIA}, "? > 0", new String[]{KEY_MEDIA}, null, null, null); - if (feeditemCursor.moveToFirst()) { - db.beginTransaction(); - ContentValues contentValues = new ContentValues(); - do { - long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION); - contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION)); - db.update(TABLE_NAME_FEED_MEDIA, contentValues, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); - contentValues.clear(); - } while (feeditemCursor.moveToNext()); - db.setTransactionSuccessful(); - db.endTransaction(); - } - feeditemCursor.close(); - } - if (oldVersion <= 9) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_AUTO_DOWNLOAD - + " INTEGER DEFAULT 1"); - } - if (oldVersion <= 10) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_FLATTR_STATUS - + " INTEGER"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_FLATTR_STATUS - + " INTEGER"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA - + " ADD COLUMN " + KEY_PLAYED_DURATION - + " INTEGER"); - } - if (oldVersion <= 11) { - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_USERNAME - + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS - + " ADD COLUMN " + KEY_PASSWORD - + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + KEY_IMAGE - + " INTEGER"); - } - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java deleted file mode 100644 index 9efc5888f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.danoeh.antennapod.core.syndication.handler; - -import de.danoeh.antennapod.core.feed.Feed; -import org.apache.commons.io.input.XmlStreamReader; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import java.io.File; -import java.io.IOException; -import java.io.Reader; - -public class FeedHandler { - - public FeedHandlerResult parseFeed(Feed feed) throws SAXException, IOException, - ParserConfigurationException, UnsupportedFeedtypeException { - TypeGetter tg = new TypeGetter(); - TypeGetter.Type type = tg.getType(feed); - SyndHandler handler = new SyndHandler(feed, type); - - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - SAXParser saxParser = factory.newSAXParser(); - File file = new File(feed.getFile_url()); - Reader inputStreamReader = new XmlStreamReader(file); - InputSource inputSource = new InputSource(inputStreamReader); - - saxParser.parse(inputSource, handler); - inputStreamReader.close(); - return new FeedHandlerResult(handler.state.feed, handler.state.alternateUrls); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java deleted file mode 100644 index 45d1413bf..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.danoeh.antennapod.core.syndication.handler; - -import de.danoeh.antennapod.core.feed.Feed; - -import java.util.Map; - -/** - * Container for results returned by the Feed parser - */ -public class FeedHandlerResult { - - public Feed feed; - public Map alternateFeedUrls; - - public FeedHandlerResult(Feed feed, Map alternateFeedUrls) { - this.feed = feed; - this.alternateFeedUrls = alternateFeedUrls; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java deleted file mode 100644 index 4fe8e1aff..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java +++ /dev/null @@ -1,98 +0,0 @@ -package de.danoeh.antennapod.core.syndication.handler; - -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.syndication.namespace.Namespace; -import de.danoeh.antennapod.core.syndication.namespace.SyndElement; - -import java.util.*; - -/** - * Contains all relevant information to describe the current state of a - * SyndHandler. - */ -public class HandlerState { - - /** - * Feed that the Handler is currently processing. - */ - protected Feed feed; - /** - * Contains links to related feeds, e.g. feeds with enclosures in other formats. The key of the map is the - * URL of the feed, the value is the title - */ - protected Map alternateUrls; - protected ArrayList items; - protected FeedItem currentItem; - protected Stack tagstack; - /** - * Namespaces that have been defined so far. - */ - protected HashMap namespaces; - protected Stack defaultNamespaces; - /** - * Buffer for saving characters. - */ - protected StringBuffer contentBuf; - - public HandlerState(Feed feed) { - this.feed = feed; - alternateUrls = new LinkedHashMap(); - items = new ArrayList(); - tagstack = new Stack(); - namespaces = new HashMap(); - defaultNamespaces = new Stack(); - } - - public Feed getFeed() { - return feed; - } - - public ArrayList getItems() { - return items; - } - - public FeedItem getCurrentItem() { - return currentItem; - } - - public Stack getTagstack() { - return tagstack; - } - - public void setFeed(Feed feed) { - this.feed = feed; - } - - public void setCurrentItem(FeedItem currentItem) { - this.currentItem = currentItem; - } - - /** - * Returns the SyndElement that comes after the top element of the tagstack. - */ - public SyndElement getSecondTag() { - SyndElement top = tagstack.pop(); - SyndElement second = tagstack.peek(); - tagstack.push(top); - return second; - } - - public SyndElement getThirdTag() { - SyndElement top = tagstack.pop(); - SyndElement second = tagstack.pop(); - SyndElement third = tagstack.peek(); - tagstack.push(second); - tagstack.push(top); - return third; - } - - public StringBuffer getContentBuf() { - return contentBuf; - } - - public void addAlternateFeedUrl(String title, String url) { - alternateUrls.put(url, title); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java deleted file mode 100644 index 573c873eb..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -package de.danoeh.antennapod.core.syndication.handler; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.syndication.namespace.*; -import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -/** Superclass for all SAX Handlers which process Syndication formats */ -public class SyndHandler extends DefaultHandler { - private static final String TAG = "SyndHandler"; - private static final String DEFAULT_PREFIX = ""; - protected HandlerState state; - - public SyndHandler(Feed feed, TypeGetter.Type type) { - state = new HandlerState(feed); - if (type == TypeGetter.Type.RSS20 || type == TypeGetter.Type.RSS091) { - state.defaultNamespaces.push(new NSRSS20()); - } - } - - @Override - public void startElement(String uri, String localName, String qName, - Attributes attributes) throws SAXException { - state.contentBuf = new StringBuffer(); - Namespace handler = getHandlingNamespace(uri, qName); - if (handler != null) { - SyndElement element = handler.handleElementStart(localName, state, - attributes); - state.tagstack.push(element); - - } - } - - @Override - public void characters(char[] ch, int start, int length) - throws SAXException { - if (!state.tagstack.empty()) { - if (state.getTagstack().size() >= 2) { - if (state.contentBuf != null) { - state.contentBuf.append(ch, start, length); - } - } - } - } - - @Override - public void endElement(String uri, String localName, String qName) - throws SAXException { - Namespace handler = getHandlingNamespace(uri, qName); - if (handler != null) { - handler.handleElementEnd(localName, state); - state.tagstack.pop(); - - } - state.contentBuf = null; - - } - - @Override - public void endPrefixMapping(String prefix) throws SAXException { - if (state.defaultNamespaces.size() > 1 && prefix.equals(DEFAULT_PREFIX)) { - state.defaultNamespaces.pop(); - } - } - - @Override - public void startPrefixMapping(String prefix, String uri) - throws SAXException { - // Find the right namespace - if (!state.namespaces.containsKey(uri)) { - if (uri.equals(NSAtom.NSURI)) { - if (prefix.equals(DEFAULT_PREFIX)) { - state.defaultNamespaces.push(new NSAtom()); - } else if (prefix.equals(NSAtom.NSTAG)) { - state.namespaces.put(uri, new NSAtom()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized Atom namespace"); - } - } else if (uri.equals(NSContent.NSURI) - && prefix.equals(NSContent.NSTAG)) { - state.namespaces.put(uri, new NSContent()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized Content namespace"); - } else if (uri.equals(NSITunes.NSURI) - && prefix.equals(NSITunes.NSTAG)) { - state.namespaces.put(uri, new NSITunes()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized ITunes namespace"); - } else if (uri.equals(NSSimpleChapters.NSURI) - && prefix.matches(NSSimpleChapters.NSTAG)) { - state.namespaces.put(uri, new NSSimpleChapters()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized SimpleChapters namespace"); - } else if (uri.equals(NSMedia.NSURI) - && prefix.equals(NSMedia.NSTAG)) { - state.namespaces.put(uri, new NSMedia()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized media namespace"); - } - } - } - - private Namespace getHandlingNamespace(String uri, String qName) { - Namespace handler = state.namespaces.get(uri); - if (handler == null && !state.defaultNamespaces.empty() - && !qName.contains(":")) { - handler = state.defaultNamespaces.peek(); - } - return handler; - } - - @Override - public void endDocument() throws SAXException { - super.endDocument(); - state.getFeed().setItems(state.getItems()); - } - - public HandlerState getState() { - return state; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java deleted file mode 100644 index e1ebd63a5..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.danoeh.antennapod.core.syndication.handler; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Feed; -import org.apache.commons.io.input.XmlStreamReader; -import org.jsoup.Jsoup; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.Reader; - -/** Gets the type of a specific feed by reading the root element. */ -public class TypeGetter { - private static final String TAG = "TypeGetter"; - - public enum Type { - RSS20, RSS091, ATOM, INVALID - } - - private static final String ATOM_ROOT = "feed"; - private static final String RSS_ROOT = "rss"; - - public Type getType(Feed feed) throws UnsupportedFeedtypeException { - XmlPullParserFactory factory; - if (feed.getFile_url() != null) { - try { - factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(createReader(feed)); - int eventType = xpp.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tag = xpp.getName(); - if (tag.equals(ATOM_ROOT)) { - feed.setType(Feed.TYPE_ATOM1); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized type Atom"); - return Type.ATOM; - } else if (tag.equals(RSS_ROOT)) { - String strVersion = xpp.getAttributeValue(null, - "version"); - if (strVersion != null) { - - if (strVersion.equals("2.0")) { - feed.setType(Feed.TYPE_RSS2); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized type RSS 2.0"); - return Type.RSS20; - } else if (strVersion.equals("0.91") - || strVersion.equals("0.92")) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Recognized type RSS 0.91/0.92"); - return Type.RSS091; - } - } - throw new UnsupportedFeedtypeException(Type.INVALID); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Type is invalid"); - throw new UnsupportedFeedtypeException(Type.INVALID, tag); - } - } else { - eventType = xpp.next(); - } - } - - } catch (XmlPullParserException e) { - e.printStackTrace(); - // XML document might actually be a HTML document -> try to parse as HTML - String rootElement = null; - try { - if (Jsoup.parse(new File(feed.getFile_url()), null) != null) { - rootElement = "html"; - } - } catch (IOException e1) { - e1.printStackTrace(); - } finally { - throw new UnsupportedFeedtypeException(Type.INVALID, rootElement); - } - - } catch (IOException e) { - e.printStackTrace(); - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Type is invalid"); - throw new UnsupportedFeedtypeException(Type.INVALID); - } - - private Reader createReader(Feed feed) { - Reader reader; - try { - reader = new XmlStreamReader(new File(feed.getFile_url())); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; - } catch (IOException e) { - e.printStackTrace(); - return null; - } - return reader; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java deleted file mode 100644 index 3da9251d9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java +++ /dev/null @@ -1,38 +0,0 @@ -package de.danoeh.antennapod.core.syndication.handler; - -import de.danoeh.antennapod.core.syndication.handler.TypeGetter.Type; - -public class UnsupportedFeedtypeException extends Exception { - private static final long serialVersionUID = 9105878964928170669L; - private TypeGetter.Type type; - private String rootElement; - - public UnsupportedFeedtypeException(Type type) { - super(); - this.type = type; - } - - public UnsupportedFeedtypeException(Type type, String rootElement) { - this.type = type; - this.rootElement = rootElement; - } - - public TypeGetter.Type getType() { - return type; - } - - public String getRootElement() { - return rootElement; - } - - @Override - public String getMessage() { - if (type == TypeGetter.Type.INVALID) { - return "Invalid type"; - } else { - return "Type " + type + " not supported"; - } - } - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java deleted file mode 100644 index 71bf69ffa..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import org.xml.sax.Attributes; - -public class NSContent extends Namespace { - public static final String NSTAG = "content"; - public static final String NSURI = "http://purl.org/rss/1.0/modules/content/"; - - private static final String ENCODED = "encoded"; - - @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - return new SyndElement(localName, this); - } - - @Override - public void handleElementEnd(String localName, HandlerState state) { - if (localName.equals(ENCODED)) { - state.getCurrentItem().setContentEncoded(state.getContentBuf().toString()); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java deleted file mode 100644 index fb794d7e0..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import org.xml.sax.Attributes; - -public class NSITunes extends Namespace { - public static final String NSTAG = "itunes"; - public static final String NSURI = "http://www.itunes.com/dtds/podcast-1.0.dtd"; - - private static final String IMAGE = "image"; - private static final String IMAGE_TITLE = "image"; - private static final String IMAGE_HREF = "href"; - - private static final String AUTHOR = "author"; - - - @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - if (localName.equals(IMAGE)) { - FeedImage image = new FeedImage(); - image.setTitle(IMAGE_TITLE); - image.setDownload_url(attributes.getValue(IMAGE_HREF)); - - if (state.getCurrentItem() != null) { - // this is an items image - image.setTitle(state.getCurrentItem().getTitle() + IMAGE_TITLE); - state.getCurrentItem().setImage(image); - - } else { - // this is the feed image - if (state.getFeed().getImage() == null) { - state.getFeed().setImage(image); - } - } - - } - - return new SyndElement(localName, this); - } - - @Override - public void handleElementEnd(String localName, HandlerState state) { - if (localName.equals(AUTHOR)) { - state.getFeed().setAuthor(state.getContentBuf().toString()); - } - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java deleted file mode 100644 index 15c377f79..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils; -import org.xml.sax.Attributes; - -import java.util.concurrent.TimeUnit; - -/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */ -public class NSMedia extends Namespace { - private static final String TAG = "NSMedia"; - - public static final String NSTAG = "media"; - public static final String NSURI = "http://search.yahoo.com/mrss/"; - - private static final String CONTENT = "content"; - private static final String DOWNLOAD_URL = "url"; - private static final String SIZE = "fileSize"; - private static final String MIME_TYPE = "type"; - private static final String DURATION = "duration"; - - @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - if (localName.equals(CONTENT)) { - String url = attributes.getValue(DOWNLOAD_URL); - String type = attributes.getValue(MIME_TYPE); - if (state.getCurrentItem().getMedia() == null - && url != null - && (SyndTypeUtils.enclosureTypeValid(type) || ((type = SyndTypeUtils - .getValidMimeTypeFromUrl(url)) != null))) { - - long size = 0; - try { - size = Long.parseLong(attributes.getValue(SIZE)); - } catch (NumberFormatException e) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Length attribute could not be parsed."); - } - - int duration = 0; - try { - String durationStr = attributes.getValue(DURATION); - if (durationStr != null) { - duration = (int) TimeUnit.MILLISECONDS.convert( - Long.parseLong(durationStr), TimeUnit.SECONDS); - } - } catch (NumberFormatException e) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Duration attribute could not be parsed"); - } - - state.getCurrentItem().setMedia( - new FeedMedia(state.getCurrentItem(), url, size, type)); - } - } - return new SyndElement(localName, this); - } - - @Override - public void handleElementEnd(String localName, HandlerState state) { - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java deleted file mode 100644 index fd8f6176b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java +++ /dev/null @@ -1,141 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import de.danoeh.antennapod.core.syndication.util.SyndDateUtils; -import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils; -import org.xml.sax.Attributes; - -/** - * SAX-Parser for reading RSS-Feeds - * - * @author daniel - * - */ -public class NSRSS20 extends Namespace { - private static final String TAG = "NSRSS20"; - public static final String NSTAG = "rss"; - public static final String NSURI = ""; - - public final static String CHANNEL = "channel"; - public final static String ITEM = "item"; - public final static String GUID = "guid"; - public final static String TITLE = "title"; - public final static String LINK = "link"; - public final static String DESCR = "description"; - public final static String PUBDATE = "pubDate"; - public final static String ENCLOSURE = "enclosure"; - public final static String IMAGE = "image"; - public final static String URL = "url"; - public final static String LANGUAGE = "language"; - - public final static String ENC_URL = "url"; - public final static String ENC_LEN = "length"; - public final static String ENC_TYPE = "type"; - - @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - if (localName.equals(ITEM)) { - state.setCurrentItem(new FeedItem()); - state.getItems().add(state.getCurrentItem()); - state.getCurrentItem().setFeed(state.getFeed()); - - } else if (localName.equals(ENCLOSURE)) { - String type = attributes.getValue(ENC_TYPE); - String url = attributes.getValue(ENC_URL); - if (state.getCurrentItem().getMedia() == null - && (SyndTypeUtils.enclosureTypeValid(type) || ((type = SyndTypeUtils - .getValidMimeTypeFromUrl(url)) != null))) { - - long size = 0; - try { - size = Long.parseLong(attributes.getValue(ENC_LEN)); - } catch (NumberFormatException e) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Length attribute could not be parsed."); - } - state.getCurrentItem().setMedia( - new FeedMedia(state.getCurrentItem(), url, size, type)); - } - - } else if (localName.equals(IMAGE)) { - if (state.getTagstack().size() >= 1) { - String parent = state.getTagstack().peek().getName(); - if (parent.equals(CHANNEL)) { - state.getFeed().setImage(new FeedImage()); - } - } - } - return new SyndElement(localName, this); - } - - @Override - public void handleElementEnd(String localName, HandlerState state) { - if (localName.equals(ITEM)) { - if (state.getCurrentItem() != null) { - // the title tag is optional in RSS 2.0. The description is used - // as a - // title if the item has no title-tag. - if (state.getCurrentItem().getTitle() == null) { - state.getCurrentItem().setTitle( - state.getCurrentItem().getDescription()); - } - } - state.setCurrentItem(null); - } else if (state.getTagstack().size() >= 2 - && state.getContentBuf() != null) { - String content = state.getContentBuf().toString(); - SyndElement topElement = state.getTagstack().peek(); - String top = topElement.getName(); - SyndElement secondElement = state.getSecondTag(); - String second = secondElement.getName(); - String third = null; - if (state.getTagstack().size() >= 3) { - third = state.getThirdTag().getName(); - } - - if (top.equals(GUID) && second.equals(ITEM)) { - // some feed creators include an empty or non-standard guid-element in their feed, which should be ignored - if (!content.isEmpty()) { - state.getCurrentItem().setItemIdentifier(content); - } - } else if (top.equals(TITLE)) { - if (second.equals(ITEM)) { - state.getCurrentItem().setTitle(content); - } else if (second.equals(CHANNEL)) { - state.getFeed().setTitle(content); - } else if (second.equals(IMAGE) && third != null - && third.equals(CHANNEL)) { - state.getFeed().getImage().setTitle(content); - } - } else if (top.equals(LINK)) { - if (second.equals(CHANNEL)) { - state.getFeed().setLink(content); - } else if (second.equals(ITEM)) { - state.getCurrentItem().setLink(content); - } - } else if (top.equals(PUBDATE) && second.equals(ITEM)) { - state.getCurrentItem().setPubDate( - SyndDateUtils.parseRFC822Date(content)); - } else if (top.equals(URL) && second.equals(IMAGE) && third != null - && third.equals(CHANNEL)) { - state.getFeed().getImage().setDownload_url(content); - } else if (localName.equals(DESCR)) { - if (second.equals(CHANNEL)) { - state.getFeed().setDescription(content); - } else if (second.equals(ITEM)) { - state.getCurrentItem().setDescription(content); - } - - } else if (localName.equals(LANGUAGE)) { - state.getFeed().setLanguage(content.toLowerCase()); - } - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java deleted file mode 100644 index 2b4a2767d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.SimpleChapter; -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import de.danoeh.antennapod.core.syndication.util.SyndDateUtils; -import org.xml.sax.Attributes; - -import java.util.ArrayList; - -public class NSSimpleChapters extends Namespace { - public static final String NSTAG = "psc|sc"; - public static final String NSURI = "http://podlove.org/simple-chapters"; - - public static final String CHAPTERS = "chapters"; - public static final String CHAPTER = "chapter"; - public static final String START = "start"; - public static final String TITLE = "title"; - public static final String HREF = "href"; - - @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - if (localName.equals(CHAPTERS)) { - state.getCurrentItem().setChapters(new ArrayList()); - } else if (localName.equals(CHAPTER)) { - state.getCurrentItem() - .getChapters() - .add(new SimpleChapter(SyndDateUtils - .parseTimeString(attributes.getValue(START)), - attributes.getValue(TITLE), state.getCurrentItem(), - attributes.getValue(HREF))); - } - - return new SyndElement(localName, this); - } - - @Override - public void handleElementEnd(String localName, HandlerState state) { - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java deleted file mode 100644 index cf118d202..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import org.xml.sax.Attributes; - - -public abstract class Namespace { - public static final String NSTAG = null; - public static final String NSURI = null; - - /** Called by a Feedhandler when in startElement and it detects a namespace element - * @return The SyndElement to push onto the stack - * */ - public abstract SyndElement handleElementStart(String localName, HandlerState state, Attributes attributes); - - /** Called by a Feedhandler when in endElement and it detects a namespace element - * @return true if namespace handled the element, false if it ignored it - * */ - public abstract void handleElementEnd(String localName, HandlerState state); - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java deleted file mode 100644 index 8adcd2086..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace; - -/** Defines a XML Element that is pushed on the tagstack */ -public class SyndElement { - protected String name; - protected Namespace namespace; - - public SyndElement(String name, Namespace namespace) { - this.name = name; - this.namespace = namespace; - } - - public Namespace getNamespace() { - return namespace; - } - - public String getName() { - return name; - } - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java deleted file mode 100644 index 43fe0edb7..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace.atom; - -import de.danoeh.antennapod.core.syndication.namespace.Namespace; -import de.danoeh.antennapod.core.syndication.namespace.SyndElement; -import org.apache.commons.lang3.StringEscapeUtils; - -/** Represents Atom Element which contains text (content, title, summary). */ -public class AtomText extends SyndElement { - public static final String TYPE_TEXT = "text"; - public static final String TYPE_HTML = "html"; - public static final String TYPE_XHTML = "xhtml"; - - private String type; - private String content; - - public AtomText(String name, Namespace namespace, String type) { - super(name, namespace); - this.type = type; - } - - /** Processes the content according to the type and returns it. */ - public String getProcessedContent() { - if (type == null) { - return content; - } else if (type.equals(TYPE_HTML)) { - return StringEscapeUtils.unescapeHtml4(content); - } else if (type.equals(TYPE_XHTML)) { - return content; - } else { // Handle as text by default - return content; - } - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getType() { - return type; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java deleted file mode 100644 index 1547dc222..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java +++ /dev/null @@ -1,194 +0,0 @@ -package de.danoeh.antennapod.core.syndication.namespace.atom; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.syndication.handler.HandlerState; -import de.danoeh.antennapod.core.syndication.namespace.NSRSS20; -import de.danoeh.antennapod.core.syndication.namespace.Namespace; -import de.danoeh.antennapod.core.syndication.namespace.SyndElement; -import de.danoeh.antennapod.core.syndication.util.SyndDateUtils; -import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils; -import org.xml.sax.Attributes; - -public class NSAtom extends Namespace { - private static final String TAG = "NSAtom"; - public static final String NSTAG = "atom"; - public static final String NSURI = "http://www.w3.org/2005/Atom"; - - private static final String FEED = "feed"; - private static final String ID = "id"; - private static final String TITLE = "title"; - private static final String ENTRY = "entry"; - private static final String LINK = "link"; - private static final String UPDATED = "updated"; - private static final String AUTHOR = "author"; - private static final String CONTENT = "content"; - private static final String IMAGE = "logo"; - private static final String SUBTITLE = "subtitle"; - private static final String PUBLISHED = "published"; - - private static final String TEXT_TYPE = "type"; - // Link - private static final String LINK_HREF = "href"; - private static final String LINK_REL = "rel"; - private static final String LINK_TYPE = "type"; - private static final String LINK_TITLE = "title"; - private static final String LINK_LENGTH = "length"; - // rel-values - private static final String LINK_REL_ALTERNATE = "alternate"; - private static final String LINK_REL_ENCLOSURE = "enclosure"; - private static final String LINK_REL_PAYMENT = "payment"; - private static final String LINK_REL_RELATED = "related"; - private static final String LINK_REL_SELF = "self"; - // type-values - private static final String LINK_TYPE_ATOM = "application/atom+xml"; - private static final String LINK_TYPE_HTML = "text/html"; - private static final String LINK_TYPE_XHTML = "application/xml+xhtml"; - - private static final String LINK_TYPE_RSS = "application/rss+xml"; - - /** - * Regexp to test whether an Element is a Text Element. - */ - private static final String isText = TITLE + "|" + CONTENT + "|" + "|" - + SUBTITLE; - - public static final String isFeed = FEED + "|" + NSRSS20.CHANNEL; - public static final String isFeedItem = ENTRY + "|" + NSRSS20.ITEM; - - @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - if (localName.equals(ENTRY)) { - state.setCurrentItem(new FeedItem()); - state.getItems().add(state.getCurrentItem()); - state.getCurrentItem().setFeed(state.getFeed()); - } else if (localName.matches(isText)) { - String type = attributes.getValue(TEXT_TYPE); - return new AtomText(localName, this, type); - } else if (localName.equals(LINK)) { - String href = attributes.getValue(LINK_HREF); - String rel = attributes.getValue(LINK_REL); - SyndElement parent = state.getTagstack().peek(); - if (parent.getName().matches(isFeedItem)) { - if (rel == null || rel.equals(LINK_REL_ALTERNATE)) { - state.getCurrentItem().setLink(href); - } else if (rel.equals(LINK_REL_ENCLOSURE)) { - String strSize = attributes.getValue(LINK_LENGTH); - long size = 0; - try { - if (strSize != null) { - size = Long.parseLong(strSize); - } - } catch (NumberFormatException e) { - if (BuildConfig.DEBUG) Log.d(TAG, "Length attribute could not be parsed."); - } - String type = attributes.getValue(LINK_TYPE); - if (SyndTypeUtils.enclosureTypeValid(type) - || (type = SyndTypeUtils - .getValidMimeTypeFromUrl(href)) != null) { - state.getCurrentItem().setMedia( - new FeedMedia(state.getCurrentItem(), href, - size, type) - ); - } - } else if (rel.equals(LINK_REL_PAYMENT)) { - state.getCurrentItem().setPaymentLink(href); - } - } else if (parent.getName().matches(isFeed)) { - if (rel == null || rel.equals(LINK_REL_ALTERNATE)) { - String type = attributes.getValue(LINK_TYPE); - /* - * Use as link if a) no type-attribute is given and - * feed-object has no link yet b) type of link is - * LINK_TYPE_HTML or LINK_TYPE_XHTML - */ - if ((type == null && state.getFeed().getLink() == null) - || (type != null && (type.equals(LINK_TYPE_HTML) || type.equals(LINK_TYPE_XHTML)))) { - state.getFeed().setLink(href); - } else if (type != null && (type.equals(LINK_TYPE_ATOM) || type.equals(LINK_TYPE_RSS))) { - // treat as podlove alternate feed - String title = attributes.getValue(LINK_TITLE); - if (title == null) { - title = href; - } - state.addAlternateFeedUrl(title, href); - } - } else if (rel.equals(LINK_REL_PAYMENT)) { - state.getFeed().setPaymentLink(href); - } - } - } - return new SyndElement(localName, this); - } - - @Override - public void handleElementEnd(String localName, HandlerState state) { - if (localName.equals(ENTRY)) { - state.setCurrentItem(null); - } - - if (state.getTagstack().size() >= 2) { - AtomText textElement = null; - String content; - if (state.getContentBuf() != null) { - content = state.getContentBuf().toString(); - } else { - content = ""; - } - SyndElement topElement = state.getTagstack().peek(); - String top = topElement.getName(); - SyndElement secondElement = state.getSecondTag(); - String second = secondElement.getName(); - - if (top.matches(isText)) { - textElement = (AtomText) topElement; - textElement.setContent(content); - } - - if (top.equals(ID)) { - if (second.equals(FEED)) { - state.getFeed().setFeedIdentifier(content); - } else if (second.equals(ENTRY)) { - state.getCurrentItem().setItemIdentifier(content); - } - } else if (top.equals(TITLE)) { - - if (second.equals(FEED)) { - state.getFeed().setTitle(textElement.getProcessedContent()); - } else if (second.equals(ENTRY)) { - state.getCurrentItem().setTitle( - textElement.getProcessedContent()); - } - } else if (top.equals(SUBTITLE)) { - if (second.equals(FEED)) { - state.getFeed().setDescription( - textElement.getProcessedContent()); - } - } else if (top.equals(CONTENT)) { - if (second.equals(ENTRY)) { - state.getCurrentItem().setDescription( - textElement.getProcessedContent()); - } - } else if (top.equals(UPDATED)) { - if (second.equals(ENTRY) - && state.getCurrentItem().getPubDate() == null) { - state.getCurrentItem().setPubDate( - SyndDateUtils.parseRFC3339Date(content)); - } - } else if (top.equals(PUBLISHED)) { - if (second.equals(ENTRY)) { - state.getCurrentItem().setPubDate( - SyndDateUtils.parseRFC3339Date(content)); - } - } else if (top.equals(IMAGE)) { - state.getFeed().setImage(new FeedImage(content, null)); - } - - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java deleted file mode 100644 index 977d92304..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java +++ /dev/null @@ -1,153 +0,0 @@ -package de.danoeh.antennapod.core.syndication.util; - -import android.util.Log; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -/** Parses several date formats. */ -public class SyndDateUtils { - private static final String TAG = "DateUtils"; - - private static final String[] RFC822DATES = { "dd MMM yy HH:mm:ss Z", }; - - /** RFC 3339 date format for UTC dates. */ - public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - - /** RFC 3339 date format for localtime dates with offset. */ - public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ"; - - private static ThreadLocal RFC822Formatter = new ThreadLocal() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(RFC822DATES[0], Locale.US); - } - - }; - - private static ThreadLocal RFC3339Formatter = new ThreadLocal() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(RFC3339UTC, Locale.US); - } - - }; - - public static Date parseRFC822Date(String date) { - Date result = null; - if (date.contains("PDT")) { - date = date.replace("PDT", "PST8PDT"); - } - if (date.contains(",")) { - // Remove day of the week - date = date.substring(date.indexOf(",") + 1).trim(); - } - SimpleDateFormat format = RFC822Formatter.get(); - for (int i = 0; i < RFC822DATES.length; i++) { - try { - format.applyPattern(RFC822DATES[i]); - result = format.parse(date); - break; - } catch (ParseException e) { - e.printStackTrace(); - } - } - if (result == null) { - Log.e(TAG, "Unable to parse feed date correctly"); - } - - return result; - } - - public static Date parseRFC3339Date(String date) { - Date result = null; - SimpleDateFormat format = RFC3339Formatter.get(); - boolean isLocal = date.endsWith("Z"); - if (date.contains(".")) { - // remove secfrac - int fracIndex = date.indexOf("."); - String first = date.substring(0, fracIndex); - String second = null; - if (isLocal) { - second = date.substring(date.length() - 1); - } else { - if (date.contains("+")) { - second = date.substring(date.indexOf("+")); - } else { - second = date.substring(date.indexOf("-")); - } - } - - date = first + second; - } - if (isLocal) { - try { - result = format.parse(date); - } catch (ParseException e) { - e.printStackTrace(); - } - } else { - format.applyPattern(RFC3339LOCAL); - // remove last colon - StringBuffer buf = new StringBuffer(date.length() - 1); - int colonIdx = date.lastIndexOf(':'); - for (int x = 0; x < date.length(); x++) { - if (x != colonIdx) - buf.append(date.charAt(x)); - } - String bufStr = buf.toString(); - try { - result = format.parse(bufStr); - } catch (ParseException e) { - e.printStackTrace(); - Log.e(TAG, "Unable to parse date"); - } finally { - format.applyPattern(RFC3339UTC); - } - - } - - return result; - - } - - /** - * Takes a string of the form [HH:]MM:SS[.mmm] and converts it to - * milliseconds. - */ - public static long parseTimeString(final String time) { - String[] parts = time.split(":"); - long result = 0; - int idx = 0; - if (parts.length == 3) { - // string has hours - result += Integer.valueOf(parts[idx]) * 3600000L; - idx++; - } - result += Integer.valueOf(parts[idx]) * 60000L; - idx++; - result += (Float.valueOf(parts[idx])) * 1000L; - return result; - } - - public static String formatRFC822Date(Date date) { - SimpleDateFormat format = RFC822Formatter.get(); - return format.format(date); - } - - public static String formatRFC3339Local(Date date) { - SimpleDateFormat format = RFC3339Formatter.get(); - format.applyPattern(RFC3339LOCAL); - String result = format.format(date); - format.applyPattern(RFC3339UTC); - return result; - } - - public static String formatRFC3339UTC(Date date) { - SimpleDateFormat format = RFC3339Formatter.get(); - format.applyPattern(RFC3339UTC); - return format.format(date); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java b/app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java deleted file mode 100644 index 8d1d8ffde..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.danoeh.antennapod.core.syndication.util; - -import android.webkit.MimeTypeMap; -import org.apache.commons.io.FilenameUtils; - -/** Utility class for handling MIME-Types of enclosures */ -public class SyndTypeUtils { - - private final static String VALID_MIMETYPE = "audio/.*" + "|" + "video/.*" - + "|" + "application/ogg"; - - private SyndTypeUtils() { - - } - - public static boolean enclosureTypeValid(String type) { - if (type == null) { - return false; - } else { - return type.matches(VALID_MIMETYPE); - } - } - - /** - * Should be used if mime-type of enclosure tag is not supported. This - * method will check if the mime-type of the file extension is supported. If - * the type is not supported, this method will return null. - */ - public static String getValidMimeTypeFromUrl(String url) { - if (url != null) { - String extension = FilenameUtils.getExtension(url); - if (extension != null) { - String type = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(extension); - if (type != null && enclosureTypeValid(type)) { - return type; - } - } - } - return null; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java deleted file mode 100644 index d6046026f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java +++ /dev/null @@ -1,261 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.util.comparator.ChapterStartTimeComparator; -import de.danoeh.antennapod.core.util.id3reader.ChapterReader; -import de.danoeh.antennapod.core.util.id3reader.ID3ReaderException; -import de.danoeh.antennapod.core.util.playback.Playable; -import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentChapterReader; -import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException; -import org.apache.commons.io.IOUtils; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; -import java.util.List; - -/** Utility class for getting chapter data from media files. */ -public class ChapterUtils { - private static final String TAG = "ChapterUtils"; - - private ChapterUtils() { - } - - /** - * Uses the download URL of a media object of a feeditem to read its ID3 - * chapters. - */ - public static void readID3ChaptersFromPlayableStreamUrl(Playable p) { - if (p != null && p.getStreamUrl() != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); - InputStream in = null; - try { - URL url = new URL(p.getStreamUrl()); - ChapterReader reader = new ChapterReader(); - - in = url.openStream(); - reader.readInputStream(in); - List chapters = reader.getChapters(); - - if (chapters != null) { - Collections - .sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, p); - if (chaptersValid(chapters)) { - p.setChapters(chapters); - Log.i(TAG, "Chapters loaded"); - } else { - Log.e(TAG, "Chapter data was invalid"); - } - } else { - Log.i(TAG, "ChapterReader could not find any ID3 chapters"); - } - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (ID3ReaderException e) { - e.printStackTrace(); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } else { - Log.e(TAG, - "Unable to read ID3 chapters: media or download URL was null"); - } - } - - /** - * Uses the file URL of a media object of a feeditem to read its ID3 - * chapters. - */ - public static void readID3ChaptersFromPlayableFileUrl(Playable p) { - if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); - File source = new File(p.getLocalMediaUrl()); - if (source.exists()) { - ChapterReader reader = new ChapterReader(); - InputStream in = null; - - try { - in = new BufferedInputStream(new FileInputStream(source)); - reader.readInputStream(in); - List chapters = reader.getChapters(); - - if (chapters != null) { - Collections.sort(chapters, - new ChapterStartTimeComparator()); - processChapters(chapters, p); - if (chaptersValid(chapters)) { - p.setChapters(chapters); - Log.i(TAG, "Chapters loaded"); - } else { - Log.e(TAG, "Chapter data was invalid"); - } - } else { - Log.i(TAG, - "ChapterReader could not find any ID3 chapters"); - } - } catch (IOException e) { - e.printStackTrace(); - } catch (ID3ReaderException e) { - e.printStackTrace(); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } else { - Log.e(TAG, "Unable to read id3 chapters: Source doesn't exist"); - } - } - } - - public static void readOggChaptersFromPlayableStreamUrl(Playable media) { - if (media != null && media.streamAvailable()) { - InputStream input = null; - try { - URL url = new URL(media.getStreamUrl()); - input = url.openStream(); - if (input != null) { - readOggChaptersFromInputStream(media, input); - } - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(input); - } - } - } - - public static void readOggChaptersFromPlayableFileUrl(Playable media) { - if (media != null && media.getLocalMediaUrl() != null) { - File source = new File(media.getLocalMediaUrl()); - if (source.exists()) { - InputStream input = null; - try { - input = new BufferedInputStream(new FileInputStream(source)); - readOggChaptersFromInputStream(media, input); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(input); - } - } - } - } - - private static void readOggChaptersFromInputStream(Playable p, - InputStream input) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Trying to read chapters from item with title " - + p.getEpisodeTitle()); - try { - VorbisCommentChapterReader reader = new VorbisCommentChapterReader(); - reader.readInputStream(input); - List chapters = reader.getChapters(); - if (chapters != null) { - Collections.sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, p); - if (chaptersValid(chapters)) { - p.setChapters(chapters); - Log.i(TAG, "Chapters loaded"); - } else { - Log.e(TAG, "Chapter data was invalid"); - } - } else { - Log.i(TAG, - "ChapterReader could not find any Ogg vorbis chapters"); - } - } catch (VorbisCommentReaderException e) { - e.printStackTrace(); - } - } - - /** Makes sure that chapter does a title and an item attribute. */ - private static void processChapters(List chapters, Playable p) { - for (int i = 0; i < chapters.size(); i++) { - Chapter c = chapters.get(i); - if (c.getTitle() == null) { - c.setTitle(Integer.toString(i)); - } - } - } - - private static boolean chaptersValid(List chapters) { - if (chapters.isEmpty()) { - return false; - } - for (Chapter c : chapters) { - if (c.getTitle() == null) { - return false; - } - if (c.getStart() < 0) { - return false; - } - } - return true; - } - - /** Calls getCurrentChapter with current position. */ - public static Chapter getCurrentChapter(Playable media) { - if (media.getChapters() != null) { - List chapters = media.getChapters(); - Chapter current = null; - if (chapters != null) { - current = chapters.get(0); - for (Chapter sc : chapters) { - if (sc.getStart() > media.getPosition()) { - break; - } else { - current = sc; - } - } - } - return current; - } else { - return null; - } - } - - public static void loadChaptersFromStreamUrl(Playable media) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting chapterLoader thread"); - ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media); - if (media.getChapters() == null) { - ChapterUtils.readOggChaptersFromPlayableStreamUrl(media); - } - - if (BuildConfig.DEBUG) - Log.d(TAG, "ChapterLoaderThread has finished"); - } - - public static void loadChaptersFromFileUrl(Playable media) { - if (media.localFileAvailable()) { - ChapterUtils.readID3ChaptersFromPlayableFileUrl(media); - if (media.getChapters() == null) { - ChapterUtils.readOggChaptersFromPlayableFileUrl(media); - } - } else { - Log.e(TAG, "Could not load chapters from file url: local file not available"); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/app/src/main/java/de/danoeh/antennapod/core/util/Converter.java deleted file mode 100644 index a0b514bd6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/Converter.java +++ /dev/null @@ -1,103 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.util.Log; - -/** Provides methods for converting various units. */ -public final class Converter { - /** Class shall not be instantiated. */ - private Converter() { - } - - /** Logging tag. */ - private static final String TAG = "Converter"; - - - /** Indicates that the value is in the Byte range.*/ - private static final int B_RANGE = 0; - /** Indicates that the value is in the Kilobyte range.*/ - private static final int KB_RANGE = 1; - /** Indicates that the value is in the Megabyte range.*/ - private static final int MB_RANGE = 2; - /** Indicates that the value is in the Gigabyte range.*/ - private static final int GB_RANGE = 3; - /** Determines the length of the number for best readability.*/ - private static final int NUM_LENGTH = 1024; - - - private static final int HOURS_MIL = 3600000; - private static final int MINUTES_MIL = 60000; - private static final int SECONDS_MIL = 1000; - - /** Takes a byte-value and converts it into a more readable - * String. - * @param input The value to convert - * @return The converted String with a unit - * */ - public static String byteToString(final long input) { - int i = 0; - int result = 0; - - for (i = 0; i < GB_RANGE + 1; i++) { - result = (int) (input / Math.pow(1024, i)); - if (result < NUM_LENGTH) { - break; - } - } - - switch (i) { - case B_RANGE: - return result + " B"; - case KB_RANGE: - return result + " KB"; - case MB_RANGE: - return result + " MB"; - case GB_RANGE: - return result + " GB"; - default: - Log.e(TAG, "Error happened in byteToString"); - return "ERROR"; - } - } - - /** Converts milliseconds to a string containing hours, minutes and seconds */ - public static String getDurationStringLong(int duration) { - int h = duration / HOURS_MIL; - int rest = duration - h * HOURS_MIL; - int m = rest / MINUTES_MIL; - rest -= m * MINUTES_MIL; - int s = rest / SECONDS_MIL; - - return String.format("%02d:%02d:%02d", h, m, s); - } - - /** Converts milliseconds to a string containing hours and minutes */ - public static String getDurationStringShort(int duration) { - int h = duration / HOURS_MIL; - int rest = duration - h * HOURS_MIL; - int m = rest / MINUTES_MIL; - - return String.format("%02d:%02d", h, m); - } - - /** Converts long duration string (HH:MM:SS) to milliseconds. */ - public static int durationStringLongToMs(String input) { - String[] parts = input.split(":"); - if (parts.length != 3) { - return 0; - } - return Integer.valueOf(parts[0]) * 3600 * 1000 + - Integer.valueOf(parts[1]) * 60 * 1000 + - Integer.valueOf(parts[2]) * 1000; - } - - /** Converts short duration string (HH:MM) to milliseconds. */ - public static int durationStringShortToMs(String input) { - String[] parts = input.split(":"); - if (parts.length != 2) { - return 0; - } - return Integer.valueOf(parts[0]) * 3600 * 1000 + - Integer.valueOf(parts[1]) * 1000 * 60; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java b/app/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java deleted file mode 100644 index 447e7d256..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.content.Context; -import de.danoeh.antennapod.R; - -/** Utility class for Download Errors. */ -public enum DownloadError { - SUCCESS(0, R.string.download_successful), - ERROR_PARSER_EXCEPTION(1, R.string.download_error_parser_exception), - ERROR_UNSUPPORTED_TYPE(2, R.string.download_error_unsupported_type), - ERROR_CONNECTION_ERROR(3, R.string.download_error_connection_error), - ERROR_MALFORMED_URL(4, R.string.download_error_error_unknown), - ERROR_IO_ERROR(5, R.string.download_error_io_error), - ERROR_FILE_EXISTS(6, R.string.download_error_error_unknown), - ERROR_DOWNLOAD_CANCELLED(7, R.string.download_error_error_unknown), - ERROR_DEVICE_NOT_FOUND(8, R.string.download_error_device_not_found), - ERROR_HTTP_DATA_ERROR(9, R.string.download_error_http_data_error), - ERROR_NOT_ENOUGH_SPACE(10, R.string.download_error_insufficient_space), - ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host), - ERROR_REQUEST_ERROR(12, R.string.download_error_request_error), - ERROR_DB_ACCESS_ERROR(13, R.string.download_error_db_access), - ERROR_UNAUTHORIZED(14, R.string.download_error_unauthorized); - - private final int code; - private final int resId; - - private DownloadError(int code, int resId) { - this.code = code; - this.resId = resId; - } - - /** Return DownloadError from its associated code. */ - public static DownloadError fromCode(int code) { - for (DownloadError reason : values()) { - if (reason.getCode() == code) { - return reason; - } - } - throw new IllegalArgumentException("unknown code: " + code); - } - - /** Get machine-readable code. */ - public int getCode() { - return code; - } - - /** Get a human-readable string. */ - public String getErrorString(Context context) { - return context.getString(resId); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/DuckType.java b/app/src/main/java/de/danoeh/antennapod/core/util/DuckType.java deleted file mode 100644 index 5d2803b84..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/DuckType.java +++ /dev/null @@ -1,117 +0,0 @@ -/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */ - -package de.danoeh.antennapod.core.util; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -import de.danoeh.antennapod.BuildConfig; - -/** - * Allows "duck typing" or dynamic invocation based on method signature rather - * than type hierarchy. In other words, rather than checking whether something - * IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck. - * - * To use first use the coerce static method to indicate the object you want to - * do Duck Typing for, then specify an interface to the to method which you want - * to coerce the type to, e.g: - * - * public interface Foo { void aMethod(); } class Bar { ... public void - * aMethod() { ... } ... } Bar bar = ...; Foo foo = - * DuckType.coerce(bar).to(Foo.class); foo.aMethod(); - * - * - */ -public class DuckType { - - private final Object objectToCoerce; - - private DuckType(Object objectToCoerce) { - this.objectToCoerce = objectToCoerce; - } - - private class CoercedProxy implements InvocationHandler { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Method delegateMethod = findMethodBySignature(method); - assert delegateMethod != null; - return delegateMethod.invoke(DuckType.this.objectToCoerce, args); - } - } - - /** - * Specify the duck typed object to coerce. - * - * @param object - * the object to coerce - * @return - */ - public static DuckType coerce(Object object) { - return new DuckType(object); - } - - /** - * Coerce the Duck Typed object to the given interface providing it - * implements all the necessary methods. - * - * @param - * @param iface - * @return an instance of the given interface that wraps the duck typed - * class - * @throws ClassCastException - * if the object being coerced does not implement all the - * methods in the given interface. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public T to(Class iface) { - if (BuildConfig.DEBUG && !iface.isInterface()) throw new AssertionError("cannot coerce object to a class, must be an interface"); - if (isA(iface)) { - return (T) iface.cast(objectToCoerce); - } - if (quacksLikeA(iface)) { - return generateProxy(iface); - } - throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface); - } - - @SuppressWarnings("rawtypes") - private boolean isA(Class iface) { - return objectToCoerce.getClass().isInstance(iface); - } - - /** - * Determine whether the duck typed object can be used with the given - * interface. - * - * @param Type - * of the interface to check. - * @param iface - * Interface class to check - * @return true if the object will support all the methods in the interface, - * false otherwise. - */ - @SuppressWarnings("rawtypes") - public boolean quacksLikeA(Class iface) { - for (Method method : iface.getMethods()) { - if (findMethodBySignature(method) == null) { - return false; - } - } - return true; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private T generateProxy(Class iface) { - return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy()); - } - - private Method findMethodBySignature(Method method) { - try { - return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes()); - } catch (NoSuchMethodException e) { - return null; - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java b/app/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java deleted file mode 100644 index 4c23b161b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import de.danoeh.antennapod.core.feed.FeedItem; - -import java.util.ArrayList; -import java.util.List; - -public class EpisodeFilter { - private EpisodeFilter() { - - } - - /** Return a copy of the itemlist without items which have no media. */ - public static ArrayList getEpisodeList(List items) { - ArrayList episodes = new ArrayList(items); - for (FeedItem item : items) { - if (item.getMedia() == null) { - episodes.remove(item); - } - } - return episodes; - } - - public static int countItemsWithEpisodes(List items) { - int count = 0; - for (FeedItem item : items) { - if (item.getMedia() != null) { - count++; - } - } - return count; - } - - public static FeedItem accessEpisodeByIndex(List items, - int position) { - int count = 0; - for (FeedItem item : items) { - - if (item.getMedia() != null) { - if (count == position) { - return item; - } else { - count++; - } - } - } - return null; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java b/app/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java deleted file mode 100644 index bf14cd23e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import de.danoeh.antennapod.core.feed.Feed; - -import java.util.Comparator; - -/** Compares the title of two feeds for sorting. */ -public class FeedtitleComparator implements Comparator { - - @Override - public int compare(Feed lhs, Feed rhs) { - return lhs.getTitle().compareToIgnoreCase(rhs.getTitle()); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java b/app/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java deleted file mode 100644 index 00c023b64..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import java.util.Arrays; - -/** Generates valid filenames for a given string. */ -public class FileNameGenerator { - - private static final char[] ILLEGAL_CHARACTERS = { '/', '\\', '?', '%', - '*', ':', '|', '"', '<', '>' }; - static { - Arrays.sort(ILLEGAL_CHARACTERS); - } - - private FileNameGenerator() { - - } - - /** - * This method will return a new string that doesn't contain any illegal - * characters of the given string. - */ - public static String generateFileName(String string) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < string.length(); i++) { - char c = string.charAt(i); - if (Arrays.binarySearch(ILLEGAL_CHARACTERS, c) < 0) { - builder.append(c); - } - } - return builder.toString().replaceFirst(" *$",""); - } - - public static long generateLong(final String str) { - return str.hashCode(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/InvalidFeedException.java b/app/src/main/java/de/danoeh/antennapod/core/util/InvalidFeedException.java deleted file mode 100644 index c98c2d82a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/InvalidFeedException.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core.util; - -/** Thrown if a feed has invalid attribute values. */ -public class InvalidFeedException extends Exception { - - public InvalidFeedException() { - } - - public InvalidFeedException(String detailMessage) { - super(detailMessage); - } - - public InvalidFeedException(Throwable throwable) { - super(throwable); - } - - public InvalidFeedException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java deleted file mode 100644 index 07432d28a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java +++ /dev/null @@ -1,120 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import java.nio.charset.Charset; -import java.util.HashMap; - -public class LangUtils { - public static final Charset UTF_8 = Charset.forName("UTF-8"); - - private static HashMap languages; - static { - languages = new HashMap(); - languages.put("af", "Afrikaans"); - languages.put("sq", "Albanian"); - languages.put("sq", "Albanian"); - languages.put("eu", "Basque"); - languages.put("be", "Belarusian"); - languages.put("bg", "Bulgarian"); - languages.put("ca", "Catalan"); - languages.put("Chinese (Simplified)", "zh-cn"); - languages.put("Chinese (Traditional)", "zh-tw"); - languages.put("hr", "Croatian"); - languages.put("cs", "Czech"); - languages.put("da", "Danish"); - languages.put("nl", "Dutch"); - languages.put("nl-be", "Dutch (Belgium)"); - languages.put("nl-nl", "Dutch (Netherlands)"); - languages.put("en", "English"); - languages.put("en-au", "English (Australia)"); - languages.put("en-bz", "English (Belize)"); - languages.put("en-ca", "English (Canada)"); - languages.put("en-ie", "English (Ireland)"); - languages.put("en-jm", "English (Jamaica)"); - languages.put("en-nz", "English (New Zealand)"); - languages.put("en-ph", "English (Phillipines)"); - languages.put("en-za", "English (South Africa)"); - languages.put("en-tt", "English (Trinidad)"); - languages.put("en-gb", "English (United Kingdom)"); - languages.put("en-us", "English (United States)"); - languages.put("en-zw", "English (Zimbabwe)"); - languages.put("et", "Estonian"); - languages.put("fo", "Faeroese"); - languages.put("fi", "Finnish"); - languages.put("fr", "French"); - languages.put("fr-be", "French (Belgium)"); - languages.put("fr-ca", "French (Canada)"); - languages.put("fr-fr", "French (France)"); - languages.put("fr-lu", "French (Luxembourg)"); - languages.put("fr-mc", "French (Monaco)"); - languages.put("fr-ch", "French (Switzerland)"); - languages.put("gl", "Galician"); - languages.put("gd", "Gaelic"); - languages.put("de", "German"); - languages.put("de-at", "German (Austria)"); - languages.put("de-de", "German (Germany)"); - languages.put("de-li", "German (Liechtenstein)"); - languages.put("de-lu", "German (Luxembourg)"); - languages.put("de-ch", "German (Switzerland)"); - languages.put("el", "Greek"); - languages.put("haw", "Hawaiian"); - languages.put("hu", "Hungarian"); - languages.put("is", "Icelandic"); - languages.put("in", "Indonesian"); - languages.put("ga", "Irish"); - languages.put("it", "Italian"); - languages.put("it-it", "Italian (Italy)"); - languages.put("it-ch", "Italian (Switzerland)"); - languages.put("ja", "Japanese"); - languages.put("ko", "Korean"); - languages.put("mk", "Macedonian"); - languages.put("no", "Norwegian"); - languages.put("pl", "Polish"); - languages.put("pt", "Portugese"); - languages.put("pt-br", "Portugese (Brazil)"); - languages.put("pt-pt", "Portugese (Portugal"); - languages.put("ro", "Romanian"); - languages.put("ro-mo", "Romanian (Moldova)"); - languages.put("ro-ro", "Romanian (Romania"); - languages.put("ru", "Russian"); - languages.put("ru-mo", "Russian (Moldova)"); - languages.put("ru-ru", "Russian (Russia)"); - languages.put("sr", "Serbian"); - languages.put("sk", "Slovak"); - languages.put("sl", "Slovenian"); - languages.put("es", "Spanish"); - languages.put("es-ar", "Spanish (Argentinia)"); - languages.put("es=bo", "Spanish (Bolivia)"); - languages.put("es-cl", "Spanish (Chile)"); - languages.put("es-co", "Spanish (Colombia)"); - languages.put("es-cr", "Spanish (Costa Rica)"); - languages.put("es-do", "Spanish (Dominican Republic)"); - languages.put("es-ec", "Spanish (Ecuador)"); - languages.put("es-sv", "Spanish (El Salvador)"); - languages.put("es-gt", "Spanish (Guatemala)"); - languages.put("es-hn", "Spanish (Honduras)"); - languages.put("es-mx", "Spanish (Mexico)"); - languages.put("es-ni", "Spanish (Nicaragua)"); - languages.put("es-pa", "Spanish (Panama)"); - languages.put("es-py", "Spanish (Paraguay)"); - languages.put("es-pe", "Spanish (Peru)"); - languages.put("es-pr", "Spanish (Puerto Rico)"); - languages.put("es-es", "Spanish (Spain)"); - languages.put("es-uy", "Spanish (Uruguay)"); - languages.put("es-ve", "Spanish (Venezuela)"); - languages.put("sv", "Swedish"); - languages.put("sv-fi", "Swedish (Finland)"); - languages.put("sv-se", "Swedish (Sweden)"); - languages.put("tr", "Turkish"); - languages.put("uk", "Ukranian"); - } - - /** Finds language string for key or returns the language key if it can't be found. */ - public static String getLanguageString(String key) { - String language = languages.get(key); - if (language != null) { - return language; - } else { - return key; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java deleted file mode 100644 index 89bba290c..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.preferences.UserPreferences; - -import java.util.Arrays; -import java.util.List; - -public class NetworkUtils { - private static final String TAG = "NetworkUtils"; - - private NetworkUtils() { - - } - - /** - * Returns true if the device is connected to Wi-Fi and the Wi-Fi filter for - * automatic downloads is disabled or the device is connected to a Wi-Fi - * network that is on the 'selected networks' list of the Wi-Fi filter for - * automatic downloads and false otherwise. - * */ - public static boolean autodownloadNetworkAvailable(Context context) { - ConnectivityManager cm = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = cm.getActiveNetworkInfo(); - if (networkInfo != null) { - if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Device is connected to Wi-Fi"); - if (networkInfo.isConnected()) { - if (!UserPreferences.isEnableAutodownloadWifiFilter()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Auto-dl filter is disabled"); - return true; - } else { - WifiManager wm = (WifiManager) context - .getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiInfo = wm.getConnectionInfo(); - List selectedNetworks = Arrays - .asList(UserPreferences - .getAutodownloadSelectedNetworks()); - if (selectedNetworks.contains(Integer.toString(wifiInfo - .getNetworkId()))) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Current network is on the selected networks list"); - return true; - } - } - } - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Network for auto-dl is not available"); - return false; - } - - public static boolean networkAvailable(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - return info != null && info.isConnected(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java b/app/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java deleted file mode 100644 index 8e40ae184..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import de.danoeh.antennapod.core.feed.FeedItem; - -import java.util.Iterator; -import java.util.List; - -/** - * Provides methods for accessing the queue. It is possible to load only a part of the information about the queue that - * is stored in the database (e.g. sometimes the user just has to test if a specific item is contained in the List. - * QueueAccess provides an interface for accessing the queue without having to care about the type of the queue - * representation. - */ -public abstract class QueueAccess { - /** - * Returns true if the item is in the queue, false otherwise. - */ - public abstract boolean contains(long id); - - /** - * Removes the item from the queue. - * - * @return true if the queue was modified by this operation. - */ - public abstract boolean remove(long id); - - private QueueAccess() { - - } - - public static QueueAccess IDListAccess(final List ids) { - return new QueueAccess() { - @Override - public boolean contains(long id) { - return (ids != null) && ids.contains(id); - } - - @Override - public boolean remove(long id) { - return ids.remove(id); - } - - - }; - } - - public static QueueAccess ItemListAccess(final List items) { - return new QueueAccess() { - @Override - public boolean contains(long id) { - if (items == null) { - return false; - } - for (FeedItem item : items) { - if (item.getId() == id) { - return true; - } - } - return false; - } - - @Override - public boolean remove(long id) { - Iterator it = items.iterator(); - FeedItem item; - while (it.hasNext()) { - item = it.next(); - if (item.getId() == id) { - it.remove(); - return true; - } - } - return false; - } - }; - } - - public static QueueAccess NotInQueueAccess() { - return new QueueAccess() { - @Override - public boolean contains(long id) { - return false; - } - - @Override - public boolean remove(long id) { - return false; - } - }; - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java deleted file mode 100644 index 85f32ed50..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.content.Context; -import android.content.Intent; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedItem; - -/** Utility methods for sharing data */ -public class ShareUtils { - private static final String TAG = "ShareUtils"; - - private ShareUtils() {} - - public static void shareLink(Context context, String link) { - Intent i = new Intent(Intent.ACTION_SEND); - i.setType("text/plain"); - i.putExtra(Intent.EXTRA_SUBJECT, "Sharing URL"); - i.putExtra(Intent.EXTRA_TEXT, link); - context.startActivity(Intent.createChooser(i, "Share URL")); - } - - public static void shareFeedItemLink(Context context, FeedItem item) { - shareLink(context, item.getLink()); - } - - public static void shareFeedDownloadLink(Context context, Feed feed) { - shareLink(context, feed.getDownload_url()); - } - - public static void shareFeedlink(Context context, Feed feed) { - shareLink(context, feed.getLink()); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java b/app/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java deleted file mode 100644 index 7e7c6c08b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import java.util.concurrent.Callable; - -/** - * Created by daniel on 04.08.13. - */ -public interface ShownotesProvider { - /** - * Loads shownotes. If the shownotes have to be loaded from a file or from a - * database, it should be done in a separate thread. After the shownotes - * have been loaded, callback.onShownotesLoaded should be called. - */ - public Callable loadShownotes(); - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java deleted file mode 100644 index f899c211f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.StatFs; -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.activity.StorageErrorActivity; -import de.danoeh.antennapod.core.preferences.UserPreferences; - -import java.io.File; - -/** Utility functions for handling storage errors */ -public class StorageUtils { - private static final String TAG = "StorageUtils"; - - public static boolean storageAvailable(Context context) { - File dir = UserPreferences.getDataFolder(context, null); - if (dir != null) { - return dir.exists() && dir.canRead() && dir.canWrite(); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Storage not available: data folder is null"); - return false; - } - } - - /** - * Checks if external storage is available. If external storage isn't - * available, the current activity is finsished an an error activity is - * launched. - * - * @param activity - * the activity which would be finished if no storage is - * available - * @return true if external storage is available - */ - public static boolean checkStorageAvailability(Activity activity) { - boolean storageAvailable = storageAvailable(activity); - if (!storageAvailable) { - activity.finish(); - activity.startActivity(new Intent(activity, - StorageErrorActivity.class)); - } - return storageAvailable; - } - - /** Get the number of free bytes that are available on the external storage. */ - public static long getFreeSpaceAvailable() { - StatFs stat = new StatFs(UserPreferences.getDataFolder( - PodcastApp.getInstance(), null).getAbsolutePath()); - long availableBlocks; - long blockSize; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - availableBlocks = stat.getAvailableBlocksLong(); - blockSize = stat.getBlockSizeLong(); - } else { - availableBlocks = stat.getAvailableBlocks(); - blockSize = stat.getBlockSize(); - } - return availableBlocks * blockSize; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java deleted file mode 100644 index 72d73138d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.util.Log; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; - -public class ThemeUtils { - private static final String TAG = "ThemeUtils"; - - public static int getSelectionBackgroundColor() { - switch (UserPreferences.getTheme()) { - case R.style.Theme_AntennaPod_Dark: - return R.color.selection_background_color_dark; - case R.style.Theme_AntennaPod_Light: - return R.color.selection_background_color_light; - default: - Log.e(TAG, - "getSelectionBackgroundColor could not match the current theme to any color!"); - return R.color.selection_background_color_light; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java b/app/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java deleted file mode 100644 index c614abbc1..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - -/** - * Utility methods for dealing with URL encoding. - */ -public class URIUtil { - private static final String TAG = "URIUtil"; - - private URIUtil() {} - - public static URI getURIFromRequestUrl(String source) { - // try without encoding the URI - try { - return new URI(source); - } catch (URISyntaxException e) { - if (BuildConfig.DEBUG) Log.d(TAG, "Source is not encoded, encoding now"); - } - try { - URL url = new URL(source); - return new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java b/app/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java deleted file mode 100644 index c707e55bc..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; - -import de.danoeh.antennapod.BuildConfig; - -/** - * Provides methods for checking and editing a URL. - */ -public final class URLChecker { - - /** - * Class shall not be instantiated. - */ - private URLChecker() { - } - - /** - * Logging tag. - */ - private static final String TAG = "URLChecker"; - - /** - * Checks if URL is valid and modifies it if necessary. - * - * @param url The url which is going to be prepared - * @return The prepared url - */ - public static String prepareURL(String url) { - StringBuilder builder = new StringBuilder(); - url = StringUtils.trim(url); - if (url.startsWith("feed://")) { - if (BuildConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://"); - url = url.replaceFirst("feed://", "http://"); - } else if (url.startsWith("pcast://")) { - if (BuildConfig.DEBUG) Log.d(TAG, "Replacing pcast:// with http://"); - url = url.replaceFirst("pcast://", "http://"); - } else if (url.startsWith("itpc")) { - if (BuildConfig.DEBUG) Log.d(TAG, "Replacing itpc:// with http://"); - url = url.replaceFirst("itpc://", "http://"); - } else if (!(url.startsWith("http://") || url.startsWith("https://"))) { - if (BuildConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL"); - builder.append("http://"); - } - builder.append(url); - - return builder.toString(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/UndoBarController.java b/app/src/main/java/de/danoeh/antennapod/core/util/UndoBarController.java deleted file mode 100644 index d0721ac23..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/UndoBarController.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2012 Roman Nurik - * - * 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 de.danoeh.antennapod.core.util; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Parcelable; -import android.text.TextUtils; -import android.view.View; -import android.widget.TextView; -import com.nineoldandroids.animation.Animator; -import com.nineoldandroids.animation.AnimatorListenerAdapter; -import com.nineoldandroids.view.ViewHelper; -import com.nineoldandroids.view.ViewPropertyAnimator; -import de.danoeh.antennapod.R; - -import static com.nineoldandroids.view.ViewPropertyAnimator.animate; - -public class UndoBarController { - private View mBarView; - private TextView mMessageView; - private ViewPropertyAnimator mBarAnimator; - private Handler mHideHandler = new Handler(); - - private UndoListener mUndoListener; - - // State objects - private Parcelable mUndoToken; - private CharSequence mUndoMessage; - - public interface UndoListener { - void onUndo(Parcelable token); - } - - public UndoBarController(View undoBarView, UndoListener undoListener) { - mBarView = undoBarView; - mBarAnimator = animate(mBarView); - mUndoListener = undoListener; - - mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message); - mBarView.findViewById(R.id.undobar_button) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - hideUndoBar(false); - mUndoListener.onUndo(mUndoToken); - } - }); - - hideUndoBar(true); - } - - public void showUndoBar(boolean immediate, CharSequence message, Parcelable undoToken) { - mUndoToken = undoToken; - mUndoMessage = message; - mMessageView.setText(mUndoMessage); - - mHideHandler.removeCallbacks(mHideRunnable); - mHideHandler.postDelayed(mHideRunnable, - mBarView.getResources().getInteger(R.integer.undobar_hide_delay)); - - mBarView.setVisibility(View.VISIBLE); - if (immediate) { - ViewHelper.setAlpha(mBarView, 1); - } else { - mBarAnimator.cancel(); - mBarAnimator - .alpha(1) - .setDuration( - mBarView.getResources() - .getInteger(android.R.integer.config_shortAnimTime)) - .setListener(null); - } - } - - public void hideUndoBar(boolean immediate) { - mHideHandler.removeCallbacks(mHideRunnable); - if (immediate) { - mBarView.setVisibility(View.GONE); - ViewHelper.setAlpha(mBarView, 0); - mUndoMessage = null; - mUndoToken = null; - - } else { - mBarAnimator.cancel(); - mBarAnimator - .alpha(0) - .setDuration(mBarView.getResources() - .getInteger(android.R.integer.config_shortAnimTime)) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBarView.setVisibility(View.GONE); - mUndoMessage = null; - mUndoToken = null; - } - }); - } - } - - public void onSaveInstanceState(Bundle outState) { - outState.putCharSequence("undo_message", mUndoMessage); - outState.putParcelable("undo_token", mUndoToken); - } - - public void onRestoreInstanceState(Bundle savedInstanceState) { - if (savedInstanceState != null) { - mUndoMessage = savedInstanceState.getCharSequence("undo_message"); - mUndoToken = savedInstanceState.getParcelable("undo_token"); - - if (mUndoToken != null || !TextUtils.isEmpty(mUndoMessage)) { - showUndoBar(true, mUndoMessage, mUndoToken); - } - } - } - - private Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - hideUndoBar(false); - } - }; -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java b/app/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java deleted file mode 100644 index 5274ffc9e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.danoeh.antennapod.core.util.comparator; - -import de.danoeh.antennapod.core.feed.Chapter; - -import java.util.Comparator; - -public class ChapterStartTimeComparator implements Comparator { - - @Override - public int compare(Chapter lhs, Chapter rhs) { - if (lhs.getStart() == rhs.getStart()) { - return 0; - } else if (lhs.getStart() < rhs.getStart()) { - return -1; - } else { - return 1; - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java b/app/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java deleted file mode 100644 index ebdbfe2a5..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.danoeh.antennapod.core.util.comparator; - -import de.danoeh.antennapod.core.service.download.DownloadStatus; - -import java.util.Comparator; - -/** Compares the completion date of two Downloadstatus objects. */ -public class DownloadStatusComparator implements Comparator { - - @Override - public int compare(DownloadStatus lhs, DownloadStatus rhs) { - return rhs.getCompletionDate().compareTo(lhs.getCompletionDate()); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java b/app/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java deleted file mode 100644 index a1f3ec699..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.danoeh.antennapod.core.util.comparator; - -import de.danoeh.antennapod.core.feed.FeedItem; - -import java.util.Comparator; - -/** Compares the pubDate of two FeedItems for sorting*/ -public class FeedItemPubdateComparator implements Comparator { - - /** Returns a new instance of this comparator in reverse order. - public static FeedItemPubdateComparator newInstance() { - FeedItemPubdateComparator - }*/ - @Override - public int compare(FeedItem lhs, FeedItem rhs) { - return rhs.getPubDate().compareTo(lhs.getPubDate()); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java b/app/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java deleted file mode 100644 index 84d244660..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.danoeh.antennapod.core.util.comparator; - -import de.danoeh.antennapod.core.feed.FeedItem; - -import java.util.Comparator; - -public class PlaybackCompletionDateComparator implements Comparator { - - public int compare(FeedItem lhs, FeedItem rhs) { - if (lhs.getMedia() != null - && lhs.getMedia().getPlaybackCompletionDate() != null - && rhs.getMedia() != null - && rhs.getMedia().getPlaybackCompletionDate() != null) { - return rhs.getMedia().getPlaybackCompletionDate() - .compareTo(lhs.getMedia().getPlaybackCompletionDate()); - } - return 0; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java b/app/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java deleted file mode 100644 index b16e0949d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.danoeh.antennapod.core.util.comparator; - -import de.danoeh.antennapod.core.feed.SearchResult; - -import java.util.Comparator; - -public class SearchResultValueComparator implements Comparator { - - @Override - public int compare(SearchResult lhs, SearchResult rhs) { - return rhs.getValue() - lhs.getValue(); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java b/app/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java deleted file mode 100644 index 287fe1100..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.danoeh.antennapod.core.util.exception; - -import de.danoeh.antennapod.core.feed.FeedMedia; - -public class MediaFileNotFoundException extends Exception { - private static final long serialVersionUID = 1L; - - private FeedMedia media; - - public MediaFileNotFoundException(String msg, FeedMedia media) { - super(msg); - this.media = media; - } - - public MediaFileNotFoundException(FeedMedia media) { - super(); - this.media = media; - } - - public FeedMedia getMedia() { - return media; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java b/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java deleted file mode 100644 index 5a7cfa47f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.danoeh.antennapod.core.util.flattr; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import org.shredzone.flattr4j.FlattrFactory; -import org.shredzone.flattr4j.FlattrService; -import org.shredzone.flattr4j.oauth.AccessToken; - -/** Ensures that only one instance of the FlattrService class exists at a time */ - -public class FlattrServiceCreator { - public static final String TAG = "FlattrServiceCreator"; - - private static volatile FlattrService flattrService; - - public static FlattrService getService(AccessToken token) { - return FlattrFactory.getInstance().createFlattrService(token); - } - - public static void deleteFlattrService() { - if (BuildConfig.DEBUG) Log.d(TAG, "Deleting service instance"); - flattrService = null; - } -} - diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java b/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java deleted file mode 100644 index d82171d1a..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.danoeh.antennapod.core.util.flattr; - -import java.util.Calendar; - -public class FlattrStatus { - public static final int STATUS_UNFLATTERED = 0; - public static final int STATUS_QUEUE = 1; - public static final int STATUS_FLATTRED = 2; - - private int status = STATUS_UNFLATTERED; - private Calendar lastFlattred; - - public FlattrStatus() { - status = STATUS_UNFLATTERED; - lastFlattred = Calendar.getInstance(); - } - - public FlattrStatus(long status) { - lastFlattred = Calendar.getInstance(); - fromLong(status); - } - - public void setFlattred() { - status = STATUS_FLATTRED; - lastFlattred = Calendar.getInstance(); - } - - public void setUnflattred() { - status = STATUS_UNFLATTERED; - } - - public boolean getUnflattred() { - return status == STATUS_UNFLATTERED; - } - - public void setFlattrQueue() { - if (flattrable()) - status = STATUS_QUEUE; - } - - public void fromLong(long status) { - if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE) - this.status = (int) status; - else { - this.status = STATUS_FLATTRED; - lastFlattred.setTimeInMillis(status); - } - } - - public long toLong() { - if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE) - return status; - else { - return lastFlattred.getTimeInMillis(); - } - } - - public boolean flattrable() { - Calendar firstOfMonth = Calendar.getInstance(); - firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH)); - - return (status == STATUS_UNFLATTERED) || (status == STATUS_FLATTRED && firstOfMonth.after(lastFlattred) ); - } - - public boolean getFlattrQueue() { - return status == STATUS_QUEUE; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java b/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java deleted file mode 100644 index 515028ab6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.danoeh.antennapod.core.util.flattr; - -public interface FlattrThing { - public String getTitle(); - public String getPaymentLink(); - public FlattrStatus getFlattrStatus(); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java deleted file mode 100644 index e07ed11e9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java +++ /dev/null @@ -1,305 +0,0 @@ -package de.danoeh.antennapod.core.util.flattr; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.preference.PreferenceManager; -import android.util.Log; - -import org.apache.commons.lang3.StringUtils; -import org.shredzone.flattr4j.FlattrService; -import org.shredzone.flattr4j.exception.FlattrException; -import org.shredzone.flattr4j.model.Flattr; -import org.shredzone.flattr4j.model.Thing; -import org.shredzone.flattr4j.oauth.AccessToken; -import org.shredzone.flattr4j.oauth.AndroidAuthenticator; -import org.shredzone.flattr4j.oauth.Scope; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; -import java.util.TimeZone; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.FlattrAuthActivity; -import de.danoeh.antennapod.core.asynctask.FlattrTokenFetcher; -import de.danoeh.antennapod.core.storage.DBWriter; - -/** - * Utility methods for doing something with flattr. - */ - -public class FlattrUtils { - private static final String TAG = "FlattrUtils"; - - private static final String HOST_NAME = "de.danoeh.antennapod"; - - private static final String PREF_ACCESS_TOKEN = "de.danoeh.antennapod.preference.flattrAccessToken"; - - // Flattr URL for this app. - public static final String APP_URL = "http://antennapod.com"; - // Human-readable flattr-page. - public static final String APP_LINK = "https://flattr.com/thing/745609/"; - public static final String APP_THING_ID = "745609"; - - private static volatile AccessToken cachedToken; - - private static AndroidAuthenticator createAuthenticator() { - return new AndroidAuthenticator(HOST_NAME, BuildConfig.FLATTR_APP_KEY, - BuildConfig.FLATTR_APP_SECRET); - } - - public static void startAuthProcess(Context context) throws FlattrException { - AndroidAuthenticator auth = createAuthenticator(); - auth.setScope(EnumSet.of(Scope.FLATTR)); - Intent intent = auth.createAuthenticateIntent(); - context.startActivity(intent); - } - - private static AccessToken retrieveToken() { - if (cachedToken == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Retrieving access token"); - String token = PreferenceManager.getDefaultSharedPreferences( - PodcastApp.getInstance()) - .getString(PREF_ACCESS_TOKEN, null); - if (token != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Found access token. Caching."); - cachedToken = new AccessToken(token); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "No access token found"); - return null; - } - } - return cachedToken; - - } - - /** - * Returns true if FLATTR_APP_KEY and FLATTR_APP_SECRET in BuildConfig are not null and not empty - */ - public static boolean hasAPICredentials() { - return StringUtils.isNotEmpty(BuildConfig.FLATTR_APP_KEY) - && StringUtils.isNotEmpty(BuildConfig.FLATTR_APP_SECRET); - } - - public static boolean hasToken() { - return retrieveToken() != null; - } - - public static void storeToken(AccessToken token) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Storing token"); - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(PodcastApp.getInstance()).edit(); - if (token != null) { - editor.putString(PREF_ACCESS_TOKEN, token.getToken()); - } else { - editor.putString(PREF_ACCESS_TOKEN, null); - } - editor.commit(); - cachedToken = token; - } - - public static void deleteToken() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Deleting flattr token"); - storeToken(null); - } - - public static Thing getAppThing(Context context) { - FlattrService fs = FlattrServiceCreator.getService(retrieveToken()); - try { - Thing thing = fs.getThing(Thing.withId(APP_THING_ID)); - return thing; - } catch (FlattrException e) { - e.printStackTrace(); - showErrorDialog(context, e.getMessage()); - return null; - } - } - - public static void clickUrl(Context context, String url) - throws FlattrException { - if (hasToken()) { - FlattrService fs = FlattrServiceCreator.getService(retrieveToken()); - fs.click(url); - } else { - Log.e(TAG, "clickUrl was called with null access token"); - } - } - - public static List retrieveFlattredThings() - throws FlattrException { - ArrayList myFlattrs = new ArrayList(); - - if (hasToken()) { - FlattrService fs = FlattrServiceCreator.getService(retrieveToken()); - - Calendar firstOfMonth = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - firstOfMonth.set(Calendar.MILLISECOND, 0); - firstOfMonth.set(Calendar.SECOND, 0); - firstOfMonth.set(Calendar.MINUTE, 0); - firstOfMonth.set(Calendar.HOUR_OF_DAY, 0); - firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH)); - - Date firstOfMonthDate = firstOfMonth.getTime(); - - // subscriptions some times get flattrd slightly before midnight - give it an hour leeway - firstOfMonthDate = new Date(firstOfMonthDate.getTime() - 60 * 60 * 1000); - - final int FLATTR_COUNT = 30; - final int FLATTR_MAXPAGE = 5; - - for (int page = 0; page < FLATTR_MAXPAGE; page++) { - for (Flattr fl : fs.getMyFlattrs(FLATTR_COUNT, page)) { - if (fl.getCreated().after(firstOfMonthDate)) - myFlattrs.add(fl); - else - break; - } - } - - if (BuildConfig.DEBUG) { - Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate); - - for (Flattr fl : myFlattrs) { - Thing thing = fl.getThing(); - Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated()); - } - } - - } else { - Log.e(TAG, "retrieveFlattrdThings was called with null access token"); - } - - return myFlattrs; - } - - public static void handleCallback(Context context, Uri uri) { - AndroidAuthenticator auth = createAuthenticator(); - new FlattrTokenFetcher(context, auth, uri).executeAsync(); - } - - public static void revokeAccessToken(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Revoking access token"); - deleteToken(); - FlattrServiceCreator.deleteFlattrService(); - showRevokeDialog(context); - DBWriter.clearAllFlattrStatus(context); - } - - // ------------------------------------------------ DIALOGS - - public static void showRevokeDialog(final Context context) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.access_revoked_title); - builder.setMessage(R.string.access_revoked_info); - builder.setNeutralButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - builder.create().show(); - } - - /** - * Opens a dialog that ask the user to either connect the app with flattr or to be redirected to - * the thing's website. - * If no API credentials are available, the user will immediately be redirected to the thing's website. - * */ - public static void showNoTokenDialogOrRedirect(final Context context, final String url) { - if (hasAPICredentials()) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.no_flattr_token_title); - builder.setMessage(R.string.no_flattr_token_msg); - builder.setPositiveButton(R.string.authenticate_now_label, - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - context.startActivity(new Intent(context, - FlattrAuthActivity.class)); - } - - } - ); - - builder.setNegativeButton(R.string.visit_website_label, - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - Uri uri = Uri.parse(url); - context.startActivity(new Intent(Intent.ACTION_VIEW, - uri)); - } - - } - ); - builder.create().show(); - } else { - Uri uri = Uri.parse(url); - context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); - } - } - - public static void showForbiddenDialog(final Context context, - final String url) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.action_forbidden_title); - builder.setMessage(R.string.action_forbidden_msg); - builder.setPositiveButton(R.string.authenticate_now_label, - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - context.startActivity(new Intent(context, - FlattrAuthActivity.class)); - } - - } - ); - builder.setNegativeButton(R.string.visit_website_label, - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - Uri uri = Uri.parse(url); - context.startActivity(new Intent(Intent.ACTION_VIEW, - uri)); - } - - } - ); - builder.create().show(); - } - - public static void showErrorDialog(final Context context, final String msg) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.error_label); - builder.setMessage(msg); - builder.setNeutralButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - builder.create().show(); - } - -} \ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java b/app/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java deleted file mode 100644 index 2c178496e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.danoeh.antennapod.core.util.flattr; - -/* SimpleFlattrThing is a trivial implementation of the FlattrThing interface */ -public class SimpleFlattrThing implements FlattrThing { - public SimpleFlattrThing(String title, String url, FlattrStatus status) - { - this.title = title; - this.url = url; - this.status = status; - } - - public String getTitle() - { - return this.title; - } - - public String getPaymentLink() - { - return this.url; - } - - public FlattrStatus getFlattrStatus() - { - return this.status; - } - - private String title; - private String url; - private FlattrStatus status; -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java b/app/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java deleted file mode 100644 index 17581d3e9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.danoeh.antennapod.core.util.gui; - -import android.os.Parcel; -import android.os.Parcelable; -import de.danoeh.antennapod.core.feed.FeedItem; - -/** - * Used by an UndoBarController for saving a removed FeedItem - */ -public class FeedItemUndoToken implements Parcelable { - private long itemId; - private long feedId; - private int position; - - public FeedItemUndoToken(FeedItem item, int position) { - this.itemId = item.getId(); - this.feedId = item.getFeed().getId(); - this.position = position; - } - - private FeedItemUndoToken(Parcel in) { - itemId = in.readLong(); - feedId = in.readLong(); - position = in.readInt(); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public FeedItemUndoToken createFromParcel(Parcel in) { - return new FeedItemUndoToken(in); - } - - public FeedItemUndoToken[] newArray(int size) { - return new FeedItemUndoToken[size]; - } - }; - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel out, int flags) { - out.writeLong(itemId); - out.writeLong(feedId); - out.writeInt(position); - } - - public long getFeedItemId() { - return itemId; - } - - public int getPosition() { - return position; - } -} - diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java b/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java deleted file mode 100644 index a0bce1c79..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java +++ /dev/null @@ -1,118 +0,0 @@ -package de.danoeh.antennapod.core.util.id3reader; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.ID3Chapter; -import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader; -import de.danoeh.antennapod.core.util.id3reader.model.TagHeader; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.List; - -public class ChapterReader extends ID3Reader { - private static final String TAG = "ID3ChapterReader"; - - private static final String FRAME_ID_CHAPTER = "CHAP"; - private static final String FRAME_ID_TITLE = "TIT2"; - private static final String FRAME_ID_LINK = "WXXX"; - - private List chapters; - private ID3Chapter currentChapter; - - @Override - public int onStartTagHeader(TagHeader header) { - chapters = new ArrayList(); - System.out.println(header.toString()); - return ID3Reader.ACTION_DONT_SKIP; - } - - @Override - public int onStartFrameHeader(FrameHeader header, InputStream input) - throws IOException, ID3ReaderException { - System.out.println(header.toString()); - if (header.getId().equals(FRAME_ID_CHAPTER)) { - if (currentChapter != null) { - if (!hasId3Chapter(currentChapter)) { - chapters.add(currentChapter); - if (BuildConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter); - currentChapter = null; - } - } - StringBuffer elementId = new StringBuffer(); - readISOString(elementId, input, Integer.MAX_VALUE); - char[] startTimeSource = readBytes(input, 4); - long startTime = ((int) startTimeSource[0] << 24) - | ((int) startTimeSource[1] << 16) - | ((int) startTimeSource[2] << 8) | startTimeSource[3]; - currentChapter = new ID3Chapter(elementId.toString(), startTime); - skipBytes(input, 12); - return ID3Reader.ACTION_DONT_SKIP; - } else if (header.getId().equals(FRAME_ID_TITLE)) { - if (currentChapter != null && currentChapter.getTitle() == null) { - StringBuffer title = new StringBuffer(); - readString(title, input, header.getSize()); - currentChapter - .setTitle(title.toString()); - if (BuildConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle()); - - return ID3Reader.ACTION_DONT_SKIP; - } - } else if (header.getId().equals(FRAME_ID_LINK)) { - if (currentChapter != null) { - // skip description - int descriptionLength = readString(null, input, header.getSize()); - StringBuffer link = new StringBuffer(); - readISOString(link, input, header.getSize() - descriptionLength); - String decodedLink = URLDecoder.decode(link.toString(), "UTF-8"); - - currentChapter.setLink(decodedLink); - - if (BuildConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink()); - return ID3Reader.ACTION_DONT_SKIP; - } - } else if (header.getId().equals("APIC")) { - Log.d(TAG, header.toString()); - } - - return super.onStartFrameHeader(header, input); - } - - private boolean hasId3Chapter(ID3Chapter chapter) { - for (Chapter c : chapters) { - if (((ID3Chapter) c).getId3ID().equals(chapter.getId3ID())) { - return true; - } - } - return false; - } - - @Override - public void onEndTag() { - if (currentChapter != null) { - if (!hasId3Chapter(currentChapter)) { - chapters.add(currentChapter); - } - } - System.out.println("Reached end of tag"); - if (chapters != null) { - for (Chapter c : chapters) { - System.out.println(c.toString()); - } - } - } - - @Override - public void onNoTagHeaderFound() { - System.out.println("No tag header found"); - super.onNoTagHeaderFound(); - } - - public List getChapters() { - return chapters; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java b/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java deleted file mode 100644 index a238c11e9..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java +++ /dev/null @@ -1,250 +0,0 @@ -package de.danoeh.antennapod.core.util.id3reader; - -import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader; -import de.danoeh.antennapod.core.util.id3reader.model.TagHeader; -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -/** - * Reads the ID3 Tag of a given file. In order to use this class, you should - * create a subclass of it and overwrite the onStart* - or onEnd* - methods. - */ -public class ID3Reader { - private static final int HEADER_LENGTH = 10; - private static final int ID3_LENGTH = 3; - private static final int FRAME_ID_LENGTH = 4; - - protected static final int ACTION_SKIP = 1; - protected static final int ACTION_DONT_SKIP = 2; - - protected int readerPosition; - - private static final byte ENCODING_UTF16_WITH_BOM = 1; - private static final byte ENCODING_UTF16_WITHOUT_BOM = 2; - private static final byte ENCODING_UTF8 = 3; - - private TagHeader tagHeader; - - public ID3Reader() { - } - - public final void readInputStream(InputStream input) throws IOException, - ID3ReaderException { - int rc; - readerPosition = 0; - char[] tagHeaderSource = readBytes(input, HEADER_LENGTH); - tagHeader = createTagHeader(tagHeaderSource); - if (tagHeader == null) { - onNoTagHeaderFound(); - } else { - rc = onStartTagHeader(tagHeader); - if (rc == ACTION_SKIP) { - onEndTag(); - } else { - while (readerPosition < tagHeader.getSize()) { - FrameHeader frameHeader = createFrameHeader(readBytes( - input, HEADER_LENGTH)); - if (checkForNullString(frameHeader.getId())) { - break; - } else { - rc = onStartFrameHeader(frameHeader, input); - if (rc == ACTION_SKIP) { - - if (frameHeader.getSize() + readerPosition > tagHeader - .getSize()) { - break; - } else { - skipBytes(input, frameHeader.getSize()); - } - } - } - } - onEndTag(); - } - } - } - - /** Returns true if string only contains null-bytes. */ - private boolean checkForNullString(String s) { - if (!s.isEmpty()) { - int i = 0; - if (s.charAt(i) == 0) { - for (i = 1; i < s.length(); i++) { - if (s.charAt(i) != 0) { - return false; - } - } - return true; - } - return false; - } else { - return true; - } - - } - - /** - * Read a certain number of bytes from the given input stream. This method - * changes the readerPosition-attribute. - */ - protected char[] readBytes(InputStream input, int number) - throws IOException, ID3ReaderException { - char[] header = new char[number]; - for (int i = 0; i < number; i++) { - int b = input.read(); - readerPosition++; - if (b != -1) { - header[i] = (char) b; - } else { - throw new ID3ReaderException("Unexpected end of stream"); - } - } - return header; - } - - /** - * Skip a certain number of bytes on the given input stream. This method - * changes the readerPosition-attribute. - */ - protected void skipBytes(InputStream input, int number) throws IOException { - if (number <= 0) { - number = 1; - } - IOUtils.skipFully(input, number); - - readerPosition += number; - } - - private TagHeader createTagHeader(char[] source) throws ID3ReaderException { - boolean hasTag = (source[0] == 0x49) && (source[1] == 0x44) - && (source[2] == 0x33); - if (source.length != HEADER_LENGTH) { - throw new ID3ReaderException("Length of header must be " - + HEADER_LENGTH); - } - if (hasTag) { - String id = new String(source, 0, ID3_LENGTH); - char version = (char) ((source[3] << 8) | source[4]); - byte flags = (byte) source[5]; - int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8) - | source[9]; - size = unsynchsafe(size); - return new TagHeader(id, size, version, flags); - } else { - return null; - } - } - - private FrameHeader createFrameHeader(char[] source) - throws ID3ReaderException { - if (source.length != HEADER_LENGTH) { - throw new ID3ReaderException("Length of header must be " - + HEADER_LENGTH); - } - String id = new String(source, 0, FRAME_ID_LENGTH); - - int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) - | (((int) source[6]) << 8) | source[7]; - if (tagHeader != null && tagHeader.getVersion() >= 0x0400) { - size = unsynchsafe(size); - } - char flags = (char) ((source[8] << 8) | source[9]); - return new FrameHeader(id, size, flags); - } - - private int unsynchsafe(int in) { - int out = 0; - int mask = 0x7F000000; - - while (mask != 0) { - out >>= 1; - out |= in & mask; - mask >>= 8; - } - - return out; - } - - protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException, - ID3ReaderException { - if (max > 0) { - char[] encoding = readBytes(input, 1); - max--; - - if (encoding[0] == ENCODING_UTF16_WITH_BOM || encoding[0] == ENCODING_UTF16_WITHOUT_BOM) { - return readUnicodeString(buffer, input, max, Charset.forName("UTF-16")) + 1; // take encoding byte into account - } else if (encoding[0] == ENCODING_UTF8) { - return readUnicodeString(buffer, input, max, Charset.forName("UTF-8")) + 1; // take encoding byte into account - } else { - return readISOString(buffer, input, max) + 1; // take encoding byte into account - } - } else { - if (buffer != null) { - buffer.append(""); - } - return 0; - } - } - - protected int readISOString(StringBuffer buffer, InputStream input, int max) - throws IOException, ID3ReaderException { - - int bytesRead = 0; - char c; - while (++bytesRead <= max && (c = (char) input.read()) > 0) { - if (buffer != null) { - buffer.append(c); - } - } - return bytesRead; - } - - private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset) - throws IOException, ID3ReaderException { - byte[] buffer = new byte[max]; - int c, cZero = -1; - int i = 0; - for (; i < max; i++) { - c = input.read(); - if (c == -1) { - break; - } else if (c == 0) { - if (cZero == 0) { - // termination character found - break; - } else { - cZero = 0; - } - } else { - buffer[i] = (byte) c; - cZero = -1; - } - } - if (strBuffer != null) { - strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString()); - } - return i; - } - - public int onStartTagHeader(TagHeader header) { - return ACTION_SKIP; - } - - public int onStartFrameHeader(FrameHeader header, InputStream input) - throws IOException, ID3ReaderException { - return ACTION_SKIP; - } - - public void onEndTag() { - - } - - public void onNoTagHeaderFound() { - - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3ReaderException.java b/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3ReaderException.java deleted file mode 100644 index 0c746d7e5..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3ReaderException.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.danoeh.antennapod.core.util.id3reader; - -public class ID3ReaderException extends Exception { - - public ID3ReaderException() { - } - - public ID3ReaderException(String arg0) { - super(arg0); - } - - public ID3ReaderException(Throwable arg0) { - super(arg0); - } - - public ID3ReaderException(String arg0, Throwable arg1) { - super(arg0, arg1); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java b/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java deleted file mode 100644 index 89eab1398..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.danoeh.antennapod.core.util.id3reader.model; - -public class FrameHeader extends Header { - - protected char flags; - - public FrameHeader(String id, int size, char flags) { - super(id, size); - this.flags = flags; - } - - @Override - public String toString() { - return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, Integer.toBinaryString(size)); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java b/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java deleted file mode 100644 index 346e2893f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.danoeh.antennapod.core.util.id3reader.model; - -public abstract class Header { - - protected String id; - protected int size; - - public Header(String id, int size) { - super(); - this.id = id; - this.size = size; - } - - public String getId() { - return id; - } - - public int getSize() { - return size; - } - - @Override - public String toString() { - return "Header [id=" + id + ", size=" + size + "]"; - } - - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java b/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java deleted file mode 100644 index 0a6b8357f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.danoeh.antennapod.core.util.id3reader.model; - -public class TagHeader extends Header { - - protected char version; - protected byte flags; - - public TagHeader(String id, int size, char version, byte flags) { - super(id, size); - this.version = version; - this.flags = flags; - } - - @Override - public String toString() { - return "TagHeader [version=" + version + ", flags=" + flags + ", id=" - + id + ", size=" + size + "]"; - } - - public char getVersion() { - return version; - } - - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedItemMenuHandler.java deleted file mode 100644 index f85ac412d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedItemMenuHandler.java +++ /dev/null @@ -1,191 +0,0 @@ -package de.danoeh.antennapod.core.util.menuhandler; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.ShareUtils; - -/** - * Handles interactions with the FeedItemMenu. - */ -public class FeedItemMenuHandler { - private static final String TAG = "FeedItemMenuHandler"; - - private FeedItemMenuHandler() { - - } - - /** - * Used by the MenuHandler to access different types of menus through one - * interface - */ - public interface MenuInterface { - /** - * Implementations of this method should call findItem(id) on their - * menu-object and call setVisibility(visibility) on the returned - * MenuItem object. - */ - abstract void setItemVisibility(int id, boolean visible); - } - - /** - * This method should be called in the prepare-methods of menus. It changes - * the visibility of the menu items depending on a FeedItem's attributes. - * - * @param mi An instance of MenuInterface that the method uses to change a - * MenuItem's visibility - * @param selectedItem The FeedItem for which the menu is supposed to be prepared - * @param showExtendedMenu True if MenuItems that let the user share information about - * the FeedItem and visit its website should be set visible. This - * parameter should be set to false if the menu space is limited. - * @param queueAccess Used for testing if the queue contains the selected item - * @return Returns true if selectedItem is not null. - */ - public static boolean onPrepareMenu(MenuInterface mi, - FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) { - if (selectedItem == null) { - return false; - } - DownloadRequester requester = DownloadRequester.getInstance(); - boolean hasMedia = selectedItem.getMedia() != null; - boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded(); - boolean downloading = hasMedia - && requester.isDownloadingFile(selectedItem.getMedia()); - boolean notLoadedAndNotLoading = hasMedia && (!downloaded) - && (!downloading); - boolean isPlaying = hasMedia - && selectedItem.getState() == FeedItem.State.PLAYING; - - FeedItem.State state = selectedItem.getState(); - - if (!isPlaying) { - mi.setItemVisibility(R.id.skip_episode_item, false); - } - if (!downloaded || isPlaying) { - mi.setItemVisibility(R.id.play_item, false); - mi.setItemVisibility(R.id.remove_item, false); - } - if (!notLoadedAndNotLoading) { - mi.setItemVisibility(R.id.download_item, false); - } - if (!(notLoadedAndNotLoading | downloading) | isPlaying) { - mi.setItemVisibility(R.id.stream_item, false); - } - if (!downloading) { - mi.setItemVisibility(R.id.cancel_download_item, false); - } - - boolean isInQueue = queueAccess.contains(selectedItem.getId()); - if (!isInQueue || isPlaying) { - mi.setItemVisibility(R.id.remove_from_queue_item, false); - } - if (!(!isInQueue && selectedItem.getMedia() != null)) { - mi.setItemVisibility(R.id.add_to_queue_item, false); - } - if (!showExtendedMenu || selectedItem.getLink() == null) { - mi.setItemVisibility(R.id.share_link_item, false); - } - - if (!BuildConfig.DEBUG - || !(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) { - mi.setItemVisibility(R.id.mark_unread_item, false); - } - if (!(state == FeedItem.State.NEW || state == FeedItem.State.IN_PROGRESS)) { - mi.setItemVisibility(R.id.mark_read_item, false); - } - - if (!showExtendedMenu || selectedItem.getLink() == null) { - mi.setItemVisibility(R.id.visit_website_item, false); - } - - if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) { - mi.setItemVisibility(R.id.support_item, false); - } - return true; - } - - /** - * The same method as onPrepareMenu(MenuInterface, FeedItem, boolean, QueueAccess), but lets the - * caller also specify a list of menu items that should not be shown. - * - * @param excludeIds Menu item that should be excluded - * @return true if selectedItem is not null. - */ - public static boolean onPrepareMenu(MenuInterface mi, - FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess, int... excludeIds) { - boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess); - if (rc && excludeIds != null) { - for (int id : excludeIds) { - mi.setItemVisibility(id, false); - } - } - - return rc; - } - - public static boolean onMenuItemClicked(Context context, int menuItemId, - FeedItem selectedItem) throws DownloadRequestException { - DownloadRequester requester = DownloadRequester.getInstance(); - switch (menuItemId) { - case R.id.skip_episode_item: - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); - break; - case R.id.download_item: - DBTasks.downloadFeedItems(context, selectedItem); - break; - case R.id.play_item: - DBTasks.playMedia(context, selectedItem.getMedia(), true, true, - false); - break; - case R.id.remove_item: - DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()); - break; - case R.id.cancel_download_item: - requester.cancelDownload(context, selectedItem.getMedia()); - break; - case R.id.mark_read_item: - DBWriter.markItemRead(context, selectedItem, true, true); - break; - case R.id.mark_unread_item: - DBWriter.markItemRead(context, selectedItem, false, true); - break; - case R.id.add_to_queue_item: - DBWriter.addQueueItem(context, selectedItem.getId()); - break; - case R.id.remove_from_queue_item: - DBWriter.removeQueueItem(context, selectedItem.getId(), true); - break; - case R.id.stream_item: - DBTasks.playMedia(context, selectedItem.getMedia(), true, true, - true); - break; - case R.id.visit_website_item: - Uri uri = Uri.parse(selectedItem.getLink()); - context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); - break; - case R.id.support_item: - DBTasks.flattrItemIfLoggedIn(context, selectedItem); - break; - case R.id.share_link_item: - ShareUtils.shareFeedItemLink(context, selectedItem); - break; - default: - return false; - } - // Refresh menu state - - return true; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedMenuHandler.java deleted file mode 100644 index 757cc5f56..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/FeedMenuHandler.java +++ /dev/null @@ -1,86 +0,0 @@ -package de.danoeh.antennapod.core.util.menuhandler; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.ShareUtils; - -/** Handles interactions with the FeedItemMenu. */ -public class FeedMenuHandler { - private static final String TAG = "FeedMenuHandler"; - - public static boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { - inflater.inflate(R.menu.feedlist, menu); - return true; - } - - public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) { - if (selectedFeed == null) { - return true; - } - - if (BuildConfig.DEBUG) - Log.d(TAG, "Preparing options menu"); - menu.findItem(R.id.mark_all_read_item).setVisible( - selectedFeed.hasNewItems(true)); - if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) - menu.findItem(R.id.support_item).setVisible(true); - else - menu.findItem(R.id.support_item).setVisible(false); - MenuItem refresh = menu.findItem(R.id.refresh_item); - if (DownloadService.isRunning - && DownloadRequester.getInstance().isDownloadingFile( - selectedFeed)) { - refresh.setVisible(false); - } else { - refresh.setVisible(true); - } - - return true; - } - - /** - * NOTE: This method does not handle clicks on the 'remove feed' - item. - * - * @throws DownloadRequestException - */ - public static boolean onOptionsItemClicked(Context context, MenuItem item, - Feed selectedFeed) throws DownloadRequestException { - switch (item.getItemId()) { - case R.id.refresh_item: - DBTasks.refreshFeed(context, selectedFeed); - break; - case R.id.mark_all_read_item: - DBWriter.markFeedRead(context, selectedFeed.getId()); - break; - case R.id.visit_website_item: - Uri uri = Uri.parse(selectedFeed.getLink()); - context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); - break; - case R.id.support_item: - DBTasks.flattrFeedIfLoggedIn(context, selectedFeed); - break; - case R.id.share_link_item: - ShareUtils.shareFeedlink(context, selectedFeed); - break; - case R.id.share_source_item: - ShareUtils.shareFeedDownloadLink(context, selectedFeed); - break; - default: - return false; - } - return true; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/MenuItemUtils.java deleted file mode 100644 index 4258c4d22..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/MenuItemUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.danoeh.antennapod.core.util.menuhandler; - -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; -import android.view.Menu; -import android.view.MenuItem; - -import de.danoeh.antennapod.R; - -/** - * Utilities for menu items - */ -public class MenuItemUtils { - - public static MenuItem addSearchItem(Menu menu, SearchView searchView) { - MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); - MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - MenuItemCompat.setActionView(item, searchView); - return item; - } - - /** - * Checks if the navigation drawer of the DrawerActivity is opened. This can be useful for Fragments - * that hide their menu if the navigation drawer is open. - * - * @return True if the drawer is open, false otherwise (also if the parameter is null) - */ - public static boolean isActivityDrawerOpen(NavDrawerActivity activity) { - return activity != null && activity.isDrawerOpen(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/NavDrawerActivity.java b/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/NavDrawerActivity.java deleted file mode 100644 index 61bf9960f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/menuhandler/NavDrawerActivity.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.danoeh.antennapod.core.util.menuhandler; - -/** - * Defines useful methods for activities that have a navigation drawer - */ -public interface NavDrawerActivity { - - public boolean isDrawerOpen(); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java deleted file mode 100644 index aafcea307..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.util.Log; -import android.view.SurfaceHolder; -import com.aocate.media.MediaPlayer; - -public class AudioPlayer extends MediaPlayer implements IPlayer { - private static final String TAG = "AudioPlayer"; - - public AudioPlayer(Context context) { - super(context); - } - - @Override - public void setScreenOnWhilePlaying(boolean screenOn) { - Log.e(TAG, "Setting screen on while playing not supported in Audio Player"); - throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player"); - - } - - @Override - public void setDisplay(SurfaceHolder sh) { - if (sh != null) { - Log.e(TAG, "Setting display not supported in Audio Player"); - throw new UnsupportedOperationException("Setting display not supported in Audio Player"); - } - } - - @Override - public void setVideoScalingMode(int mode) { - throw new UnsupportedOperationException("Setting scaling mode is not supported in Audio Player"); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java deleted file mode 100644 index 49769f4f0..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java +++ /dev/null @@ -1,235 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.util.ChapterUtils; - -import java.util.List; -import java.util.concurrent.Callable; - -/** Represents a media file that is stored on the local storage device. */ -public class ExternalMedia implements Playable { - - public static final int PLAYABLE_TYPE_EXTERNAL_MEDIA = 2; - public static final String PREF_SOURCE_URL = "ExternalMedia.PrefSourceUrl"; - public static final String PREF_POSITION = "ExternalMedia.PrefPosition"; - public static final String PREF_MEDIA_TYPE = "ExternalMedia.PrefMediaType"; - - private String source; - - private String episodeTitle; - private String feedTitle; - private MediaType mediaType = MediaType.AUDIO; - private List chapters; - private int duration; - private int position; - - public ExternalMedia(String source, MediaType mediaType) { - super(); - this.source = source; - this.mediaType = mediaType; - } - - public ExternalMedia(String source, MediaType mediaType, int position) { - this(source, mediaType); - this.position = position; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(source); - dest.writeString(mediaType.toString()); - dest.writeInt(position); - } - - @Override - public void writeToPreferences(Editor prefEditor) { - prefEditor.putString(PREF_SOURCE_URL, source); - prefEditor.putString(PREF_MEDIA_TYPE, mediaType.toString()); - prefEditor.putInt(PREF_POSITION, position); - } - - @Override - public void loadMetadata() throws PlayableException { - MediaMetadataRetriever mmr = new MediaMetadataRetriever(); - try { - mmr.setDataSource(source); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - throw new PlayableException( - "IllegalArgumentException when setting up MediaMetadataReceiver"); - } catch (RuntimeException e) { - // http://code.google.com/p/android/issues/detail?id=39770 - e.printStackTrace(); - throw new PlayableException( - "RuntimeException when setting up MediaMetadataRetriever"); - } - episodeTitle = mmr - .extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); - feedTitle = mmr - .extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); - try { - duration = Integer.parseInt(mmr - .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); - } catch (NumberFormatException e) { - e.printStackTrace(); - throw new PlayableException("NumberFormatException when reading duration of media file"); - } - ChapterUtils.loadChaptersFromFileUrl(this); - } - - @Override - public void loadChapterMarks() { - - } - - @Override - public String getEpisodeTitle() { - return episodeTitle; - } - - @Override - public Callable loadShownotes() { - return new Callable() { - @Override - public String call() throws Exception { - return ""; - } - }; - } - - @Override - public List getChapters() { - return chapters; - } - - @Override - public String getWebsiteLink() { - return null; - } - - @Override - public String getPaymentLink() { - return null; - } - - @Override - public String getFeedTitle() { - return feedTitle; - } - - @Override - public Object getIdentifier() { - return source; - } - - @Override - public int getDuration() { - return duration; - } - - @Override - public int getPosition() { - return position; - } - - @Override - public MediaType getMediaType() { - return mediaType; - } - - @Override - public String getLocalMediaUrl() { - return source; - } - - @Override - public String getStreamUrl() { - return null; - } - - @Override - public boolean localFileAvailable() { - return true; - } - - @Override - public boolean streamAvailable() { - return false; - } - - @Override - public void saveCurrentPosition(SharedPreferences pref, int newPosition) { - SharedPreferences.Editor editor = pref.edit(); - editor.putInt(PREF_POSITION, newPosition); - position = newPosition; - editor.commit(); - } - - @Override - public void setPosition(int newPosition) { - position = newPosition; - } - - @Override - public void setDuration(int newDuration) { - duration = newDuration; - } - - @Override - public void onPlaybackStart() { - - } - - @Override - public void onPlaybackCompleted() { - - } - - @Override - public int getPlayableType() { - return PLAYABLE_TYPE_EXTERNAL_MEDIA; - } - - @Override - public void setChapters(List chapters) { - this.chapters = chapters; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public ExternalMedia createFromParcel(Parcel in) { - String source = in.readString(); - MediaType type = MediaType.valueOf(in.readString()); - int position = 0; - if (in.dataAvail() > 0) { - position = in.readInt(); - } - ExternalMedia extMedia = new ExternalMedia(source, type, position); - return extMedia; - } - - public ExternalMedia[] newArray(int size) { - return new ExternalMedia[size]; - } - }; - - @Override - public Uri getImageUri() { - if (localFileAvailable()) { - return new Uri.Builder().scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl()).build(); - } else { - return null; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java deleted file mode 100644 index 147c7848d..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.view.SurfaceHolder; - -import java.io.IOException; - -public interface IPlayer { - boolean canSetPitch(); - - boolean canSetSpeed(); - - float getCurrentPitchStepsAdjustment(); - - int getCurrentPosition(); - - float getCurrentSpeedMultiplier(); - - int getDuration(); - - float getMaxSpeedMultiplier(); - - float getMinSpeedMultiplier(); - - boolean isLooping(); - - boolean isPlaying(); - - void pause(); - - void prepare() throws IllegalStateException, IOException; - - void prepareAsync(); - - void release(); - - void reset(); - - void seekTo(int msec); - - void setAudioStreamType(int streamtype); - - void setScreenOnWhilePlaying(boolean screenOn); - - void setDataSource(String path) throws IllegalStateException, IOException, - IllegalArgumentException, SecurityException; - - void setDisplay(SurfaceHolder sh); - - void setEnableSpeedAdjustment(boolean enableSpeedAdjustment); - - void setLooping(boolean looping); - - void setPitchStepsAdjustment(float pitchSteps); - - void setPlaybackPitch(float f); - - void setPlaybackSpeed(float f); - - void setVolume(float left, float right); - - void start(); - - void stop(); - - public void setVideoScalingMode(int mode); - - public void setWakeMode(Context context, int mode); -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java deleted file mode 100644 index a3a907e48..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.media.MediaPlayer; -import de.danoeh.antennapod.R; - -/** Utility class for MediaPlayer errors. */ -public class MediaPlayerError { - - /** Get a human-readable string for a specific error code. */ - public static String getErrorString(Context context, int code) { - int resId; - switch(code) { - case MediaPlayer.MEDIA_ERROR_SERVER_DIED: - resId = R.string.playback_error_server_died; - break; - default: - resId = R.string.playback_error_unknown; - break; - } - return context.getString(resId); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java deleted file mode 100644 index 7ebd580f7..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ /dev/null @@ -1,207 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Parcelable; -import android.util.Log; - -import java.util.List; - -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.ShownotesProvider; - -/** - * Interface for objects that can be played by the PlaybackService. - */ -public interface Playable extends Parcelable, - ShownotesProvider, PicassoImageResource { - - /** - * Save information about the playable in a preference so that it can be - * restored later via PlayableUtils.createInstanceFromPreferences. - * Implementations must NOT call commit() after they have written the values - * to the preferences file. - */ - public void writeToPreferences(SharedPreferences.Editor prefEditor); - - /** - * This method is called from a separate thread by the PlaybackService. - * Playable objects should load their metadata in this method. This method - * should execute as quickly as possible and NOT load chapter marks if no - * local file is available. - */ - public void loadMetadata() throws PlayableException; - - /** - * This method is called from a separate thread by the PlaybackService. - * Playable objects should load their chapter marks in this method if no - * local file was available when loadMetadata() was called. - */ - public void loadChapterMarks(); - - /** - * Returns the title of the episode that this playable represents - */ - public String getEpisodeTitle(); - - /** - * Returns a list of chapter marks or null if this Playable has no chapters. - */ - public List getChapters(); - - /** - * Returns a link to a website that is meant to be shown in a browser - */ - public String getWebsiteLink(); - - public String getPaymentLink(); - - /** - * Returns the title of the feed this Playable belongs to. - */ - public String getFeedTitle(); - - /** - * Returns a unique identifier, for example a file url or an ID from a - * database. - */ - public Object getIdentifier(); - - /** - * Return duration of object or 0 if duration is unknown. - */ - public int getDuration(); - - /** - * Return position of object or 0 if position is unknown. - */ - public int getPosition(); - - /** - * Returns the type of media. This method should return the correct value - * BEFORE loadMetadata() is called. - */ - public MediaType getMediaType(); - - /** - * Returns an url to a local file that can be played or null if this file - * does not exist. - */ - public String getLocalMediaUrl(); - - /** - * Returns an url to a file that can be streamed by the player or null if - * this url is not known. - */ - public String getStreamUrl(); - - /** - * Returns true if a local file that can be played is available. getFileUrl - * MUST return a non-null string if this method returns true. - */ - public boolean localFileAvailable(); - - /** - * Returns true if a streamable file is available. getStreamUrl MUST return - * a non-null string if this method returns true. - */ - public boolean streamAvailable(); - - /** - * Saves the current position of this object. Implementations can use the - * provided SharedPreference to save this information and retrieve it later - * via PlayableUtils.createInstanceFromPreferences. - */ - public void saveCurrentPosition(SharedPreferences pref, int newPosition); - - public void setPosition(int newPosition); - - public void setDuration(int newDuration); - - /** - * Is called by the PlaybackService when playback starts. - */ - public void onPlaybackStart(); - - /** - * Is called by the PlaybackService when playback is completed. - */ - public void onPlaybackCompleted(); - - /** - * Returns an integer that must be unique among all Playable classes. The - * return value is later used by PlayableUtils to determine the type of the - * Playable object that is restored. - */ - public int getPlayableType(); - - public void setChapters(List chapters); - - /** - * Provides utility methods for Playable objects. - */ - public static class PlayableUtils { - private static final String TAG = "PlayableUtils"; - - /** - * Restores a playable object from a sharedPreferences file. This method might load data from the database, - * depending on the type of playable that was restored. - * - * @param type An integer that represents the type of the Playable object - * that is restored. - * @param pref The SharedPreferences file from which the Playable object - * is restored - * @return The restored Playable object - */ - public static Playable createInstanceFromPreferences(Context context, int type, - SharedPreferences pref) { - // ADD new Playable types here: - switch (type) { - case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA: - long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1); - if (mediaId != -1) { - return DBReader.getFeedMedia(context, mediaId); - } - break; - case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: - String source = pref.getString(ExternalMedia.PREF_SOURCE_URL, - null); - String mediaType = pref.getString( - ExternalMedia.PREF_MEDIA_TYPE, null); - if (source != null && mediaType != null) { - int position = pref.getInt(ExternalMedia.PREF_POSITION, 0); - return new ExternalMedia(source, - MediaType.valueOf(mediaType), position); - } - break; - } - Log.e(TAG, "Could not restore Playable object from preferences"); - return null; - } - } - - public static class PlayableException extends Exception { - private static final long serialVersionUID = 1L; - - public PlayableException() { - super(); - } - - public PlayableException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - - public PlayableException(String detailMessage) { - super(detailMessage); - } - - public PlayableException(Throwable throwable) { - super(throwable); - } - - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java deleted file mode 100644 index 35bd27057..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ /dev/null @@ -1,784 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.app.Activity; -import android.content.*; -import android.content.res.TypedArray; -import android.media.MediaPlayer; -import android.os.AsyncTask; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.util.Log; -import android.util.Pair; -import android.view.SurfaceHolder; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageButton; -import android.widget.SeekBar; -import android.widget.TextView; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.playback.Playable.PlayableUtils; - -import java.util.concurrent.*; - -/** - * Communicates with the playback service. GUI classes should use this class to - * control playback instead of communicating with the PlaybackService directly. - */ -public abstract class PlaybackController { - private static final String TAG = "PlaybackController"; - - public static final int INVALID_TIME = -1; - - private final Activity activity; - - private PlaybackService playbackService; - private Playable media; - private PlayerStatus status; - - private ScheduledThreadPoolExecutor schedExecutor; - private static final int SCHED_EX_POOLSIZE = 1; - - protected MediaPositionObserver positionObserver; - protected ScheduledFuture positionObserverFuture; - - private boolean mediaInfoLoaded = false; - private boolean released = false; - - /** - * True if controller should reinit playback service if 'pause' button is - * pressed. - */ - private boolean reinitOnPause; - - public PlaybackController(Activity activity, boolean reinitOnPause) { - Validate.notNull(activity); - - this.activity = activity; - this.reinitOnPause = reinitOnPause; - schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOLSIZE, - new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }, new RejectedExecutionHandler() { - - @Override - public void rejectedExecution(Runnable r, - ThreadPoolExecutor executor) { - Log.w(TAG, - "Rejected execution of runnable in schedExecutor"); - } - } - ); - } - - /** - * Creates a new connection to the playbackService. Should be called in the - * activity's onResume() method. - */ - public void init() { - activity.registerReceiver(statusUpdate, new IntentFilter( - PlaybackService.ACTION_PLAYER_STATUS_CHANGED)); - - activity.registerReceiver(notificationReceiver, new IntentFilter( - PlaybackService.ACTION_PLAYER_NOTIFICATION)); - - activity.registerReceiver(shutdownReceiver, new IntentFilter( - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - - if (!released) { - bindToService(); - } else { - throw new IllegalStateException( - "Can't call init() after release() has been called"); - } - } - - /** - * Should be called if the PlaybackController is no longer needed, for - * example in the activity's onStop() method. - */ - public void release() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Releasing PlaybackController"); - - try { - activity.unregisterReceiver(statusUpdate); - } catch (IllegalArgumentException e) { - // ignore - } - - try { - activity.unregisterReceiver(notificationReceiver); - } catch (IllegalArgumentException e) { - // ignore - } - - try { - activity.unbindService(mConnection); - } catch (IllegalArgumentException e) { - // ignore - } - - try { - activity.unregisterReceiver(shutdownReceiver); - } catch (IllegalArgumentException e) { - // ignore - } - cancelPositionObserver(); - schedExecutor.shutdownNow(); - media = null; - released = true; - - } - - /** - * Should be called in the activity's onPause() method. - */ - public void pause() { - mediaInfoLoaded = false; - } - - /** - * Tries to establish a connection to the PlaybackService. If it isn't - * running, the PlaybackService will be started with the last played media - * as the arguments of the launch intent. - */ - private void bindToService() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Trying to connect to service"); - AsyncTask intentLoader = new AsyncTask() { - @Override - protected Intent doInBackground(Void... voids) { - return getPlayLastPlayedMediaIntent(); - } - - @Override - protected void onPostExecute(Intent serviceIntent) { - boolean bound = false; - if (!PlaybackService.started) { - if (serviceIntent != null) { - if (BuildConfig.DEBUG) Log.d(TAG, "Calling start service"); - activity.startService(serviceIntent); - bound = activity.bindService(serviceIntent, mConnection, 0); - } else { - status = PlayerStatus.STOPPED; - setupGUI(); - handleStatus(); - } - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "PlaybackService is running, trying to connect without start command."); - bound = activity.bindService(new Intent(activity, - PlaybackService.class), mConnection, 0); - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Result for service binding: " + bound); - } - }; - intentLoader.execute(); - } - - /** - * Returns an intent that starts the PlaybackService and plays the last - * played media or null if no last played media could be found. - */ - private Intent getPlayLastPlayedMediaIntent() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Trying to restore last played media"); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(activity.getApplicationContext()); - long currentlyPlayingMedia = PlaybackPreferences - .getCurrentlyPlayingMedia(); - if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) { - Playable media = PlayableUtils.createInstanceFromPreferences(activity, - (int) currentlyPlayingMedia, prefs); - if (media != null) { - Intent serviceIntent = new Intent(activity, - PlaybackService.class); - serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); - serviceIntent.putExtra( - PlaybackService.EXTRA_START_WHEN_PREPARED, false); - serviceIntent.putExtra( - PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false); - boolean fileExists = media.localFileAvailable(); - boolean lastIsStream = PlaybackPreferences - .getCurrentEpisodeIsStream(); - if (!fileExists && !lastIsStream && media instanceof FeedMedia) { - DBTasks.notifyMissingFeedMediaFile( - activity, (FeedMedia) media); - } - serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, - lastIsStream || !fileExists); - return serviceIntent; - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "No last played media found"); - return null; - } - - public abstract void setupGUI(); - - private void setupPositionObserver() { - if ((positionObserverFuture != null && positionObserverFuture - .isCancelled()) - || (positionObserverFuture != null && positionObserverFuture - .isDone()) || positionObserverFuture == null) { - - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting up position observer"); - positionObserver = new MediaPositionObserver(); - positionObserverFuture = schedExecutor.scheduleWithFixedDelay( - positionObserver, MediaPositionObserver.WAITING_INTERVALL, - MediaPositionObserver.WAITING_INTERVALL, - TimeUnit.MILLISECONDS); - } - } - - private void cancelPositionObserver() { - if (positionObserverFuture != null) { - boolean result = positionObserverFuture.cancel(true); - if (BuildConfig.DEBUG) - Log.d(TAG, "PositionObserver cancelled. Result: " + result); - } - } - - public abstract void onPositionObserverUpdate(); - - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - playbackService = ((PlaybackService.LocalBinder) service) - .getService(); - if (!released) { - queryService(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Connection to Service established"); - } else { - Log.i(TAG, "Connection to playback service has been established, but controller has already been released"); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - playbackService = null; - if (BuildConfig.DEBUG) - Log.d(TAG, "Disconnected from Service"); - - } - }; - - protected BroadcastReceiver statusUpdate = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received statusUpdate Intent."); - if (isConnectedToPlaybackService()) { - PlaybackServiceMediaPlayer.PSMPInfo info = playbackService.getPSMPInfo(); - status = info.playerStatus; - media = info.playable; - handleStatus(); - } else { - Log.w(TAG, - "Couldn't receive status update: playbackService was null"); - bindToService(); - } - } - }; - - protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (isConnectedToPlaybackService()) { - int type = intent.getIntExtra( - PlaybackService.EXTRA_NOTIFICATION_TYPE, -1); - int code = intent.getIntExtra( - PlaybackService.EXTRA_NOTIFICATION_CODE, -1); - if (code != -1 && type != -1) { - switch (type) { - case PlaybackService.NOTIFICATION_TYPE_ERROR: - handleError(code); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE: - float progress = ((float) code) / 100; - onBufferUpdate(progress); - break; - case PlaybackService.NOTIFICATION_TYPE_RELOAD: - cancelPositionObserver(); - mediaInfoLoaded = false; - queryService(); - onReloadNotification(intent.getIntExtra( - PlaybackService.EXTRA_NOTIFICATION_CODE, -1)); - break; - case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE: - onSleepTimerUpdate(); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_START: - onBufferStart(); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_END: - onBufferEnd(); - break; - case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END: - onPlaybackEnd(); - break; - case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE: - onPlaybackSpeedChange(); - break; - } - - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Bad arguments. Won't handle intent"); - } - } else { - bindToService(); - } - } - - }; - - private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (isConnectedToPlaybackService()) { - if (StringUtils.equals(intent.getAction(), - PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - release(); - onShutdownNotification(); - } - } - } - }; - - public abstract void onPlaybackSpeedChange(); - - public abstract void onShutdownNotification(); - - /** - * Called when the currently displayed information should be refreshed. - */ - public abstract void onReloadNotification(int code); - - public abstract void onBufferStart(); - - public abstract void onBufferEnd(); - - public abstract void onBufferUpdate(float progress); - - public abstract void onSleepTimerUpdate(); - - public abstract void handleError(int code); - - public abstract void onPlaybackEnd(); - - /** - * Is called whenever the PlaybackService changes it's status. This method - * should be used to update the GUI or start/cancel background threads. - */ - private void handleStatus() { - final int playResource; - final int pauseResource; - final CharSequence playText = activity.getString(R.string.play_label); - final CharSequence pauseText = activity.getString(R.string.pause_label); - - if (PlaybackService.getCurrentMediaType() == MediaType.AUDIO) { - TypedArray res = activity.obtainStyledAttributes(new int[]{ - R.attr.av_play, R.attr.av_pause}); - playResource = res.getResourceId(0, R.drawable.av_play); - pauseResource = res.getResourceId(1, R.drawable.av_pause); - res.recycle(); - } else { - playResource = R.drawable.ic_action_play_over_video; - pauseResource = R.drawable.ic_action_pause_over_video; - } - - switch (status) { - - case ERROR: - postStatusMsg(R.string.player_error_msg); - handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN); - break; - case PAUSED: - clearStatusMsg(); - checkMediaInfoLoaded(); - cancelPositionObserver(); - updatePlayButtonAppearance(playResource, playText); - if (PlaybackService.getCurrentMediaType() == MediaType.VIDEO) { - setScreenOn(false); - } - break; - case PLAYING: - clearStatusMsg(); - checkMediaInfoLoaded(); - if (PlaybackService.getCurrentMediaType() == MediaType.VIDEO) { - onAwaitingVideoSurface(); - setScreenOn(true); - } - setupPositionObserver(); - updatePlayButtonAppearance(pauseResource, pauseText); - break; - case PREPARING: - postStatusMsg(R.string.player_preparing_msg); - checkMediaInfoLoaded(); - if (playbackService != null) { - if (playbackService.isStartWhenPrepared()) { - updatePlayButtonAppearance(pauseResource, pauseText); - } else { - updatePlayButtonAppearance(playResource, playText); - } - } - break; - case STOPPED: - postStatusMsg(R.string.player_stopped_msg); - break; - case PREPARED: - checkMediaInfoLoaded(); - postStatusMsg(R.string.player_ready_msg); - updatePlayButtonAppearance(playResource, playText); - break; - case SEEKING: - postStatusMsg(R.string.player_seeking_msg); - break; - case INITIALIZED: - checkMediaInfoLoaded(); - clearStatusMsg(); - updatePlayButtonAppearance(playResource, playText); - break; - } - } - - private void checkMediaInfoLoaded() { - mediaInfoLoaded = (mediaInfoLoaded || loadMediaInfo()); - } - - private void updatePlayButtonAppearance(int resource, CharSequence contentDescription) { - ImageButton butPlay = getPlayButton(); - butPlay.setImageResource(resource); - butPlay.setContentDescription(contentDescription); - } - - public abstract ImageButton getPlayButton(); - - public abstract void postStatusMsg(int msg); - - public abstract void clearStatusMsg(); - - public abstract boolean loadMediaInfo(); - - public abstract void onAwaitingVideoSurface(); - - /** - * Called when connection to playback service has been established or - * information has to be refreshed - */ - void queryService() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Querying service info"); - if (playbackService != null) { - status = playbackService.getStatus(); - media = playbackService.getPlayable(); - /* - if (media == null) { - Log.w(TAG, - "PlaybackService has no media object. Trying to restore last played media."); - Intent serviceIntent = getPlayLastPlayedMediaIntent(); - if (serviceIntent != null) { - activity.startService(serviceIntent); - } - } - */ - onServiceQueried(); - - setupGUI(); - handleStatus(); - // make sure that new media is loaded if it's available - mediaInfoLoaded = false; - - } else { - Log.e(TAG, - "queryService() was called without an existing connection to playbackservice"); - } - } - - public abstract void onServiceQueried(); - - /** - * Should be used by classes which implement the OnSeekBarChanged interface. - */ - public float onSeekBarProgressChanged(SeekBar seekBar, int progress, - boolean fromUser, TextView txtvPosition) { - if (fromUser && playbackService != null && media != null) { - float prog = progress / ((float) seekBar.getMax()); - int duration = media.getDuration(); - txtvPosition.setText(Converter - .getDurationStringLong((int) (prog * duration))); - return prog; - } - return 0; - - } - - /** - * Should be used by classes which implement the OnSeekBarChanged interface. - */ - public void onSeekBarStartTrackingTouch(SeekBar seekBar) { - // interrupt position Observer, restart later - cancelPositionObserver(); - } - - /** - * Should be used by classes which implement the OnSeekBarChanged interface. - */ - public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) { - if (playbackService != null) { - playbackService.seekTo((int) (prog * media.getDuration())); - setupPositionObserver(); - } - } - - /** - * Should be implemented by classes that show a video. The default implementation - * does nothing - * - * @param enable True if the screen should be kept on, false otherwise - */ - protected void setScreenOn(boolean enable) { - - } - - public OnClickListener newOnPlayButtonClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - if (playbackService != null) { - switch (status) { - case PLAYING: - playbackService.pause(true, reinitOnPause); - break; - case PAUSED: - case PREPARED: - playbackService.resume(); - break; - case PREPARING: - playbackService.setStartWhenPrepared(!playbackService - .isStartWhenPrepared()); - if (reinitOnPause - && playbackService.isStartWhenPrepared() == false) { - playbackService.reinit(); - } - break; - case INITIALIZED: - playbackService.setStartWhenPrepared(true); - playbackService.prepare(); - break; - } - } else { - Log.w(TAG, - "Play/Pause button was pressed, but playbackservice was null!"); - } - } - - }; - } - - public OnClickListener newOnRevButtonClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - if (status == PlayerStatus.PLAYING) { - playbackService.seekDelta(-UserPreferences.getSeekDeltaMs()); - } - } - }; - } - - public OnClickListener newOnFFButtonClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - if (status == PlayerStatus.PLAYING) { - playbackService.seekDelta(UserPreferences.getSeekDeltaMs()); - } - } - }; - } - - public boolean serviceAvailable() { - return playbackService != null; - } - - public int getPosition() { - if (playbackService != null) { - return playbackService.getCurrentPosition(); - } else { - return PlaybackService.INVALID_TIME; - } - } - - public int getDuration() { - if (playbackService != null) { - return playbackService.getDuration(); - } else { - return PlaybackService.INVALID_TIME; - } - } - - public Playable getMedia() { - return media; - } - - public boolean sleepTimerActive() { - return playbackService != null && playbackService.sleepTimerActive(); - } - - public boolean sleepTimerNotActive() { - return playbackService != null && !playbackService.sleepTimerActive(); - } - - public void disableSleepTimer() { - if (playbackService != null) { - playbackService.disableSleepTimer(); - } - } - - public long getSleepTimerTimeLeft() { - if (playbackService != null) { - return playbackService.getSleepTimerTimeLeft(); - } else { - return INVALID_TIME; - } - } - - public void setSleepTimer(long time) { - if (playbackService != null) { - playbackService.setSleepTimer(time); - } - } - - public void seekToChapter(Chapter chapter) { - if (playbackService != null) { - playbackService.seekToChapter(chapter); - } - } - - public void seekTo(int time) { - if (playbackService != null) { - playbackService.seekTo(time); - } - } - - public void setVideoSurface(SurfaceHolder holder) { - if (playbackService != null) { - playbackService.setVideoSurface(holder); - } - } - - public PlayerStatus getStatus() { - return status; - } - - public boolean canSetPlaybackSpeed() { - return playbackService != null && playbackService.canSetSpeed(); - } - - public void setPlaybackSpeed(float speed) { - if (playbackService != null) { - playbackService.setSpeed(speed); - } - } - - public float getCurrentPlaybackSpeedMultiplier() { - if (canSetPlaybackSpeed()) { - return playbackService.getCurrentPlaybackSpeed(); - } else { - return -1; - } - } - - public boolean isPlayingVideo() { - if (playbackService != null) { - return PlaybackService.getCurrentMediaType() == MediaType.VIDEO; - } - return false; - } - - public Pair getVideoSize() { - if (playbackService != null) { - return playbackService.getVideoSize(); - } else { - return null; - } - } - - - /** - * Returns true if PlaybackController can communicate with the playback - * service. - */ - public boolean isConnectedToPlaybackService() { - return playbackService != null; - } - - public void notifyVideoSurfaceAbandoned() { - if (playbackService != null) { - playbackService.notifyVideoSurfaceAbandoned(); - } - } - - /** - * Move service into INITIALIZED state if it's paused to save bandwidth - */ - public void reinitServiceIfPaused() { - if (playbackService != null - && playbackService.isStreaming() - && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService - .getStatus() == PlayerStatus.PREPARING && playbackService - .isStartWhenPrepared() == false))) { - playbackService.reinit(); - } - } - - /** - * Refreshes the current position of the media file that is playing. - */ - public class MediaPositionObserver implements Runnable { - - public static final int WAITING_INTERVALL = 1000; - - @Override - public void run() { - if (playbackService != null && playbackService.getStatus() == PlayerStatus.PLAYING) { - activity.runOnUiThread(new Runnable() { - - @Override - public void run() { - onPositionObserverUpdate(); - } - }); - } - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java deleted file mode 100644 index 5177bbca3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ /dev/null @@ -1,161 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.Log; -import android.util.TypedValue; - -import org.apache.commons.lang3.Validate; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.ShownotesProvider; - -/** - * Connects chapter information and shownotes of a shownotesProvider, for example by making it possible to use the - * shownotes to navigate to another position in the podcast or by highlighting certain parts of the shownotesProvider's - * shownotes. - *

- * A timeline object needs a shownotesProvider from which the chapter information is retrieved and shownotes are generated. - */ -public class Timeline { - private static final String TAG = "Timeline"; - - private static final String WEBVIEW_STYLE = "@font-face { font-family: 'Roboto-Light'; src: url('file:///android_asset/Roboto-Light.ttf'); } * { color: %s; font-family: roboto-Light; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } a.timecode { color: #669900; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }"; - - - private ShownotesProvider shownotesProvider; - - - private final String colorString; - private final int pageMargin; - - public Timeline(Context context, ShownotesProvider shownotesProvider) { - if (shownotesProvider == null) throw new IllegalArgumentException("shownotesProvider = null"); - this.shownotesProvider = shownotesProvider; - - TypedArray res = context - .getTheme() - .obtainStyledAttributes( - new int[]{android.R.attr.textColorPrimary}); - int colorResource = res.getColor(0, 0); - colorString = String.format("#%06X", - 0xFFFFFF & colorResource); - res.recycle(); - - pageMargin = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 8, context.getResources() - .getDisplayMetrics() - ); - } - - private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))"); - private static final String TIMECODE_LINK = "%s"; - private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(([0-9][0-9])):))?(([0-9][0-9])):(([0-9][0-9]))\\b"); - - /** - * Applies an app-specific CSS stylesheet and adds timecode links (optional). - *

- * This method does NOT change the original shownotes string of the shownotesProvider object and it should - * also not be changed by the caller. - * - * @param addTimecodes True if this method should add timecode links - * @return The processed HTML string. - */ - public String processShownotes(final boolean addTimecodes) { - final Playable playable = (shownotesProvider instanceof Playable) ? (Playable) shownotesProvider : null; - - // load shownotes - - String shownotes; - try { - shownotes = shownotesProvider.loadShownotes().call(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - if (shownotes == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "shownotesProvider contained no shownotes. Returning empty string"); - return ""; - } - - Document document = Jsoup.parse(shownotes); - - // apply style - String styleStr = String.format(WEBVIEW_STYLE, colorString, "100%", pageMargin, - pageMargin, pageMargin, pageMargin); - document.head().appendElement("style").attr("type", "text/css").text(styleStr); - - // apply timecode links - if (addTimecodes) { - Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX); - if (BuildConfig.DEBUG) - Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes"); - for (Element element : elementsWithTimeCodes) { - Matcher matcherLong = TIMECODE_REGEX.matcher(element.text()); - StringBuffer buffer = new StringBuffer(); - while (matcherLong.find()) { - String h = matcherLong.group(1); - String group = matcherLong.group(0); - int time = (h != null) ? Converter.durationStringLongToMs(group) : - Converter.durationStringShortToMs(group); - - String rep; - if (playable == null || playable.getDuration() > time) { - rep = String.format(TIMECODE_LINK, time, group); - } else { - rep = group; - } - matcherLong.appendReplacement(buffer, rep); - } - matcherLong.appendTail(buffer); - - element.html(buffer.toString()); - } - } - - Log.i(TAG, "Out: " + document.toString()); - return document.toString(); - } - - - /** - * Returns true if the given link is a timecode link. - */ - public static boolean isTimecodeLink(String link) { - return link != null && link.matches(TIMECODE_LINK_REGEX.pattern()); - } - - /** - * Returns the time in milliseconds that is attached to this link or -1 - * if the link is no valid timecode link. - */ - public static int getTimecodeLinkTime(String link) { - if (isTimecodeLink(link)) { - Matcher m = TIMECODE_LINK_REGEX.matcher(link); - - try { - if (m.find()) { - return Integer.valueOf(m.group(1)); - } - } catch (NumberFormatException e) { - e.printStackTrace(); - } - } - return -1; - } - - - public void setShownotesProvider(ShownotesProvider shownotesProvider) { - Validate.notNull(shownotesProvider); - this.shownotesProvider = shownotesProvider; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java b/app/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java deleted file mode 100644 index dc5270d8f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.media.MediaPlayer; -import android.util.Log; - -public class VideoPlayer extends MediaPlayer implements IPlayer { - private static final String TAG = "VideoPlayer"; - - @Override - public boolean canSetPitch() { - return false; - } - - @Override - public boolean canSetSpeed() { - return false; - } - - @Override - public float getCurrentPitchStepsAdjustment() { - return 1; - } - - @Override - public float getCurrentSpeedMultiplier() { - return 1; - } - - @Override - public float getMaxSpeedMultiplier() { - return 1; - } - - @Override - public float getMinSpeedMultiplier() { - return 1; - } - - @Override - public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException { - Log.e(TAG, "Setting enable speed adjustment unsupported in video player"); - throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player"); - } - - @Override - public void setPitchStepsAdjustment(float pitchSteps) { - Log.e(TAG, "Setting pitch steps adjustment unsupported in video player"); - throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player"); - } - - @Override - public void setPlaybackPitch(float f) { - Log.e(TAG, "Setting playback pitch unsupported in video player"); - throw new UnsupportedOperationException("Setting playback pitch unsupported in video player"); - } - - @Override - public void setPlaybackSpeed(float f) { - Log.e(TAG, "Setting playback speed unsupported in video player"); - throw new UnsupportedOperationException("Setting playback speed unsupported in video player"); - } - - @Override - public void setVideoScalingMode(int mode) { - super.setVideoScalingMode(mode); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java b/app/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java deleted file mode 100644 index 9588265b8..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java +++ /dev/null @@ -1,78 +0,0 @@ -package de.danoeh.antennapod.core.util.syndication; - -import android.net.Uri; -import org.apache.commons.lang3.StringUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.io.File; -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Finds RSS/Atom URLs in a HTML document using the auto-discovery techniques described here: - *

- * http://www.rssboard.org/rss-autodiscovery - *

- * http://blog.whatwg.org/feed-autodiscovery - */ -public class FeedDiscoverer { - - private static final String MIME_RSS = "application/rss+xml"; - private static final String MIME_ATOM = "application/atom+xml"; - - /** - * Discovers links to RSS and Atom feeds in the given File which must be a HTML document. - * - * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if - * a title cannot be found). - */ - public Map findLinks(File in, String baseUrl) throws IOException { - return findLinks(Jsoup.parse(in, null), baseUrl); - } - - /** - * Discovers links to RSS and Atom feeds in the given File which must be a HTML document. - * - * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if - * a title cannot be found). - */ - public Map findLinks(String in, String baseUrl) throws IOException { - return findLinks(Jsoup.parse(in), baseUrl); - } - - private Map findLinks(Document document, String baseUrl) { - Map res = new LinkedHashMap(); - Elements links = document.head().getElementsByTag("link"); - for (Element link : links) { - String rel = link.attr("rel"); - String href = link.attr("href"); - if (!StringUtils.isEmpty(href) && - (rel.equals("alternate") || rel.equals("feed"))) { - String type = link.attr("type"); - if (type.equals(MIME_RSS) || type.equals(MIME_ATOM)) { - String title = link.attr("title"); - String processedUrl = processURL(baseUrl, href); - if (processedUrl != null) { - res.put(processedUrl, - (StringUtils.isEmpty(title)) ? href : title); - } - } - } - } - return res; - } - - private String processURL(String baseUrl, String strUrl) { - Uri uri = Uri.parse(strUrl); - if (uri.isRelative()) { - Uri res = Uri.parse(baseUrl).buildUpon().path(strUrl).build(); - return (res != null) ? res.toString() : null; - } else { - return strUrl; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java b/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java deleted file mode 100644 index 4799d3881..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java +++ /dev/null @@ -1,81 +0,0 @@ -package de.danoeh.antennapod.core.util.vorbiscommentreader; - -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; - -public class OggInputStream extends InputStream { - private InputStream input; - - /** True if OggInputStream is currently inside an Ogg page. */ - private boolean isInPage; - private long bytesLeft; - - public OggInputStream(InputStream input) { - super(); - isInPage = false; - this.input = input; - } - - @Override - public int read() throws IOException { - if (!isInPage) { - readOggPage(); - } - - if (isInPage && bytesLeft > 0) { - int result = input.read(); - bytesLeft -= 1; - if (bytesLeft == 0) { - isInPage = false; - } - return result; - } - return -1; - } - - private void readOggPage() throws IOException { - // find OggS - int[] buffer = new int[4]; - int c = 0; - boolean isInOggS = false; - while ((c = input.read()) != -1) { - switch (c) { - case 'O': - isInOggS = true; - buffer[0] = c; - break; - case 'g': - if (buffer[1] != c) { - buffer[1] = c; - } else { - buffer[2] = c; - } - break; - case 'S': - buffer[3] = c; - break; - default: - if (isInOggS) { - Arrays.fill(buffer, 0); - isInOggS = false; - } - } - if (buffer[0] == 'O' && buffer[1] == 'g' && buffer[2] == 'g' - && buffer[3] == 'S') { - break; - } - } - // read segments - IOUtils.skipFully(input, 22); - bytesLeft = 0; - int numSegments = input.read(); - for (int i = 0; i < numSegments; i++) { - bytesLeft += input.read(); - } - isInPage = true; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java b/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java deleted file mode 100644 index a6934c60e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java +++ /dev/null @@ -1,101 +0,0 @@ -package de.danoeh.antennapod.core.util.vorbiscommentreader; - -import android.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.VorbisCommentChapter; - -import java.util.ArrayList; -import java.util.List; - -public class VorbisCommentChapterReader extends VorbisCommentReader { - private static final String TAG = "VorbisCommentChapterReader"; - - private static final String CHAPTER_KEY = "chapter\\d\\d\\d.*"; - private static final String CHAPTER_ATTRIBUTE_TITLE = "name"; - private static final String CHAPTER_ATTRIBUTE_LINK = "url"; - - private List chapters; - - public VorbisCommentChapterReader() { - } - - @Override - public void onVorbisCommentFound() { - System.out.println("Vorbis comment found"); - } - - @Override - public void onVorbisCommentHeaderFound(VorbisCommentHeader header) { - chapters = new ArrayList(); - System.out.println(header.toString()); - } - - @Override - public boolean onContentVectorKey(String content) { - return content.matches(CHAPTER_KEY); - } - - @Override - public void onContentVectorValue(String key, String value) - throws VorbisCommentReaderException { - if (BuildConfig.DEBUG) - Log.d(TAG, "Key: " + key + ", value: " + value); - String attribute = VorbisCommentChapter.getAttributeTypeFromKey(key); - int id = VorbisCommentChapter.getIDFromKey(key); - Chapter chapter = getChapterById(id); - if (attribute == null) { - if (getChapterById(id) == null) { - // new chapter - long start = VorbisCommentChapter.getStartTimeFromValue(value); - chapter = new VorbisCommentChapter(id); - chapter.setStart(start); - chapters.add(chapter); - } else { - throw new VorbisCommentReaderException( - "Found chapter with duplicate ID (" + key + ", " - + value + ")"); - } - } else if (attribute.equals(CHAPTER_ATTRIBUTE_TITLE)) { - if (chapter != null) { - chapter.setTitle(value); - } - } else if (attribute.equals(CHAPTER_ATTRIBUTE_LINK)) { - if (chapter != null) { - chapter.setLink(value); - } - } - } - - @Override - public void onNoVorbisCommentFound() { - System.out.println("No vorbis comment found"); - } - - @Override - public void onEndOfComment() { - System.out.println("End of comment"); - for (Chapter c : chapters) { - System.out.println(c.toString()); - } - } - - @Override - public void onError(VorbisCommentReaderException exception) { - exception.printStackTrace(); - } - - private Chapter getChapterById(long id) { - for (Chapter c : chapters) { - if (((VorbisCommentChapter) c).getVorbisCommentId() == id) { - return c; - } - } - return null; - } - - public List getChapters() { - return chapters; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java b/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java deleted file mode 100644 index 5f9dd0faf..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.danoeh.antennapod.core.util.vorbiscommentreader; -public class VorbisCommentHeader { - private String vendorString; - private long userCommentLength; - - public VorbisCommentHeader(String vendorString, long userCommentLength) { - super(); - this.vendorString = vendorString; - this.userCommentLength = userCommentLength; - } - - @Override - public String toString() { - return "VorbisCommentHeader [vendorString=" + vendorString - + ", userCommentLength=" + userCommentLength + "]"; - } - - public String getVendorString() { - return vendorString; - } - - public long getUserCommentLength() { - return userCommentLength; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java b/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java deleted file mode 100644 index 9639b9c42..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java +++ /dev/null @@ -1,194 +0,0 @@ -package de.danoeh.antennapod.core.util.vorbiscommentreader; - -import org.apache.commons.io.EndianUtils; -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.Arrays; - - -public abstract class VorbisCommentReader { - /** Length of first page in an ogg file in bytes. */ - private static final int FIRST_PAGE_LENGTH = 58; - private static final int SECOND_PAGE_MAX_LENGTH = 64 * 1024 * 1024; - private static final int PACKET_TYPE_IDENTIFICATION = 1; - private static final int PACKET_TYPE_COMMENT = 3; - - /** Called when Reader finds identification header. */ - public abstract void onVorbisCommentFound(); - - public abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header); - - /** - * Is called every time the Reader finds a content vector. The handler - * should return true if it wants to handle the content vector. - */ - public abstract boolean onContentVectorKey(String content); - - /** - * Is called if onContentVectorKey returned true for the key. - * - * @throws VorbisCommentReaderException - */ - public abstract void onContentVectorValue(String key, String value) - throws VorbisCommentReaderException; - - public abstract void onNoVorbisCommentFound(); - - public abstract void onEndOfComment(); - - public abstract void onError(VorbisCommentReaderException exception); - - public void readInputStream(InputStream input) - throws VorbisCommentReaderException { - try { - // look for identification header - if (findIdentificationHeader(input)) { - - onVorbisCommentFound(); - input = new OggInputStream(input); - if (findCommentHeader(input)) { - VorbisCommentHeader commentHeader = readCommentHeader(input); - if (commentHeader != null) { - onVorbisCommentHeaderFound(commentHeader); - for (int i = 0; i < commentHeader - .getUserCommentLength(); i++) { - try { - long vectorLength = EndianUtils - .readSwappedUnsignedInteger(input); - String key = readContentVectorKey(input, - vectorLength).toLowerCase(); - boolean readValue = onContentVectorKey(key); - if (readValue) { - String value = readUTF8String( - input, - (int) (vectorLength - key.length() - 1)); - onContentVectorValue(key, value); - } else { - IOUtils.skipFully(input, - vectorLength - key.length() - 1); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - onEndOfComment(); - } - - } else { - onError(new VorbisCommentReaderException( - "No comment header found")); - } - } else { - onNoVorbisCommentFound(); - } - } catch (IOException e) { - onError(new VorbisCommentReaderException(e)); - } - } - - private String readUTF8String(InputStream input, long length) - throws IOException { - byte[] buffer = new byte[(int) length]; - - IOUtils.readFully(input, buffer); - Charset charset = Charset.forName("UTF-8"); - return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); - } - - /** - * Looks for an identification header in the first page of the file. If an - * identification header is found, it will be skipped completely and the - * method will return true, otherwise false. - * - * @throws IOException - */ - private boolean findIdentificationHeader(InputStream input) - throws IOException { - byte[] buffer = new byte[FIRST_PAGE_LENGTH]; - IOUtils.readFully(input, buffer); - int i; - for (i = 6; i < buffer.length; i++) { - if (buffer[i - 5] == 'v' && buffer[i - 4] == 'o' - && buffer[i - 3] == 'r' && buffer[i - 2] == 'b' - && buffer[i - 1] == 'i' && buffer[i] == 's' - && buffer[i - 6] == PACKET_TYPE_IDENTIFICATION) { - return true; - } - } - return false; - } - - private boolean findCommentHeader(InputStream input) throws IOException { - char[] buffer = new char["vorbis".length() + 1]; - for (int bytesRead = 0; bytesRead < SECOND_PAGE_MAX_LENGTH; bytesRead++) { - char c = (char) input.read(); - int dest = -1; - switch (c) { - case PACKET_TYPE_COMMENT: - dest = 0; - break; - case 'v': - dest = 1; - break; - case 'o': - dest = 2; - break; - case 'r': - dest = 3; - break; - case 'b': - dest = 4; - break; - case 'i': - dest = 5; - break; - case 's': - dest = 6; - break; - } - if (dest >= 0) { - buffer[dest] = c; - if (buffer[1] == 'v' && buffer[2] == 'o' && buffer[3] == 'r' - && buffer[4] == 'b' && buffer[5] == 'i' - && buffer[6] == 's' && buffer[0] == PACKET_TYPE_COMMENT) { - return true; - } - } else { - Arrays.fill(buffer, (char) 0); - } - } - return false; - } - - private VorbisCommentHeader readCommentHeader(InputStream input) - throws IOException, VorbisCommentReaderException { - try { - long vendorLength = EndianUtils.readSwappedUnsignedInteger(input); - String vendorName = readUTF8String(input, vendorLength); - long userCommentLength = EndianUtils - .readSwappedUnsignedInteger(input); - return new VorbisCommentHeader(vendorName, userCommentLength); - } catch (UnsupportedEncodingException e) { - throw new VorbisCommentReaderException(e); - } - } - - private String readContentVectorKey(InputStream input, long vectorLength) - throws IOException { - StringBuffer buffer = new StringBuffer(); - for (int i = 0; i < vectorLength; i++) { - char c = (char) input.read(); - if (c == '=') { - return buffer.toString(); - } else { - buffer.append(c); - } - } - return null; // no key found - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReaderException.java b/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReaderException.java deleted file mode 100644 index 89ab20db0..000000000 --- a/app/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReaderException.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.danoeh.antennapod.core.util.vorbiscommentreader; -public class VorbisCommentReaderException extends Exception { - - public VorbisCommentReaderException() { - super(); - // TODO Auto-generated constructor stub - } - - public VorbisCommentReaderException(String arg0, Throwable arg1) { - super(arg0, arg1); - // TODO Auto-generated constructor stub - } - - public VorbisCommentReaderException(String arg0) { - super(arg0); - // TODO Auto-generated constructor stub - } - - public VorbisCommentReaderException(Throwable arg0) { - super(arg0); - // TODO Auto-generated constructor stub - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java index e62daa08b..8cdddc121 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java @@ -42,7 +42,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.QueueAccess; import de.danoeh.antennapod.core.util.ShownotesProvider; -import de.danoeh.antennapod.core.util.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; /** * Shows information about a specific FeedItem and provides actions like playing, downloading, etc. diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java new file mode 100644 index 000000000..6561d501e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java @@ -0,0 +1,138 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; +import android.widget.*; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.R; + +import java.util.concurrent.TimeUnit; + +public abstract class TimeDialog extends Dialog { + private static final String TAG = "TimeDialog"; + + private static final int DEFAULT_SPINNER_POSITION = 1; + + private Context context; + + private EditText etxtTime; + private Spinner spTimeUnit; + private Button butConfirm; + private Button butCancel; + + private TimeUnit[] units = {TimeUnit.SECONDS, TimeUnit.MINUTES, + TimeUnit.HOURS}; + + public TimeDialog(Context context, int titleTextId, int leftButtonTextId) { + super(context); + this.context = context; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + String[] spinnerContent = new String[]{context.getString(R.string.time_unit_seconds), + context.getString(R.string.time_unit_minutes), + context.getString(R.string.time_unit_hours)}; + + setContentView(R.layout.time_dialog); + etxtTime = (EditText) findViewById(R.id.etxtTime); + spTimeUnit = (Spinner) findViewById(R.id.spTimeUnit); + butConfirm = (Button) findViewById(R.id.butConfirm); + butCancel = (Button) findViewById(R.id.butCancel); + + butConfirm.setText(R.string.set_sleeptimer_label); + butCancel.setText(R.string.cancel_label); + setTitle(R.string.set_sleeptimer_label); + ArrayAdapter spinnerAdapter = new ArrayAdapter( + this.getContext(), android.R.layout.simple_spinner_item, + spinnerContent); + spinnerAdapter + .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spTimeUnit.setAdapter(spinnerAdapter); + spTimeUnit.setSelection(DEFAULT_SPINNER_POSITION); + butCancel.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + dismiss(); + } + }); + butConfirm.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + try { + long input = readTimeMillis(); + onTimeEntered(input); + dismiss(); + } catch (NumberFormatException e) { + e.printStackTrace(); + Toast toast = Toast.makeText(context, + R.string.time_dialog_invalid_input, + Toast.LENGTH_LONG); + toast.show(); + } + } + }); + etxtTime.addTextChangedListener(new TextWatcher() { + + @Override + public void afterTextChanged(Editable s) { + checkInputLength(s.length()); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + + } + }); + checkInputLength(etxtTime.getText().length()); + etxtTime.postDelayed(new Runnable() { + @Override + public void run() { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); + } + }, 100); + + + + } + + private void checkInputLength(int length) { + if (length > 0) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Length is larger than 0, enabling confirm button"); + butConfirm.setEnabled(true); + } else { + if (BuildConfig.DEBUG) + Log.d(TAG, "Length is smaller than 0, disabling confirm button"); + butConfirm.setEnabled(false); + } + } + + public abstract void onTimeEntered(long millis); + + private long readTimeMillis() { + TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()]; + long value = Long.valueOf(etxtTime.getText().toString()); + return selectedUnit.toMillis(value); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java index b16e4f930..9eaeb56dd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -48,9 +48,9 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.menuhandler.FeedMenuHandler; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.FeedMenuHandler; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; /** * Displays a list of FeedItems. diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index 4f37f4613..d126f2980 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -33,8 +33,8 @@ import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import java.util.List; import java.util.concurrent.atomic.AtomicReference; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 61e4ae1bb..4a07ce2b7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -25,8 +25,8 @@ import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import java.util.List; import java.util.concurrent.atomic.AtomicReference; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index c1191d933..3192a84de 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -37,8 +37,8 @@ import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; /** * Shows all items in the queue diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 23cc1d0b8..c16ba426e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -20,8 +20,8 @@ import de.danoeh.antennapod.core.feed.*; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.FeedSearcher; import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import java.util.List; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index 14b3a9c40..15a0b55b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -18,8 +18,8 @@ import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter; import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import java.util.List; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java index b099953a8..635842196 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java @@ -13,8 +13,8 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; /** * Performs a search on the gpodder.net directory and displays the results. diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index 819a28c2d..24e0e4caa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -21,8 +21,8 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag; -import de.danoeh.antennapod.core.util.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.core.util.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; public class TagListFragment extends ListFragment { private static final String TAG = "TagListFragment"; diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java new file mode 100644 index 000000000..8ccbdafc6 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -0,0 +1,191 @@ +package de.danoeh.antennapod.menuhandler; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.QueueAccess; +import de.danoeh.antennapod.core.util.ShareUtils; + +/** + * Handles interactions with the FeedItemMenu. + */ +public class FeedItemMenuHandler { + private static final String TAG = "FeedItemMenuHandler"; + + private FeedItemMenuHandler() { + + } + + /** + * Used by the MenuHandler to access different types of menus through one + * interface + */ + public interface MenuInterface { + /** + * Implementations of this method should call findItem(id) on their + * menu-object and call setVisibility(visibility) on the returned + * MenuItem object. + */ + abstract void setItemVisibility(int id, boolean visible); + } + + /** + * This method should be called in the prepare-methods of menus. It changes + * the visibility of the menu items depending on a FeedItem's attributes. + * + * @param mi An instance of MenuInterface that the method uses to change a + * MenuItem's visibility + * @param selectedItem The FeedItem for which the menu is supposed to be prepared + * @param showExtendedMenu True if MenuItems that let the user share information about + * the FeedItem and visit its website should be set visible. This + * parameter should be set to false if the menu space is limited. + * @param queueAccess Used for testing if the queue contains the selected item + * @return Returns true if selectedItem is not null. + */ + public static boolean onPrepareMenu(MenuInterface mi, + FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) { + if (selectedItem == null) { + return false; + } + DownloadRequester requester = DownloadRequester.getInstance(); + boolean hasMedia = selectedItem.getMedia() != null; + boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded(); + boolean downloading = hasMedia + && requester.isDownloadingFile(selectedItem.getMedia()); + boolean notLoadedAndNotLoading = hasMedia && (!downloaded) + && (!downloading); + boolean isPlaying = hasMedia + && selectedItem.getState() == FeedItem.State.PLAYING; + + FeedItem.State state = selectedItem.getState(); + + if (!isPlaying) { + mi.setItemVisibility(R.id.skip_episode_item, false); + } + if (!downloaded || isPlaying) { + mi.setItemVisibility(R.id.play_item, false); + mi.setItemVisibility(R.id.remove_item, false); + } + if (!notLoadedAndNotLoading) { + mi.setItemVisibility(R.id.download_item, false); + } + if (!(notLoadedAndNotLoading | downloading) | isPlaying) { + mi.setItemVisibility(R.id.stream_item, false); + } + if (!downloading) { + mi.setItemVisibility(R.id.cancel_download_item, false); + } + + boolean isInQueue = queueAccess.contains(selectedItem.getId()); + if (!isInQueue || isPlaying) { + mi.setItemVisibility(R.id.remove_from_queue_item, false); + } + if (!(!isInQueue && selectedItem.getMedia() != null)) { + mi.setItemVisibility(R.id.add_to_queue_item, false); + } + if (!showExtendedMenu || selectedItem.getLink() == null) { + mi.setItemVisibility(R.id.share_link_item, false); + } + + if (!BuildConfig.DEBUG + || !(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) { + mi.setItemVisibility(R.id.mark_unread_item, false); + } + if (!(state == FeedItem.State.NEW || state == FeedItem.State.IN_PROGRESS)) { + mi.setItemVisibility(R.id.mark_read_item, false); + } + + if (!showExtendedMenu || selectedItem.getLink() == null) { + mi.setItemVisibility(R.id.visit_website_item, false); + } + + if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) { + mi.setItemVisibility(R.id.support_item, false); + } + return true; + } + + /** + * The same method as onPrepareMenu(MenuInterface, FeedItem, boolean, QueueAccess), but lets the + * caller also specify a list of menu items that should not be shown. + * + * @param excludeIds Menu item that should be excluded + * @return true if selectedItem is not null. + */ + public static boolean onPrepareMenu(MenuInterface mi, + FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess, int... excludeIds) { + boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess); + if (rc && excludeIds != null) { + for (int id : excludeIds) { + mi.setItemVisibility(id, false); + } + } + + return rc; + } + + public static boolean onMenuItemClicked(Context context, int menuItemId, + FeedItem selectedItem) throws DownloadRequestException { + DownloadRequester requester = DownloadRequester.getInstance(); + switch (menuItemId) { + case R.id.skip_episode_item: + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); + break; + case R.id.download_item: + DBTasks.downloadFeedItems(context, selectedItem); + break; + case R.id.play_item: + DBTasks.playMedia(context, selectedItem.getMedia(), true, true, + false); + break; + case R.id.remove_item: + DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()); + break; + case R.id.cancel_download_item: + requester.cancelDownload(context, selectedItem.getMedia()); + break; + case R.id.mark_read_item: + DBWriter.markItemRead(context, selectedItem, true, true); + break; + case R.id.mark_unread_item: + DBWriter.markItemRead(context, selectedItem, false, true); + break; + case R.id.add_to_queue_item: + DBWriter.addQueueItem(context, selectedItem.getId()); + break; + case R.id.remove_from_queue_item: + DBWriter.removeQueueItem(context, selectedItem.getId(), true); + break; + case R.id.stream_item: + DBTasks.playMedia(context, selectedItem.getMedia(), true, true, + true); + break; + case R.id.visit_website_item: + Uri uri = Uri.parse(selectedItem.getLink()); + context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + break; + case R.id.support_item: + DBTasks.flattrItemIfLoggedIn(context, selectedItem); + break; + case R.id.share_link_item: + ShareUtils.shareFeedItemLink(context, selectedItem); + break; + default: + return false; + } + // Refresh menu state + + return true; + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java new file mode 100644 index 000000000..62ae28820 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -0,0 +1,86 @@ +package de.danoeh.antennapod.menuhandler; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.service.download.DownloadService; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.ShareUtils; + +/** Handles interactions with the FeedItemMenu. */ +public class FeedMenuHandler { + private static final String TAG = "FeedMenuHandler"; + + public static boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { + inflater.inflate(R.menu.feedlist, menu); + return true; + } + + public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) { + if (selectedFeed == null) { + return true; + } + + if (BuildConfig.DEBUG) + Log.d(TAG, "Preparing options menu"); + menu.findItem(R.id.mark_all_read_item).setVisible( + selectedFeed.hasNewItems(true)); + if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) + menu.findItem(R.id.support_item).setVisible(true); + else + menu.findItem(R.id.support_item).setVisible(false); + MenuItem refresh = menu.findItem(R.id.refresh_item); + if (DownloadService.isRunning + && DownloadRequester.getInstance().isDownloadingFile( + selectedFeed)) { + refresh.setVisible(false); + } else { + refresh.setVisible(true); + } + + return true; + } + + /** + * NOTE: This method does not handle clicks on the 'remove feed' - item. + * + * @throws DownloadRequestException + */ + public static boolean onOptionsItemClicked(Context context, MenuItem item, + Feed selectedFeed) throws DownloadRequestException { + switch (item.getItemId()) { + case R.id.refresh_item: + DBTasks.refreshFeed(context, selectedFeed); + break; + case R.id.mark_all_read_item: + DBWriter.markFeedRead(context, selectedFeed.getId()); + break; + case R.id.visit_website_item: + Uri uri = Uri.parse(selectedFeed.getLink()); + context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + break; + case R.id.support_item: + DBTasks.flattrFeedIfLoggedIn(context, selectedFeed); + break; + case R.id.share_link_item: + ShareUtils.shareFeedlink(context, selectedFeed); + break; + case R.id.share_source_item: + ShareUtils.shareFeedDownloadLink(context, selectedFeed); + break; + default: + return false; + } + return true; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java new file mode 100644 index 000000000..c4a96ac3f --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -0,0 +1,31 @@ +package de.danoeh.antennapod.menuhandler; + +import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuItem; + +import de.danoeh.antennapod.core.R; + +/** + * Utilities for menu items + */ +public class MenuItemUtils { + + public static MenuItem addSearchItem(Menu menu, SearchView searchView) { + MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + MenuItemCompat.setActionView(item, searchView); + return item; + } + + /** + * Checks if the navigation drawer of the DrawerActivity is opened. This can be useful for Fragments + * that hide their menu if the navigation drawer is open. + * + * @return True if the drawer is open, false otherwise (also if the parameter is null) + */ + public static boolean isActivityDrawerOpen(NavDrawerActivity activity) { + return activity != null && activity.isDrawerOpen(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java new file mode 100644 index 000000000..6ceaaada4 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java @@ -0,0 +1,9 @@ +package de.danoeh.antennapod.menuhandler; + +/** + * Defines useful methods for activities that have a navigation drawer + */ +public interface NavDrawerActivity { + + public boolean isDrawerOpen(); +} diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java new file mode 100644 index 000000000..7ab386edf --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java @@ -0,0 +1,49 @@ +package de.danoeh.antennapod.receiver; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import org.apache.commons.lang3.StringUtils; + +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.service.PlayerWidgetService; + +public class PlayerWidget extends AppWidgetProvider { + private static final String TAG = "PlayerWidget"; + + @Override + public void onReceive(Context context, Intent intent) { + if (StringUtils.equals(intent.getAction(), PlaybackService.FORCE_WIDGET_UPDATE)) { + startUpdate(context); + } else if (StringUtils.equals(intent.getAction(), PlaybackService.STOP_WIDGET_UPDATE)) { + stopUpdate(context); + } + + } + + @Override + public void onEnabled(Context context) { + super.onEnabled(context); + if (BuildConfig.DEBUG) + Log.d(TAG, "Widget enabled"); + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, + int[] appWidgetIds) { + startUpdate(context); + } + + private void startUpdate(Context context) { + context.startService(new Intent(context, PlayerWidgetService.class)); + } + + private void stopUpdate(Context context) { + context.stopService(new Intent(context, PlayerWidgetService.class)); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java b/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java new file mode 100644 index 000000000..514cbb74e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java @@ -0,0 +1,192 @@ +package de.danoeh.antennapod.service; + +import android.app.PendingIntent; +import android.app.Service; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.IBinder; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.widget.RemoteViews; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; +import de.danoeh.antennapod.receiver.PlayerWidget; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.service.playback.PlayerStatus; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.playback.Playable; + +/** Updates the state of the player widget */ +public class PlayerWidgetService extends Service { + private static final String TAG = "PlayerWidgetService"; + + private PlaybackService playbackService; + /** True while service is updating the widget */ + private volatile boolean isUpdating; + + public PlayerWidgetService() { + } + + @Override + public void onCreate() { + super.onCreate(); + if (BuildConfig.DEBUG) + Log.d(TAG, "Service created"); + isUpdating = false; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (BuildConfig.DEBUG) + Log.d(TAG, "Service is about to be destroyed"); + try { + unbindService(mConnection); + } catch (IllegalArgumentException e) { + Log.w(TAG, "IllegalArgumentException when trying to unbind service"); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (!isUpdating) { + if (playbackService == null && PlaybackService.isRunning) { + bindService(new Intent(this, PlaybackService.class), + mConnection, 0); + } else { + startViewUpdaterIfNotRunning(); + } + } else { + if (BuildConfig.DEBUG) + Log.d(TAG, + "Service was called while updating. Ignoring update request"); + } + return Service.START_NOT_STICKY; + } + + private void updateViews() { + if (playbackService == null) { + return; + } + isUpdating = true; + + ComponentName playerWidget = new ComponentName(this, PlayerWidget.class); + AppWidgetManager manager = AppWidgetManager.getInstance(this); + RemoteViews views = new RemoteViews(getPackageName(), + R.layout.player_widget); + PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0, + PlaybackService.getPlayerActivityIntent(this), 0); + + views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); + final Playable media = playbackService.getPlayable(); + if (playbackService != null && media != null) { + PlayerStatus status = playbackService.getStatus(); + + views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); + + if (status == PlayerStatus.PLAYING) { + String progressString = getProgressString(playbackService); + if (progressString != null) { + views.setTextViewText(R.id.txtvProgress, progressString); + } + views.setImageViewResource(R.id.butPlay, R.drawable.av_pause_dark); + if (Build.VERSION.SDK_INT >= 15) { + views.setContentDescription(R.id.butPlay, getString(R.string.pause_label)); + } + } else { + views.setImageViewResource(R.id.butPlay, R.drawable.av_play_dark); + if (Build.VERSION.SDK_INT >= 15) { + views.setContentDescription(R.id.butPlay, getString(R.string.play_label)); + } + } + views.setOnClickPendingIntent(R.id.butPlay, + createMediaButtonIntent()); + } else { + views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE); + views.setTextViewText(R.id.txtvTitle, + this.getString(R.string.no_media_playing_label)); + views.setImageViewResource(R.id.butPlay, R.drawable.av_play); + + } + + manager.updateAppWidget(playerWidget, views); + isUpdating = false; + } + + /** Creates an intent which fakes a mediabutton press */ + private PendingIntent createMediaButtonIntent() { + KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE); + Intent startingIntent = new Intent( + MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); + startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); + + return PendingIntent.getBroadcast(this, 0, startingIntent, 0); + } + + private String getProgressString(PlaybackService ps) { + int position = ps.getCurrentPosition(); + int duration = ps.getDuration(); + if (position != PlaybackService.INVALID_TIME + && duration != PlaybackService.INVALID_TIME) { + return Converter.getDurationStringLong(position) + " / " + + Converter.getDurationStringLong(duration); + } else { + return null; + } + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Connection to service established"); + playbackService = ((PlaybackService.LocalBinder) service) + .getService(); + startViewUpdaterIfNotRunning(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + playbackService = null; + if (BuildConfig.DEBUG) + Log.d(TAG, "Disconnected from service"); + } + + }; + + private void startViewUpdaterIfNotRunning() { + if (!isUpdating) { + ViewUpdater updateThread = new ViewUpdater(this); + updateThread.start(); + } + } + + static class ViewUpdater extends Thread { + private static final String THREAD_NAME = "ViewUpdater"; + private PlayerWidgetService service; + + public ViewUpdater(PlayerWidgetService service) { + super(); + setName(THREAD_NAME); + this.service = service; + + } + + @Override + public void run() { + service.updateViews(); + } + + } + +} -- cgit v1.2.3