summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/build.gradle59
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java27
-rw-r--r--core/src/main/AndroidManifest.xml2
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl18
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl19
-rw-r--r--core/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl75
-rw-r--r--core/src/main/java/com/aocate/media/AndroidMediaPlayer.java470
-rw-r--r--core/src/main/java/com/aocate/media/MediaPlayer.java1310
-rw-r--r--core/src/main/java/com/aocate/media/MediaPlayerImpl.java118
-rw-r--r--core/src/main/java/com/aocate/media/ServiceBackedMediaPlayer.java1203
-rw-r--r--core/src/main/java/com/aocate/media/SpeedAdjustmentAlgorithm.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java27
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java177
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java (renamed from core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java)2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java510
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java53
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java38
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java48
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java36
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java83
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java92
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java111
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java161
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java84
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java188
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java102
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java51
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java141
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java267
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java138
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java76
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java193
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java790
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java54
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java168
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java22
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java348
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java42
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java62
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/MediaButtonIntentReceiver.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java462
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java479
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java135
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java64
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java92
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java39
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java81
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java139
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java72
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java854
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java194
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java1196
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java45
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java51
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java862
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java38
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java25
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Converter.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java78
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java108
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java47
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java49
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java59
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java125
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java165
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java11
-rw-r--r--core/src/main/res/drawable-hdpi-v11/stat_notify_sync.pngbin1012 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi-v11/stat_notify_sync_error.pngbin1103 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_av_fast_forward_80dp.pngbin0 -> 1228 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_av_rewind_80dp.pngbin0 -> 1277 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_grey600_24dp.pngbin0 -> 584 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_grey600_24dp.pngbin0 -> 397 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.pngbin0 -> 340 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_white_24dp.pngbin0 -> 505 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_drag_handle.9.pngbin268 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_drag_handle_dark.9.pngbin244 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_drag_vertical_grey600_48dp.9.pngbin0 -> 389 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_drag_vertical_white_48dp.9.pngbin0 -> 361 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.pngbin2359 -> 1159 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_white_24dp.pngbin1663 -> 727 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_grey600_24dp.pngbin0 -> 405 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_white_24dp.pngbin0 -> 348 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_skip_grey600_36dp.pngbin0 -> 302 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_skip_white_36dp.pngbin0 -> 304 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_grey600_24dp.pngbin0 -> 421 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_off_grey600_24dp.pngbin0 -> 659 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_off_white_24dp.pngbin0 -> 431 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_white_24dp.pngbin0 -> 361 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sort_grey600_24dp.pngbin0 -> 264 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sort_white_24dp.pngbin0 -> 238 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_border_grey600_24dp.pngbin0 -> 637 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_border_white_24dp.pngbin0 -> 637 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_grey600_24dp.pngbin0 -> 460 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_white_24dp.pngbin0 -> 454 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_widget_preview.pngbin0 -> 18320 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/stat_notify_sync.pngbin674 -> 421 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/stat_notify_sync_error.pngbin708 -> 436 bytes
-rw-r--r--core/src/main/res/drawable-mdpi-v11/stat_notify_sync.pngbin732 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi-v11/stat_notify_sync_error.pngbin746 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_av_fast_forward_80dp.pngbin0 -> 760 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_av_rewind_80dp.pngbin0 -> 853 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_grey600_24dp.pngbin0 -> 397 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_grey600_24dp.pngbin0 -> 254 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.pngbin0 -> 230 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_white_24dp.pngbin0 -> 331 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_drag_handle.9.pngbin217 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_drag_handle_dark.9.pngbin188 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_drag_vertical_grey600_48dp.9.pngbin0 -> 253 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_drag_vertical_white_48dp.9.pngbin0 -> 231 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.pngbin1566 -> 773 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_white_24dp.pngbin1156 -> 492 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_grey600_24dp.pngbin0 -> 259 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_white_24dp.pngbin0 -> 241 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_skip_grey600_36dp.pngbin0 -> 218 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_skip_white_36dp.pngbin0 -> 216 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_grey600_24dp.pngbin0 -> 336 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_off_grey600_24dp.pngbin0 -> 472 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_off_white_24dp.pngbin0 -> 323 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_white_24dp.pngbin0 -> 252 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sort_grey600_24dp.pngbin0 -> 193 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sort_white_24dp.pngbin0 -> 192 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_border_grey600_24dp.pngbin0 -> 410 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_border_white_24dp.pngbin0 -> 410 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_grey600_24dp.pngbin0 -> 307 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_white_24dp.pngbin0 -> 302 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/stat_notify_sync.pngbin628 -> 272 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/stat_notify_sync_error.pngbin627 -> 274 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi-v11/stat_notify_sync.pngbin1306 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi-v11/stat_notify_sync_error.pngbin1434 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_av_fast_forward_80dp.pngbin0 -> 1968 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_av_rewind_80dp.pngbin0 -> 1992 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_grey600_24dp.pngbin0 -> 658 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_grey600_24dp.pngbin0 -> 406 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.pngbin0 -> 364 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.pngbin0 -> 526 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_drag_handle.9.pngbin318 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_drag_handle_dark.9.pngbin297 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_drag_vertical_grey600_48dp.9.pngbin0 -> 548 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_drag_vertical_white_48dp.9.pngbin0 -> 517 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.pngbin3200 -> 1420 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_white_24dp.pngbin2314 -> 910 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_grey600_24dp.pngbin0 -> 407 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_white_24dp.pngbin0 -> 370 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_skip_grey600_36dp.pngbin0 -> 364 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_skip_white_36dp.pngbin0 -> 368 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_grey600_24dp.pngbin0 -> 494 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_off_grey600_24dp.pngbin0 -> 769 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_off_white_24dp.pngbin0 -> 486 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_white_24dp.pngbin0 -> 367 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sort_grey600_24dp.pngbin0 -> 215 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sort_white_24dp.pngbin0 -> 212 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_border_grey600_24dp.pngbin0 -> 828 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_border_white_24dp.pngbin0 -> 821 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.pngbin0 -> 593 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_white_24dp.pngbin0 -> 582 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/stat_notify_sync.pngbin0 -> 475 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/stat_notify_sync_error.pngbin0 -> 533 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_av_fast_forward_80dp.pngbin0 -> 3207 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_av_rewind_80dp.pngbin0 -> 3502 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_grey600_24dp.pngbin0 -> 920 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_grey600_24dp.pngbin0 -> 582 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.pngbin0 -> 502 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.pngbin0 -> 800 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_drag_handle.9.pngbin410 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_drag_handle_dark.9.pngbin380 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_drag_vertical_grey600_48dp.9.pngbin0 -> 853 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_drag_vertical_white_48dp.9.pngbin0 -> 809 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.pngbin4754 -> 2087 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.pngbin3406 -> 1366 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_grey600_24dp.pngbin0 -> 580 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_white_24dp.pngbin0 -> 514 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_skip_grey600_36dp.pngbin0 -> 533 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_skip_white_36dp.pngbin0 -> 543 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_grey600_24dp.pngbin0 -> 737 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_off_grey600_24dp.pngbin0 -> 1113 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_off_white_24dp.pngbin0 -> 664 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_white_24dp.pngbin0 -> 509 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sort_grey600_24dp.pngbin0 -> 244 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.pngbin0 -> 241 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_border_grey600_24dp.pngbin0 -> 1227 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_border_white_24dp.pngbin0 -> 1222 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.pngbin0 -> 877 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.pngbin0 -> 870 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/stat_notify_sync.pngbin0 -> 705 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/stat_notify_sync_error.pngbin0 -> 761 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxxhdpi/ic_av_fast_forward_80dp.pngbin0 -> 5878 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxxhdpi/ic_av_rewind_80dp.pngbin0 -> 6299 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_skip_grey600_36dp.pngbin0 -> 737 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_skip_white_36dp.pngbin0 -> 749 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_grey600_24dp.pngbin0 -> 955 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_off_grey600_24dp.pngbin0 -> 1514 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_off_white_24dp.pngbin0 -> 882 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_white_24dp.pngbin0 -> 647 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_border_grey600_24dp.pngbin0 -> 1675 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_border_white_24dp.pngbin0 -> 1684 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.pngbin0 -> 1179 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.pngbin0 -> 1183 bytes
-rw-r--r--core/src/main/res/drawable/progress_bar_horizontal_dark.xml15
-rw-r--r--core/src/main/res/drawable/progress_bar_horizontal_light.xml15
-rw-r--r--core/src/main/res/values-az/strings.xml12
-rw-r--r--core/src/main/res/values-ca/strings.xml165
-rw-r--r--core/src/main/res/values-cs-rCZ/strings.xml244
-rw-r--r--core/src/main/res/values-da/strings.xml18
-rw-r--r--core/src/main/res/values-de/strings.xml219
-rw-r--r--core/src/main/res/values-es-rES/strings.xml62
-rw-r--r--core/src/main/res/values-es/strings.xml227
-rw-r--r--core/src/main/res/values-fr/strings.xml243
-rw-r--r--core/src/main/res/values-hi-rIN/strings.xml12
-rw-r--r--core/src/main/res/values-it-rIT/strings.xml113
-rw-r--r--core/src/main/res/values-iw-rIL/strings.xml30
-rw-r--r--core/src/main/res/values-ja/strings.xml210
-rw-r--r--core/src/main/res/values-ko/strings.xml212
-rw-r--r--core/src/main/res/values-nb/strings.xml510
-rw-r--r--core/src/main/res/values-nl/strings.xml296
-rw-r--r--core/src/main/res/values-pl-rPL/strings.xml172
-rw-r--r--core/src/main/res/values-pt-rBR/strings.xml58
-rw-r--r--core/src/main/res/values-pt/strings.xml385
-rw-r--r--core/src/main/res/values-ro-rRO/strings.xml12
-rw-r--r--core/src/main/res/values-ru/strings.xml182
-rw-r--r--core/src/main/res/values-sv-rSE/strings.xml327
-rw-r--r--core/src/main/res/values-tr/strings.xml171
-rw-r--r--core/src/main/res/values-uk-rUA/strings.xml200
-rw-r--r--core/src/main/res/values-v16/styles.xml2
-rw-r--r--core/src/main/res/values-v21/styles.xml8
-rw-r--r--core/src/main/res/values-zh-rCN/strings.xml199
-rw-r--r--core/src/main/res/values/arrays.xml126
-rw-r--r--core/src/main/res/values/attrs.xml14
-rw-r--r--core/src/main/res/values/colors.xml10
-rw-r--r--core/src/main/res/values/dimens.xml6
-rw-r--r--core/src/main/res/values/strings.xml306
-rw-r--r--core/src/main/res/values/styles.xml150
285 files changed, 9964 insertions, 8879 deletions
diff --git a/core/build.gradle b/core/build.gradle
index ae2c11070..09d13a476 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -1,12 +1,13 @@
-apply plugin: 'com.android.library'
+apply plugin: "com.android.library"
+apply plugin: "me.tatarka.retrolambda"
android {
- compileSdkVersion 21
- buildToolsVersion "21.1.2"
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
- minSdkVersion 10
- targetSdkVersion 21
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testApplicationId "de.danoeh.antennapod.core.tests"
@@ -15,37 +16,45 @@ android {
buildTypes {
release {
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
packagingOptions {
- exclude 'META-INF/LICENSE.txt'
- exclude 'META-INF/NOTICE.txt'
+ exclude "META-INF/LICENSE.txt"
+ exclude "META-INF/NOTICE.txt"
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
}
+repositories {
+ maven { url "https://jitpack.io" }
+ mavenCentral()
+}
+
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.android.support:appcompat-v7:21.0.3'
- compile 'com.android.support:support-v4:21.0.3'
- compile 'org.apache.commons:commons-lang3:3.3.2'
- compile ('org.shredzone.flattr4j:flattr4j-core:2.12') {
- exclude group: 'org.json', module: 'json'
+ compile "com.android.support:support-v4:$supportVersion"
+ compile "com.android.support:appcompat-v7:$supportVersion"
+ compile "org.apache.commons:commons-lang3:$commonslangVersion"
+ compile ("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") {
+ exclude group: "org.json", module: "json"
}
- compile 'commons-io:commons-io:2.4'
- compile 'com.jayway.android.robotium:robotium-solo:5.2.1'
- compile 'org.jsoup:jsoup:1.7.3'
- compile 'com.squareup.picasso:picasso:2.5.2'
- compile 'com.squareup.okhttp:okhttp:2.3.0'
- compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
- compile 'com.squareup.okio:okio:1.2.0'
- compile 'com.nineoldandroids:library:2.4.0'
- compile 'de.greenrobot:eventbus:2.4.0'
+ compile "commons-io:commons-io:$commonsioVersion"
+ compile "com.jayway.android.robotium:robotium-solo:5.5.3"
+ compile "org.jsoup:jsoup:$jsoupVersion"
+ compile "com.github.bumptech.glide:glide:$glideVersion"
+ compile "com.github.bumptech.glide:okhttp-integration:1.3.1"
+ compile "com.squareup.okhttp:okhttp:$okhttpVersion"
+ compile "com.squareup.okhttp:okhttp-urlconnection:$okhttpVersion"
+ compile "com.squareup.okio:okio:$okioVersion"
+ compile "com.nineoldandroids:library:2.4.0"
+ compile "de.greenrobot:eventbus:$eventbusVersion"
+ compile "io.reactivex:rxandroid:$rxAndroidVersion"
+
+ compile "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java
index 2a2d6414a..2727b1447 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java
@@ -13,6 +13,7 @@ public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithMicroseconds() throws Exception {
GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
Date expected = new Date(exp.getTimeInMillis() + 963);
Date actual = DateUtils.parse("2015-03-28T13:31:04.963870");
assertEquals(expected, actual);
@@ -20,6 +21,7 @@ public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithCentiseconds() throws Exception {
GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
Date expected = new Date(exp.getTimeInMillis() + 960);
Date actual = DateUtils.parse("2015-03-28T13:31:04.96");
assertEquals(expected, actual);
@@ -27,6 +29,7 @@ public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithDeciseconds() throws Exception {
GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
Date expected = new Date(exp.getTimeInMillis() + 900);
Date actual = DateUtils.parse("2015-03-28T13:31:04.9");
assertEquals(expected.getTime()/1000, actual.getTime()/1000);
@@ -66,6 +69,14 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(expected, actual);
}
+ public void testParseDateWithTimezoneName2() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 6, 31, 0);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis());
+ Date actual = DateUtils.parse("Sat, 28 Mar 2015 01:31 EST");
+ assertEquals(expected, actual);
+ }
+
public void testParseDateWithTimeZoneOffset() throws Exception {
GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 12, 16, 12);
exp.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -74,4 +85,20 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(expected, actual);
}
+ public void testAsctime() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2011, 4, 25, 12, 33, 00);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis());
+ Date actual = DateUtils.parse("Wed, 25 May 2011 12:33:00");
+ assertEquals(expected, actual);
+ }
+
+ public void testMultipleConsecutiveSpaces() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2010, 2, 23, 6, 6, 26);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis());
+ Date actual = DateUtils.parse("Tue, 23 Mar 2010 01:06:26 -0500");
+ assertEquals(expected, actual);
+ }
+
}
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index 3ec519844..47bd8b666 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.VIBRATE"/>
<application
android:allowBackup="true"
diff --git a/core/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl
deleted file mode 100644
index 6bdc76801..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IDeathCallback_0_8.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-oneway interface IDeathCallback_0_8 {
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl
deleted file mode 100644
index 7357e402e..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnBufferingUpdateListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnBufferingUpdateListenerCallback_0_8 {
- void onBufferingUpdate(int percent);
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl
deleted file mode 100644
index d5edea729..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnCompletionListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnCompletionListenerCallback_0_8 {
- void onCompletion();
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl
deleted file mode 100644
index 2c4f2df3e..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnErrorListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnErrorListenerCallback_0_8 {
- boolean onError(int what, int extra);
-}
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl
deleted file mode 100644
index 9dbd1d260..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnInfoListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnInfoListenerCallback_0_8 {
- boolean onInfo(int what, int extra);
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl
deleted file mode 100644
index 41223a97b..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 {
- void onPitchAdjustmentAvailableChanged(boolean pitchAdjustmentAvailable);
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl
deleted file mode 100644
index 7be8f1237..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnPreparedListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnPreparedListenerCallback_0_8 {
- void onPrepared();
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl
deleted file mode 100644
index 5bdda98b6..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnSeekCompleteListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnSeekCompleteListenerCallback_0_8 {
- void onSeekComplete();
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl
deleted file mode 100644
index a69c1cf34..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-interface IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 {
- void onSpeedAdjustmentAvailableChanged(boolean speedAdjustmentAvailable);
-} \ No newline at end of file
diff --git a/core/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl b/core/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl
deleted file mode 100644
index 12a6047de..000000000
--- a/core/src/main/aidl/com/aocate/presto/service/IPlayMedia_0_8.aidl
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.presto.service;
-
-import com.aocate.presto.service.IDeathCallback_0_8;
-import com.aocate.presto.service.IOnBufferingUpdateListenerCallback_0_8;
-import com.aocate.presto.service.IOnCompletionListenerCallback_0_8;
-import com.aocate.presto.service.IOnErrorListenerCallback_0_8;
-import com.aocate.presto.service.IOnPitchAdjustmentAvailableChangedListenerCallback_0_8;
-import com.aocate.presto.service.IOnPreparedListenerCallback_0_8;
-import com.aocate.presto.service.IOnSeekCompleteListenerCallback_0_8;
-import com.aocate.presto.service.IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8;
-import com.aocate.presto.service.IOnInfoListenerCallback_0_8;
-
-interface IPlayMedia_0_8 {
- boolean canSetPitch(long sessionId);
- boolean canSetSpeed(long sessionId);
- float getCurrentPitchStepsAdjustment(long sessionId);
- int getCurrentPosition(long sessionId);
- float getCurrentSpeedMultiplier(long sessionId);
- int getDuration(long sessionId);
- float getMaxSpeedMultiplier(long sessionId);
- float getMinSpeedMultiplier(long sessionId);
- int getVersionCode();
- String getVersionName();
- boolean isLooping(long sessionId);
- boolean isPlaying(long sessionId);
- void pause(long sessionId);
- void prepare(long sessionId);
- void prepareAsync(long sessionId);
- void registerOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb);
- void registerOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb);
- void registerOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb);
- void registerOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb);
- void registerOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb);
- void registerOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb);
- void registerOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb);
- void registerOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb);
- void release(long sessionId);
- void reset(long sessionId);
- void seekTo(long sessionId, int msec);
- void setAudioStreamType(long sessionId, int streamtype);
- void setDataSourceString(long sessionId, String path);
- void setDataSourceUri(long sessionId, in Uri uri);
- void setEnableSpeedAdjustment(long sessionId, boolean enableSpeedAdjustment);
- void setLooping(long sessionId, boolean looping);
- void setPitchStepsAdjustment(long sessionId, float pitchSteps);
- void setPlaybackPitch(long sessionId, float f);
- void setPlaybackSpeed(long sessionId, float f);
- void setSpeedAdjustmentAlgorithm(long sessionId, int algorithm);
- void setVolume(long sessionId, float left, float right);
- void start(long sessionId);
- long startSession(IDeathCallback_0_8 cb);
- void stop(long sessionId);
- void unregisterOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb);
- void unregisterOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb);
- void unregisterOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb);
- void unregisterOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb);
- void unregisterOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb);
- void unregisterOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb);
- void unregisterOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb);
- void unregisterOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb);
-} \ No newline at end of file
diff --git a/core/src/main/java/com/aocate/media/AndroidMediaPlayer.java b/core/src/main/java/com/aocate/media/AndroidMediaPlayer.java
deleted file mode 100644
index 7c2ea3d61..000000000
--- a/core/src/main/java/com/aocate/media/AndroidMediaPlayer.java
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.media;
-
-import android.content.Context;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.util.Log;
-
-import java.io.IOException;
-
-public class AndroidMediaPlayer extends MediaPlayerImpl {
- private final static String AMP_TAG = "AocateAndroidMediaPlayer";
-
- // private static final long TIMEOUT_DURATION_MS = 500;
-
- android.media.MediaPlayer mp = null;
-
- private android.media.MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
- public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
- if (owningMediaPlayer != null) {
- owningMediaPlayer.lock.lock();
- try {
- if ((owningMediaPlayer.onBufferingUpdateListener != null)
- && (owningMediaPlayer.mpi == AndroidMediaPlayer.this)) {
- owningMediaPlayer.onBufferingUpdateListener.onBufferingUpdate(owningMediaPlayer, percent);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- }
- };
-
- private android.media.MediaPlayer.OnCompletionListener onCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
- public void onCompletion(android.media.MediaPlayer mp) {
- Log.d(AMP_TAG, "onCompletionListener being called");
- if (owningMediaPlayer != null) {
- owningMediaPlayer.lock.lock();
- try {
- if (owningMediaPlayer.onCompletionListener != null) {
- owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- }
- };
-
- private android.media.MediaPlayer.OnErrorListener onErrorListener = new android.media.MediaPlayer.OnErrorListener() {
- public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
- // Once we're in errored state, any received messages are going to be junked
- if (owningMediaPlayer != null) {
- owningMediaPlayer.lock.lock();
- try {
- if (owningMediaPlayer.onErrorListener != null) {
- return owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- return false;
- }
- };
-
- private android.media.MediaPlayer.OnInfoListener onInfoListener = new android.media.MediaPlayer.OnInfoListener() {
- public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
- if (owningMediaPlayer != null) {
- owningMediaPlayer.lock.lock();
- try {
- if ((owningMediaPlayer.onInfoListener != null)
- && (owningMediaPlayer.mpi == AndroidMediaPlayer.this)) {
- return owningMediaPlayer.onInfoListener.onInfo(owningMediaPlayer, what, extra);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- return false;
- }
- };
-
- // We have to assign this.onPreparedListener because the
- // onPreparedListener in owningMediaPlayer sets the state
- // to PREPARED. Due to prepareAsync, that's the only
- // reasonable place to do it
- // The others it just didn't make sense to have a setOnXListener that didn't use the parameter
- private android.media.MediaPlayer.OnPreparedListener onPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
- public void onPrepared(android.media.MediaPlayer mp) {
- Log.d(AMP_TAG, "Calling onPreparedListener.onPrepared()");
- if (AndroidMediaPlayer.this.owningMediaPlayer != null) {
- AndroidMediaPlayer.this.lockMuteOnPreparedCount.lock();
- try {
- if (AndroidMediaPlayer.this.muteOnPreparedCount > 0) {
- AndroidMediaPlayer.this.muteOnPreparedCount--;
- }
- else {
- AndroidMediaPlayer.this.muteOnPreparedCount = 0;
- if (AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener != null) {
- Log.d(AMP_TAG, "Invoking AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared");
- AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared(AndroidMediaPlayer.this.owningMediaPlayer);
- }
- }
- }
- finally {
- AndroidMediaPlayer.this.lockMuteOnPreparedCount.unlock();
- }
- if (owningMediaPlayer.mpi != AndroidMediaPlayer.this) {
- Log.d(AMP_TAG, "owningMediaPlayer has changed implementation");
- }
- }
- }
- };
-
- private android.media.MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
- public void onSeekComplete(android.media.MediaPlayer mp) {
- if (owningMediaPlayer != null) {
- owningMediaPlayer.lock.lock();
- try {
- lockMuteOnSeekCount.lock();
- try {
- if (AndroidMediaPlayer.this.muteOnSeekCount > 0) {
- AndroidMediaPlayer.this.muteOnSeekCount--;
- }
- else {
- AndroidMediaPlayer.this.muteOnSeekCount = 0;
- if (AndroidMediaPlayer.this.owningMediaPlayer.onSeekCompleteListener != null) {
- owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer);
- }
- }
- }
- finally {
- lockMuteOnSeekCount.unlock();
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- }
- };
-
- public AndroidMediaPlayer(com.aocate.media.MediaPlayer owningMediaPlayer, Context context) {
- super(owningMediaPlayer, context);
-
- mp = new MediaPlayer();
-
-// final ReentrantLock lock = new ReentrantLock();
-// Handler handler = new Handler(Looper.getMainLooper()) {
-// @Override
-// public void handleMessage(Message msg) {
-// Log.d(AMP_TAG, "Instantiating new AndroidMediaPlayer from Handler");
-// lock.lock();
-// if (mp == null) {
-// mp = new MediaPlayer();
-// }
-// lock.unlock();
-// }
-// };
-//
-// long endTime = System.currentTimeMillis() + TIMEOUT_DURATION_MS;
-//
-// while (true) {
-// // Retry messages until mp isn't null or it's time to give up
-// handler.sendMessage(handler.obtainMessage());
-// if ((mp != null)
-// || (endTime < System.currentTimeMillis())) {
-// break;
-// }
-// try {
-// Thread.sleep(50);
-// } catch (InterruptedException e) {
-// // TODO Auto-generated catch block
-// e.printStackTrace();
-// }
-// }
-
- if (mp == null) {
- throw new IllegalStateException("Did not instantiate android.media.MediaPlayer successfully");
- }
-
- mp.setOnBufferingUpdateListener(this.onBufferingUpdateListener);
- mp.setOnCompletionListener(this.onCompletionListener);
- mp.setOnErrorListener(this.onErrorListener);
- mp.setOnInfoListener(this.onInfoListener);
- Log.d(AMP_TAG, " ++++++++++++++++++++++++++++++++ Setting prepared listener to this.onPreparedListener");
- mp.setOnPreparedListener(this.onPreparedListener);
- mp.setOnSeekCompleteListener(this.onSeekCompleteListener);
- }
-
- @Override
- public boolean canSetPitch() {
- return false;
- }
-
- @Override
- public boolean canSetSpeed() {
- return false;
- }
-
- @Override
- public float getCurrentPitchStepsAdjustment() {
- return 0;
- }
-
- @Override
- public int getCurrentPosition() {
- owningMediaPlayer.lock.lock();
- try {
- return mp.getCurrentPosition();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public float getCurrentSpeedMultiplier() {
- return 1f;
- }
-
- @Override
- public int getDuration() {
- owningMediaPlayer.lock.lock();
- try {
- return mp.getDuration();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public float getMaxSpeedMultiplier() {
- return 1f;
- }
-
- @Override
- public float getMinSpeedMultiplier() {
- return 1f;
- }
-
- @Override
- public boolean isLooping() {
- owningMediaPlayer.lock.lock();
- try {
- return mp.isLooping();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public boolean isPlaying() {
- owningMediaPlayer.lock.lock();
- try {
- return mp.isPlaying();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void pause() {
- owningMediaPlayer.lock.lock();
- try {
- mp.pause();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void prepare() throws IllegalStateException, IOException {
- owningMediaPlayer.lock.lock();
- Log.d(AMP_TAG, "prepare()");
- try {
- mp.prepare();
- Log.d(AMP_TAG, "Finish prepare()");
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void prepareAsync() {
- mp.prepareAsync();
- }
-
- @Override
- public void release() {
- owningMediaPlayer.lock.lock();
- try {
- if (mp != null) {
- Log.d(AMP_TAG, "mp.release()");
- mp.release();
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void reset() {
- owningMediaPlayer.lock.lock();
- try {
- mp.reset();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void seekTo(int msec) throws IllegalStateException {
- owningMediaPlayer.lock.lock();
- try {
- mp.setOnSeekCompleteListener(this.onSeekCompleteListener);
- mp.seekTo(msec);
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void setAudioStreamType(int streamtype) {
- owningMediaPlayer.lock.lock();
- try {
- mp.setAudioStreamType(streamtype);
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void setDataSource(Context context, Uri uri)
- throws IllegalArgumentException, IllegalStateException, IOException {
- owningMediaPlayer.lock.lock();
- try {
- Log.d(AMP_TAG, "setDataSource(context, " + uri.toString() + ")");
- mp.setDataSource(context, uri);
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void setDataSource(String path) throws IllegalArgumentException,
- IllegalStateException, IOException {
- owningMediaPlayer.lock.lock();
- try {
- Log.d(AMP_TAG, "setDataSource(" + path + ")");
- mp.setDataSource(path);
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
- // Can't!
- }
-
- @Override
- public void setLooping(boolean loop) {
- owningMediaPlayer.lock.lock();
- try {
- mp.setLooping(loop);
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void setPitchStepsAdjustment(float pitchSteps) {
- // Can't!
- }
-
- @Override
- public void setPlaybackPitch(float f) {
- // Can't!
- }
-
- @Override
- public void setPlaybackSpeed(float f) {
- // Can't!
- Log.d(AMP_TAG, "setPlaybackSpeed(" + f + ")");
- }
-
- @Override
- public void setSpeedAdjustmentAlgorithm(int algorithm) {
- // Can't!
- Log.d(AMP_TAG, "setSpeedAdjustmentAlgorithm(" + algorithm + ")");
- }
-
- @Override
- public void setVolume(float leftVolume, float rightVolume) {
- owningMediaPlayer.lock.lock();
- try {
- mp.setVolume(leftVolume, rightVolume);
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void setWakeMode(Context context, int mode) {
- owningMediaPlayer.lock.lock();
- try {
- if (mode != 0) {
- mp.setWakeMode(context, mode);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void start() {
- owningMediaPlayer.lock.lock();
- try {
- mp.start();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- @Override
- public void stop() {
- owningMediaPlayer.lock.lock();
- try {
- mp.stop();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-}
diff --git a/core/src/main/java/com/aocate/media/MediaPlayer.java b/core/src/main/java/com/aocate/media/MediaPlayer.java
deleted file mode 100644
index 79e63d03d..000000000
--- a/core/src/main/java/com/aocate/media/MediaPlayer.java
+++ /dev/null
@@ -1,1310 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.media;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
-
-import de.danoeh.antennapod.core.BuildConfig;
-
-public class MediaPlayer {
- public static final String TAG = "com.aocate.media.MediaPlayer";
-
- public interface OnBufferingUpdateListener {
- public abstract void onBufferingUpdate(MediaPlayer arg0, int percent);
- }
-
- public interface OnCompletionListener {
- public abstract void onCompletion(MediaPlayer arg0);
- }
-
- public interface OnErrorListener {
- public abstract boolean onError(MediaPlayer arg0, int what, int extra);
- }
-
- public interface OnInfoListener {
- public abstract boolean onInfo(MediaPlayer arg0, int what, int extra);
- }
-
- public interface OnPitchAdjustmentAvailableChangedListener {
- /**
- * @param arg0 The owning media player
- * @param pitchAdjustmentAvailable True if pitch adjustment is available, false if not
- */
- public abstract void onPitchAdjustmentAvailableChanged(
- MediaPlayer arg0, boolean pitchAdjustmentAvailable);
- }
-
- public interface OnPreparedListener {
- public abstract void onPrepared(MediaPlayer arg0);
- }
-
- public interface OnSeekCompleteListener {
- public abstract void onSeekComplete(MediaPlayer arg0);
- }
-
- public interface OnSpeedAdjustmentAvailableChangedListener {
- /**
- * @param arg0 The owning media player
- * @param speedAdjustmentAvailable True if speed adjustment is available, false if not
- */
- public abstract void onSpeedAdjustmentAvailableChanged(
- MediaPlayer arg0, boolean speedAdjustmentAvailable);
- }
-
- public enum State {
- IDLE, INITIALIZED, PREPARED, STARTED, PAUSED, STOPPED, PREPARING, PLAYBACK_COMPLETED, END, ERROR
- }
-
- private static Uri SPEED_ADJUSTMENT_MARKET_URI = Uri
- .parse("market://details?id=com.aocate.presto");
-
- private static Intent prestoMarketIntent = null;
-
- public static final int MEDIA_ERROR_SERVER_DIED = android.media.MediaPlayer.MEDIA_ERROR_SERVER_DIED;
- public static final int MEDIA_ERROR_UNKNOWN = android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN;
- public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = android.media.MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK;
-
- /**
- * Indicates whether the specified action can be used as an intent. This
- * method queries the package manager for installed packages that can
- * respond to an intent with the specified action. If no suitable package is
- * found, this method returns false.
- *
- * @param context The application's environment.
- * @param action The Intent action to check for availability.
- * @return True if an Intent with the specified action can be sent and
- * responded to, false otherwise.
- */
- public static boolean isIntentAvailable(Context context, String action) {
- final PackageManager packageManager = context.getPackageManager();
- final Intent intent = new Intent(action);
- List<ResolveInfo> list = packageManager.queryIntentServices(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- return list.size() > 0;
- }
-
- /**
- * Returns an explicit Intent for a service that accepts the given Intent
- * or null if no such service was found.
- *
- * @param context The application's environment.
- * @param action The Intent action to check for availability.
- * @return The explicit service Intent or null if no service was found.
- */
- public static Intent getPrestoServiceIntent(Context context, String action) {
- final PackageManager packageManager = context.getPackageManager();
- final Intent actionIntent = new Intent(action);
- List<ResolveInfo> list = packageManager.queryIntentServices(actionIntent,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (list.size() > 0) {
- ResolveInfo first = list.get(0);
- if (first.serviceInfo != null) {
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(first.serviceInfo.packageName,
- first.serviceInfo.name));
- Log.i(TAG, "Returning intent:" + intent.toString());
- return intent;
- } else {
- Log.e(TAG, "Found service that accepts " + action + ", but serviceInfo was null");
- return null;
- }
- } else {
- return null;
- }
- }
-
- /**
- * Indicates whether the Presto library is installed
- *
- * @param context The context to use to query the package manager.
- * @return True if the Presto library is installed, false if not.
- */
- public static boolean isPrestoLibraryInstalled(Context context) {
- return isIntentAvailable(context, ServiceBackedMediaPlayer.INTENT_NAME);
- }
-
- /**
- * Return an Intent that opens the Android Market page for the speed
- * alteration library
- *
- * @return The Intent for the Presto library on the Android Market
- */
- public static Intent getPrestoMarketIntent() {
- if (prestoMarketIntent == null) {
- prestoMarketIntent = new Intent(Intent.ACTION_VIEW,
- SPEED_ADJUSTMENT_MARKET_URI);
- }
- return prestoMarketIntent;
- }
-
- /**
- * Open the Android Market page for the Presto library
- *
- * @param context The context from which to open the Android Market page
- */
- public static void openPrestoMarketIntent(Context context) {
- context.startActivity(getPrestoMarketIntent());
- }
-
- private static final String MP_TAG = "AocateReplacementMediaPlayer";
-
- private static final double PITCH_STEP_CONSTANT = 1.0594630943593;
-
- private AndroidMediaPlayer amp = null;
- // This is whether speed adjustment should be enabled (by the Service)
- // To avoid the Service entirely, set useService to false
- protected boolean enableSpeedAdjustment = true;
- private int lastKnownPosition = 0;
- // In some cases, we're going to have to replace the
- // android.media.MediaPlayer on the fly, and we don't want to touch the
- // wrong media player, so lock it way too much.
- ReentrantLock lock = new ReentrantLock();
- private int mAudioStreamType = AudioManager.STREAM_MUSIC;
- private Context mContext;
- private boolean mIsLooping = false;
- private float mLeftVolume = 1f;
- private float mPitchStepsAdjustment = 0f;
- private float mRightVolume = 1f;
- private float mSpeedMultiplier = 1f;
- private int mWakeMode = 0;
- MediaPlayerImpl mpi = null;
- protected boolean pitchAdjustmentAvailable = false;
- private ServiceBackedMediaPlayer sbmp = null;
- protected boolean speedAdjustmentAvailable = false;
-
- private Handler mServiceDisconnectedHandler = null;
-
- // Some parts of state cannot be found by calling MediaPlayerImpl functions,
- // so store our own state. This also helps copy state when changing
- // implementations
- State state = State.INITIALIZED;
- String stringDataSource = null;
- Uri uriDataSource = null;
- private boolean useService = false;
-
- // Naming Convention for Listeners
- // Most listeners can both be set by clients and called by MediaPlayImpls
- // There are a few that have to do things in this class as well as calling
- // the function. In all cases, onX is what is called by MediaPlayerImpl
- // If there is work to be done in this class, then the listener that is
- // set by setX is X (with the first letter lowercase).
- OnBufferingUpdateListener onBufferingUpdateListener = null;
- OnCompletionListener onCompletionListener = null;
- OnErrorListener onErrorListener = null;
- OnInfoListener onInfoListener = null;
-
- // Special case. Pitch adjustment ceases to be available when we switch
- // to the android.media.MediaPlayer (though it is not guaranteed to be
- // available when using the ServiceBackedMediaPlayer)
- OnPitchAdjustmentAvailableChangedListener onPitchAdjustmentAvailableChangedListener = new OnPitchAdjustmentAvailableChangedListener() {
- public void onPitchAdjustmentAvailableChanged(MediaPlayer arg0,
- boolean pitchAdjustmentAvailable) {
- lock.lock();
- try {
- Log
- .d(
- MP_TAG,
- "onPitchAdjustmentAvailableChangedListener.onPitchAdjustmentAvailableChanged being called");
- if (MediaPlayer.this.pitchAdjustmentAvailable != pitchAdjustmentAvailable) {
- Log.d(MP_TAG, "Pitch adjustment state has changed from "
- + MediaPlayer.this.pitchAdjustmentAvailable
- + " to " + pitchAdjustmentAvailable);
- MediaPlayer.this.pitchAdjustmentAvailable = pitchAdjustmentAvailable;
- if (MediaPlayer.this.pitchAdjustmentAvailableChangedListener != null) {
- MediaPlayer.this.pitchAdjustmentAvailableChangedListener
- .onPitchAdjustmentAvailableChanged(arg0,
- pitchAdjustmentAvailable);
- }
- }
- } finally {
- lock.unlock();
- }
- }
- };
- OnPitchAdjustmentAvailableChangedListener pitchAdjustmentAvailableChangedListener = null;
-
- MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() {
- public void onPrepared(MediaPlayer arg0) {
- Log.d(MP_TAG, "onPreparedListener 242 setting state to PREPARED");
- MediaPlayer.this.state = State.PREPARED;
- if (MediaPlayer.this.preparedListener != null) {
- Log.d(MP_TAG, "Calling preparedListener");
- MediaPlayer.this.preparedListener.onPrepared(arg0);
- }
- Log.d(MP_TAG, "Wrap up onPreparedListener");
- }
- };
-
- OnPreparedListener preparedListener = null;
- OnSeekCompleteListener onSeekCompleteListener = null;
-
- // Special case. Speed adjustment ceases to be available when we switch
- // to the android.media.MediaPlayer (though it is not guaranteed to be
- // available when using the ServiceBackedMediaPlayer)
- OnSpeedAdjustmentAvailableChangedListener onSpeedAdjustmentAvailableChangedListener = new OnSpeedAdjustmentAvailableChangedListener() {
- public void onSpeedAdjustmentAvailableChanged(MediaPlayer arg0,
- boolean speedAdjustmentAvailable) {
- lock.lock();
- try {
- Log
- .d(
- MP_TAG,
- "onSpeedAdjustmentAvailableChangedListener.onSpeedAdjustmentAvailableChanged being called");
- if (MediaPlayer.this.speedAdjustmentAvailable != speedAdjustmentAvailable) {
- Log.d(MP_TAG, "Speed adjustment state has changed from "
- + MediaPlayer.this.speedAdjustmentAvailable
- + " to " + speedAdjustmentAvailable);
- MediaPlayer.this.speedAdjustmentAvailable = speedAdjustmentAvailable;
- if (MediaPlayer.this.speedAdjustmentAvailableChangedListener != null) {
- MediaPlayer.this.speedAdjustmentAvailableChangedListener
- .onSpeedAdjustmentAvailableChanged(arg0,
- speedAdjustmentAvailable);
- }
- }
- } finally {
- lock.unlock();
- }
- }
- };
- OnSpeedAdjustmentAvailableChangedListener speedAdjustmentAvailableChangedListener = null;
-
- private int speedAdjustmentAlgorithm = SpeedAdjustmentAlgorithm.SONIC;
-
- public MediaPlayer(final Context context) {
- this(context, true);
- }
-
- public MediaPlayer(final Context context, boolean useService) {
- this.mContext = context;
- this.useService = useService;
-
- // So here's the major problem
- // Sometimes the service won't exist or won't be connected,
- // so start with an android.media.MediaPlayer, and when
- // the service is connected, use that from then on
- this.mpi = this.amp = new AndroidMediaPlayer(this, context);
-
- // setupMpi will go get the Service, if it can, then bring that
- // implementation into sync
- Log.d(MP_TAG, "setupMpi");
- setupMpi(context);
- }
-
- private boolean invalidServiceConnectionConfiguration() {
- if (!(this.mpi instanceof ServiceBackedMediaPlayer)) {
- if (this.useService && isPrestoLibraryInstalled()) {
- // In this case, the Presto library has been installed
- // or something while playing sound
- // We could be using the service, but we're not
- Log.d(MP_TAG, "We could be using the service, but we're not 316");
- return true;
- }
- // If useService is false, then we shouldn't be using the SBMP
- // If the Presto library isn't installed, ditto
- Log.d(MP_TAG, "this.mpi is not a ServiceBackedMediaPlayer, but we couldn't use it anyway 321");
- return false;
- } else {
- if (BuildConfig.DEBUG && !(this.mpi instanceof ServiceBackedMediaPlayer))
- throw new AssertionError();
- if (this.useService && isPrestoLibraryInstalled()) {
- // We should be using the service, and we are. Great!
- Log.d(MP_TAG, "We could be using a ServiceBackedMediaPlayer and we are 327");
- return false;
- }
- // We're trying to use the service when we shouldn't,
- // that's an invalid configuration
- Log.d(MP_TAG, "We're trying to use a ServiceBackedMediaPlayer but we shouldn't be 332");
- return true;
- }
- }
-
- private void setupMpi(final Context context) {
- lock.lock();
- try {
- Log.d(MP_TAG, "setupMpi 336");
- // Check if the client wants to use the service at all,
- // then if we're already using the right kind of media player
- if (this.useService && isPrestoLibraryInstalled()) {
- if ((this.mpi != null)
- && (this.mpi instanceof ServiceBackedMediaPlayer)) {
- Log.d(MP_TAG, "Already using ServiceBackedMediaPlayer");
- return;
- }
- if (this.sbmp == null) {
- Log.d(MP_TAG, "Instantiating new ServiceBackedMediaPlayer 346");
- this.sbmp = new ServiceBackedMediaPlayer(this, context,
- new ServiceConnection() {
- public void onServiceConnected(
- ComponentName className,
- final IBinder service) {
- Thread t = new Thread(new Runnable() {
- public void run() {
- // This lock probably isn't granular
- // enough
- MediaPlayer.this.lock.lock();
- Log.d(MP_TAG,
- "onServiceConnected 257");
- try {
- MediaPlayer.this
- .switchMediaPlayerImpl(
- MediaPlayer.this.amp,
- MediaPlayer.this.sbmp);
- Log.d(MP_TAG, "End onServiceConnected 362");
- } finally {
- MediaPlayer.this.lock.unlock();
- }
- }
- });
- t.start();
- }
-
- public void onServiceDisconnected(
- ComponentName className) {
- MediaPlayer.this.lock.lock();
- try {
- // Can't get any more useful information
- // out of sbmp
- if (MediaPlayer.this.sbmp != null) {
- MediaPlayer.this.sbmp.release();
- }
- // Unlike most other cases, sbmp gets set
- // to null since there's nothing useful
- // backing it now
- MediaPlayer.this.sbmp = null;
-
- if (mServiceDisconnectedHandler == null) {
- mServiceDisconnectedHandler = new Handler(new Callback() {
- public boolean handleMessage(Message msg) {
- // switchMediaPlayerImpl won't try to
- // clone anything from null
- lock.lock();
- try {
- if (MediaPlayer.this.amp == null) {
- // This should never be in this state
- MediaPlayer.this.amp = new AndroidMediaPlayer(
- MediaPlayer.this,
- MediaPlayer.this.mContext);
- }
- // Use sbmp instead of null in case by some miracle it's
- // been restored in the meantime
- MediaPlayer.this.switchMediaPlayerImpl(
- MediaPlayer.this.sbmp,
- MediaPlayer.this.amp);
- return true;
- } finally {
- lock.unlock();
- }
- }
- });
- }
-
- // This code needs to execute on the
- // original thread to instantiate
- // the new object in the right place
- mServiceDisconnectedHandler
- .sendMessage(
- mServiceDisconnectedHandler
- .obtainMessage());
- // Note that we do NOT want to set
- // useService. useService is about
- // what the user wants, not what they
- // get
- } finally {
- MediaPlayer.this.lock.unlock();
- }
- }
- }
- );
- }
- switchMediaPlayerImpl(this.amp, this.sbmp);
- } else {
- if ((this.mpi != null)
- && (this.mpi instanceof AndroidMediaPlayer)) {
- Log.d(MP_TAG, "Already using AndroidMediaPlayer");
- return;
- }
- if (this.amp == null) {
- Log.d(MP_TAG, "Instantiating new AndroidMediaPlayer (this should be impossible)");
- this.amp = new AndroidMediaPlayer(this, context);
- }
- switchMediaPlayerImpl(this.sbmp, this.amp);
- }
- } finally {
- lock.unlock();
- }
- }
-
- private void switchMediaPlayerImpl(MediaPlayerImpl from, MediaPlayerImpl to) {
- lock.lock();
- try {
- Log.d(MP_TAG, "switchMediaPlayerImpl");
- if ((from == to)
- // Same object, nothing to synchronize
- || (to == null)
- // Nothing to copy to (maybe this should throw an error?)
- || ((to instanceof ServiceBackedMediaPlayer) && !((ServiceBackedMediaPlayer) to).isConnected())
- // ServiceBackedMediaPlayer hasn't yet connected, onServiceConnected will take care of the transition
- || (MediaPlayer.this.state == State.END)) {
- // State.END is after a release(), no further functions should
- // be called on this class and from is likely to have problems
- // retrieving state that won't be used anyway
- return;
- }
- // Extract all that we can from the existing implementation
- // and copy it to the new implementation
-
- Log.d(MP_TAG, "switchMediaPlayerImpl(), current state is "
- + this.state.toString());
-
- to.reset();
-
- // Do this first so we don't have to prepare the same
- // data file twice
- to.setEnableSpeedAdjustment(MediaPlayer.this.enableSpeedAdjustment);
-
- // This is a reasonable place to set all of these,
- // none of them require prepare() or the like first
- to.setAudioStreamType(this.mAudioStreamType);
- to.setSpeedAdjustmentAlgorithm(this.speedAdjustmentAlgorithm);
- to.setLooping(this.mIsLooping);
- to.setPitchStepsAdjustment(this.mPitchStepsAdjustment);
- Log.d(MP_TAG, "Setting playback speed to " + this.mSpeedMultiplier);
- to.setPlaybackSpeed(this.mSpeedMultiplier);
- to.setVolume(MediaPlayer.this.mLeftVolume,
- MediaPlayer.this.mRightVolume);
- to.setWakeMode(this.mContext, this.mWakeMode);
-
- Log.d(MP_TAG, "asserting at least one data source is null");
- assert ((MediaPlayer.this.stringDataSource == null) || (MediaPlayer.this.uriDataSource == null));
-
- if (uriDataSource != null) {
- Log.d(MP_TAG, "switchMediaPlayerImpl(): uriDataSource != null");
- try {
- to.setDataSource(this.mContext, uriDataSource);
- } catch (IllegalArgumentException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IllegalStateException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if (stringDataSource != null) {
- Log.d(MP_TAG,
- "switchMediaPlayerImpl(): stringDataSource != null");
- try {
- to.setDataSource(stringDataSource);
- } catch (IllegalArgumentException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IllegalStateException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if ((this.state == State.PREPARED)
- || (this.state == State.PREPARING)
- || (this.state == State.PAUSED)
- || (this.state == State.STOPPED)
- || (this.state == State.STARTED)
- || (this.state == State.PLAYBACK_COMPLETED)) {
- Log.d(MP_TAG, "switchMediaPlayerImpl(): prepare and seek");
- // Use prepare here instead of prepareAsync so that
- // we wait for it to be ready before we try to use it
- try {
- to.muteNextOnPrepare();
- to.prepare();
- } catch (IllegalStateException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- int seekPos = 0;
- if (from != null) {
- seekPos = from.getCurrentPosition();
- } else if (this.lastKnownPosition < to.getDuration()) {
- // This can happen if the Service unexpectedly
- // disconnected. Because it would result in too much
- // information being passed around, we don't constantly
- // poll for the lastKnownPosition, but we'll save it
- // when getCurrentPosition is called
- seekPos = this.lastKnownPosition;
- }
- to.muteNextSeek();
- to.seekTo(seekPos);
- }
- if ((from != null)
- && from.isPlaying()) {
- from.pause();
- }
- if ((this.state == State.STARTED)
- || (this.state == State.PAUSED)
- || (this.state == State.STOPPED)) {
- Log.d(MP_TAG, "switchMediaPlayerImpl(): start");
- if (to != null) {
- to.start();
- }
- }
-
- if (this.state == State.PAUSED) {
- Log.d(MP_TAG, "switchMediaPlayerImpl(): paused");
- if (to != null) {
- to.pause();
- }
- } else if (this.state == State.STOPPED) {
- Log.d(MP_TAG, "switchMediaPlayerImpl(): stopped");
- if (to != null) {
- to.stop();
- }
- }
-
- this.mpi = to;
-
- // Cheating here by relying on the side effect in
- // on(Pitch|Speed)AdjustmentAvailableChanged
- if ((to.canSetPitch() != this.pitchAdjustmentAvailable)
- && (this.onPitchAdjustmentAvailableChangedListener != null)) {
- this.onPitchAdjustmentAvailableChangedListener
- .onPitchAdjustmentAvailableChanged(this, to
- .canSetPitch());
- }
- if ((to.canSetSpeed() != this.speedAdjustmentAvailable)
- && (this.onSpeedAdjustmentAvailableChangedListener != null)) {
- this.onSpeedAdjustmentAvailableChangedListener
- .onSpeedAdjustmentAvailableChanged(this, to
- .canSetSpeed());
- }
- Log.d(MP_TAG, "switchMediaPlayerImpl() 625 " + this.state.toString());
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns true if pitch can be changed at this moment
- *
- * @return True if pitch can be changed
- */
- public boolean canSetPitch() {
- lock.lock();
- try {
- return this.mpi.canSetPitch();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns true if speed can be changed at this moment
- *
- * @return True if speed can be changed
- */
- public boolean canSetSpeed() {
- lock.lock();
- try {
- return this.mpi.canSetSpeed();
- } finally {
- lock.unlock();
- }
- }
-
- protected void finalize() throws Throwable {
- lock.lock();
- try {
- Log.d(MP_TAG, "finalize() 626");
- this.release();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns the number of steps (in a musical scale) by which playback is
- * currently shifted. When greater than zero, pitch is shifted up. When less
- * than zero, pitch is shifted down.
- *
- * @return The number of steps pitch is currently shifted by
- */
- public float getCurrentPitchStepsAdjustment() {
- lock.lock();
- try {
- return this.mpi.getCurrentPitchStepsAdjustment();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.getCurrentPosition()
- * Accurate only to frame size of encoded data (26 ms for MP3s)
- *
- * @return Current position (in milliseconds)
- */
- public int getCurrentPosition() {
- lock.lock();
- try {
- return (this.lastKnownPosition = this.mpi.getCurrentPosition());
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns the current speed multiplier. Defaults to 1.0 (normal speed)
- *
- * @return The current speed multiplier
- */
- public float getCurrentSpeedMultiplier() {
- lock.lock();
- try {
- return this.mpi.getCurrentSpeedMultiplier();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.getDuration()
- *
- * @return Length of the track (in milliseconds)
- */
- public int getDuration() {
- lock.lock();
- try {
- return this.mpi.getDuration();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Get the maximum value that can be passed to setPlaybackSpeed
- *
- * @return The maximum speed multiplier
- */
- public float getMaxSpeedMultiplier() {
- lock.lock();
- try {
- return this.mpi.getMaxSpeedMultiplier();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Get the minimum value that can be passed to setPlaybackSpeed
- *
- * @return The minimum speed multiplier
- */
- public float getMinSpeedMultiplier() {
- lock.lock();
- try {
- return this.mpi.getMinSpeedMultiplier();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Gets the version code of the backing service
- *
- * @return -1 if ServiceBackedMediaPlayer is not used, 0 if the service is not
- * connected, otherwise the version code retrieved from the service
- */
- public int getServiceVersionCode() {
- lock.lock();
- try {
- if (this.mpi instanceof ServiceBackedMediaPlayer) {
- return ((ServiceBackedMediaPlayer) this.mpi).getServiceVersionCode();
- } else {
- return -1;
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Gets the version name of the backing service
- *
- * @return null if ServiceBackedMediaPlayer is not used, empty string if
- * the service is not connected, otherwise the version name retrieved from
- * the service
- */
- public String getServiceVersionName() {
- lock.lock();
- try {
- if (this.mpi instanceof ServiceBackedMediaPlayer) {
- return ((ServiceBackedMediaPlayer) this.mpi).getServiceVersionName();
- } else {
- return null;
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.isLooping()
- *
- * @return True if the track is looping
- */
- public boolean isLooping() {
- lock.lock();
- try {
- return this.mpi.isLooping();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.isPlaying()
- *
- * @return True if the track is playing
- */
- public boolean isPlaying() {
- lock.lock();
- try {
- return this.mpi.isPlaying();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns true if this MediaPlayer has access to the Presto
- * library
- *
- * @return True if the Presto library is installed
- */
- public boolean isPrestoLibraryInstalled() {
- if ((this.mpi == null) || (this.mpi.mContext == null)) {
- return false;
- }
- return isPrestoLibraryInstalled(this.mpi.mContext);
- }
-
- /**
- * Open the Android Market page in the same context as this MediaPlayer
- */
- public void openPrestoMarketIntent() {
- if ((this.mpi != null) && (this.mpi.mContext != null)) {
- openPrestoMarketIntent(this.mpi.mContext);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.pause() Pauses the
- * track
- */
- public void pause() {
- lock.lock();
- try {
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.state = State.PAUSED;
- this.mpi.pause();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.prepare() Prepares the
- * track. This or prepareAsync must be called before start()
- */
- public void prepare() throws IllegalStateException, IOException {
- lock.lock();
- try {
- Log.d(MP_TAG, "prepare() 746 using " + ((this.mpi == null) ? "null (this shouldn't happen)" : this.mpi.getClass().toString()) + " state " + this.state.toString());
- Log.d(MP_TAG, "onPreparedListener is: " + ((this.onPreparedListener == null) ? "null" : "non-null"));
- Log.d(MP_TAG, "preparedListener is: " + ((this.preparedListener == null) ? "null" : "non-null"));
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.mpi.prepare();
- this.state = State.PREPARED;
- Log.d(MP_TAG, "prepare() finished 778");
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.prepareAsync()
- * Prepares the track. This or prepare must be called before start()
- */
- public void prepareAsync() {
- lock.lock();
- try {
- Log.d(MP_TAG, "prepareAsync() 779");
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.state = State.PREPARING;
- this.mpi.prepareAsync();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.release() Releases the
- * underlying resources used by the media player.
- */
- public void release() {
- lock.lock();
- try {
- Log.d(MP_TAG, "Releasing MediaPlayer 791");
-
- this.state = State.END;
- if (this.amp != null) {
- this.amp.release();
- }
- if (this.sbmp != null) {
- this.sbmp.release();
- }
-
- this.onBufferingUpdateListener = null;
- this.onCompletionListener = null;
- this.onErrorListener = null;
- this.onInfoListener = null;
- this.preparedListener = null;
- this.onPitchAdjustmentAvailableChangedListener = null;
- this.pitchAdjustmentAvailableChangedListener = null;
- Log.d(MP_TAG, "Setting onSeekCompleteListener to null 871");
- this.onSeekCompleteListener = null;
- this.onSpeedAdjustmentAvailableChangedListener = null;
- this.speedAdjustmentAvailableChangedListener = null;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.reset() Resets the
- * track to idle state
- */
- public void reset() {
- lock.lock();
- try {
- this.state = State.IDLE;
- this.stringDataSource = null;
- this.uriDataSource = null;
- this.mpi.reset();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.seekTo(int msec) Seeks
- * to msec in the track
- */
- public void seekTo(int msec) throws IllegalStateException {
- lock.lock();
- try {
- this.mpi.seekTo(msec);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setAudioStreamType(int
- * streamtype) Sets the audio stream type.
- */
- public void setAudioStreamType(int streamtype) {
- lock.lock();
- try {
- this.mAudioStreamType = streamtype;
- this.mpi.setAudioStreamType(streamtype);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setDataSource(Context
- * context, Uri uri) Sets uri as data source in the context given
- */
- public void setDataSource(Context context, Uri uri)
- throws IllegalArgumentException, IllegalStateException, IOException {
- lock.lock();
- try {
- Log.d(MP_TAG, "In setDataSource(context, " + uri.toString() + "), using " + this.mpi.getClass().toString());
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.state = State.INITIALIZED;
- this.stringDataSource = null;
- this.uriDataSource = uri;
- this.mpi.setDataSource(context, uri);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setDataSource(String
- * path) Sets the data source of the track to a file given.
- */
- public void setDataSource(String path) throws IllegalArgumentException,
- IllegalStateException, IOException {
- lock.lock();
- try {
- Log.d(MP_TAG, "In setDataSource(context, " + path + ")");
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.state = State.INITIALIZED;
- this.stringDataSource = path;
- this.uriDataSource = null;
- this.mpi.setDataSource(path);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Sets whether to use speed adjustment or not. Speed adjustment on is more
- * computation-intensive than with it off.
- *
- * @param enableSpeedAdjustment Whether speed adjustment should be supported.
- */
- public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
- lock.lock();
- try {
- this.enableSpeedAdjustment = enableSpeedAdjustment;
- this.mpi.setEnableSpeedAdjustment(enableSpeedAdjustment);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setLooping(boolean
- * loop) Sets the track to loop infinitely if loop is true, play once if
- * loop is false
- */
- public void setLooping(boolean loop) {
- lock.lock();
- try {
- this.mIsLooping = loop;
- this.mpi.setLooping(loop);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Sets the number of steps (in a musical scale) by which playback is
- * currently shifted. When greater than zero, pitch is shifted up. When less
- * than zero, pitch is shifted down.
- *
- * @param pitchSteps The number of steps by which to shift playback
- */
- public void setPitchStepsAdjustment(float pitchSteps) {
- lock.lock();
- try {
- this.mPitchStepsAdjustment = pitchSteps;
- this.mpi.setPitchStepsAdjustment(pitchSteps);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Set the algorithm to use for changing the speed and pitch of audio
- * See SpeedAdjustmentAlgorithm constants for more details
- *
- * @param algorithm The algorithm to use.
- */
- public void setSpeedAdjustmentAlgorithm(int algorithm) {
- lock.lock();
- try {
- this.speedAdjustmentAlgorithm = algorithm;
- if (this.mpi != null) {
- this.mpi.setSpeedAdjustmentAlgorithm(algorithm);
- }
- } finally {
- lock.unlock();
- }
- }
-
- private static float getPitchStepsAdjustment(float pitch) {
- return (float) (Math.log(pitch) / (2 * Math.log(PITCH_STEP_CONSTANT)));
- }
-
- /**
- * Sets the percentage by which pitch is currently shifted. When greater
- * than zero, pitch is shifted up. When less than zero, pitch is shifted
- * down
- *
- * @param f The percentage to shift pitch
- */
- public void setPlaybackPitch(float pitch) {
- lock.lock();
- try {
- this.mPitchStepsAdjustment = getPitchStepsAdjustment(pitch);
- this.mpi.setPlaybackPitch(pitch);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Set playback speed. 1.0 is normal speed, 2.0 is double speed, and so on.
- * Speed should never be set to 0 or below.
- *
- * @param f The speed multiplier to use for further playback
- */
- public void setPlaybackSpeed(float f) {
- lock.lock();
- try {
- this.mSpeedMultiplier = f;
- this.mpi.setPlaybackSpeed(f);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Sets whether to use speed adjustment or not. Speed adjustment on is more
- * computation-intensive than with it off.
- *
- * @param enableSpeedAdjustment Whether speed adjustment should be supported.
- */
- public void setUseService(boolean useService) {
- lock.lock();
- try {
- this.useService = useService;
- setupMpi(this.mpi.mContext);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setVolume(float
- * leftVolume, float rightVolume) Sets the stereo volume
- */
- public void setVolume(float leftVolume, float rightVolume) {
- lock.lock();
- try {
- this.mLeftVolume = leftVolume;
- this.mRightVolume = rightVolume;
- this.mpi.setVolume(leftVolume, rightVolume);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setWakeMode(Context
- * context, int mode) Acquires a wake lock in the context given. You must
- * request the appropriate permissions in your AndroidManifest.xml file.
- */
- public void setWakeMode(Context context, int mode) {
- lock.lock();
- try {
- this.mWakeMode = mode;
- this.mpi.setWakeMode(context, mode);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to
- * android.media.MediaPlayer.setOnCompletionListener(OnCompletionListener
- * listener) Sets a listener to be used when a track completes playing.
- */
- public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) {
- lock.lock();
- try {
- this.onBufferingUpdateListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to
- * android.media.MediaPlayer.setOnCompletionListener(OnCompletionListener
- * listener) Sets a listener to be used when a track completes playing.
- */
- public void setOnCompletionListener(OnCompletionListener listener) {
- lock.lock();
- try {
- this.onCompletionListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to
- * android.media.MediaPlayer.setOnErrorListener(OnErrorListener listener)
- * Sets a listener to be used when a track encounters an error.
- */
- public void setOnErrorListener(OnErrorListener listener) {
- lock.lock();
- try {
- this.onErrorListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to
- * android.media.MediaPlayer.setOnInfoListener(OnInfoListener listener) Sets
- * a listener to be used when a track has info.
- */
- public void setOnInfoListener(OnInfoListener listener) {
- lock.lock();
- try {
- this.onInfoListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Sets a listener that will fire when pitch adjustment becomes available or
- * stops being available
- */
- public void setOnPitchAdjustmentAvailableChangedListener(
- OnPitchAdjustmentAvailableChangedListener listener) {
- lock.lock();
- try {
- this.pitchAdjustmentAvailableChangedListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to
- * android.media.MediaPlayer.setOnPreparedListener(OnPreparedListener
- * listener) Sets a listener to be used when a track finishes preparing.
- */
- public void setOnPreparedListener(OnPreparedListener listener) {
- lock.lock();
- Log.d(MP_TAG, " ++++++++++++++++++++++++++++++++++++++++++++ setOnPreparedListener");
- try {
- this.preparedListener = listener;
- // For this one, we do not explicitly set the MediaPlayer or the
- // Service listener. This is because in addition to calling the
- // listener provided by the client, it's necessary to change
- // state to PREPARED. See prepareAsync for implementation details
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to
- * android.media.MediaPlayer.setOnSeekCompleteListener
- * (OnSeekCompleteListener listener) Sets a listener to be used when a track
- * finishes seeking.
- */
- public void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
- lock.lock();
- try {
- this.onSeekCompleteListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Sets a listener that will fire when speed adjustment becomes available or
- * stops being available
- */
- public void setOnSpeedAdjustmentAvailableChangedListener(
- OnSpeedAdjustmentAvailableChangedListener listener) {
- lock.lock();
- try {
- this.speedAdjustmentAvailableChangedListener = listener;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.start() Starts a track
- * playing
- */
- public void start() {
- lock.lock();
- try {
- Log.d(MP_TAG, "start() 1149");
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.state = State.STARTED;
- Log.d(MP_TAG, "start() 1154");
- this.mpi.start();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.stop() Stops a track
- * playing and resets its position to the start.
- */
- public void stop() {
- lock.lock();
- try {
- if (invalidServiceConnectionConfiguration()) {
- setupMpi(this.mpi.mContext);
- }
- this.state = State.STOPPED;
- this.mpi.stop();
- } finally {
- lock.unlock();
- }
- }
-} \ No newline at end of file
diff --git a/core/src/main/java/com/aocate/media/MediaPlayerImpl.java b/core/src/main/java/com/aocate/media/MediaPlayerImpl.java
deleted file mode 100644
index 856ab47ce..000000000
--- a/core/src/main/java/com/aocate/media/MediaPlayerImpl.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.media;
-
-import java.io.IOException;
-import java.util.concurrent.locks.ReentrantLock;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.Log;
-
-public abstract class MediaPlayerImpl {
- private static final String MPI_TAG = "AocateMediaPlayerImpl";
- protected final MediaPlayer owningMediaPlayer;
- protected final Context mContext;
- protected int muteOnPreparedCount = 0;
- protected int muteOnSeekCount = 0;
-
- public MediaPlayerImpl(MediaPlayer owningMediaPlayer, Context context) {
- this.owningMediaPlayer = owningMediaPlayer;
-
- this.mContext = context;
- }
-
- public abstract boolean canSetPitch();
-
- public abstract boolean canSetSpeed();
-
- public abstract float getCurrentPitchStepsAdjustment();
-
- public abstract int getCurrentPosition();
-
- public abstract float getCurrentSpeedMultiplier();
-
- public abstract int getDuration();
-
- public abstract float getMaxSpeedMultiplier();
-
- public abstract float getMinSpeedMultiplier();
-
- public abstract boolean isLooping();
-
- public abstract boolean isPlaying();
-
- public abstract void pause();
-
- public abstract void prepare() throws IllegalStateException, IOException;
-
- public abstract void prepareAsync();
-
- public abstract void release();
-
- public abstract void reset();
-
- public abstract void seekTo(int msec) throws IllegalStateException;
-
- public abstract void setAudioStreamType(int streamtype);
-
- public abstract void setDataSource(Context context, Uri uri) throws IllegalArgumentException, IllegalStateException, IOException;
-
- public abstract void setDataSource(String path) throws IllegalArgumentException, IllegalStateException, IOException;
-
- public abstract void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
-
- public abstract void setLooping(boolean loop);
-
- public abstract void setPitchStepsAdjustment(float pitchSteps);
-
- public abstract void setPlaybackPitch(float f);
-
- public abstract void setPlaybackSpeed(float f);
-
- public abstract void setSpeedAdjustmentAlgorithm(int algorithm);
-
- public abstract void setVolume(float leftVolume, float rightVolume);
-
- public abstract void setWakeMode(Context context, int mode);
-
- public abstract void start();
-
- public abstract void stop();
-
- protected ReentrantLock lockMuteOnPreparedCount = new ReentrantLock();
- public void muteNextOnPrepare() {
- lockMuteOnPreparedCount.lock();
- Log.d(MPI_TAG, "muteNextOnPrepare()");
- try {
- this.muteOnPreparedCount++;
- }
- finally {
- lockMuteOnPreparedCount.unlock();
- }
- }
-
- protected ReentrantLock lockMuteOnSeekCount = new ReentrantLock();
- public void muteNextSeek() {
- lockMuteOnSeekCount.lock();
- Log.d(MPI_TAG, "muteNextOnSeek()");
- try {
- this.muteOnSeekCount++;
- }
- finally {
- lockMuteOnSeekCount.unlock();
- }
- }
-}
diff --git a/core/src/main/java/com/aocate/media/ServiceBackedMediaPlayer.java b/core/src/main/java/com/aocate/media/ServiceBackedMediaPlayer.java
deleted file mode 100644
index 0e27a8014..000000000
--- a/core/src/main/java/com/aocate/media/ServiceBackedMediaPlayer.java
+++ /dev/null
@@ -1,1203 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// 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.
-//
-// -----------------------------------------------------------------------
-// Compared to the original version, this class been slightly modified so
-// that any acquired WakeLocks are only held while the MediaPlayer is
-// playing (see the stayAwake method for more details).
-
-
-package com.aocate.media;
-
-import java.io.IOException;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-import com.aocate.media.MediaPlayer.State;
-import com.aocate.presto.service.IDeathCallback_0_8;
-import com.aocate.presto.service.IOnBufferingUpdateListenerCallback_0_8;
-import com.aocate.presto.service.IOnCompletionListenerCallback_0_8;
-import com.aocate.presto.service.IOnErrorListenerCallback_0_8;
-import com.aocate.presto.service.IOnInfoListenerCallback_0_8;
-import com.aocate.presto.service.IOnPitchAdjustmentAvailableChangedListenerCallback_0_8;
-import com.aocate.presto.service.IOnPreparedListenerCallback_0_8;
-import com.aocate.presto.service.IOnSeekCompleteListenerCallback_0_8;
-import com.aocate.presto.service.IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8;
-import com.aocate.presto.service.IPlayMedia_0_8;
-
-import de.danoeh.antennapod.core.BuildConfig;
-
-/**
- * Class for connecting to remote speed-altering, media playing Service
- * Note that there is unusually high coupling between MediaPlayer and this
- * class. This is an unfortunate compromise, since the alternative was to
- * track state in two different places in this code (plus the internal state
- * of the remote media player).
- * @author aocate
- *
- */
-public class ServiceBackedMediaPlayer extends MediaPlayerImpl {
- static final String INTENT_NAME = "com.aocate.intent.PLAY_AUDIO_ADJUST_SPEED_0_8";
-
- private static final String SBMP_TAG = "AocateServiceBackedMediaPlayer";
-
- private ServiceConnection mPlayMediaServiceConnection = null;
- protected IPlayMedia_0_8 pmInterface = null;
- private Intent playMediaServiceIntent = null;
- // In some cases, we're going to have to replace the
- // android.media.MediaPlayer on the fly, and we don't want to touch the
- // wrong media player.
-
- private long sessionId = 0;
- private boolean isErroring = false;
- private int mAudioStreamType = AudioManager.STREAM_MUSIC;
-
- private WakeLock mWakeLock = null;
-
- // So here's the major problem
- // Sometimes the service won't exist or won't be connected,
- // so start with an android.media.MediaPlayer, and when
- // the service is connected, use that from then on
- public ServiceBackedMediaPlayer(MediaPlayer owningMediaPlayer, final Context context, final ServiceConnection serviceConnection) {
- super(owningMediaPlayer, context);
- Log.d(SBMP_TAG, "Instantiating ServiceBackedMediaPlayer 87");
- this.playMediaServiceIntent =
- MediaPlayer.getPrestoServiceIntent(context, INTENT_NAME);
- this.mPlayMediaServiceConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName name, IBinder service) {
- IPlayMedia_0_8 tmpPlayMediaInterface = IPlayMedia_0_8.Stub.asInterface((IBinder) service);
-
- Log.d(SBMP_TAG, "Setting up pmInterface 94");
- if (ServiceBackedMediaPlayer.this.sessionId == 0) {
- try {
- // The IDeathCallback isn't a conventional callback.
- // It exists so that if the client ceases to exist,
- // the Service becomes aware of that and can shut
- // down whatever it needs to shut down
- ServiceBackedMediaPlayer.this.sessionId = tmpPlayMediaInterface.startSession(new IDeathCallback_0_8.Stub() {
- });
- // This is really bad if this fails
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- Log.d(SBMP_TAG, "Assigning pmInterface");
-
- ServiceBackedMediaPlayer.this.setOnBufferingUpdateCallback(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnCompletionCallback(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnErrorCallback(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnInfoCallback(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnPitchAdjustmentAvailableChangedListener(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnPreparedCallback(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnSeekCompleteCallback(tmpPlayMediaInterface);
- ServiceBackedMediaPlayer.this.setOnSpeedAdjustmentAvailableChangedCallback(tmpPlayMediaInterface);
-
- // In order to avoid race conditions from the sessionId or listener not being assigned
- pmInterface = tmpPlayMediaInterface;
-
- Log.d(SBMP_TAG, "Invoking onServiceConnected");
- serviceConnection.onServiceConnected(name, service);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- Log.d(SBMP_TAG, "onServiceDisconnected 114");
-
- pmInterface = null;
-
- sessionId = 0;
-
- serviceConnection.onServiceDisconnected(name);
- }
- };
-
- Log.d(SBMP_TAG, "Connecting PlayMediaService 124");
- if (!ConnectPlayMediaService()) {
- Log.e(SBMP_TAG, "bindService failed");
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private boolean ConnectPlayMediaService() {
- Log.d(SBMP_TAG, "ConnectPlayMediaService()");
-
- if (MediaPlayer.isIntentAvailable(mContext, INTENT_NAME)) {
- Log.d(SBMP_TAG, INTENT_NAME + " is available");
- if (pmInterface == null) {
- try {
- Log.d(SBMP_TAG, "Binding service");
- return mContext.bindService(playMediaServiceIntent, mPlayMediaServiceConnection, Context.BIND_AUTO_CREATE);
- } catch (Exception e) {
- Log.e(SBMP_TAG, "Could not bind with service", e);
- return false;
- }
- } else {
- Log.d(SBMP_TAG, "Service already bound");
- return true;
- }
- }
- else {
- Log.d(SBMP_TAG, INTENT_NAME + " is not available");
- return false;
- }
- }
-
- /**
- * Returns true if pitch can be changed at this moment
- * @return True if pitch can be changed
- */
- @Override
- public boolean canSetPitch() {
- Log.d(SBMP_TAG, "canSetPitch() 155");
-
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set pitch if the service isn't connected
- try {
- return pmInterface.canSetPitch(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return false;
- }
-
- /**
- * Returns true if speed can be changed at this moment
- * @return True if speed can be changed
- */
- @Override
- public boolean canSetSpeed() {
- Log.d(SBMP_TAG, "canSetSpeed() 180");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the service isn't connected
- try {
- return pmInterface.canSetSpeed(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return false;
- }
-
- void error(int what, int extra) {
- owningMediaPlayer.lock.lock();
- Log.e(SBMP_TAG, "error(" + what + ", " + extra + ")");
- stayAwake(false);
- try {
- if (!this.isErroring) {
- this.isErroring = true;
- owningMediaPlayer.state = State.ERROR;
- if (owningMediaPlayer.onErrorListener != null) {
- if (owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra)) {
- return;
- }
- }
- if (owningMediaPlayer.onCompletionListener != null) {
- owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
- }
- }
- }
- finally {
- this.isErroring = false;
- owningMediaPlayer.lock.unlock();
- }
- }
-
- protected void finalize() throws Throwable {
- owningMediaPlayer.lock.lock();
- try {
- Log.d(SBMP_TAG, "finalize() 224");
- this.release();
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
- /**
- * Returns the number of steps (in a musical scale) by which playback is
- * currently shifted. When greater than zero, pitch is shifted up.
- * When less than zero, pitch is shifted down.
- * @return The number of steps pitch is currently shifted by
- */
- @Override
- public float getCurrentPitchStepsAdjustment() {
- Log.d(SBMP_TAG, "getCurrentPitchStepsAdjustment() 240");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set pitch if the service isn't connected
- try {
- return pmInterface.getCurrentPitchStepsAdjustment(
- ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return 0f;
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.getCurrentPosition()
- * @return Current position (in milliseconds)
- */
- @Override
- public int getCurrentPosition() {
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- return pmInterface.getCurrentPosition(
- ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- return 0;
- }
-
- /**
- * Returns the current speed multiplier. Defaults to 1.0 (normal speed)
- * @return The current speed multiplier
- */
- @Override
- public float getCurrentSpeedMultiplier() {
- Log.d(SBMP_TAG, "getCurrentSpeedMultiplier() 286");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the service isn't connected
- try {
- return pmInterface.getCurrentSpeedMultiplier(
- ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return 1;
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.getDuration()
- * @return Length of the track (in milliseconds)
- */
- @Override
- public int getDuration() {
- Log.d(SBMP_TAG, "getDuration() 311");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- return pmInterface.getDuration(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- return 0;
- }
-
- /**
- * Get the maximum value that can be passed to setPlaybackSpeed
- * @return The maximum speed multiplier
- */
- @Override
- public float getMaxSpeedMultiplier() {
- Log.d(SBMP_TAG, "getMaxSpeedMultiplier() 332");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the Service isn't connected
- try {
- return pmInterface.getMaxSpeedMultiplier(
- ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return 1f;
- }
-
- /**
- * Get the minimum value that can be passed to setPlaybackSpeed
- * @return The minimum speed multiplier
- */
- @Override
- public float getMinSpeedMultiplier() {
- Log.d(SBMP_TAG, "getMinSpeedMultiplier() 357");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the Service isn't connected
- try {
- return pmInterface.getMinSpeedMultiplier(
- ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return 1f;
- }
-
- public int getServiceVersionCode() {
- Log.d(SBMP_TAG, "getVersionCode");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- return pmInterface.getVersionCode();
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- return 0;
- }
-
- public String getServiceVersionName() {
- Log.d(SBMP_TAG, "getVersionName");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- return pmInterface.getVersionName();
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- return "";
- }
-
- public boolean isConnected() {
- return (pmInterface != null);
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.isLooping()
- * @return True if the track is looping
- */
- @Override
- public boolean isLooping() {
- Log.d(SBMP_TAG, "isLooping() 382");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- return pmInterface.isLooping(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- return false;
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.isPlaying()
- * @return True if the track is playing
- */
- @Override
- public boolean isPlaying() {
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- try {
- return pmInterface.isPlaying(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- return false;
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.pause()
- * Pauses the track
- */
- @Override
- public void pause() {
- Log.d(SBMP_TAG, "pause() 424");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.pause(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- stayAwake(false);
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.prepare()
- * Prepares the track. This or prepareAsync must be called before start()
- */
- @Override
- public void prepare() throws IllegalStateException, IOException {
- Log.d(SBMP_TAG, "prepare() 444");
- Log.d(SBMP_TAG, "onPreparedCallback is: " + ((this.mOnPreparedCallback == null) ? "null" : "non-null"));
- if (pmInterface == null) {
- Log.d(SBMP_TAG, "prepare: pmInterface is null");
- if (!ConnectPlayMediaService()) {
- Log.d(SBMP_TAG, "prepare: Failed to connect play media service");
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- Log.d(SBMP_TAG, "prepare: pmInterface isn't null");
- try {
- Log.d(SBMP_TAG, "prepare: Remote invoke pmInterface.prepare(" + ServiceBackedMediaPlayer.this.sessionId + ")");
- pmInterface.prepare(ServiceBackedMediaPlayer.this.sessionId);
- Log.d(SBMP_TAG, "prepare: prepared");
- } catch (RemoteException e) {
- Log.d(SBMP_TAG, "prepare: RemoteException");
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- Log.d(SBMP_TAG, "Done with prepare()");
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.prepareAsync()
- * Prepares the track. This or prepare must be called before start()
- */
- @Override
- public void prepareAsync() {
- Log.d(SBMP_TAG, "prepareAsync() 469");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.prepareAsync(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.release()
- * Releases the underlying resources used by the media player.
- */
- @Override
- public void release() {
- Log.d(SBMP_TAG, "release() 492");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- Log.d(SBMP_TAG, "release() 500");
- try {
- pmInterface.release(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- mContext.unbindService(this.mPlayMediaServiceConnection);
- // Don't try to keep awake (if we were)
- this.setWakeMode(mContext, 0);
- pmInterface = null;
- this.sessionId = 0;
- }
-
- if ((this.mWakeLock != null) && this.mWakeLock.isHeld()) {
- Log.d(SBMP_TAG, "Releasing wakelock");
- this.mWakeLock.release();
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.reset()
- * Resets the track to idle state
- */
- @Override
- public void reset() {
- Log.d(SBMP_TAG, "reset() 523");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.reset(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- stayAwake(false);
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.seekTo(int msec)
- * Seeks to msec in the track
- */
- @Override
- public void seekTo(int msec) throws IllegalStateException {
- Log.d(SBMP_TAG, "seekTo(" + msec + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.seekTo(ServiceBackedMediaPlayer.this.sessionId, msec);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setAudioStreamType(int streamtype)
- * Sets the audio stream type.
- */
- @Override
- public void setAudioStreamType(int streamtype) {
- Log.d(SBMP_TAG, "setAudioStreamType(" + streamtype + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.setAudioStreamType(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mAudioStreamType);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
-
- /**
- * Functions identically to android.media.MediaPlayer.setDataSource(Context context, Uri uri)
- * Sets uri as data source in the context given
- */
- @Override
- public void setDataSource(Context context, Uri uri) throws IllegalArgumentException, IllegalStateException, IOException {
- Log.d(SBMP_TAG, "setDataSource(context, uri)");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.setDataSourceUri(
- ServiceBackedMediaPlayer.this.sessionId,
- uri);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setDataSource(String path)
- * Sets the data source of the track to a file given.
- */
- @Override
- public void setDataSource(String path) throws IllegalArgumentException, IllegalStateException, IOException {
- Log.d(SBMP_TAG, "setDataSource(path)");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface == null) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- else {
- try {
- pmInterface.setDataSourceString(
- ServiceBackedMediaPlayer.this.sessionId,
- path);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- }
-
- /**
- * Sets whether to use speed adjustment or not. Speed adjustment on is
- * more computation-intensive than with it off.
- * @param enableSpeedAdjustment Whether speed adjustment should be supported.
- */
- @Override
- public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
- // TODO: This has no business being here, I think
- owningMediaPlayer.lock.lock();
- Log.d(SBMP_TAG, "setEnableSpeedAdjustment(enableSpeedAdjustment)");
- try {
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the Service isn't connected
- try {
- pmInterface.setEnableSpeedAdjustment(
- ServiceBackedMediaPlayer.this.sessionId,
- enableSpeedAdjustment);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
-
-
- /**
- * Functions identically to android.media.MediaPlayer.setLooping(boolean loop)
- * Sets the track to loop infinitely if loop is true, play once if loop is false
- */
- @Override
- public void setLooping(boolean loop) {
- Log.d(SBMP_TAG, "setLooping(" + loop + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.setLooping(ServiceBackedMediaPlayer.this.sessionId, loop);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Sets the number of steps (in a musical scale) by which playback is
- * currently shifted. When greater than zero, pitch is shifted up.
- * When less than zero, pitch is shifted down.
- *
- * @param pitchSteps The number of steps by which to shift playback
- */
- @Override
- public void setPitchStepsAdjustment(float pitchSteps) {
- Log.d(SBMP_TAG, "setPitchStepsAdjustment(" + pitchSteps + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the Service isn't connected
- try {
- pmInterface.setPitchStepsAdjustment(
- ServiceBackedMediaPlayer.this.sessionId,
- pitchSteps);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- }
-
- /**
- * Sets the percentage by which pitch is currently shifted. When
- * greater than zero, pitch is shifted up. When less than zero, pitch
- * is shifted down
- * @param f The percentage to shift pitch
- */
- @Override
- public void setPlaybackPitch(float f) {
- Log.d(SBMP_TAG, "setPlaybackPitch(" + f + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the Service isn't connected
- try {
- pmInterface.setPlaybackPitch(
- ServiceBackedMediaPlayer.this.sessionId,
- f);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- }
-
- /**
- * Set playback speed. 1.0 is normal speed, 2.0 is double speed, and so
- * on. Speed should never be set to 0 or below.
- * @param f The speed multiplier to use for further playback
- */
- @Override
- public void setPlaybackSpeed(float f) {
- Log.d(SBMP_TAG, "setPlaybackSpeed(" + f + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- if (pmInterface != null) {
- // Can't set speed if the Service isn't connected
- try {
- pmInterface.setPlaybackSpeed(
- ServiceBackedMediaPlayer.this.sessionId,
- f);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- }
-
- @Override
- public void setSpeedAdjustmentAlgorithm(int algorithm) {
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.setSpeedAdjustmentAlgorithm(
- ServiceBackedMediaPlayer.this.sessionId,
- algorithm);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setVolume(float leftVolume, float rightVolume)
- * Sets the stereo volume
- */
- @Override
- public void setVolume(float leftVolume, float rightVolume) {
- Log.d(SBMP_TAG, "setVolume(" + leftVolume + ", " + rightVolume + ")");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.setVolume(
- ServiceBackedMediaPlayer.this.sessionId,
- leftVolume,
- rightVolume);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.setWakeMode(Context context, int mode)
- * Acquires a wake lock in the context given. You must request the appropriate permissions
- * in your AndroidManifest.xml file.
- */
- @Override
- // This does not just call .setWakeMode() in the Service because doing so
- // would add a permission requirement to the Service. Do it here, and it's
- // the client app's responsibility to request that permission
- public void setWakeMode(Context context, int mode) {
- Log.d(SBMP_TAG, "setWakeMode(context, " + mode + ")");
- if ((this.mWakeLock != null)
- && (this.mWakeLock.isHeld())) {
- this.mWakeLock.release();
- }
- if (mode != 0) {
- if (this.mWakeLock == null) {
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- // Since mode can't be changed on the fly, we have to allocate a new one
- this.mWakeLock = pm.newWakeLock(mode, this.getClass().getName());
- this.mWakeLock.setReferenceCounted(false);
- }
-
- this.mWakeLock.acquire();
- }
- }
-
- /**
- * Changes the state of the WakeLock if it has been acquired.
- * If no WakeLock has been acquired with setWakeMode, this method does nothing.
- * */
- private void stayAwake(boolean awake) {
- if (BuildConfig.DEBUG) Log.d(SBMP_TAG, "stayAwake(" + awake + ")");
- if (mWakeLock != null) {
- if (awake && !mWakeLock.isHeld()) {
- mWakeLock.acquire();
- } else if (!awake && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
- }
-
- private IOnBufferingUpdateListenerCallback_0_8.Stub mOnBufferingUpdateCallback = null;
- private void setOnBufferingUpdateCallback(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnBufferingUpdateCallback == null) {
- mOnBufferingUpdateCallback = new IOnBufferingUpdateListenerCallback_0_8.Stub() {
- public void onBufferingUpdate(int percent)
- throws RemoteException {
- owningMediaPlayer.lock.lock();
- try {
- if ((owningMediaPlayer.onBufferingUpdateListener != null)
- && (owningMediaPlayer.mpi == ServiceBackedMediaPlayer.this)) {
- owningMediaPlayer.onBufferingUpdateListener.onBufferingUpdate(owningMediaPlayer, percent);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnBufferingUpdateCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- mOnBufferingUpdateCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnCompletionListenerCallback_0_8.Stub mOnCompletionCallback = null;
- private void setOnCompletionCallback(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnCompletionCallback == null) {
- this.mOnCompletionCallback = new IOnCompletionListenerCallback_0_8.Stub() {
- public void onCompletion() throws RemoteException {
- owningMediaPlayer.lock.lock();
- Log.d(SBMP_TAG, "onCompletionListener being called");
- stayAwake(false);
- try {
- if (owningMediaPlayer.onCompletionListener != null) {
- owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnCompletionCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnCompletionCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnErrorListenerCallback_0_8.Stub mOnErrorCallback = null;
- private void setOnErrorCallback(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnErrorCallback == null) {
- this.mOnErrorCallback = new IOnErrorListenerCallback_0_8.Stub() {
- public boolean onError(int what, int extra) throws RemoteException {
- owningMediaPlayer.lock.lock();
- stayAwake(false);
- try {
- if (owningMediaPlayer.onErrorListener != null) {
- return owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra);
- }
- return false;
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnErrorCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnErrorCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnInfoListenerCallback_0_8.Stub mOnInfoCallback = null;
- private void setOnInfoCallback(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnInfoCallback == null) {
- this.mOnInfoCallback = new IOnInfoListenerCallback_0_8.Stub() {
- public boolean onInfo(int what, int extra) throws RemoteException {
- owningMediaPlayer.lock.lock();
- try {
- if ((owningMediaPlayer.onInfoListener != null)
- && (owningMediaPlayer.mpi == ServiceBackedMediaPlayer.this)) {
- return owningMediaPlayer.onInfoListener.onInfo(owningMediaPlayer, what, extra);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- return false;
- }
- };
- }
- iface.registerOnInfoCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnInfoCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.Stub mOnPitchAdjustmentAvailableChangedCallback = null;
- private void setOnPitchAdjustmentAvailableChangedListener(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnPitchAdjustmentAvailableChangedCallback == null) {
- this.mOnPitchAdjustmentAvailableChangedCallback = new IOnPitchAdjustmentAvailableChangedListenerCallback_0_8.Stub() {
- public void onPitchAdjustmentAvailableChanged(
- boolean pitchAdjustmentAvailable)
- throws RemoteException {
- owningMediaPlayer.lock.lock();
- try {
- if (owningMediaPlayer.onPitchAdjustmentAvailableChangedListener != null) {
- owningMediaPlayer.onPitchAdjustmentAvailableChangedListener.onPitchAdjustmentAvailableChanged(owningMediaPlayer, pitchAdjustmentAvailable);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnPitchAdjustmentAvailableChangedCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnPitchAdjustmentAvailableChangedCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnPreparedListenerCallback_0_8.Stub mOnPreparedCallback = null;
- private void setOnPreparedCallback(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnPreparedCallback == null) {
- this.mOnPreparedCallback = new IOnPreparedListenerCallback_0_8.Stub() {
- public void onPrepared() throws RemoteException {
- owningMediaPlayer.lock.lock();
- Log.d(SBMP_TAG, "setOnPreparedCallback.mOnPreparedCallback.onPrepared 1050");
- try {
- Log.d(SBMP_TAG, "owningMediaPlayer.onPreparedListener is " + ((owningMediaPlayer.onPreparedListener == null) ? "null" : "non-null"));
- Log.d(SBMP_TAG, "owningMediaPlayer.mpi is " + ((owningMediaPlayer.mpi == ServiceBackedMediaPlayer.this) ? "this" : "not this"));
- ServiceBackedMediaPlayer.this.lockMuteOnPreparedCount.lock();
- try {
- if (ServiceBackedMediaPlayer.this.muteOnPreparedCount > 0) {
- ServiceBackedMediaPlayer.this.muteOnPreparedCount--;
- }
- else {
- ServiceBackedMediaPlayer.this.muteOnPreparedCount = 0;
- if (ServiceBackedMediaPlayer.this.owningMediaPlayer.onPreparedListener != null) {
- owningMediaPlayer.onPreparedListener.onPrepared(owningMediaPlayer);
- }
- }
- }
- finally {
- ServiceBackedMediaPlayer.this.lockMuteOnPreparedCount.unlock();
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnPreparedCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnPreparedCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnSeekCompleteListenerCallback_0_8.Stub mOnSeekCompleteCallback = null;
- private void setOnSeekCompleteCallback(IPlayMedia_0_8 iface) {
- try {
- if (this.mOnSeekCompleteCallback == null) {
- this.mOnSeekCompleteCallback = new IOnSeekCompleteListenerCallback_0_8.Stub() {
- public void onSeekComplete() throws RemoteException {
- Log.d(SBMP_TAG, "onSeekComplete() 941");
- owningMediaPlayer.lock.lock();
- try {
- if (ServiceBackedMediaPlayer.this.muteOnSeekCount > 0) {
- Log.d(SBMP_TAG, "The next " + ServiceBackedMediaPlayer.this.muteOnSeekCount + " seek events are muted (counting this one)");
- ServiceBackedMediaPlayer.this.muteOnSeekCount--;
- }
- else {
- ServiceBackedMediaPlayer.this.muteOnSeekCount = 0;
- Log.d(SBMP_TAG, "Attempting to invoke next seek event");
- if (ServiceBackedMediaPlayer.this.owningMediaPlayer.onSeekCompleteListener != null) {
- Log.d(SBMP_TAG, "Invoking onSeekComplete");
- owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer);
- }
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnSeekCompleteCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnSeekCompleteCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- private IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.Stub mOnSpeedAdjustmentAvailableChangedCallback = null;
- private void setOnSpeedAdjustmentAvailableChangedCallback(IPlayMedia_0_8 iface) {
- try {
- Log.d(SBMP_TAG, "Setting the service of on speed adjustment available changed");
- if (this.mOnSpeedAdjustmentAvailableChangedCallback == null) {
- this.mOnSpeedAdjustmentAvailableChangedCallback = new IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8.Stub() {
- public void onSpeedAdjustmentAvailableChanged(
- boolean speedAdjustmentAvailable)
- throws RemoteException {
- owningMediaPlayer.lock.lock();
- try {
- if (owningMediaPlayer.onSpeedAdjustmentAvailableChangedListener != null) {
- owningMediaPlayer.onSpeedAdjustmentAvailableChangedListener.onSpeedAdjustmentAvailableChanged(owningMediaPlayer, speedAdjustmentAvailable);
- }
- }
- finally {
- owningMediaPlayer.lock.unlock();
- }
- }
- };
- }
- iface.registerOnSpeedAdjustmentAvailableChangedCallback(
- ServiceBackedMediaPlayer.this.sessionId,
- this.mOnSpeedAdjustmentAvailableChangedCallback);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.start()
- * Starts a track playing
- */
- @Override
- public void start() {
- Log.d(SBMP_TAG, "start()");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.start(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- stayAwake(true);
- }
-
- /**
- * Functions identically to android.media.MediaPlayer.stop()
- * Stops a track playing and resets its position to the start.
- */
- @Override
- public void stop() {
- Log.d(SBMP_TAG, "stop()");
- if (pmInterface == null) {
- if (!ConnectPlayMediaService()) {
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- }
- try {
- pmInterface.stop(ServiceBackedMediaPlayer.this.sessionId);
- } catch (RemoteException e) {
- e.printStackTrace();
- ServiceBackedMediaPlayer.this.error(MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
- }
- stayAwake(false);
- }
-} \ No newline at end of file
diff --git a/core/src/main/java/com/aocate/media/SpeedAdjustmentAlgorithm.java b/core/src/main/java/com/aocate/media/SpeedAdjustmentAlgorithm.java
deleted file mode 100644
index d337a0452..000000000
--- a/core/src/main/java/com/aocate/media/SpeedAdjustmentAlgorithm.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2011, Aocate, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.aocate.media;
-
-public class SpeedAdjustmentAlgorithm {
- /**
- * Use this to use the user-specified algorithm
- */
- public static int DEFAULT = 0;
-
- /**
- * Better for voice audio
- */
- public static int SONIC = 1;
- /**
- * Better for music audio
- */
- public static int WSOLA = 2;
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java
index 3bc1ce4eb..1064e98ac 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java
@@ -20,5 +20,4 @@ public interface ApplicationCallbacks {
*/
public Intent getStorageErrorActivity(Context context);
- public void setUpdateInterval(long updateInterval);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
index 1a2671555..6619e706b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
@@ -21,7 +21,5 @@ public class ClientConfig {
public static FlattrCallbacks flattrCallbacks;
- public static StorageCallbacks storageCallbacks;
-
public static DBTasksCallbacks dbTasksCallbacks;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/StorageCallbacks.java
deleted file mode 100644
index 5d1a0fffc..000000000
--- a/core/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/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java
deleted file mode 100644
index a13130082..000000000
--- a/core/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.core.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<Downloader> 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<Downloader> 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<Downloader> 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/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java
index 255b95119..7ff622f34 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java
@@ -3,20 +3,22 @@ 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.content.Intent;
import android.os.AsyncTask;
+
+import java.util.concurrent.ExecutionException;
+
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBWriter;
-import java.util.concurrent.ExecutionException;
-
/** Removes a feed in the background. */
public class FeedRemover extends AsyncTask<Void, Void, Void> {
Context context;
ProgressDialog dialog;
Feed feed;
+ public boolean skipOnCompletion = false;
public FeedRemover(Context context, Feed feed) {
super();
@@ -35,30 +37,22 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
}
return null;
}
-
- @Override
- protected void onCancelled() {
- dialog.dismiss();
- }
-
+
@Override
protected void onPostExecute(Void result) {
dialog.dismiss();
+ if(skipOnCompletion) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
+ }
}
@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.setIndeterminate(true);
+ dialog.setCancelable(false);
dialog.show();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
index 5d2d5d441..ac032fcc0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
@@ -6,11 +6,11 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.AsyncTask;
+import android.support.annotation.NonNull;
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;
@@ -63,8 +63,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
*
* @param context A context for accessing the database and posting notifications. Must not be null.
*/
- public FlattrClickWorker(Context context) {
- Validate.notNull(context);
+ public FlattrClickWorker(@NonNull Context context) {
this.context = context.getApplicationContext();
}
@@ -90,11 +89,11 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
return ExitCode.NO_TOKEN;
}
- if (!NetworkUtils.networkAvailable(context)) {
+ if (!NetworkUtils.networkAvailable()) {
return ExitCode.NO_NETWORK;
}
- final List<FlattrThing> flattrQueue = DBReader.getFlattrQueue(context);
+ final List<FlattrThing> flattrQueue = DBReader.getFlattrQueue();
if (extraFlattrThing != null) {
flattrQueue.add(extraFlattrThing);
} else if (flattrQueue.size() == 1) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java
index c4aa76ac7..888591e89 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java
@@ -32,7 +32,7 @@ public class FlattrStatusFetcher extends Thread {
try {
List<Flattr> flattredThings = FlattrUtils.retrieveFlattredThings();
- DBWriter.setFlattredStatus(context, flattredThings).get();
+ DBWriter.setFlattredStatus(flattredThings).get();
} catch (FlattrException e) {
e.printStackTrace();
Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage());
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java
index c0d8049db..edd69f15b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java
@@ -6,7 +6,7 @@ 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 {
+public interface ImageResource {
/**
* This scheme should be used by PicassoImageResources to
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
deleted file mode 100644
index 4f2d5b204..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
+++ /dev/null
@@ -1,510 +0,0 @@
-package de.danoeh.antennapod.core.asynctask;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.squareup.okhttp.Interceptor;
-import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.Response;
-import com.squareup.picasso.Cache;
-import com.squareup.picasso.LruCache;
-import com.squareup.picasso.OkHttpDownloader;
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Request;
-import com.squareup.picasso.RequestHandler;
-import com.squareup.picasso.Transformation;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import de.danoeh.antennapod.core.service.download.HttpDownloader;
-import de.danoeh.antennapod.core.storage.DBReader;
-
-/**
- * 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 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;
- }
-
- private static volatile boolean picassoSetup = false;
-
- public static synchronized void setupPicassoInstance(Context appContext) {
- if (picassoSetup) {
- return;
- }
- OkHttpClient client = new OkHttpClient();
- client.interceptors().add(new BasicAuthenticationInterceptor(appContext));
- Picasso picasso = new Picasso.Builder(appContext)
- .indicatorsEnabled(DEBUG)
- .loggingEnabled(DEBUG)
- .downloader(new OkHttpDownloader(client))
- .addRequestHandler(new MediaRequestHandler(appContext))
- .executor(getExecutorService())
- .memoryCache(getMemoryCache(appContext))
- .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();
- Picasso.setSingletonInstance(picasso);
- picassoSetup = true;
- }
-
- private static class BasicAuthenticationInterceptor implements Interceptor {
-
- private final Context context;
-
- public BasicAuthenticationInterceptor(Context context) {
- this.context = context;
- }
-
- @Override
- public Response intercept(Chain chain) throws IOException {
- com.squareup.okhttp.Request request = chain.request();
- String url = request.urlString();
- String authentication = DBReader.getImageAuthentication(context, url);
-
- if(TextUtils.isEmpty(authentication)) {
- Log.d(TAG, "no credentials for '" + url + "'");
- return chain.proceed(request);
- }
-
- // add authentication
- String[] auth = authentication.split(":");
- String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1");
- com.squareup.okhttp.Request newRequest = request
- .newBuilder()
- .addHeader("Authorization", credentials)
- .build();
- Log.d(TAG, "Basic authentication with ISO-8859-1 encoding");
- Response response = chain.proceed(newRequest);
- if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
- credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8");
- newRequest = request
- .newBuilder()
- .addHeader("Authorization", credentials)
- .build();
- Log.d(TAG, "Basic authentication with UTF-8 encoding");
- return chain.proceed(newRequest);
- } else {
- return response;
- }
- }
- }
-
- private static class MediaRequestHandler extends RequestHandler {
-
- final Context context;
-
- public MediaRequestHandler(Context context) {
- super();
- this.context = context;
- }
-
- @Override
- public boolean canHandleRequest(Request data) {
- return StringUtils.equals(data.uri.getScheme(), PicassoImageResource.SCHEME_MEDIA);
- }
-
- @Override
- public Result load(Request data, int networkPolicy) throws IOException {
- Bitmap bitmap = null;
- MediaMetadataRetriever mmr = null;
- try {
- mmr = new MediaMetadataRetriever();
- mmr.setDataSource(data.uri.getPath());
- byte[] image = mmr.getEmbeddedPicture();
- if (image != null) {
- bitmap = decodeStreamFromByteArray(data, image);
- }
- } catch (RuntimeException e) {
- Log.e(TAG, "Failed to decode image in media file", e);
- } finally {
- if (mmr != null) {
- mmr.release();
- }
- }
-
- if (bitmap == null) {
- Log.wtf(TAG, "THIS SHOULD NEVER EVER HAPPEN!!");
- }
- return new Result(bitmap, Picasso.LoadedFrom.DISK);
-
- }
-
- /* Copied/Adapted from Picasso RequestHandler classes */
-
- private Bitmap decodeStreamFromByteArray(Request data, byte[] bytes) throws IOException {
-
- final BitmapFactory.Options options = createBitmapOptions(data);
- final ByteArrayInputStream in = new ByteArrayInputStream(bytes);
- in.mark(0);
- if (requiresInSampleSize(options)) {
- try {
- BitmapFactory.decodeStream(in, null, options);
- } finally {
- in.reset();
- }
- calculateInSampleSize(data.targetWidth, data.targetHeight, options, data);
- }
- try {
- return BitmapFactory.decodeStream(in, null, options);
- } finally {
- IOUtils.closeQuietly(in);
- }
- }
-
- private Bitmap decodeStreamFromFile(Request data, Uri uri) throws IOException {
- ContentResolver contentResolver = context.getContentResolver();
- final BitmapFactory.Options options = createBitmapOptions(data);
- if (requiresInSampleSize(options)) {
- InputStream is = null;
- try {
- is = contentResolver.openInputStream(uri);
- BitmapFactory.decodeStream(is, null, options);
- } finally {
- IOUtils.closeQuietly(is);
- }
- calculateInSampleSize(data.targetWidth, data.targetHeight, options, data);
- }
- InputStream is = contentResolver.openInputStream(uri);
- try {
- return BitmapFactory.decodeStream(is, null, options);
- } finally {
- IOUtils.closeQuietly(is);
- }
- }
-
- private BitmapFactory.Options createBitmapOptions(Request data) {
- final boolean justBounds = data.hasSize();
- final boolean hasConfig = data.config != null;
- BitmapFactory.Options options = null;
- if (justBounds || hasConfig) {
- options = new BitmapFactory.Options();
- options.inJustDecodeBounds = justBounds;
- if (hasConfig) {
- options.inPreferredConfig = data.config;
- }
- }
- return options;
- }
-
- private static boolean requiresInSampleSize(BitmapFactory.Options options) {
- return options != null && options.inJustDecodeBounds;
- }
-
- private static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options,
- Request request) {
- calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options,
- request);
- }
-
- private static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height,
- BitmapFactory.Options options, Request request) {
- int sampleSize = 1;
- if (height > reqHeight || width > reqWidth) {
- final int heightRatio;
- final int widthRatio;
- if (reqHeight == 0) {
- sampleSize = (int) Math.floor((float) width / (float) reqWidth);
- } else if (reqWidth == 0) {
- sampleSize = (int) Math.floor((float) height / (float) reqHeight);
- } else {
- heightRatio = (int) Math.floor((float) height / (float) reqHeight);
- widthRatio = (int) Math.floor((float) width / (float) reqWidth);
- sampleSize = request.centerInside
- ? Math.max(heightRatio, widthRatio)
- : Math.min(heightRatio, widthRatio);
- }
- }
- options.inSampleSize = sampleSize;
- options.inJustDecodeBounds = false;
- }
- }
-
- public static final int BLUR_RADIUS = 1;
- public static final int BLUR_IMAGE_SIZE = 100;
- public static final String BLUR_KEY = "blur";
-
- public static final Transformation blurTransformation = new Transformation() {
- @Override
- public Bitmap transform(Bitmap source) {
- Bitmap result = fastblur(source, BLUR_RADIUS);
- source.recycle();
- return result;
- }
-
- @Override
- public String key() {
- return BLUR_KEY;
- }
- };
-
- public static Bitmap fastblur(Bitmap sentBitmap, int radius) {
-
- // Stack Blur v1.0 from
- // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
- //
- // Java Author: Mario Klingemann <mario at quasimondo.com>
- // http://incubator.quasimondo.com
- // created Feburary 29, 2004
- // Android port : Yahel Bouaziz <yahel at kayenko.com>
- // http://www.kayenko.com
- // ported april 5th, 2012
-
- // This is a compromise between Gaussian Blur and Box blur
- // It creates much better looking blurs than Box Blur, but is
- // 7x faster than my Gaussian Blur implementation.
- //
- // I called it Stack Blur because this describes best how this
- // filter works internally: it creates a kind of moving stack
- // of colors whilst scanning through the image. Thereby it
- // just has to add one new block of color to the right side
- // of the stack and remove the leftmost color. The remaining
- // colors on the topmost layer of the stack are either added on
- // or reduced by one, depending on if they are on the right or
- // on the left side of the stack.
- //
- // If you are using this algorithm in your code please add
- // the following line:
- //
- // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
-
- Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
-
- if (radius < 1) {
- return (null);
- }
-
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
-
- int[] pix = new int[w * h];
- Log.e("pix", w + " " + h + " " + pix.length);
- bitmap.getPixels(pix, 0, w, 0, 0, w, h);
-
- int wm = w - 1;
- int hm = h - 1;
- int wh = w * h;
- int div = radius + radius + 1;
-
- int r[] = new int[wh];
- int g[] = new int[wh];
- int b[] = new int[wh];
- int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
- int vmin[] = new int[Math.max(w, h)];
-
- int divsum = (div + 1) >> 1;
- divsum *= divsum;
- int dv[] = new int[256 * divsum];
- for (i = 0; i < 256 * divsum; i++) {
- dv[i] = (i / divsum);
- }
-
- yw = yi = 0;
-
- int[][] stack = new int[div][3];
- int stackpointer;
- int stackstart;
- int[] sir;
- int rbs;
- int r1 = radius + 1;
- int routsum, goutsum, boutsum;
- int rinsum, ginsum, binsum;
-
- for (y = 0; y < h; y++) {
- rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
- for (i = -radius; i <= radius; i++) {
- p = pix[yi + Math.min(wm, Math.max(i, 0))];
- sir = stack[i + radius];
- sir[0] = (p & 0xff0000) >> 16;
- sir[1] = (p & 0x00ff00) >> 8;
- sir[2] = (p & 0x0000ff);
- rbs = r1 - Math.abs(i);
- rsum += sir[0] * rbs;
- gsum += sir[1] * rbs;
- bsum += sir[2] * rbs;
- if (i > 0) {
- rinsum += sir[0];
- ginsum += sir[1];
- binsum += sir[2];
- } else {
- routsum += sir[0];
- goutsum += sir[1];
- boutsum += sir[2];
- }
- }
- stackpointer = radius;
-
- for (x = 0; x < w; x++) {
-
- r[yi] = dv[rsum];
- g[yi] = dv[gsum];
- b[yi] = dv[bsum];
-
- rsum -= routsum;
- gsum -= goutsum;
- bsum -= boutsum;
-
- stackstart = stackpointer - radius + div;
- sir = stack[stackstart % div];
-
- routsum -= sir[0];
- goutsum -= sir[1];
- boutsum -= sir[2];
-
- if (y == 0) {
- vmin[x] = Math.min(x + radius + 1, wm);
- }
- p = pix[yw + vmin[x]];
-
- sir[0] = (p & 0xff0000) >> 16;
- sir[1] = (p & 0x00ff00) >> 8;
- sir[2] = (p & 0x0000ff);
-
- rinsum += sir[0];
- ginsum += sir[1];
- binsum += sir[2];
-
- rsum += rinsum;
- gsum += ginsum;
- bsum += binsum;
-
- stackpointer = (stackpointer + 1) % div;
- sir = stack[(stackpointer) % div];
-
- routsum += sir[0];
- goutsum += sir[1];
- boutsum += sir[2];
-
- rinsum -= sir[0];
- ginsum -= sir[1];
- binsum -= sir[2];
-
- yi++;
- }
- yw += w;
- }
- for (x = 0; x < w; x++) {
- rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
- yp = -radius * w;
- for (i = -radius; i <= radius; i++) {
- yi = Math.max(0, yp) + x;
-
- sir = stack[i + radius];
-
- sir[0] = r[yi];
- sir[1] = g[yi];
- sir[2] = b[yi];
-
- rbs = r1 - Math.abs(i);
-
- rsum += r[yi] * rbs;
- gsum += g[yi] * rbs;
- bsum += b[yi] * rbs;
-
- if (i > 0) {
- rinsum += sir[0];
- ginsum += sir[1];
- binsum += sir[2];
- } else {
- routsum += sir[0];
- goutsum += sir[1];
- boutsum += sir[2];
- }
-
- if (i < hm) {
- yp += w;
- }
- }
- yi = x;
- stackpointer = radius;
- for (y = 0; y < h; y++) {
- // Preserve alpha channel: ( 0xff000000 & pix[yi] )
- pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
-
- rsum -= routsum;
- gsum -= goutsum;
- bsum -= boutsum;
-
- stackstart = stackpointer - radius + div;
- sir = stack[stackstart % div];
-
- routsum -= sir[0];
- goutsum -= sir[1];
- boutsum -= sir[2];
-
- if (x == 0) {
- vmin[y] = Math.min(y + r1, hm) * w;
- }
- p = x + vmin[y];
-
- sir[0] = r[p];
- sir[1] = g[p];
- sir[2] = b[p];
-
- rinsum += sir[0];
- ginsum += sir[1];
- binsum += sir[2];
-
- rsum += rinsum;
- gsum += ginsum;
- bsum += binsum;
-
- stackpointer = (stackpointer + 1) % div;
- sir = stack[stackpointer];
-
- routsum += sir[0];
- goutsum += sir[1];
- boutsum += sir[2];
-
- rinsum -= sir[0];
- ginsum -= sir[1];
- binsum -= sir[2];
-
- yi += w;
- }
- }
-
- Log.e("pix", w + " " + h + " " + pix.length);
- bitmap.setPixels(pix, 0, w, 0, 0, w, h);
-
- return (bitmap);
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
index 1535e2e9a..5ea0ba904 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
@@ -89,7 +89,7 @@ public class OpmlBackupAgent extends BackupAgentHelper {
try {
// Write OPML
- new OpmlWriter().writeDocument(DBReader.getFeedList(mContext), writer);
+ new OpmlWriter().writeDocument(DBReader.getFeedList(), writer);
// Compare checksum of new and old file to see if we need to perform a backup at all
if (digester != null) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
index ba1add895..abb75e5e7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
@@ -1,10 +1,10 @@
package de.danoeh.antennapod.core.dialog;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
+
import de.danoeh.antennapod.core.R;
/**
@@ -12,12 +12,16 @@ import de.danoeh.antennapod.core.R;
* classes can handle events like confirmation or cancellation.
*/
public abstract class ConfirmationDialog {
- private static final String TAG = "ConfirmationDialog";
- Context context;
+ private static final String TAG = ConfirmationDialog.class.getSimpleName();
+
+ protected Context context;
int titleId;
int messageId;
+ int positiveText;
+ int negativeText;
+
public ConfirmationDialog(Context context, int titleId, int messageId) {
this.context = context;
this.titleId = titleId;
@@ -25,18 +29,26 @@ public abstract class ConfirmationDialog {
}
public void onCancelButtonPressed(DialogInterface dialog) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Dialog was cancelled");
+ Log.d(TAG, "Dialog was cancelled");
dialog.dismiss();
}
+ public void setPositiveText(int id) {
+ this.positiveText = id;
+ }
+
+ public void setNegativeText(int id) {
+ this.negativeText = id;
+ }
+
+
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,
+ builder.setPositiveButton(positiveText != 0 ? positiveText : R.string.confirm_label,
new DialogInterface.OnClickListener() {
@Override
@@ -44,7 +56,7 @@ public abstract class ConfirmationDialog {
onConfirmButtonPressed(dialog);
}
});
- builder.setNegativeButton(R.string.cancel_label,
+ builder.setNegativeButton(negativeText != 0 ? negativeText : R.string.cancel_label,
new DialogInterface.OnClickListener() {
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java
index 3d174bd8e..b7e79431d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java
@@ -1,8 +1,9 @@
package de.danoeh.antennapod.core.dialog;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+
import de.danoeh.antennapod.core.R;
/** Creates Alert Dialogs if a DownloadRequestException has happened. */
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java
new file mode 100644
index 000000000..124fd3e64
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java
@@ -0,0 +1,28 @@
+package de.danoeh.antennapod.core.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.core.service.download.Downloader;
+
+public class DownloadEvent {
+
+ public final DownloaderUpdate update;
+
+ private DownloadEvent(DownloaderUpdate downloader) {
+ this.update = downloader;
+ }
+
+ public static DownloadEvent refresh(List<Downloader> list) {
+ list = new ArrayList<>(list);
+ DownloaderUpdate update = new DownloaderUpdate(list);
+ return new DownloadEvent(update);
+ }
+
+ @Override
+ public String toString() {
+ return "DownloadEvent{" +
+ "update=" + update +
+ '}';
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java
new file mode 100644
index 000000000..dcb033267
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java
@@ -0,0 +1,53 @@
+package de.danoeh.antennapod.core.event;
+
+import java.util.Arrays;
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.service.download.Downloader;
+import de.danoeh.antennapod.core.util.LongList;
+
+public class DownloaderUpdate {
+
+ /* Downloaders that are currently running */
+ public final List<Downloader> downloaders;
+
+ /**
+ * IDs of feeds that are currently being downloaded
+ * Often used to show some progress wheel in the action bar
+ */
+ public final long[] feedIds;
+
+ /**
+ * IDs of feed media that are currently being downloaded
+ * Can be used to show and update download progress bars
+ */
+ public final long[] mediaIds;
+
+ public DownloaderUpdate(List<Downloader> downloaders) {
+ this.downloaders = downloaders;
+ LongList feedIds1 = new LongList(), mediaIds1 = new LongList();
+ for(Downloader d1 : downloaders) {
+ int type = d1.getDownloadRequest().getFeedfileType();
+ long id = d1.getDownloadRequest().getFeedfileId();
+ if(type == Feed.FEEDFILETYPE_FEED) {
+ feedIds1.add(id);
+ } else if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ mediaIds1.add(id);
+ }
+ }
+
+ this.feedIds = feedIds1.toArray();
+ this.mediaIds = mediaIds1.toArray();
+ }
+
+ @Override
+ public String toString() {
+ return "DownloaderUpdate{" +
+ "downloaders=" + downloaders +
+ ", feedIds=" + Arrays.toString(feedIds) +
+ ", mediaIds=" + Arrays.toString(mediaIds) +
+ '}';
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java
new file mode 100644
index 000000000..d09f6802f
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java
@@ -0,0 +1,38 @@
+package de.danoeh.antennapod.core.event;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+public class FavoritesEvent {
+
+ public enum Action {
+ ADDED, REMOVED
+ }
+
+ public final Action action;
+ public final FeedItem item;
+
+ private FavoritesEvent(Action action, FeedItem item) {
+ this.action = action;
+ this.item = item;
+ }
+
+ public static FavoritesEvent added(FeedItem item) {
+ return new FavoritesEvent(Action.ADDED, item);
+ }
+
+ public static FavoritesEvent removed(FeedItem item) {
+ return new FavoritesEvent(Action.REMOVED, item);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("item", item)
+ .toString();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
new file mode 100644
index 000000000..7ff241456
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
@@ -0,0 +1,48 @@
+package de.danoeh.antennapod.core.event;
+
+
+import android.support.annotation.NonNull;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.Arrays;
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+public class FeedItemEvent {
+
+ public enum Action {
+ UPDATE, DELETE_MEDIA
+ }
+
+ @NonNull public final Action action;
+ @NonNull public final List<FeedItem> items;
+
+ private FeedItemEvent(Action action, List<FeedItem> items) {
+ this.action = action;
+ this.items = items;
+ }
+
+ public static FeedItemEvent deletedMedia(List<FeedItem> items) {
+ return new FeedItemEvent(Action.DELETE_MEDIA, items);
+ }
+
+ public static FeedItemEvent updated(List<FeedItem> items) {
+ return new FeedItemEvent(Action.UPDATE, items);
+ }
+
+ public static FeedItemEvent updated(FeedItem... items) {
+ return new FeedItemEvent(Action.UPDATE, Arrays.asList(items));
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("items", items)
+ .toString();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java
new file mode 100644
index 000000000..864d0a405
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java
@@ -0,0 +1,23 @@
+package de.danoeh.antennapod.core.event;
+
+import de.danoeh.antennapod.core.feed.FeedMedia;
+
+public class FeedMediaEvent {
+
+ public enum Action {
+ UPDATE
+ }
+
+ public final Action action;
+ public final FeedMedia media;
+
+ private FeedMediaEvent(Action action, FeedMedia media) {
+ this.action = action;
+ this.media = media;
+ }
+
+ public static FeedMediaEvent update(FeedMedia media) {
+ return new FeedMediaEvent(Action.UPDATE, media);
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java
new file mode 100644
index 000000000..3769d6bb1
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/ProgressEvent.java
@@ -0,0 +1,36 @@
+package de.danoeh.antennapod.core.event;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public class ProgressEvent {
+
+ public enum Action {
+ START, END
+ }
+
+ public final Action action;
+ public final String message;
+
+ private ProgressEvent(Action action, String message) {
+ this.action = action;
+ this.message = message;
+ }
+
+ public static ProgressEvent start(String message) {
+ return new ProgressEvent(Action.START, message);
+ }
+
+ public static ProgressEvent end() {
+ return new ProgressEvent(Action.END, null);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("message", message)
+ .toString();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java
new file mode 100644
index 000000000..a84e8456f
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java
@@ -0,0 +1,83 @@
+package de.danoeh.antennapod.core.event;
+
+import android.support.annotation.Nullable;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+public class QueueEvent {
+
+ public enum Action {
+ ADDED, ADDED_ITEMS, SET_QUEUE, REMOVED, IRREVERSIBLE_REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED
+ }
+
+ public final Action action;
+ public final FeedItem item;
+ public final int position;
+ public final List<FeedItem> items;
+
+
+ private QueueEvent(Action action,
+ @Nullable FeedItem item,
+ @Nullable List<FeedItem> items,
+ int position) {
+ this.action = action;
+ this.item = item;
+ this.items = items;
+ this.position = position;
+ }
+
+ public static QueueEvent added(FeedItem item, int position) {
+ return new QueueEvent(Action.ADDED, item, null, position);
+ }
+
+ public static QueueEvent setQueue(List<FeedItem> queue) {
+ return new QueueEvent(Action.SET_QUEUE, null, queue, -1);
+ }
+
+ public static QueueEvent removed(FeedItem item) {
+ return new QueueEvent(Action.REMOVED, item, null, -1);
+ }
+
+ public static QueueEvent irreversibleRemoved(FeedItem item) {
+ return new QueueEvent(Action.IRREVERSIBLE_REMOVED, item, null, -1);
+ }
+
+ public static QueueEvent cleared() {
+ return new QueueEvent(Action.CLEARED, null, null, -1);
+ }
+
+ public static QueueEvent sorted(List<FeedItem> sortedQueue) {
+ return new QueueEvent(Action.SORTED, null, sortedQueue, -1);
+ }
+
+ public static QueueEvent moved(FeedItem item, int newPosition) {
+ return new QueueEvent(Action.MOVED, item, null, newPosition);
+ }
+
+ public boolean contains(long id) {
+ if(item != null) {
+ return item.getId() == id;
+ }
+ for(FeedItem item : items) {
+ if(item.getId() == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("item", item)
+ .append("items", items)
+ .append("position", position)
+ .toString();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java
index ce3352ed6..bb594ff87 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java
@@ -1,5 +1,9 @@
package de.danoeh.antennapod.core.feed;
+import android.database.Cursor;
+
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+
public abstract class Chapter extends FeedComponent {
/** Defines starting point in milliseconds. */
@@ -22,6 +26,33 @@ public abstract class Chapter extends FeedComponent {
this.link = link;
}
+ public static Chapter fromCursor(Cursor cursor, FeedItem item) {
+ int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
+ int indexStart = cursor.getColumnIndex(PodDBAdapter.KEY_START);
+ int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK);
+ int indexChapterType = cursor.getColumnIndex(PodDBAdapter.KEY_CHAPTER_TYPE);
+
+ String title = cursor.getString(indexTitle);
+ long start = cursor.getLong(indexStart);
+ String link = cursor.getString(indexLink);
+ int chapterType = cursor.getInt(indexChapterType);
+
+ Chapter chapter = null;
+ 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;
+ }
+ return chapter;
+ }
+
+
public abstract int getChapterType();
public long getStart() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
index 20a85d43f..7ccb742fb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
@@ -3,8 +3,6 @@ package de.danoeh.antennapod.core.feed;
import android.os.Handler;
import android.util.Log;
-import org.apache.commons.lang3.Validate;
-
import java.util.AbstractQueue;
import java.util.Observable;
import java.util.Observer;
@@ -26,7 +24,6 @@ public class EventDistributor extends Observable {
public static final int UNREAD_ITEMS_UPDATE = 2;
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;
public static final int PLAYER_STATUS_UPDATE = 128;
@@ -85,11 +82,6 @@ public class EventDistributor extends Observable {
@Override
public void addObserver(Observer observer) {
super.addObserver(observer);
- Validate.isInstanceOf(EventListener.class, observer);
- }
-
- public void sendDownloadQueuedBroadcast() {
- addEvent(DOWNLOAD_QUEUED);
}
public void sendUnreadItemsUpdateBroadcast() {
@@ -108,13 +100,7 @@ public class EventDistributor extends Observable {
addEvent(DOWNLOADLOG_UPDATE);
}
- public void sendDownloadHandledBroadcast() {
- addEvent(DOWNLOAD_HANDLED);
- }
-
- public void sendPlayerStatusUpdateBroadcast() {
- addEvent(PLAYER_STATUS_UPDATE);
- }
+ public void sendPlayerStatusUpdateBroadcast() { addEvent(PLAYER_STATUS_UPDATE); }
public static abstract class EventListener implements Observer {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index 29ba721fe..4be788f33 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -1,17 +1,18 @@
package de.danoeh.antennapod.core.feed;
import android.content.Context;
+import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
-
-import org.apache.commons.lang3.StringUtils;
+import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
+import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
@@ -20,7 +21,7 @@ import de.danoeh.antennapod.core.util.flattr.FlattrThing;
*
* @author daniel
*/
-public class Feed extends FeedFile implements FlattrThing, PicassoImageResource {
+public class Feed extends FeedFile implements FlattrThing, ImageResource {
public static final int FEEDFILETYPE_FEED = 0;
public static final String TYPE_RSS2 = "rss";
public static final String TYPE_RSS091 = "rss";
@@ -167,20 +168,80 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
public Feed(String url, Date lastUpdate, String title, String username, String password) {
this(url, lastUpdate, title);
- preferences = new FeedPreferences(0, true, username, password);
+ preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, username, password);
+ }
+
+ public static Feed fromCursor(Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexLastUpdate = cursor.getColumnIndex(PodDBAdapter.KEY_LASTUPDATE);
+ int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
+ int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK);
+ int indexDescription = cursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION);
+ int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK);
+ int indexAuthor = cursor.getColumnIndex(PodDBAdapter.KEY_AUTHOR);
+ int indexLanguage = cursor.getColumnIndex(PodDBAdapter.KEY_LANGUAGE);
+ int indexType = cursor.getColumnIndex(PodDBAdapter.KEY_TYPE);
+ int indexFeedIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_IDENTIFIER);
+ int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
+ int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
+ int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
+ int indexFlattrStatus = cursor.getColumnIndex(PodDBAdapter.KEY_FLATTR_STATUS);
+ int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED);
+ int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
+ int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
+ int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
+
+ Date lastUpdate = new Date(cursor.getLong(indexLastUpdate));
+
+ Feed feed = new Feed(
+ cursor.getLong(indexId),
+ lastUpdate,
+ cursor.getString(indexTitle),
+ cursor.getString(indexLink),
+ cursor.getString(indexDescription),
+ cursor.getString(indexPaymentLink),
+ cursor.getString(indexAuthor),
+ cursor.getString(indexLanguage),
+ cursor.getString(indexType),
+ cursor.getString(indexFeedIdentifier),
+ null,
+ cursor.getString(indexFileUrl),
+ cursor.getString(indexDownloadUrl),
+ cursor.getInt(indexDownloaded) > 0,
+ new FlattrStatus(cursor.getLong(indexFlattrStatus)),
+ cursor.getInt(indexIsPaged) > 0,
+ cursor.getString(indexNextPageLink),
+ cursor.getString(indexHide),
+ cursor.getInt(indexLastUpdateFailed) > 0
+ );
+
+ FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
+ feed.setPreferences(preferences);
+ return feed;
+ }
+
+
+ /**
+ * Returns true if at least one item in the itemlist is unread.
+ *
+ */
+ public boolean hasNewItems() {
+ for (FeedItem item : items) {
+ if (item.isNew()) {
+ return true;
+ }
+ }
+ return false;
}
-
/**
* Returns true if at least one item in the itemlist is unread.
*
*/
- public boolean hasNewItems() {
+ public boolean hasUnplayedItems() {
for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.UNREAD) {
- if (item.getMedia() != null) {
- return true;
- }
+ if (false == item.isNew() && false == item.isPlayed()) {
+ return true;
}
}
return false;
@@ -230,7 +291,8 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
}
public void updateFromOther(Feed other) {
- super.updateFromOther(other);
+ // don't update feed's download_url, we do that manually if redirected
+ // see AntennapodHttpClient
if (other.title != null) {
title = other.title;
}
@@ -304,7 +366,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
if (other.isPaged() && !this.isPaged()) {
return true;
}
- if (!StringUtils.equals(other.getNextPageLink(), this.getNextPageLink())) {
+ if (!TextUtils.equals(other.getNextPageLink(), this.getNextPageLink())) {
return true;
}
return false;
@@ -433,7 +495,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
}
public void savePreferences(Context context) {
- DBWriter.setFeedPreferences(context, preferences);
+ DBWriter.setFeedPreferences(preferences);
}
@Override
@@ -482,7 +544,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
return itemfilter;
}
- public void setHiddenItemProperties(String[] properties) {
+ public void setItemFilter(String[] properties) {
if (properties != null) {
this.itemfilter = new FeedItemFilter(properties);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java
new file mode 100644
index 000000000..35abb8de6
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java
@@ -0,0 +1,111 @@
+package de.danoeh.antennapod.core.feed;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FeedFilter {
+
+ private static final String TAG = "FeedFilter";
+
+ private String includeFilter;
+ private String excludeFilter;
+
+ public FeedFilter() {
+ this("", "");
+ }
+
+ public FeedFilter(String includeFilter, String excludeFilter) {
+ // We're storing the strings and not the parsed terms because
+ // 1. It's easier to show the user exactly what they typed in this way
+ // (we don't have to recreate it)
+ // 2. We don't know if we'll actually be asked to parse anything anyways.
+ this.includeFilter = includeFilter;
+ this.excludeFilter = excludeFilter;
+ }
+
+ /**
+ * Parses the text in to a list of single words or quoted strings.
+ * Example: "One "Two Three"" returns ["One", "Two Three"]
+ * @param filter string to parse in to terms
+ * @return list of terms
+ */
+ private List<String> parseTerms(String filter) {
+ // from http://stackoverflow.com/questions/7804335/split-string-on-spaces-in-java-except-if-between-quotes-i-e-treat-hello-wor
+ List<String> list = new ArrayList<>();
+ Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(filter);
+ while (m.find())
+ list.add(m.group(1).replace("\"", ""));
+ return list;
+ }
+
+ /**
+ * @param item
+ * @return true if the item should be downloaded
+ */
+ public boolean shouldAutoDownload(FeedItem item) {
+
+ List<String> includeTerms = parseTerms(includeFilter);
+ List<String> excludeTerms = parseTerms(excludeFilter);
+
+ if (includeTerms.size() == 0 && excludeTerms.size() == 0) {
+ // nothing has been specified, so include everything
+ return true;
+ }
+
+ // check using lowercase so the users don't have to worry about case.
+ String title = item.getTitle().toLowerCase();
+
+ // if it's explicitly excluded, it shouldn't be autodownloaded
+ // even if it has include terms
+ for (String term : excludeTerms) {
+ if (title.contains(term.trim().toLowerCase())) {
+ return false;
+ }
+ }
+
+ for (String term : includeTerms) {
+ if (title.contains(term.trim().toLowerCase())) {
+ return true;
+ }
+ }
+
+ // now's the tricky bit
+ // if they haven't set an include filter, but they have set an exclude filter
+ // default to including, but if they've set both, then exclude
+ if (!hasIncludeFilter() && hasExcludeFilter()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public String getIncludeFilter() {
+ return includeFilter;
+ }
+
+ public String getExcludeFilter() { return excludeFilter; }
+
+ /**
+ * @return true if only include is set
+ */
+ public boolean includeOnly() {
+ return hasIncludeFilter() && !hasExcludeFilter();
+ }
+
+ /**
+ * @return true if only exclude is set
+ */
+ public boolean excludeOnly() {
+ return hasExcludeFilter() && !hasIncludeFilter();
+ }
+
+ public boolean hasIncludeFilter() {
+ return includeFilter.length() > 0;
+ }
+
+ public boolean hasExcludeFilter() {
+ return excludeFilter.length() > 0;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java
index c6f24367e..bd7ceb54f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java
@@ -1,13 +1,15 @@
package de.danoeh.antennapod.core.feed;
+import android.database.Cursor;
import android.net.Uri;
import java.io.File;
-import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
+import de.danoeh.antennapod.core.asynctask.ImageResource;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
-public class FeedImage extends FeedFile implements PicassoImageResource {
+public class FeedImage extends FeedFile implements ImageResource {
public static final int FEEDFILETYPE_FEEDIMAGE = 1;
protected String title;
@@ -31,6 +33,23 @@ public class FeedImage extends FeedFile implements PicassoImageResource {
super();
}
+ public static FeedImage fromCursor(Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
+ int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
+ int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
+ int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
+
+ return new FeedImage(
+ cursor.getLong(indexId),
+ cursor.getString(indexTitle),
+ cursor.getString(indexFileUrl),
+ cursor.getString(indexDownloadUrl),
+ cursor.getInt(indexDownloaded) > 0
+ );
+ }
+
+
@Override
public String getHumanReadableIdentifier() {
if (owner != null && owner.getHumanReadableIdentifier() != null) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
index 11348953e..d8c32f55e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -1,17 +1,21 @@
package de.danoeh.antennapod.core.feed;
+import android.database.Cursor;
import android.net.Uri;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
+import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
@@ -21,7 +25,12 @@ import de.danoeh.antennapod.core.util.flattr.FlattrThing;
*
* @author daniel
*/
-public class FeedItem extends FeedComponent implements ShownotesProvider, FlattrThing, PicassoImageResource {
+public class FeedItem extends FeedComponent implements ShownotesProvider, FlattrThing, ImageResource {
+
+ /** tag that indicates this item is in the queue */
+ public static final String TAG_QUEUE = "Queue";
+ /** tag that indicates this item is in favorites */
+ public static final String TAG_FAVORITE = "Favorite";
/**
* The id/guid that can be found in the rss/atom feed. Might not be set.
@@ -44,7 +53,11 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
private Feed feed;
private long feedId;
- private boolean read;
+ private int state;
+ public final static int NEW = -1;
+ public final static int UNPLAYED = 0;
+ public final static int PLAYED = 1;
+
private String paymentLink;
private FlattrStatus flattrStatus;
@@ -63,10 +76,21 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
private List<Chapter> chapters;
private FeedImage image;
- private boolean autoDownload = true;
+ /*
+ * 0: auto download disabled
+ * 1: auto download enabled (default)
+ * > 1: auto download enabled, (approx.) timestamp of the last failed attempt
+ * where last digit denotes the number of failed attempts
+ */
+ private long autoDownload = 1;
+
+ /**
+ * Any tags assigned to this item
+ */
+ private Set<String> tags = new HashSet<>();
public FeedItem() {
- this.read = true;
+ this.state = UNPLAYED;
this.flattrStatus = new FlattrStatus();
this.hasChapters = false;
}
@@ -75,8 +99,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* This constructor is used by DBReader.
* */
public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId,
- FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, boolean read,
- String itemIdentifier, boolean autoDownload) {
+ FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, int state,
+ String itemIdentifier, long autoDownload) {
this.id = id;
this.title = title;
this.link = link;
@@ -86,7 +110,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.flattrStatus = flattrStatus;
this.hasChapters = hasChapters;
this.image = image;
- this.read = read;
+ this.state = state;
this.itemIdentifier = itemIdentifier;
this.autoDownload = autoDownload;
}
@@ -94,13 +118,13 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
/**
* 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) {
+ public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, int state, 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.state = state;
this.feed = feed;
this.flattrStatus = new FlattrStatus();
this.hasChapters = false;
@@ -109,18 +133,49 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
/**
* This constructor should be used for creating test objects involving chapter marks.
*/
- public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, boolean read, Feed feed, boolean hasChapters) {
+ public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, int state, Feed feed, boolean hasChapters) {
this.id = id;
this.title = title;
this.itemIdentifier = itemIdentifier;
this.link = link;
this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
- this.read = read;
+ this.state = state;
this.feed = feed;
this.flattrStatus = new FlattrStatus();
this.hasChapters = hasChapters;
}
+ public static FeedItem fromCursor(Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
+ int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK);
+ int indexPubDate = cursor.getColumnIndex(PodDBAdapter.KEY_PUBDATE);
+ int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK);
+ int indexFeedId = cursor.getColumnIndex(PodDBAdapter.KEY_FEED);
+ int indexFlattrStatus = cursor.getColumnIndex(PodDBAdapter.KEY_FLATTR_STATUS);
+ int indexHasChapters = cursor.getColumnIndex(PodDBAdapter.KEY_HAS_CHAPTERS);
+ int indexRead = cursor.getColumnIndex(PodDBAdapter.KEY_READ);
+ int indexItemIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_ITEM_IDENTIFIER);
+ int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD);
+
+ long id = cursor.getInt(indexId);
+ assert(id > 0);
+ String title = cursor.getString(indexTitle);
+ String link = cursor.getString(indexLink);
+ Date pubDate = new Date(cursor.getLong(indexPubDate));
+ String paymentLink = cursor.getString(indexPaymentLink);
+ long feedId = cursor.getLong(indexFeedId);
+ boolean hasChapters = cursor.getInt(indexHasChapters) > 0;
+ FlattrStatus flattrStatus = new FlattrStatus(cursor.getLong(indexFlattrStatus));
+ int state = cursor.getInt(indexRead);
+ String itemIdentifier = cursor.getString(indexItemIdentifier);
+ long autoDownload = cursor.getLong(indexAutoDownload);
+
+ FeedItem item = new FeedItem(id, title, link, pubDate, paymentLink, feedId, flattrStatus,
+ hasChapters, null, state, itemIdentifier, autoDownload);
+ return item;
+ }
+
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
if (other.title != null) {
@@ -238,12 +293,25 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.feed = feed;
}
- public boolean isRead() {
- return read;
+ public boolean isNew() {
+ return state == NEW;
}
- public void setRead(boolean read) {
- this.read = read;
+
+ public void setNew() {
+ state = NEW;
+ }
+
+ public boolean isPlayed() {
+ return state == PLAYED;
+ }
+
+ public void setPlayed(boolean played) {
+ if(played) {
+ state = PLAYED;
+ } else {
+ state = UNPLAYED;
+ }
}
private boolean isInProgress() {
@@ -257,11 +325,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public void setContentEncoded(String contentEncoded) {
this.contentEncoded = contentEncoded;
}
-
- public void setFlattrStatus(FlattrStatus status) {
- this.flattrStatus = status;
- }
-
+
public FlattrStatus getFlattrStatus() {
return flattrStatus;
}
@@ -308,7 +372,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public String call() throws Exception {
if (contentEncoded == null || description == null) {
- DBReader.loadExtraInformationOfFeedItem(ClientConfig.applicationCallbacks.getApplicationInstance(), FeedItem.this);
+ DBReader.loadExtraInformationOfFeedItem(FeedItem.this);
}
return (contentEncoded != null) ? contentEncoded : description;
@@ -320,7 +384,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public Uri getImageUri() {
if(media != null && media.hasEmbeddedPicture()) {
return media.getImageUri();
- } else if (hasItemImageDownloaded()) {
+ } else if (image != null) {
return image.getImageUri();
} else if (feed != null) {
return feed.getImageUri();
@@ -342,7 +406,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
return State.IN_PROGRESS;
}
}
- return (isRead() ? State.READ : State.UNREAD);
+ return (isPlayed() ? State.READ : State.UNREAD);
}
public long getFeedId() {
@@ -392,21 +456,54 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public void setAutoDownload(boolean autoDownload) {
- this.autoDownload = autoDownload;
+ this.autoDownload = autoDownload ? 1 : 0;
}
public boolean getAutoDownload() {
- return this.autoDownload;
+ return this.autoDownload > 0;
+ }
+
+ public int getFailedAutoDownloadAttempts() {
+ if (autoDownload <= 1) {
+ return 0;
+ }
+ int failedAttempts = (int)(autoDownload % 10);
+ if (failedAttempts == 0) {
+ failedAttempts = 10;
+ }
+ return failedAttempts;
}
public boolean isAutoDownloadable() {
- return this.hasMedia() &&
- false == this.getMedia().isPlaying() &&
- false == this.getMedia().isDownloaded() &&
- false == this.isRead() &&
- this.getAutoDownload();
+ if (media == null || media.isPlaying() || media.isDownloaded() || autoDownload == 0) {
+ return false;
+ }
+ if (autoDownload == 1) {
+ return true;
+ }
+ int failedAttempts = getFailedAutoDownloadAttempts();
+ double magicValue = 1.767; // 1.767^(10[=#maxNumAttempts]-1) = 168 hours / 7 days
+ int millisecondsInHour = 3600000;
+ long waitingTime = (long) (Math.pow(magicValue, failedAttempts - 1) * millisecondsInHour);
+ long grace = TimeUnit.MINUTES.toMillis(5);
+ return System.currentTimeMillis() > (autoDownload + waitingTime - grace);
}
+ /**
+ * @return true if the item has this tag
+ */
+ public boolean isTagged(String tag) { return tags.contains(tag); }
+
+ /**
+ * @param tag adds this tag to the item. NOTE: does NOT persist to the database
+ */
+ public void addTag(String tag) { tags.add(tag); }
+
+ /**
+ * @param tag the to remove
+ */
+ public void removeTag(String tag) { tags.remove(tag); }
+
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
index 4ad084b39..fdde4b34c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
@@ -1,8 +1,6 @@
package de.danoeh.antennapod.core.feed;
-import android.content.Context;
-
-import org.apache.commons.lang3.StringUtils;
+import android.text.TextUtils;
import java.util.ArrayList;
import java.util.List;
@@ -10,73 +8,87 @@ import java.util.List;
import de.danoeh.antennapod.core.storage.DBReader;
public class FeedItemFilter {
+ private final String[] mProperties;
- private final String[] properties;
-
- private boolean hideUnplayed = false;
- private boolean hidePaused = false;
- private boolean hidePlayed = false;
- private boolean hideQueued = false;
- private boolean hideNotQueued = false;
- private boolean hideDownloaded = false;
- private boolean hideNotDownloaded = false;
+ private boolean showPlayed = false;
+ private boolean showUnplayed = false;
+ private boolean showPaused = false;
+ private boolean showQueued = false;
+ private boolean showNotQueued = false;
+ private boolean showDownloaded = false;
+ private boolean showNotDownloaded = false;
public FeedItemFilter(String properties) {
- this(StringUtils.split(properties, ','));
+ this(TextUtils.split(properties, ","));
}
public FeedItemFilter(String[] properties) {
- this.properties = properties;
+ this.mProperties = properties;
for(String property : properties) {
// see R.arrays.feed_filter_values
switch(property) {
case "unplayed":
- hideUnplayed = true;
+ showUnplayed = true;
break;
case "paused":
- hidePaused = true;
+ showPaused = true;
break;
case "played":
- hidePlayed = true;
+ showPlayed = true;
break;
case "queued":
- hideQueued = true;
+ showQueued = true;
break;
case "not_queued":
- hideNotQueued = true;
+ showNotQueued = true;
break;
case "downloaded":
- hideDownloaded = true;
+ showDownloaded = true;
break;
case "not_downloaded":
- hideNotDownloaded = true;
+ showNotDownloaded = true;
break;
}
}
}
- public List<FeedItem> filter(Context context, List<FeedItem> items) {
- if(properties.length == 0) {
- return items;
- }
- List<FeedItem> result = new ArrayList<FeedItem>();
+ /**
+ * Run a list of feed items through the filter.
+ */
+ public List<FeedItem> filter(List<FeedItem> items) {
+ if(mProperties.length == 0) return items;
+
+ List<FeedItem> result = new ArrayList<>();
+
+ // Check for filter combinations that will always return an empty list
+ // (e.g. requiring played and unplayed at the same time)
+ if (showPlayed && showUnplayed) return result;
+ if (showQueued && showNotQueued) return result;
+ if (showDownloaded && showNotDownloaded) return result;
+
for(FeedItem item : items) {
- if(hideUnplayed && false == item.isRead()) continue;
- if(hidePaused && item.getState() == FeedItem.State.IN_PROGRESS) continue;
- if(hidePlayed && item.isRead()) continue;
- boolean isQueued = DBReader.getQueueIDList(context).contains(item.getId());
- if(hideQueued && isQueued) continue;
- if(hideNotQueued && false == isQueued) continue;
- boolean isDownloaded = item.getMedia() != null && item.getMedia().isDownloaded();
- if(hideDownloaded && isDownloaded) continue;
- if(hideNotDownloaded && false == isDownloaded) continue;
+ // If the item does not meet a requirement, skip it.
+ if (showPlayed && !item.isPlayed()) continue;
+ if (showUnplayed && item.isPlayed()) continue;
+ if (showPaused && item.getState() != FeedItem.State.IN_PROGRESS) continue;
+
+ boolean queued = DBReader.getQueueIDList().contains(item.getId());
+ if (showQueued && !queued) continue;
+ if (showNotQueued && queued) continue;
+
+ boolean downloaded = item.getMedia() != null && item.getMedia().isDownloaded();
+ if (showDownloaded && !downloaded) continue;
+ if (showNotDownloaded && downloaded) continue;
+
+ // If the item reaches here, it meets all criteria
result.add(item);
}
+
return result;
}
public String[] getValues() {
- return properties.clone();
+ return mProperties.clone();
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index f875eb812..56b996d1c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -2,20 +2,23 @@ package de.danoeh.antennapod.core.feed;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.database.Cursor;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
-import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -28,14 +31,26 @@ public class FeedMedia extends FeedFile implements Playable {
public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
+ /**
+ * Indicates we've checked on the size of the item via the network
+ * and got an invalid response. Using Integer.MIN_VALUE because
+ * 1) we'll still check on it in case it gets downloaded (it's <= 0)
+ * 2) By default all FeedMedia have a size of 0 if we don't know it,
+ * so this won't conflict with existing practice.
+ */
+ private static final int CHECKED_ON_SIZE_BUT_UNKNOWN = Integer.MIN_VALUE;
+
private int duration;
private int position; // Current position in file
+ private long lastPlayedTime; // Last time this media was played (in ms)
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;
+ @Nullable private volatile FeedItem item;
private Date playbackCompletionDate;
- private boolean hasEmbeddedPicture;
+
+ // if null: unknown, will be checked
+ private Boolean hasEmbeddedPicture;
/* Used for loading item when restoring from parcel. */
private long itemID;
@@ -50,9 +65,9 @@ public class FeedMedia extends FeedFile implements Playable {
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) {
+ boolean downloaded, Date playbackCompletionDate, int played_duration,
+ long lastPlayedTime) {
super(file_url, download_url, downloaded);
- checkEmbeddedPicture();
this.id = id;
this.item = item;
this.duration = duration;
@@ -62,8 +77,69 @@ public class FeedMedia extends FeedFile implements Playable {
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
+ this.lastPlayedTime = lastPlayedTime;
}
+ 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,
+ Boolean hasEmbeddedPicture, long lastPlayedTime) {
+ this(id, item, duration, position, size, mime_type, file_url, download_url, downloaded,
+ playbackCompletionDate, played_duration, lastPlayedTime);
+ this.hasEmbeddedPicture = hasEmbeddedPicture;
+ }
+
+ public static FeedMedia fromCursor(Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexPlaybackCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE);
+ int indexDuration = cursor.getColumnIndex(PodDBAdapter.KEY_DURATION);
+ int indexPosition = cursor.getColumnIndex(PodDBAdapter.KEY_POSITION);
+ int indexSize = cursor.getColumnIndex(PodDBAdapter.KEY_SIZE);
+ int indexMimeType = cursor.getColumnIndex(PodDBAdapter.KEY_MIME_TYPE);
+ int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
+ int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
+ int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
+ int indexPlayedDuration = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYED_DURATION);
+ int indexLastPlayedTime = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_PLAYED_TIME);
+
+ long mediaId = cursor.getLong(indexId);
+ Date playbackCompletionDate = null;
+ long playbackCompletionTime = cursor.getLong(indexPlaybackCompletionDate);
+ if (playbackCompletionTime > 0) {
+ playbackCompletionDate = new Date(playbackCompletionTime);
+ }
+
+ Boolean hasEmbeddedPicture;
+ switch(cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE))) {
+ case 1:
+ hasEmbeddedPicture = Boolean.TRUE;
+ break;
+ case 0:
+ hasEmbeddedPicture = Boolean.FALSE;
+ break;
+ default:
+ hasEmbeddedPicture = null;
+ break;
+ }
+
+ return new FeedMedia(
+ mediaId,
+ null,
+ cursor.getInt(indexDuration),
+ cursor.getInt(indexPosition),
+ cursor.getLong(indexSize),
+ cursor.getString(indexMimeType),
+ cursor.getString(indexFileUrl),
+ cursor.getString(indexDownloadUrl),
+ cursor.getInt(indexDownloaded) > 0,
+ playbackCompletionDate,
+ cursor.getInt(indexPlayedDuration),
+ hasEmbeddedPicture,
+ cursor.getLong(indexLastPlayedTime)
+ );
+ }
+
+
@Override
public String getHumanReadableIdentifier() {
if (item != null && item.getTitle() != null) {
@@ -92,6 +168,10 @@ public class FeedMedia extends FeedFile implements Playable {
}
public void updateFromOther(FeedMedia other) {
+ // reset to new if feed item did link to a file before
+ if(TextUtils.isEmpty(download_url) && !TextUtils.isEmpty(other.download_url)) {
+ item.setNew();
+ }
super.updateFromOther(other);
if (other.size > 0) {
size = other.size;
@@ -162,6 +242,11 @@ public class FeedMedia extends FeedFile implements Playable {
this.duration = duration;
}
+ @Override
+ public void setLastPlayedTime(long lastPlayedTime) {
+ this.lastPlayedTime = lastPlayedTime;
+ }
+
public int getPlayedDuration() {
return played_duration;
}
@@ -174,8 +259,16 @@ public class FeedMedia extends FeedFile implements Playable {
return position;
}
+ @Override
+ public long getLastPlayedTime() {
+ return lastPlayedTime;
+ }
+
public void setPosition(int position) {
this.position = position;
+ if(position > 0 && item != null && item.isNew()) {
+ this.item.setPlayed(false);
+ }
}
public long getSize() {
@@ -186,6 +279,18 @@ public class FeedMedia extends FeedFile implements Playable {
this.size = size;
}
+ /**
+ * Indicates we asked the service what the size was, but didn't
+ * get a valid answer and we shoudln't check using the network again.
+ */
+ public void setCheckedOnSizeButUnknown() {
+ this.size = CHECKED_ON_SIZE_BUT_UNKNOWN;
+ }
+
+ public boolean checkedOnSizeButUnknown() {
+ return (CHECKED_ON_SIZE_BUT_UNKNOWN == this.size);
+ }
+
public String getMime_type() {
return mime_type;
}
@@ -194,6 +299,7 @@ public class FeedMedia extends FeedFile implements Playable {
this.mime_type = mime_type;
}
+ @Nullable
public FeedItem getItem() {
return item;
}
@@ -230,13 +336,18 @@ public class FeedMedia extends FeedFile implements Playable {
}
public boolean hasEmbeddedPicture() {
- return this.hasEmbeddedPicture;
+ return false;
+ // TODO: reenable!
+ //if(hasEmbeddedPicture == null) {
+ // checkEmbeddedPicture();
+ //}
+ //return hasEmbeddedPicture;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
- dest.writeLong(item.getId());
+ dest.writeLong(item != null ? item.getId() : 0L);
dest.writeInt(duration);
dest.writeInt(position);
@@ -247,33 +358,38 @@ public class FeedMedia extends FeedFile implements Playable {
dest.writeByte((byte) ((downloaded) ? 1 : 0));
dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0);
dest.writeInt(played_duration);
+ dest.writeLong(lastPlayedTime);
}
@Override
public void writeToPreferences(Editor prefEditor) {
- prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
+ if(item != null && item.getFeed() != null) {
+ prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
+ } else {
+ prefEditor.putLong(PREF_FEED_ID, 0L);
+ }
prefEditor.putLong(PREF_MEDIA_ID, id);
}
@Override
public void loadMetadata() throws PlayableException {
if (item == null && itemID != 0) {
- item = DBReader.getFeedItem(ClientConfig.applicationCallbacks.getApplicationInstance(), itemID);
+ item = DBReader.getFeedItem(itemID);
}
}
@Override
public void loadChapterMarks() {
if (item == null && itemID != 0) {
- item = DBReader.getFeedItem(ClientConfig.applicationCallbacks.getApplicationInstance(), itemID);
+ item = DBReader.getFeedItem(itemID);
}
// check if chapters are stored in db and not loaded yet.
if (item != null && item.hasChapters() && item.getChapters() == null) {
- DBReader.loadChaptersOfFeedItem(ClientConfig.applicationCallbacks.getApplicationInstance(), item);
+ DBReader.loadChaptersOfFeedItem(item);
} else if (item != null && item.getChapters() == null && !localFileAvailable()) {
ChapterUtils.loadChaptersFromStreamUrl(this);
if (getChapters() != null && item != null) {
- DBWriter.setFeedItem(ClientConfig.applicationCallbacks.getApplicationInstance(),
+ DBWriter.setFeedItem(
item);
}
}
@@ -349,15 +465,18 @@ public class FeedMedia extends FeedFile implements Playable {
}
@Override
- public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timeStamp) {
+ if(item != null && item.isNew()) {
+ DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
+ }
setPosition(newPosition);
- DBWriter.setFeedMediaPlaybackInformation(ClientConfig.applicationCallbacks.getApplicationInstance(), this);
+ setLastPlayedTime(timeStamp);
+ DBWriter.setFeedMediaPlaybackInformation(this);
}
@Override
public void onPlaybackStart() {
}
-
@Override
public void onPlaybackCompleted() {
@@ -380,11 +499,11 @@ public class FeedMedia extends FeedFile implements Playable {
public String call() throws Exception {
if (item == null) {
item = DBReader.getFeedItem(
- ClientConfig.applicationCallbacks.getApplicationInstance(), itemID);
+ itemID);
}
if (item.getContentEncoded() == null || item.getDescription() == null) {
DBReader.loadExtraInformationOfFeedItem(
- ClientConfig.applicationCallbacks.getApplicationInstance(), item);
+ item);
}
return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
@@ -397,7 +516,7 @@ public class FeedMedia extends FeedFile implements Playable {
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());
+ in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt(), in.readLong());
result.itemID = itemID;
return result;
}
@@ -409,30 +528,44 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public Uri getImageUri() {
- if (hasEmbeddedPicture) {
+ if (hasEmbeddedPicture()) {
Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl());
+
+ if (item != null && item.getFeed() != null) {
+ final Uri feedImgUri = item.getFeed().getImageUri();
+ if (feedImgUri != null) {
+ builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString());
+ }
+ }
return builder.build();
- } else {
+ } else if(item != null) {
return item.getImageUri();
+ } else {
+ return null;
}
}
+ public void setHasEmbeddedPicture(Boolean hasEmbeddedPicture) {
+ this.hasEmbeddedPicture = hasEmbeddedPicture;
+ }
+
@Override
public void setDownloaded(boolean downloaded) {
super.setDownloaded(downloaded);
- checkEmbeddedPicture();
+ if(item != null && downloaded) {
+ item.setPlayed(false);
+ }
}
@Override
public void setFile_url(String file_url) {
super.setFile_url(file_url);
- checkEmbeddedPicture();
}
private void checkEmbeddedPicture() {
if (!localFileAvailable()) {
- hasEmbeddedPicture = false;
+ hasEmbeddedPicture = Boolean.FALSE;
return;
}
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
@@ -440,14 +573,13 @@ public class FeedMedia extends FeedFile implements Playable {
mmr.setDataSource(getLocalMediaUrl());
byte[] image = mmr.getEmbeddedPicture();
if(image != null) {
- hasEmbeddedPicture = true;
- }
- else {
- hasEmbeddedPicture = false;
+ hasEmbeddedPicture = Boolean.TRUE;
+ } else {
+ hasEmbeddedPicture = Boolean.FALSE;
}
} catch (Exception e) {
e.printStackTrace();
- hasEmbeddedPicture = false;
+ hasEmbeddedPicture = Boolean.FALSE;
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
index 2f0304182..faf23a37a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
@@ -1,29 +1,95 @@
package de.danoeh.antennapod.core.feed;
import android.content.Context;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBWriter;
-import org.apache.commons.lang3.StringUtils;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
/**
* Contains preferences for a single feed.
*/
public class FeedPreferences {
+ @NonNull
+ private FeedFilter filter;
private long feedID;
private boolean autoDownload;
+ private boolean keepUpdated;
+
+ public enum AutoDeleteAction {
+ GLOBAL,
+ YES,
+ NO
+ }
+ private AutoDeleteAction auto_delete_action;
private String username;
private String password;
- public FeedPreferences(long feedID, boolean autoDownload, String username, String password) {
+ public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, String username, String password) {
+ this(feedID, autoDownload, true, auto_delete_action, username, password, new FeedFilter());
+ }
+
+ public FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, String username, String password, @NonNull FeedFilter filter) {
this.feedID = feedID;
this.autoDownload = autoDownload;
+ this.keepUpdated = keepUpdated;
+ this.auto_delete_action = auto_delete_action;
this.username = username;
this.password = password;
+ this.filter = filter;
+ }
+
+ public static FeedPreferences fromCursor(Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD);
+ int indexAutoRefresh = cursor.getColumnIndex(PodDBAdapter.KEY_KEEP_UPDATED);
+ int indexAutoDeleteAction = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DELETE_ACTION);
+ int indexUsername = cursor.getColumnIndex(PodDBAdapter.KEY_USERNAME);
+ int indexPassword = cursor.getColumnIndex(PodDBAdapter.KEY_PASSWORD);
+ int indexIncludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_INCLUDE_FILTER);
+ int indexExcludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_EXCLUDE_FILTER);
+
+ long feedId = cursor.getLong(indexId);
+ boolean autoDownload = cursor.getInt(indexAutoDownload) > 0;
+ boolean autoRefresh = cursor.getInt(indexAutoRefresh) > 0;
+ int autoDeleteActionIndex = cursor.getInt(indexAutoDeleteAction);
+ AutoDeleteAction autoDeleteAction = AutoDeleteAction.values()[autoDeleteActionIndex];
+ String username = cursor.getString(indexUsername);
+ String password = cursor.getString(indexPassword);
+ String includeFilter = cursor.getString(indexIncludeFilter);
+ String excludeFilter = cursor.getString(indexExcludeFilter);
+ return new FeedPreferences(feedId, autoDownload, autoRefresh, autoDeleteAction, username, password, new FeedFilter(includeFilter, excludeFilter));
}
+ /**
+ * @return the filter for this feed
+ */
+ public FeedFilter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(@NonNull FeedFilter filter) {
+ this.filter = filter;
+ }
/**
- * Compare another FeedPreferences with this one. The feedID and autoDownload attribute are excluded from the
+ * @return true if this feed should be refreshed when everything else is being refreshed
+ * if false the feed should only be refreshed if requested directly.
+ */
+ public boolean getKeepUpdated() {
+ return keepUpdated;
+ }
+
+ public void setKeepUpdated(boolean keepUpdated) {
+ this.keepUpdated = keepUpdated;
+ }
+
+ /**
+ * Compare another FeedPreferences with this one. The feedID, autoDownload and AutoDeleteAction attribute are excluded from the
* comparison.
*
* @return True if the two objects are different.
@@ -31,17 +97,17 @@ public class FeedPreferences {
public boolean compareWithOther(FeedPreferences other) {
if (other == null)
return true;
- if (!StringUtils.equals(username, other.username)) {
+ if (!TextUtils.equals(username, other.username)) {
return true;
}
- if (!StringUtils.equals(password, other.password)) {
+ if (!TextUtils.equals(password, other.password)) {
return true;
}
return false;
}
/**
- * Update this FeedPreferences object from another one. The feedID and autoDownload attributes are excluded
+ * Update this FeedPreferences object from another one. The feedID, autoDownload and AutoDeleteAction attributes are excluded
* from the update.
*/
public void updateFromOther(FeedPreferences other) {
@@ -67,8 +133,30 @@ public class FeedPreferences {
this.autoDownload = autoDownload;
}
+ public AutoDeleteAction getAutoDeleteAction() {
+ return auto_delete_action;
+ }
+
+ public void setAutoDeleteAction(AutoDeleteAction auto_delete_action) {
+ this.auto_delete_action = auto_delete_action;
+ }
+
+ public boolean getCurrentAutoDelete() {
+ switch (auto_delete_action) {
+ case GLOBAL:
+ return UserPreferences.isAutoDelete();
+
+ case YES:
+ return true;
+
+ case NO:
+ return false;
+ }
+ return false; // TODO - add exceptions here
+ }
+
public void save(Context context) {
- DBWriter.setFeedPreferences(context, this);
+ DBWriter.setFeedPreferences(this);
}
public String getUsername() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
deleted file mode 100644
index c8497f509..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package de.danoeh.antennapod.core.feed;
-
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-import java.util.List;
-
-public class QueueEvent {
-
- public enum Action {
- ADDED, ADDED_ITEMS, REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED
- }
-
- public final Action action;
- public final FeedItem item;
- public final int position;
- public final List<FeedItem> items;
-
- public QueueEvent(Action action) {
- this(action, null, null, -1);
- }
-
- public QueueEvent(Action action, FeedItem item) {
- this(action, item, null, -1);
- }
-
- public QueueEvent(Action action, FeedItem item, int position) {
- this(action, item, null, position);
- }
-
- public QueueEvent(Action action, List<FeedItem> items) {
- this(action, null, items, -1);
- }
-
- private QueueEvent(Action action, FeedItem item, List<FeedItem> items, int position) {
- this.action = action;
- this.item = item;
- this.items = items;
- this.position = position;
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
- .append("action", action)
- .append("item", item)
- .append("items", items)
- .append("position", position)
- .toString();
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
new file mode 100644
index 000000000..0baff9723
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
@@ -0,0 +1,33 @@
+package de.danoeh.antennapod.core.glide;
+
+import android.content.Context;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.load.DecodeFormat;
+import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.module.GlideModule;
+
+import java.io.InputStream;
+
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
+/**
+ * {@see com.bumptech.glide.integration.okhttp.OkHttpGlideModule}
+ */
+public class ApGlideModule implements GlideModule {
+
+ @Override
+ public void applyOptions(Context context, GlideBuilder builder) {
+ builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
+ builder.setDiskCache(new InternalCacheDiskCacheFactory(context,
+ UserPreferences.getImageCacheSize()));
+ }
+
+ @Override
+ public void registerComponents(Context context, Glide glide) {
+ glide.register(GlideUrl.class, InputStream.class, new ApOkHttpUrlLoader.Factory());
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java
new file mode 100644
index 000000000..fc1acd0e1
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java
@@ -0,0 +1,11 @@
+package de.danoeh.antennapod.core.glide;
+
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+
+/**
+ * The settings that AntennaPod will use for various Glide options
+ */
+public class ApGlideSettings {
+
+ public static final DiskCacheStrategy AP_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
new file mode 100644
index 000000000..86baa459c
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
@@ -0,0 +1,141 @@
+package de.danoeh.antennapod.core.glide;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.bumptech.glide.integration.okhttp.OkHttpStreamFetcher;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.squareup.okhttp.Interceptor;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Response;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.core.service.download.HttpDownloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.util.NetworkUtils;
+
+/**
+ * @see com.bumptech.glide.integration.okhttp.OkHttpUrlLoader
+ */
+public class ApOkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
+
+ private static final String TAG = ApOkHttpUrlLoader.class.getSimpleName();
+
+ /**
+ * The default factory for {@link ApOkHttpUrlLoader}s.
+ */
+ public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
+
+ private static volatile OkHttpClient internalClient;
+ private OkHttpClient client;
+
+ private static OkHttpClient getInternalClient() {
+ if (internalClient == null) {
+ synchronized (Factory.class) {
+ if (internalClient == null) {
+ internalClient = AntennapodHttpClient.newHttpClient();
+ internalClient.interceptors().add(new NetworkAllowanceInterceptor());
+ internalClient.interceptors().add(new BasicAuthenticationInterceptor());
+ }
+ }
+ }
+ return internalClient;
+ }
+
+ /**
+ * Constructor for a new Factory that runs requests using a static singleton client.
+ */
+ public Factory() {
+ this(getInternalClient());
+ }
+
+ /**
+ * Constructor for a new Factory that runs requests using given client.
+ */
+ public Factory(OkHttpClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
+ return new ApOkHttpUrlLoader(client);
+ }
+
+ @Override
+ public void teardown() {
+ // Do nothing, this instance doesn't own the client.
+ }
+ }
+
+ private final OkHttpClient client;
+
+ public ApOkHttpUrlLoader(OkHttpClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
+ return new OkHttpStreamFetcher(client, model);
+ }
+
+ private static class NetworkAllowanceInterceptor implements Interceptor {
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ if (NetworkUtils.isDownloadAllowed()) {
+ return chain.proceed(chain.request());
+ } else {
+ return null;
+ }
+ }
+
+ }
+
+ private static class BasicAuthenticationInterceptor implements Interceptor {
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ com.squareup.okhttp.Request request = chain.request();
+ String url = request.urlString();
+ Context context = ClientConfig.applicationCallbacks.getApplicationInstance();
+ String authentication = DBReader.getImageAuthentication(url);
+
+ if(TextUtils.isEmpty(authentication)) {
+ Log.d(TAG, "no credentials for '" + url + "'");
+ return chain.proceed(request);
+ }
+
+ // add authentication
+ String[] auth = authentication.split(":");
+ String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1");
+ com.squareup.okhttp.Request newRequest = request
+ .newBuilder()
+ .addHeader("Authorization", credentials)
+ .build();
+ Log.d(TAG, "Basic authentication with ISO-8859-1 encoding");
+ Response response = chain.proceed(newRequest);
+ if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8");
+ newRequest = request
+ .newBuilder()
+ .addHeader("Authorization", credentials)
+ .build();
+ Log.d(TAG, "Basic authentication with UTF-8 encoding");
+ return chain.proceed(newRequest);
+ } else {
+ return response;
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java
new file mode 100644
index 000000000..ee58c2f39
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java
@@ -0,0 +1,267 @@
+package de.danoeh.antennapod.core.glide;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.ThumbnailUtils;
+import android.util.Log;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+
+public class FastBlurTransformation extends BitmapTransformation {
+
+ private static final String TAG = FastBlurTransformation.class.getSimpleName();
+
+ private static final int STACK_BLUR_RADIUS = 1;
+ private static final int BLUR_IMAGE_WIDTH = 150;
+
+ public FastBlurTransformation(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected Bitmap transform(BitmapPool pool, Bitmap source,
+ int outWidth, int outHeight) {
+ int targetWidth = BLUR_IMAGE_WIDTH;
+ int targetHeight = (int) (1.0 * outHeight * targetWidth / outWidth);
+ Bitmap resized = ThumbnailUtils.extractThumbnail(source, targetWidth, targetHeight);
+ Bitmap result = fastBlur(resized, STACK_BLUR_RADIUS);
+ if (result == null) {
+ Log.w(TAG, "result was null");
+ return source;
+ }
+ return result;
+ }
+
+ @Override
+ public String getId() {
+ return "FastBlurTransformation[width=" + BLUR_IMAGE_WIDTH + "px,radius=" + STACK_BLUR_RADIUS +"]";
+ }
+
+ private static Bitmap fastBlur(Bitmap bitmap, int radius) {
+
+ // Stack Blur v1.0 from
+ // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
+ //
+ // Java Author: Mario Klingemann <mario at quasimondo.com>
+ // http://incubator.quasimondo.com
+ // created Feburary 29, 2004
+ // Android port : Yahel Bouaziz <yahel at kayenko.com>
+ // http://www.kayenko.com
+ // ported april 5th, 2012
+
+ // This is a compromise between Gaussian Blur and Box blur
+ // It creates much better looking blurs than Box Blur, but is
+ // 7x faster than my Gaussian Blur implementation.
+ //
+ // I called it Stack Blur because this describes best how this
+ // filter works internally: it creates a kind of moving stack
+ // of colors whilst scanning through the image. Thereby it
+ // just has to add one new block of color to the right side
+ // of the stack and remove the leftmost color. The remaining
+ // colors on the topmost layer of the stack are either added on
+ // or reduced by one, depending on if they are on the right or
+ // on the left side of the stack.
+ //
+ // If you are using this algorithm in your code please add
+ // the following line:
+ //
+ // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
+
+ if (radius < 1) {
+ return null;
+ }
+
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ int[] pix = new int[w * h];
+ bitmap.getPixels(pix, 0, w, 0, 0, w, h);
+
+ int wm = w - 1;
+ int hm = h - 1;
+ int wh = w * h;
+ int div = radius + radius + 1;
+
+ int r[] = new int[wh];
+ int g[] = new int[wh];
+ int b[] = new int[wh];
+ int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
+ int vmin[] = new int[Math.max(w, h)];
+
+ int divsum = (div + 1) >> 1;
+ divsum *= divsum;
+ int dv[] = new int[256 * divsum];
+ for (i = 0; i < 256 * divsum; i++) {
+ dv[i] = (i / divsum);
+ }
+
+ yw = yi = 0;
+
+ int[][] stack = new int[div][3];
+ int stackpointer;
+ int stackstart;
+ int[] sir;
+ int rbs;
+ int r1 = radius + 1;
+ int routsum, goutsum, boutsum;
+ int rinsum, ginsum, binsum;
+
+ for (y = 0; y < h; y++) {
+ rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
+ for (i = -radius; i <= radius; i++) {
+ p = pix[yi + Math.min(wm, Math.max(i, 0))];
+ sir = stack[i + radius];
+ sir[0] = (p & 0xff0000) >> 16;
+ sir[1] = (p & 0x00ff00) >> 8;
+ sir[2] = (p & 0x0000ff);
+ rbs = r1 - Math.abs(i);
+ rsum += sir[0] * rbs;
+ gsum += sir[1] * rbs;
+ bsum += sir[2] * rbs;
+ if (i > 0) {
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+ } else {
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+ }
+ }
+ stackpointer = radius;
+
+ for (x = 0; x < w; x++) {
+
+ r[yi] = dv[rsum];
+ g[yi] = dv[gsum];
+ b[yi] = dv[bsum];
+
+ rsum -= routsum;
+ gsum -= goutsum;
+ bsum -= boutsum;
+
+ stackstart = stackpointer - radius + div;
+ sir = stack[stackstart % div];
+
+ routsum -= sir[0];
+ goutsum -= sir[1];
+ boutsum -= sir[2];
+
+ if (y == 0) {
+ vmin[x] = Math.min(x + radius + 1, wm);
+ }
+ p = pix[yw + vmin[x]];
+
+ sir[0] = (p & 0xff0000) >> 16;
+ sir[1] = (p & 0x00ff00) >> 8;
+ sir[2] = (p & 0x0000ff);
+
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+
+ rsum += rinsum;
+ gsum += ginsum;
+ bsum += binsum;
+
+ stackpointer = (stackpointer + 1) % div;
+ sir = stack[(stackpointer) % div];
+
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+
+ rinsum -= sir[0];
+ ginsum -= sir[1];
+ binsum -= sir[2];
+
+ yi++;
+ }
+ yw += w;
+ }
+ for (x = 0; x < w; x++) {
+ rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
+ yp = -radius * w;
+ for (i = -radius; i <= radius; i++) {
+ yi = Math.max(0, yp) + x;
+
+ sir = stack[i + radius];
+
+ sir[0] = r[yi];
+ sir[1] = g[yi];
+ sir[2] = b[yi];
+
+ rbs = r1 - Math.abs(i);
+
+ rsum += r[yi] * rbs;
+ gsum += g[yi] * rbs;
+ bsum += b[yi] * rbs;
+
+ if (i > 0) {
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+ } else {
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+ }
+
+ if (i < hm) {
+ yp += w;
+ }
+ }
+ yi = x;
+ stackpointer = radius;
+ for (y = 0; y < h; y++) {
+ // Preserve alpha channel: ( 0xff000000 & pix[yi] )
+ pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
+
+ rsum -= routsum;
+ gsum -= goutsum;
+ bsum -= boutsum;
+
+ stackstart = stackpointer - radius + div;
+ sir = stack[stackstart % div];
+
+ routsum -= sir[0];
+ goutsum -= sir[1];
+ boutsum -= sir[2];
+
+ if (x == 0) {
+ vmin[y] = Math.min(y + r1, hm) * w;
+ }
+ p = x + vmin[y];
+
+ sir[0] = r[p];
+ sir[1] = g[p];
+ sir[2] = b[p];
+
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+
+ rsum += rinsum;
+ gsum += ginsum;
+ bsum += binsum;
+
+ stackpointer = (stackpointer + 1) % div;
+ sir = stack[stackpointer];
+
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+
+ rinsum -= sir[0];
+ ginsum -= sir[1];
+ binsum -= sir[2];
+
+ yi += w;
+ }
+ }
+ bitmap.setPixels(pix, 0, w, 0, 0, w, h);
+ return bitmap;
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
index 1a40120e2..a24e3a485 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.core.gpoddernet;
-import android.os.Build;
-import android.util.Log;
+import android.support.annotation.NonNull;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.MediaType;
@@ -11,9 +10,6 @@ import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
-import org.apache.commons.lang3.Validate;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.ClientProtocolException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -21,27 +17,15 @@ import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
-import java.security.KeyStore;
-import java.security.Principal;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-import javax.security.auth.x500.X500Principal;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
@@ -117,10 +101,9 @@ public class GpodnetService {
*
* @throws IllegalArgumentException if tag is null
*/
- public List<GpodnetPodcast> getPodcastsForTag(GpodnetTag tag, int count)
+ public List<GpodnetPodcast> getPodcastsForTag(@NonNull GpodnetTag tag,
+ int count)
throws GpodnetServiceException {
- Validate.notNull(tag);
-
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/tag/%s/%d.json", tag.getTag(), count), null).toURL();
@@ -144,7 +127,9 @@ public class GpodnetService {
*/
public List<GpodnetPodcast> getPodcastToplist(int count)
throws GpodnetServiceException {
- Validate.isTrue(count >= 1 && count <= 100, "Count must be in range 1..100");
+ if(count < 1 || count > 100) {
+ throw new IllegalArgumentException("Count must be in range 1..100");
+ }
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
@@ -174,7 +159,9 @@ public class GpodnetService {
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public List<GpodnetPodcast> getSuggestions(int count) throws GpodnetServiceException {
- Validate.isTrue(count >= 1 && count <= 100, "Count must be in range 1..100");
+ if(count < 1 || count > 100) {
+ throw new IllegalArgumentException("Count must be in range 1..100");
+ }
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
@@ -231,10 +218,8 @@ public class GpodnetService {
* @throws IllegalArgumentException If username is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public List<GpodnetDevice> getDevices(String username)
+ public List<GpodnetDevice> getDevices(@NonNull String username)
throws GpodnetServiceException {
- Validate.notNull(username);
-
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/devices/%s.json", username), null).toURL();
@@ -259,12 +244,11 @@ public class GpodnetService {
* @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)
+ public void configureDevice(@NonNull String username,
+ @NonNull String deviceId,
+ String caption,
+ GpodnetDevice.DeviceType type)
throws GpodnetServiceException {
- Validate.notNull(username);
- Validate.notNull(deviceId);
-
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/devices/%s/%s.json", username, deviceId), null).toURL();
@@ -302,11 +286,9 @@ public class GpodnetService {
* @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public String getSubscriptionsOfDevice(String username, String deviceId)
+ public String getSubscriptionsOfDevice(@NonNull String username,
+ @NonNull String deviceId)
throws GpodnetServiceException {
- Validate.notNull(username);
- Validate.notNull(deviceId);
-
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/subscriptions/%s/%s.opml", username, deviceId), null).toURL();
@@ -329,9 +311,8 @@ public class GpodnetService {
* @throws IllegalArgumentException If username is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
- public String getSubscriptionsOfUser(String username)
+ public String getSubscriptionsOfUser(@NonNull String username)
throws GpodnetServiceException {
- Validate.notNull(username);
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
@@ -358,12 +339,11 @@ public class GpodnetService {
* @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<String> subscriptions) throws GpodnetServiceException {
- if (username == null || deviceId == null || subscriptions == null) {
- throw new IllegalArgumentException(
- "Username, device ID and subscriptions must not be null");
- }
+ public void uploadSubscriptions(@NonNull String username,
+ @NonNull String deviceId,
+ @NonNull List<String> subscriptions)
+ throws GpodnetServiceException {
+
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/subscriptions/%s/%s.txt", username, deviceId), null).toURL();
@@ -379,6 +359,7 @@ public class GpodnetService {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
+
}
/**
@@ -397,12 +378,11 @@ public class GpodnetService {
* @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<String> added,
- Collection<String> removed) throws GpodnetServiceException {
- Validate.notNull(username);
- Validate.notNull(deviceId);
- Validate.notNull(added);
- Validate.notNull(removed);
+ public GpodnetUploadChangesResponse uploadChanges(@NonNull String username,
+ @NonNull String deviceId,
+ @NonNull Collection<String> added,
+ @NonNull Collection<String> removed)
+ throws GpodnetServiceException {
try {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
@@ -438,10 +418,9 @@ public class GpodnetService {
* @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);
+ public GpodnetSubscriptionChange getSubscriptionChanges(@NonNull String username,
+ @NonNull String deviceId,
+ long timestamp) throws GpodnetServiceException {
String params = String.format("since=%d", timestamp);
String path = String.format("/api/2/subscriptions/%s/%s.json",
@@ -476,11 +455,9 @@ public class GpodnetService {
* @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
* is an authentication error.
*/
- public GpodnetEpisodeActionPostResponse uploadEpisodeActions(Collection<GpodnetEpisodeAction> episodeActions)
+ public GpodnetEpisodeActionPostResponse uploadEpisodeActions(@NonNull Collection<GpodnetEpisodeAction> episodeActions)
throws GpodnetServiceException {
- Validate.notNull(episodeActions);
-
String username = GpodnetPreferences.getUsername();
try {
@@ -549,11 +526,9 @@ public class GpodnetService {
*
* @throws IllegalArgumentException If username or password is null.
*/
- public void authenticate(String username, String password)
+ public void authenticate(@NonNull String username,
+ @NonNull String password)
throws GpodnetServiceException {
- Validate.notNull(username);
- Validate.notNull(password);
-
URL url;
try {
url = new URI(BASE_SCHEME, BASE_HOST, String.format(
@@ -562,7 +537,8 @@ public class GpodnetService {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
- Request.Builder request = new Request.Builder().url(url).post(null);
+ RequestBody body = RequestBody.create(TEXT, "");
+ Request.Builder request = new Request.Builder().url(url).post(body);
executeRequestWithAuthentication(request, username, password);
}
@@ -579,10 +555,8 @@ public class GpodnetService {
}.start();
}
- private String executeRequest(Request.Builder requestB)
+ private String executeRequest(@NonNull Request.Builder requestB)
throws GpodnetServiceException {
- Validate.notNull(requestB);
-
Request request = requestB.header("User-Agent", ClientConfig.USER_AGENT).build();
String responseString = null;
Response response = null;
@@ -627,10 +601,7 @@ public class GpodnetService {
checkStatusCode(response);
body = response.body();
result = getStringFromResponseBody(body);
- } catch (ClientProtocolException e) {
- e.printStackTrace();
- throw new GpodnetServiceException(e);
- } catch (IOException e) {
+ } catch (Exception e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} finally {
@@ -646,10 +617,8 @@ public class GpodnetService {
return result;
}
- private String getStringFromResponseBody(ResponseBody body)
+ private String getStringFromResponseBody(@NonNull ResponseBody body)
throws GpodnetServiceException {
- Validate.notNull(body);
-
ByteArrayOutputStream outputStream;
int contentLength = 0;
try {
@@ -676,31 +645,27 @@ public class GpodnetService {
return outputStream.toString();
}
- private void checkStatusCode(Response response)
+ private void checkStatusCode(@NonNull Response response)
throws GpodnetServiceException {
- Validate.notNull(response);
int responseCode = response.code();
- if (responseCode != HttpStatus.SC_OK) {
- if (responseCode == HttpStatus.SC_UNAUTHORIZED) {
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
throw new GpodnetServiceAuthenticationException("Wrong username or password");
} else {
- throw new GpodnetServiceBadStatusCodeException(
- "Bad response code: " + responseCode, responseCode);
+ throw new GpodnetServiceBadStatusCodeException("Bad response code: "
+ + responseCode, responseCode);
}
}
}
- private List<GpodnetPodcast> readPodcastListFromJSONArray(JSONArray array)
+ private List<GpodnetPodcast> readPodcastListFromJSONArray(@NonNull JSONArray array)
throws JSONException {
- Validate.notNull(array);
-
List<GpodnetPodcast> result = new ArrayList<GpodnetPodcast>(
array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readPodcastFromJSONObject(array.getJSONObject(i)));
}
return result;
-
}
private GpodnetPodcast readPodcastFromJSONObject(JSONObject object)
@@ -745,10 +710,8 @@ public class GpodnetService {
logoUrl, website, mygpoLink);
}
- private List<GpodnetDevice> readDeviceListFromJSONArray(JSONArray array)
+ private List<GpodnetDevice> readDeviceListFromJSONArray(@NonNull JSONArray array)
throws JSONException {
- Validate.notNull(array);
-
List<GpodnetDevice> result = new ArrayList<GpodnetDevice>(
array.length());
for (int i = 0; i < array.length(); i++) {
@@ -767,8 +730,7 @@ public class GpodnetService {
}
private GpodnetSubscriptionChange readSubscriptionChangesFromJSONObject(
- JSONObject object) throws JSONException {
- Validate.notNull(object);
+ @NonNull JSONObject object) throws JSONException {
List<String> added = new LinkedList<String>();
JSONArray jsonAdded = object.getJSONArray("add");
@@ -787,8 +749,7 @@ public class GpodnetService {
}
private GpodnetEpisodeActionGetResponse readEpisodeActionsFromJSONObject(
- JSONObject object) throws JSONException {
- Validate.notNull(object);
+ @NonNull JSONObject object) throws JSONException {
List<GpodnetEpisodeAction> episodeActions = new ArrayList<GpodnetEpisodeAction>();
@@ -804,5 +765,4 @@ public class GpodnetService {
return new GpodnetEpisodeActionGetResponse(episodeActions, timestamp);
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
index 4885a243a..2d49c170a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
@@ -1,6 +1,6 @@
package de.danoeh.antennapod.core.gpoddernet.model;
-import org.apache.commons.lang3.Validate;
+import android.support.annotation.NonNull;
public class GpodnetDevice {
@@ -9,10 +9,10 @@ public class GpodnetDevice {
private DeviceType type;
private int subscriptions;
- public GpodnetDevice(String id, String caption, String type,
+ public GpodnetDevice(@NonNull String id,
+ String caption,
+ String type,
int subscriptions) {
- Validate.notNull(id);
-
this.id = id;
this.caption = caption;
this.type = DeviceType.fromString(type);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
index bd6210d13..2d174a6bc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
@@ -1,13 +1,9 @@
package de.danoeh.antennapod.core.gpoddernet.model;
+import android.text.TextUtils;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
import org.json.JSONException;
import org.json.JSONObject;
@@ -90,15 +86,20 @@ public class GpodnetEpisodeAction {
String podcast = object.optString("podcast", null);
String episode = object.optString("episode", null);
String actionString = object.optString("action", null);
- if(StringUtils.isEmpty(podcast) || StringUtils.isEmpty(episode) || StringUtils.isEmpty(actionString)) {
+ if(TextUtils.isEmpty(podcast) || TextUtils.isEmpty(episode) || TextUtils.isEmpty(actionString)) {
+ return null;
+ }
+ GpodnetEpisodeAction.Action action;
+ try {
+ action = GpodnetEpisodeAction.Action.valueOf(actionString.toUpperCase());
+ } catch (IllegalArgumentException e) {
return null;
}
- GpodnetEpisodeAction.Action action = GpodnetEpisodeAction.Action.valueOf(actionString.toUpperCase());
String deviceId = object.optString("device", "");
GpodnetEpisodeAction.Builder builder = new GpodnetEpisodeAction.Builder(podcast, episode, action)
.deviceId(deviceId);
String utcTimestamp = object.optString("timestamp", null);
- if(StringUtils.isNotEmpty(utcTimestamp)) {
+ if(!TextUtils.isEmpty(utcTimestamp)) {
builder.timestamp(DateUtils.parse(utcTimestamp));
}
if(action == GpodnetEpisodeAction.Action.PLAY) {
@@ -168,34 +169,34 @@ public class GpodnetEpisodeAction {
@Override
public boolean equals(Object o) {
- if(o == null) return false;
- if(this == o) return true;
- if(this.getClass() != o.getClass()) return false;
- GpodnetEpisodeAction that = (GpodnetEpisodeAction)o;
- return new EqualsBuilder()
- .append(this.podcast, that.podcast)
- .append(this.episode, that.episode)
- .append(this.deviceId, that.deviceId)
- .append(this.action, that.action)
- .append(this.timestamp, that.timestamp)
- .append(this.started, that.started)
- .append(this.position, that.position)
- .append(this.total, that.total)
- .isEquals();
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GpodnetEpisodeAction that = (GpodnetEpisodeAction) o;
+
+ if (started != that.started) return false;
+ if (position != that.position) return false;
+ if (total != that.total) return false;
+ if (podcast != null ? !podcast.equals(that.podcast) : that.podcast != null) return false;
+ if (episode != null ? !episode.equals(that.episode) : that.episode != null) return false;
+ if (deviceId != null ? !deviceId.equals(that.deviceId) : that.deviceId != null)
+ return false;
+ if (action != that.action) return false;
+ return !(timestamp != null ? !timestamp.equals(that.timestamp) : that.timestamp != null);
+
}
@Override
public int hashCode() {
- return new HashCodeBuilder()
- .append(this.podcast)
- .append(this.episode)
- .append(this.deviceId)
- .append(this.action)
- .append(this.timestamp)
- .append(this.started)
- .append(this.position)
- .append(this.total)
- .toHashCode();
+ int result = podcast != null ? podcast.hashCode() : 0;
+ result = 31 * result + (episode != null ? episode.hashCode() : 0);
+ result = 31 * result + (deviceId != null ? deviceId.hashCode() : 0);
+ result = 31 * result + (action != null ? action.hashCode() : 0);
+ result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
+ result = 31 * result + started;
+ result = 31 * result + position;
+ result = 31 * result + total;
+ return result;
}
public String writeToString() {
@@ -240,7 +241,16 @@ public class GpodnetEpisodeAction {
@Override
public String toString() {
- return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ return "GpodnetEpisodeAction{" +
+ "podcast='" + podcast + '\'' +
+ ", episode='" + episode + '\'' +
+ ", deviceId='" + deviceId + '\'' +
+ ", action=" + action +
+ ", timestamp=" + timestamp +
+ ", started=" + started +
+ ", position=" + position +
+ ", total=" + total +
+ '}';
}
public static class Builder {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java
index 50420f0a3..1e21efcda 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java
@@ -1,9 +1,7 @@
package de.danoeh.antennapod.core.gpoddernet.model;
-import org.apache.commons.lang3.Validate;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
+import android.support.annotation.NonNull;
import java.util.List;
@@ -12,8 +10,8 @@ public class GpodnetEpisodeActionGetResponse {
private final List<GpodnetEpisodeAction> episodeActions;
private final long timestamp;
- public GpodnetEpisodeActionGetResponse(List<GpodnetEpisodeAction> episodeActions, long timestamp) {
- Validate.notNull(episodeActions);
+ public GpodnetEpisodeActionGetResponse(@NonNull List<GpodnetEpisodeAction> episodeActions,
+ long timestamp) {
this.episodeActions = episodeActions;
this.timestamp = timestamp;
}
@@ -28,7 +26,9 @@ public class GpodnetEpisodeActionGetResponse {
@Override
public String toString() {
- return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ return "GpodnetEpisodeActionGetResponse{" +
+ "episodeActions=" + episodeActions +
+ ", timestamp=" + timestamp +
+ '}';
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
index e06a88d5c..5f096db14 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
@@ -1,12 +1,13 @@
package de.danoeh.antennapod.core.gpoddernet.model;
+import android.support.v4.util.ArrayMap;
+
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import java.util.HashMap;
import java.util.Map;
public class GpodnetEpisodeActionPostResponse {
@@ -36,8 +37,8 @@ public class GpodnetEpisodeActionPostResponse {
public static GpodnetEpisodeActionPostResponse fromJSONObject(String objectString) throws JSONException {
final JSONObject object = new JSONObject(objectString);
final long timestamp = object.getLong("timestamp");
- Map<String, String> updatedUrls = new HashMap<String, String>();
JSONArray urls = object.getJSONArray("update_urls");
+ Map<String, String> updatedUrls = new ArrayMap<String, String>(urls.length());
for (int i = 0; i < urls.length(); i++) {
JSONArray urlPair = urls.getJSONArray(i);
updatedUrls.put(urlPair.getString(0), urlPair.getString(1));
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
index afebf66ac..191c0fa39 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
@@ -1,6 +1,6 @@
package de.danoeh.antennapod.core.gpoddernet.model;
-import org.apache.commons.lang3.Validate;
+import android.support.annotation.NonNull;
public class GpodnetPodcast {
private String url;
@@ -11,12 +11,13 @@ public class GpodnetPodcast {
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);
-
+ public GpodnetPodcast(@NonNull String url,
+ @NonNull String title,
+ @NonNull String description,
+ int subscribers,
+ String logoUrl,
+ String website,
+ String mygpoLink) {
this.url = url;
this.title = title;
this.description = description;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
index a5cb8c0f0..6cc9b79a3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
@@ -1,6 +1,6 @@
package de.danoeh.antennapod.core.gpoddernet.model;
-import org.apache.commons.lang3.Validate;
+import android.support.annotation.NonNull;
import java.util.List;
@@ -9,11 +9,9 @@ public class GpodnetSubscriptionChange {
private List<String> removed;
private long timestamp;
- public GpodnetSubscriptionChange(List<String> added, List<String> removed,
+ public GpodnetSubscriptionChange(@NonNull List<String> added,
+ @NonNull List<String> removed,
long timestamp) {
- Validate.notNull(added);
- Validate.notNull(removed);
-
this.added = added;
this.removed = removed;
this.timestamp = timestamp;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
index cd865731b..42a31afc5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
@@ -2,8 +2,7 @@ package de.danoeh.antennapod.core.gpoddernet.model;
import android.os.Parcel;
import android.os.Parcelable;
-
-import org.apache.commons.lang3.Validate;
+import android.support.annotation.NonNull;
public class GpodnetTag implements Parcelable {
@@ -11,20 +10,16 @@ public class GpodnetTag implements Parcelable {
private final String tag;
private final int usage;
- public GpodnetTag(String title, String tag, int usage) {
- Validate.notNull(title);
- Validate.notNull(tag);
-
+ public GpodnetTag(@NonNull String title, @NonNull String tag, int usage) {
this.title = title;
this.tag = tag;
this.usage = usage;
}
- public static GpodnetTag createFromParcel(Parcel in) {
- final String title = in.readString();
- final String tag = in.readString();
- final int usage = in.readInt();
- return new GpodnetTag(title, tag, usage);
+ protected GpodnetTag(Parcel in) {
+ title = in.readString();
+ tag = in.readString();
+ usage = in.readInt();
}
@Override
@@ -56,5 +51,16 @@ public class GpodnetTag implements Parcelable {
dest.writeInt(usage);
}
+ public static final Creator<GpodnetTag> CREATOR = new Creator<GpodnetTag>() {
+ @Override
+ public GpodnetTag createFromParcel(Parcel in) {
+ return new GpodnetTag(in);
+ }
+
+ @Override
+ public GpodnetTag[] newArray(int size) {
+ return new GpodnetTag[size];
+ }
+ };
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
index 5a37efa5e..9bd1881e4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
@@ -1,10 +1,11 @@
package de.danoeh.antennapod.core.gpoddernet.model;
+import android.support.v4.util.ArrayMap;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -37,7 +38,7 @@ public class GpodnetUploadChangesResponse {
public static GpodnetUploadChangesResponse fromJSONObject(String objectString) throws JSONException {
final JSONObject object = new JSONObject(objectString);
final long timestamp = object.getLong("timestamp");
- Map<String, String> updatedUrls = new HashMap<String, String>();
+ Map<String, String> updatedUrls = new ArrayMap<>();
JSONArray urls = object.getJSONArray("update_urls");
for (int i = 0; i < urls.length(); i++) {
JSONArray urlPair = urls.getJSONArray(i);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java b/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java
index 2b831ca2a..c973713cb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlSymbols.java
@@ -13,6 +13,7 @@ public final class OpmlSymbols {
public static final String VERSION = "version";
public static final String HEAD = "head";
public static final String TITLE = "title";
+ public static final String DATE_CREATED = "dateCreated";
private OpmlSymbols() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java b/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java
index 641190f62..673c602df 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlWriter.java
@@ -2,14 +2,17 @@ package de.danoeh.antennapod.core.opml;
import android.util.Log;
import android.util.Xml;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.Feed;
+
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.Writer;
+import java.util.Date;
import java.util.List;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.util.DateUtils;
+
/** Writes OPML documents. */
public class OpmlWriter {
private static final String TAG = "OpmlWriter";
@@ -27,23 +30,38 @@ public class OpmlWriter {
*/
public void writeDocument(List<Feed> feeds, Writer writer)
throws IllegalArgumentException, IllegalStateException, IOException {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting to write document");
+ Log.d(TAG, "Starting to write document");
XmlSerializer xs = Xml.newSerializer();
xs.setOutput(writer);
xs.startDocument(ENCODING, false);
+ xs.text("\n");
xs.startTag(null, OpmlSymbols.OPML);
xs.attribute(null, OpmlSymbols.VERSION, OPML_VERSION);
+ xs.text("\n");
+ xs.text(" ");
xs.startTag(null, OpmlSymbols.HEAD);
+ xs.text("\n");
+ xs.text(" ");
xs.startTag(null, OpmlSymbols.TITLE);
xs.text(OPML_TITLE);
xs.endTag(null, OpmlSymbols.TITLE);
+ xs.text("\n");
+ xs.text(" ");
+ xs.startTag(null, OpmlSymbols.DATE_CREATED);
+ xs.text(DateUtils.formatRFC822Date(new Date()));
+ xs.endTag(null, OpmlSymbols.DATE_CREATED);
+ xs.text("\n");
+ xs.text(" ");
xs.endTag(null, OpmlSymbols.HEAD);
+ xs.text("\n");
+ xs.text(" ");
xs.startTag(null, OpmlSymbols.BODY);
+ xs.text("\n");
for (Feed feed : feeds) {
+ xs.text(" ");
xs.startTag(null, OpmlSymbols.OUTLINE);
xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle());
xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle());
@@ -55,11 +73,14 @@ public class OpmlWriter {
xs.attribute(null, OpmlSymbols.HTMLURL, feed.getLink());
}
xs.endTag(null, OpmlSymbols.OUTLINE);
+ xs.text("\n");
}
+ xs.text(" ");
xs.endTag(null, OpmlSymbols.BODY);
+ xs.text("\n");
xs.endTag(null, OpmlSymbols.OPML);
+ xs.text("\n");
xs.endDocument();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Finished writing document");
+ Log.d(TAG, "Finished writing document");
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
index c3c6ce8c5..edd7b807a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
@@ -2,13 +2,11 @@ package de.danoeh.antennapod.core.preferences;
import android.content.Context;
import android.content.SharedPreferences;
+import android.text.TextUtils;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -217,26 +215,36 @@ public class GpodnetPreferences {
public static void removeRemovedFeeds(Collection<String> removed) {
ensurePreferencesLoaded();
+ feedListLock.lock();
removedFeeds.removeAll(removed);
writePreference(PREF_SYNC_REMOVED, removedFeeds);
+ feedListLock.unlock();
}
- public static synchronized void enqueueEpisodeAction(GpodnetEpisodeAction action) {
+ public static void enqueueEpisodeAction(GpodnetEpisodeAction action) {
ensurePreferencesLoaded();
+ feedListLock.lock();
queuedEpisodeActions.add(action);
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
+ feedListLock.unlock();
GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
}
public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {
ensurePreferencesLoaded();
- return Collections.unmodifiableList(queuedEpisodeActions);
+ List<GpodnetEpisodeAction> copy = new ArrayList();
+ feedListLock.lock();
+ copy.addAll(queuedEpisodeActions);
+ feedListLock.unlock();
+ return copy;
}
- public static synchronized void removeQueuedEpisodeActions(Collection<GpodnetEpisodeAction> queued) {
+ public static void removeQueuedEpisodeActions(Collection<GpodnetEpisodeAction> queued) {
ensurePreferencesLoaded();
+ feedListLock.lock();
queuedEpisodeActions.removeAll(queued);
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
+ feedListLock.unlock();
}
/**
@@ -252,12 +260,14 @@ public class GpodnetPreferences {
setUsername(null);
setPassword(null);
setDeviceID(null);
+ feedListLock.lock();
addedFeeds.clear();
writePreference(PREF_SYNC_ADDED, addedFeeds);
removedFeeds.clear();
writePreference(PREF_SYNC_REMOVED, removedFeeds);
queuedEpisodeActions.clear();
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
+ feedListLock.unlock();
setLastSubscriptionSyncTimestamp(0);
}
@@ -282,7 +292,7 @@ public class GpodnetPreferences {
String[] lines = s.split("\n");
List<GpodnetEpisodeAction> result = new ArrayList<GpodnetEpisodeAction>(lines.length);
for(String line : lines) {
- if(StringUtils.isNotBlank(line)) {
+ if(TextUtils.isEmpty(line)) {
GpodnetEpisodeAction action = GpodnetEpisodeAction.readFromString(line);
if(action != null) {
result.add(GpodnetEpisodeAction.readFromString(line));
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java
index 714f1b051..dfe056f14 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java
@@ -3,11 +3,7 @@ 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.core.BuildConfig;
import de.danoeh.antennapod.core.feed.EventDistributor;
/**
@@ -15,159 +11,104 @@ import de.danoeh.antennapod.core.feed.EventDistributor;
* 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";
-
- /** The current player status as int. */
+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";
+
+ /**
+ * The current player status as int.
+ */
public static final String PREF_CURRENT_PLAYER_STATUS = "de.danoeh.antennapod.preferences.currentPlayerStatus";
- /** Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. */
- public static final long NO_MEDIA_PLAYING = -1;
+ /**
+ * Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing.
+ */
+ public static final long NO_MEDIA_PLAYING = -1;
- /** Value of PREF_CURRENT_PLAYER_STATUS if media player status is playing. */
+ /**
+ * Value of PREF_CURRENT_PLAYER_STATUS if media player status is playing.
+ */
public static final int PLAYER_STATUS_PLAYING = 1;
- /** Value of PREF_CURRENT_PLAYER_STATUS if media player status is paused. */
+ /**
+ * Value of PREF_CURRENT_PLAYER_STATUS if media player status is paused.
+ */
public static final int PLAYER_STATUS_PAUSED = 2;
- /** Value of PREF_CURRENT_PLAYER_STATUS if media player status is neither playing nor paused. */
+ /**
+ * Value of PREF_CURRENT_PLAYER_STATUS if media player status is neither playing nor paused.
+ */
public static final int PLAYER_STATUS_OTHER = 3;
- private long currentlyPlayingFeedId;
- private long currentlyPlayingFeedMediaId;
- private long currentlyPlayingMedia;
- private boolean currentEpisodeIsStream;
- private boolean currentEpisodeIsVideo;
- private int currentPlayerStatus;
-
- private static PlaybackPreferences instance;
- private Context context;
-
- private PlaybackPreferences(Context context) {
- this.context = context;
- loadPreferences();
- }
+ private static PlaybackPreferences instance;
+ private static SharedPreferences prefs;
- /**
- * 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 PlaybackPreferences() {
+ }
- 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);
- currentPlayerStatus = sp.getInt(PREF_CURRENT_PLAYER_STATUS,
- PLAYER_STATUS_OTHER);
- }
+ public static void init(Context context) {
+ instance = new PlaybackPreferences();
+ prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.registerOnSharedPreferenceChangeListener(instance);
+ }
- @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);
- }
- else if (key.equals(PREF_CURRENT_PLAYER_STATUS)) {
- currentPlayerStatus = sp.getInt(PREF_CURRENT_PLAYER_STATUS,
- PLAYER_STATUS_OTHER);
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(PREF_CURRENT_PLAYER_STATUS)) {
EventDistributor.getInstance().sendPlayerStatusUpdateBroadcast();
}
- }
-
- 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;
+ return prefs.getLong(PREF_CURRENTLY_PLAYING_FEED_ID, -1);
}
public static long getCurrentlyPlayingMedia() {
- instanceAvailable();
- return instance.currentlyPlayingMedia;
+ return prefs.getLong(PREF_CURRENTLY_PLAYING_MEDIA, NO_MEDIA_PLAYING);
}
public static long getCurrentlyPlayingFeedMediaId() {
- return instance.currentlyPlayingFeedMediaId;
+ return prefs.getLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING);
}
public static boolean getCurrentEpisodeIsStream() {
- instanceAvailable();
- return instance.currentEpisodeIsStream;
+ return prefs.getBoolean(PREF_CURRENT_EPISODE_IS_STREAM, true);
}
public static boolean getCurrentEpisodeIsVideo() {
- instanceAvailable();
- return instance.currentEpisodeIsVideo;
+ return prefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false);
}
public static int getCurrentPlayerStatus() {
- instanceAvailable();
- return instance.currentPlayerStatus;
+ return prefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER);
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
index 594241573..6c0aff15e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
@@ -5,12 +5,13 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.SystemClock;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
+import android.text.TextUtils;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Validate;
import org.json.JSONArray;
import org.json.JSONException;
@@ -18,32 +19,38 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedList;
+import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
+import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
/**
* 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
+ * init() or otherwise every public method will throw an Exception
* when called.
*/
-public class UserPreferences implements
- SharedPreferences.OnSharedPreferenceChangeListener {
+public class UserPreferences {
public static final String IMPORT_DIR = "import/";
private static final String TAG = "UserPreferences";
- // User Infercasce
+ // User Interface
public static final String PREF_THEME = "prefTheme";
public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems";
+ public static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder";
+ public static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator";
public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
public static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
+ public static final String PREF_LOCKSCREEN_BACKGROUND = "prefLockscreenBackground";
+ public static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport";
// Queue
public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
@@ -51,16 +58,20 @@ public class UserPreferences implements
// Playback
public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect";
public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect";
+ public static final String PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT = "prefUnpauseOnBluetoothReconnect";
+ public static final String PREF_HARDWARE_FOWARD_BUTTON_SKIPS = "prefHardwareForwardButtonSkips";
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
+ public static final String PREF_SKIP_KEEPS_EPISODE = "prefSkipKeepsEpisode";
public static final String PREF_AUTO_DELETE = "prefAutoDelete";
public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
- private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
+ public static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
public static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall";
// Network
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
+ public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup";
public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
@@ -74,193 +85,52 @@ public class UserPreferences implements
// Other
public static final String PREF_DATA_FOLDER = "prefDataFolder";
+ public static final String PREF_IMAGE_CACHE_SIZE = "prefImageCacheSize";
// Mediaplayer
public static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs";
private static final String PREF_REWIND_SECS = "prefRewindSecs";
public static final String PREF_QUEUE_LOCKED = "prefQueueLocked";
-
- // TODO: Make this value configurable
- private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f;
-
+ public static final String IMAGE_CACHE_DEFAULT_VALUE = "100";
+ public static final int IMAGE_CACHE_SIZE_MINIMUM = 20;
+ public static final String PREF_LEFT_VOLUME = "prefLeftVolume";
+ public static final String PREF_RIGHT_VOLUME = "prefRightVolume";
+
+ // Experimental
+ public static final String PREF_SONIC = "prefSonic";
+ public static final String PREF_STEREO_TO_MONO = "PrefStereoToMono";
+ public static final String PREF_NORMALIZER = "prefNormalizer";
+ public static final int EPISODE_CLEANUP_QUEUE = -1;
+ public static final int EPISODE_CLEANUP_NULL = -2;
+ public static final int EPISODE_CLEANUP_DEFAULT = 0;
+
+ // Constants
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
+ public static int FEED_ORDER_COUNTER = 0;
+ public static int FEED_ORDER_ALPHABETICAL = 1;
+ public static int FEED_ORDER_LAST_UPDATE = 2;
+ public static int FEED_COUNTER_SHOW_NEW_UNPLAYED_SUM = 0;
+ public static int FEED_COUNTER_SHOW_NEW = 1;
+ public static int FEED_COUNTER_SHOW_UNPLAYED = 2;
+ public static int FEED_COUNTER_SHOW_NONE = 3;
- private static UserPreferences instance;
- private final Context context;
-
- // User Interface
- private int theme;
- private List<String> hiddenDrawerItems;
- private int notifyPriority;
- private boolean persistNotify;
-
- // Queue
- private boolean enqueueAtFront;
-
- // Playback
- private boolean pauseOnHeadsetDisconnect;
- private boolean unpauseOnHeadsetReconnect;
- private boolean followQueue;
- private boolean autoDelete;
- private int smartMarkAsPlayedSecs;
- private String[] playbackSpeedArray;
- private boolean pauseForFocusLoss;
- private boolean resumeAfterCall;
-
- // Network
- private long updateInterval;
- private boolean allowMobileUpdate;
- private int parallelDownloads;
- private int episodeCacheSize;
- private boolean enableAutodownload;
- private boolean enableAutodownloadOnBattery;
- private boolean enableAutodownloadWifiFilter;
- private String[] autodownloadSelectedNetworks;
-
- // Services
- private boolean autoFlattr;
- private float autoFlattrPlayedDurationThreshold;
-
- // Settings somewhere in the GUI
- private String playbackSpeed;
- private int fastForwardSecs;
- private int rewindSecs;
- private boolean queueLocked;
-
-
- private UserPreferences(Context context) {
- this.context = context;
- loadPreferences();
- }
+ private static Context context;
+ private static SharedPreferences prefs;
/**
* Sets up the UserPreferences class.
*
* @throws IllegalArgumentException if context is null
*/
- public static void createInstance(Context context) {
+ public static void init(@NonNull Context context) {
Log.d(TAG, "Creating new instance of UserPreferences");
- Validate.notNull(context);
- instance = new UserPreferences(context);
+ UserPreferences.context = context.getApplicationContext();
+ UserPreferences.prefs = PreferenceManager.getDefaultSharedPreferences(context);
createImportDirectory();
createNoMediaFile();
- PreferenceManager.getDefaultSharedPreferences(context)
- .registerOnSharedPreferenceChangeListener(instance);
-
- }
-
- private void loadPreferences() {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
-
- // User Interface
- theme = readThemeValue(sp.getString(PREF_THEME, "0"));
- if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
- notifyPriority = NotificationCompat.PRIORITY_MAX;
- } else {
- notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
- }
- hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ','));
- persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
-
- // Queue
- enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
-
- // Playback
- pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
- unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
- followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
- autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
- smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
- playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
- PREF_PLAYBACK_SPEED_ARRAY, null));
- pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
-
- // Network
- updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0"));
- allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
- parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
- EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger(
- R.integer.episode_cache_size_unlimited);
- episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20"));
- enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
- enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
- enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
- autodownloadSelectedNetworks = StringUtils.split(
- sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
-
- // Services
- autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
- autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
- PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
-
- // MediaPlayer
- playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
- fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30);
- rewindSecs = sp.getInt(PREF_REWIND_SECS, 30);
- queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false);
- }
-
- 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<String> speedList = new LinkedList<String>();
- 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");
- }
}
/**
@@ -269,8 +139,7 @@ public class UserPreferences implements
* @return R.style.Theme_AntennaPod_Light or R.style.Theme_AntennaPod_Dark
*/
public static int getTheme() {
- instanceAvailable();
- return instance.theme;
+ return readThemeValue(prefs.getString(PREF_THEME, "0"));
}
public static int getNoTitleTheme() {
@@ -283,8 +152,18 @@ public class UserPreferences implements
}
public static List<String> getHiddenDrawerItems() {
- instanceAvailable();
- return new ArrayList<String>(instance.hiddenDrawerItems);
+ String hiddenItems = prefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "");
+ return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenItems, ",")));
+ }
+
+ public static int getFeedOrder() {
+ String value = prefs.getString(PREF_DRAWER_FEED_ORDER, "0");
+ return Integer.valueOf(value);
+ }
+
+ public static int getFeedCounterSetting() {
+ String value = prefs.getString(PREF_DRAWER_FEED_COUNTER, "0");
+ return Integer.valueOf(value);
}
/**
@@ -293,8 +172,11 @@ public class UserPreferences implements
* @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT
*/
public static int getNotifyPriority() {
- instanceAvailable();
- return instance.notifyPriority;
+ if (prefs.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
+ return NotificationCompat.PRIORITY_MAX;
+ } else {
+ return NotificationCompat.PRIORITY_DEFAULT;
+ }
}
/**
@@ -303,8 +185,25 @@ public class UserPreferences implements
* @return {@code true} if notifications are persistent, {@code false} otherwise
*/
public static boolean isPersistNotify() {
- instanceAvailable();
- return instance.persistNotify;
+ return prefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, true);
+ }
+
+ /**
+ * Returns true if notifications are persistent
+ *
+ * @return {@code true} if notifications are persistent, {@code false} otherwise
+ */
+ public static boolean setLockscreenBackground() {
+ return prefs.getBoolean(PREF_LOCKSCREEN_BACKGROUND, true);
+ }
+
+ /**
+ * Returns true if download reports are shown
+ *
+ * @return {@code true} if download reports are shown, {@code false} otherwise
+ */
+ public static boolean showDownloadReport() {
+ return prefs.getBoolean(PREF_SHOW_DOWNLOAD_REPORT, true);
}
/**
@@ -313,73 +212,107 @@ public class UserPreferences implements
* @return {@code true} if new queue elements are added to the front; {@code false} otherwise
*/
public static boolean enqueueAtFront() {
- instanceAvailable();
- return instance.enqueueAtFront;
+ return prefs.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
}
public static boolean isPauseOnHeadsetDisconnect() {
- instanceAvailable();
- return instance.pauseOnHeadsetDisconnect;
+ return prefs.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
}
public static boolean isUnpauseOnHeadsetReconnect() {
- instanceAvailable();
- return instance.unpauseOnHeadsetReconnect;
+ return prefs.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
+ }
+
+ public static boolean isUnpauseOnBluetoothReconnect() {
+ return prefs.getBoolean(PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT, false);
+ }
+
+ public static boolean shouldHardwareButtonSkip() {
+ return prefs.getBoolean(PREF_HARDWARE_FOWARD_BUTTON_SKIPS, false);
}
public static boolean isFollowQueue() {
- instanceAvailable();
- return instance.followQueue;
+ return prefs.getBoolean(PREF_FOLLOW_QUEUE, true);
}
+ public static boolean shouldSkipKeepEpisode() { return prefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true); }
+
public static boolean isAutoDelete() {
- instanceAvailable();
- return instance.autoDelete;
+ return prefs.getBoolean(PREF_AUTO_DELETE, false);
}
public static int getSmartMarkAsPlayedSecs() {
- instanceAvailable();
- return instance.smartMarkAsPlayedSecs;
+ return Integer.valueOf(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
}
public static boolean isAutoFlattr() {
- instanceAvailable();
- return instance.autoFlattr;
+ return prefs.getBoolean(PREF_AUTO_FLATTR, false);
}
public static String getPlaybackSpeed() {
- instanceAvailable();
- return instance.playbackSpeed;
+ return prefs.getString(PREF_PLAYBACK_SPEED, "1.00");
}
public static String[] getPlaybackSpeedArray() {
- instanceAvailable();
- return instance.playbackSpeedArray;
+ return readPlaybackSpeedArray(prefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null));
+ }
+
+ public static float getLeftVolume() {
+ int volume = prefs.getInt(PREF_LEFT_VOLUME, 100);
+ if(volume == 100) {
+ return 1.0f;
+ } else {
+ return (float) (1 - (Math.log(100 - volume) / Math.log(100)));
+ }
+ }
+
+ public static float getRightVolume() {
+ int volume = prefs.getInt(PREF_RIGHT_VOLUME, 100);
+ if(volume == 100) {
+ return 1.0f;
+ } else {
+ return (float) (1 - (Math.log(100 - volume) / Math.log(100)));
+ }
}
public static boolean shouldPauseForFocusLoss() {
- instanceAvailable();
- return instance.pauseForFocusLoss;
+ return prefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
}
+
+
public static long getUpdateInterval() {
- instanceAvailable();
- return instance.updateInterval;
+ String updateInterval = prefs.getString(PREF_UPDATE_INTERVAL, "0");
+ if(false == updateInterval.contains(":")) {
+ return readUpdateInterval(updateInterval);
+ } else {
+ return 0;
+ }
+ }
+
+ public static int[] getUpdateTimeOfDay() {
+ String datetime = prefs.getString(PREF_UPDATE_INTERVAL, "");
+ if(datetime.length() >= 3 && datetime.contains(":")) {
+ String[] parts = datetime.split(":");
+ int hourOfDay = Integer.valueOf(parts[0]);
+ int minute = Integer.valueOf(parts[1]);
+ return new int[] { hourOfDay, minute };
+ } else {
+ return new int[0];
+ }
}
public static boolean isAllowMobileUpdate() {
- instanceAvailable();
- return instance.allowMobileUpdate;
+ return prefs.getBoolean(PREF_MOBILE_UPDATE, false);
}
public static int getParallelDownloads() {
- instanceAvailable();
- return instance.parallelDownloads;
+ return Integer.valueOf(prefs.getString(PREF_PARALLEL_DOWNLOADS, "4"));
}
public static int getEpisodeCacheSizeUnlimited() {
- return EPISODE_CACHE_SIZE_UNLIMITED;
+ return context.getResources().getInteger(R.integer.episode_cache_size_unlimited);
}
/**
@@ -388,33 +321,40 @@ public class UserPreferences implements
* 'unlimited'.
*/
public static int getEpisodeCacheSize() {
- instanceAvailable();
- return instance.episodeCacheSize;
+ return readEpisodeCacheSizeInternal(prefs.getString(PREF_EPISODE_CACHE_SIZE, "20"));
}
public static boolean isEnableAutodownload() {
- instanceAvailable();
- return instance.enableAutodownload;
+ return prefs.getBoolean(PREF_ENABLE_AUTODL, false);
}
public static boolean isEnableAutodownloadOnBattery() {
- instanceAvailable();
- return instance.enableAutodownloadOnBattery;
+ return prefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
}
public static boolean isEnableAutodownloadWifiFilter() {
- instanceAvailable();
- return instance.enableAutodownloadWifiFilter;
+ return prefs.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
+ }
+
+ public static int getImageCacheSize() {
+ String cacheSizeString = prefs.getString(PREF_IMAGE_CACHE_SIZE, IMAGE_CACHE_DEFAULT_VALUE);
+ int cacheSizeInt = Integer.valueOf(cacheSizeString);
+ // if the cache size is too small the user won't get any images at all
+ // that's bad, force it back to the default.
+ if (cacheSizeInt < IMAGE_CACHE_SIZE_MINIMUM) {
+ prefs.edit().putString(PREF_IMAGE_CACHE_SIZE, IMAGE_CACHE_DEFAULT_VALUE).apply();
+ cacheSizeInt = Integer.valueOf(IMAGE_CACHE_DEFAULT_VALUE);
+ }
+ int cacheSizeMB = cacheSizeInt * 1024 * 1024;
+ return cacheSizeMB;
}
public static int getFastFowardSecs() {
- instanceAvailable();
- return instance.fastForwardSecs;
+ return prefs.getInt(PREF_FAST_FORWARD_SECS, 30);
}
public static int getRewindSecs() {
- instanceAvailable();
- return instance.rewindSecs;
+ return prefs.getInt(PREF_REWIND_SECS, 30);
}
@@ -423,145 +363,38 @@ public class UserPreferences implements
* duration.
*/
public static float getAutoFlattrPlayedDurationThreshold() {
- instanceAvailable();
- return instance.autoFlattrPlayedDurationThreshold;
+ return prefs.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, 0.8f);
}
public static String[] getAutodownloadSelectedNetworks() {
- instanceAvailable();
- return instance.autodownloadSelectedNetworks;
+ String selectedNetWorks = prefs.getString(PREF_AUTODL_SELECTED_NETWORKS, "");
+ return TextUtils.split(selectedNetWorks, ",");
}
public static boolean shouldResumeAfterCall() {
- instanceAvailable();
- return instance.resumeAfterCall;
+ return prefs.getBoolean(PREF_RESUME_AFTER_CALL, true);
}
public static boolean isQueueLocked() {
- instanceAvailable();
- return instance.queueLocked;
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
- Log.d(TAG, "Registered change of user preferences. Key: " + key);
- switch(key) {
- // User Interface
- case PREF_THEME:
- theme = readThemeValue(sp.getString(PREF_THEME, ""));
- break;
- case PREF_HIDDEN_DRAWER_ITEMS:
- hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ','));
- break;
- case PREF_EXPANDED_NOTIFICATION:
- if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
- notifyPriority = NotificationCompat.PRIORITY_MAX;
- } else {
- notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
- }
- break;
- case PREF_PERSISTENT_NOTIFICATION:
- persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
- break;
- // Queue
- case PREF_QUEUE_ADD_TO_FRONT:
- enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
- break;
- // Playback
- case PREF_PAUSE_ON_HEADSET_DISCONNECT:
- pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
- break;
- case PREF_UNPAUSE_ON_HEADSET_RECONNECT:
- unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
- break;
- case PREF_FOLLOW_QUEUE:
- followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
- break;
- case PREF_AUTO_DELETE:
- autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
- break;
- case PREF_SMART_MARK_AS_PLAYED_SECS:
- smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
- break;
- case PREF_PLAYBACK_SPEED_ARRAY:
- playbackSpeedArray = readPlaybackSpeedArray(sp.getString(PREF_PLAYBACK_SPEED_ARRAY, null));
- break;
- case PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS:
- pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
- break;
- case PREF_RESUME_AFTER_CALL:
- resumeAfterCall = sp.getBoolean(PREF_RESUME_AFTER_CALL, true);
- break;
- // Network
- case PREF_UPDATE_INTERVAL:
- updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0"));
- ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval);
- break;
- case PREF_MOBILE_UPDATE:
- allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
- break;
- case PREF_PARALLEL_DOWNLOADS:
- parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
- break;
- case PREF_EPISODE_CACHE_SIZE:
- episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20"));
- break;
- case PREF_ENABLE_AUTODL:
- enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
- break;
- case PREF_ENABLE_AUTODL_ON_BATTERY:
- enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
- break;
- case PREF_ENABLE_AUTODL_WIFI_FILTER:
- enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
- break;
- case PREF_AUTODL_SELECTED_NETWORKS:
- autodownloadSelectedNetworks = StringUtils.split(
- sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
- break;
- // Services
- case PREF_AUTO_FLATTR:
- autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
- break;
- case PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD:
- autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
- PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
- break;
- // Mediaplayer
- case PREF_PLAYBACK_SPEED:
- playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
- break;
- case PREF_FAST_FORWARD_SECS:
- fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30);
- break;
- case PREF_REWIND_SECS:
- rewindSecs = sp.getInt(PREF_REWIND_SECS, 30);
- break;
- case PREF_QUEUE_LOCKED:
- queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false);
- break;
- default:
- Log.w(TAG, "Unhandled key: " + key);
- }
+ return prefs.getBoolean(PREF_QUEUE_LOCKED, false);
}
public static void setPrefFastForwardSecs(int secs) {
- Log.d(TAG, "setPrefFastForwardSecs(" + secs +")");
- SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit();
- editor.putInt(PREF_FAST_FORWARD_SECS, secs);
- editor.commit();
+ prefs.edit()
+ .putInt(PREF_FAST_FORWARD_SECS, secs)
+ .apply();
}
public static void setPrefRewindSecs(int secs) {
- Log.d(TAG, "setPrefRewindSecs(" + secs +")");
- SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit();
- editor.putInt(PREF_REWIND_SECS, secs);
- editor.commit();
+ prefs.edit()
+ .putInt(PREF_REWIND_SECS, secs)
+ .apply();
}
public static void setPlaybackSpeed(String speed) {
- PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
- .putString(PREF_PLAYBACK_SPEED, speed).apply();
+ prefs.edit()
+ .putString(PREF_PLAYBACK_SPEED, speed)
+ .apply();
}
public static void setPlaybackSpeedArray(String[] speeds) {
@@ -569,74 +402,161 @@ public class UserPreferences implements
for (String speed : speeds) {
jsonArray.put(speed);
}
- PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
- .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString())
- .apply();
+ prefs.edit()
+ .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString())
+ .apply();
+ }
+
+ public static void setVolume(int leftVolume, int rightVolume) {
+ assert(0 <= leftVolume && leftVolume <= 100);
+ assert(0 <= rightVolume && rightVolume <= 100);
+ prefs.edit()
+ .putInt(PREF_LEFT_VOLUME, leftVolume)
+ .putInt(PREF_RIGHT_VOLUME, rightVolume)
+ .apply();
+ }
+
+ public static void setAutodownloadSelectedNetworks(String[] value) {
+ prefs.edit()
+ .putString(PREF_AUTODL_SELECTED_NETWORKS, TextUtils.join(",", value))
+ .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.
+ */
+ public static void setUpdateInterval(long hours) {
+ prefs.edit()
+ .putString(PREF_UPDATE_INTERVAL, String.valueOf(hours))
+ .apply();
+ // when updating with an interval, we assume the user wants
+ // to update *now* and then every 'hours' interval thereafter.
+ restartUpdateAlarm(true);
}
/**
- * Sets the update interval value. Should only be used for testing purposes!
+ * Sets the update interval value.
*/
- 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;
+ public static void setUpdateTimeOfDay(int hourOfDay, int minute) {
+ prefs.edit()
+ .putString(PREF_UPDATE_INTERVAL, hourOfDay + ":" + minute)
+ .apply();
+ restartUpdateAlarm(false);
}
/**
* 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;
- }
-
- public static void setHiddenDrawerItems(Context context, List<String> items) {
- instanceAvailable();
- instance.hiddenDrawerItems = items;
- String str = StringUtils.join(items, ',');
- PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext())
- .edit()
- .putString(PREF_HIDDEN_DRAWER_ITEMS, str)
- .commit();
+ public static void setAutoFlattrSettings( boolean enabled, float autoFlattrThreshold) {
+ if(autoFlattrThreshold < 0.0 || autoFlattrThreshold > 1.0) {
+ throw new IllegalArgumentException("Flattr threshold must be in range [0.0, 1.0]");
+ }
+ prefs.edit()
+ .putBoolean(PREF_AUTO_FLATTR, enabled)
+ .putFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, autoFlattrThreshold)
+ .apply();
+ }
+
+ public static void setHiddenDrawerItems(List<String> items) {
+ String str = TextUtils.join(",", items);
+ prefs.edit()
+ .putString(PREF_HIDDEN_DRAWER_ITEMS, str)
+ .apply();
}
public static void setQueueLocked(boolean locked) {
- instanceAvailable();
- instance.queueLocked = locked;
- PreferenceManager.getDefaultSharedPreferences(instance.context)
- .edit()
- .putBoolean(PREF_QUEUE_LOCKED, locked)
- .commit();
+ prefs.edit()
+ .putBoolean(PREF_QUEUE_LOCKED, locked)
+ .apply();
+ }
+
+ private static 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 static long readUpdateInterval(String valueFromPrefs) {
+ int hours = Integer.parseInt(valueFromPrefs);
+ return TimeUnit.HOURS.toMillis(hours);
+ }
+
+ private static 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 static 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<String> speedList = new ArrayList<>();
+ 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;
+ }
+
+ public static boolean useSonic() {
+ return prefs.getBoolean(PREF_SONIC, false);
+ }
+
+ public static void enableSonic(boolean enable) {
+ prefs.edit()
+ .putBoolean(PREF_SONIC, enable)
+ .apply();
+ }
+
+ public static boolean stereoToMono() {
+ return prefs.getBoolean(PREF_STEREO_TO_MONO, false);
+ }
+
+ public static void stereoToMono(boolean enable) {
+ prefs.edit()
+ .putBoolean(PREF_STEREO_TO_MONO, enable)
+ .apply();
+ }
+
+
+ public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() {
+ int cleanupValue = Integer.valueOf(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
+ if (cleanupValue == EPISODE_CLEANUP_QUEUE) {
+ return new APQueueCleanupAlgorithm();
+ } else if (cleanupValue == EPISODE_CLEANUP_NULL) {
+ return new APNullCleanupAlgorithm();
+ } else {
+ return new APCleanupAlgorithm(cleanupValue);
+ }
+ }
/**
* Return the folder where the app stores all of its data. This method will
@@ -647,10 +567,7 @@ public class UserPreferences implements
* @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());
+ public static File getDataFolder(String type) {
String strDir = prefs.getString(PREF_DATA_FOLDER, null);
if (strDir == null) {
Log.d(TAG, "Using default data folder");
@@ -672,7 +589,7 @@ public class UserPreferences implements
for (int i = 0; i < dirs.length; i++) {
if (dirs.length > 0) {
if (i < dirs.length - 1) {
- dataDir = getDataFolder(context, dirs[i]);
+ dataDir = getDataFolder(dirs[i]);
if (dataDir == null) {
return null;
}
@@ -695,13 +612,10 @@ public class UserPreferences implements
}
public static void setDataFolder(String dir) {
- 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();
+ Log.d(TAG, "setDataFolder(dir: " + dir + ")");
+ prefs.edit()
+ .putString(PREF_DATA_FOLDER, dir)
+ .apply();
createImportDirectory();
}
@@ -709,8 +623,7 @@ public class UserPreferences implements
* Create a .nomedia file to prevent scanning by the media scanner.
*/
private static void createNoMediaFile() {
- File f = new File(instance.context.getExternalFilesDir(null),
- ".nomedia");
+ File f = new File(context.getExternalFilesDir(null), ".nomedia");
if (!f.exists()) {
try {
f.createNewFile();
@@ -727,8 +640,7 @@ public class UserPreferences implements
* available
*/
private static void createImportDirectory() {
- File importDir = getDataFolder(instance.context,
- IMPORT_DIR);
+ File importDir = getDataFolder(IMPORT_DIR);
if (importDir != null) {
if (importDir.exists()) {
Log.d(TAG, "Import directory already exists");
@@ -741,32 +653,68 @@ public class UserPreferences implements
}
}
+ public static void restartUpdateAlarm(boolean now) {
+ int[] timeOfDay = getUpdateTimeOfDay();
+ Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay));
+ if (timeOfDay.length == 2) {
+ restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1]);
+ } else {
+ long hours = getUpdateInterval();
+ long startTrigger = hours;
+ if (now) {
+ startTrigger = TimeUnit.SECONDS.toMillis(10);
+ }
+ restartUpdateIntervalAlarm(startTrigger, hours);
+ }
+ }
+
/**
- * Updates alarm registered with the AlarmManager service or deactivates it.
+ * Sets the interval in which the feeds are refreshed automatically
*/
- public static void restartUpdateAlarm(long triggerAtMillis, long intervalMillis) {
- instanceAvailable();
+ public static void restartUpdateIntervalAlarm(long triggerAtMillis, long intervalMillis) {
Log.d(TAG, "Restarting update alarm.");
- AlarmManager alarmManager = (AlarmManager) instance.context
- .getSystemService(Context.ALARM_SERVICE);
- PendingIntent updateIntent = PendingIntent.getBroadcast(
- instance.context, 0, new Intent(ClientConfig.applicationCallbacks.getApplicationInstance(), FeedUpdateReceiver.class), 0);
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(context, FeedUpdateReceiver.class);
+ PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmManager.cancel(updateIntent);
- if (intervalMillis != 0) {
- alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, intervalMillis,
+ if (intervalMillis > 0) {
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + triggerAtMillis,
updateIntent);
- Log.d(TAG, "Changed alarm to new interval");
+ Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
} else {
Log.d(TAG, "Automatic update was deactivated");
}
}
+ /**
+ * Sets time of day the feeds are refreshed automatically
+ */
+ public static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute) {
+ Log.d(TAG, "Restarting update alarm.");
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, FeedUpdateReceiver.class), 0);
+ alarmManager.cancel(updateIntent);
+
+ Calendar now = Calendar.getInstance();
+ Calendar alarm = (Calendar)now.clone();
+ alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
+ alarm.set(Calendar.MINUTE, minute);
+ if (alarm.before(now) || alarm.equals(now)) {
+ alarm.add(Calendar.DATE, 1);
+ }
+ Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
+ alarmManager.set(AlarmManager.RTC_WAKEUP,
+ alarm.getTimeInMillis(),
+ updateIntent);
+ Log.d(TAG, "Changed alarm to new time of day " + hoursOfDay + ":" + minute);
+ }
/**
* 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);
+ return readEpisodeCacheSizeInternal(valueFromPrefs);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java
index 84277b6d5..ce5004a98 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java
@@ -3,32 +3,28 @@ package de.danoeh.antennapod.core.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.text.TextUtils;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
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");
+ Log.d(TAG, "Received intent");
+ if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) {
+ Log.d(TAG, "Resetting update alarm after reboot");
+ } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_PACKAGE_REPLACED)) {
+ Log.d(TAG, "Resetting update alarm after app upgrade");
}
-
- ClientConfig.applicationCallbacks.setUpdateInterval(UserPreferences.getUpdateInterval());
-
+ PlaybackPreferences.init(context);
+ UserPreferences.init(context);
+ UserPreferences.restartUpdateAlarm(false);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index d37f97a5f..b959c7301 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.util.Log;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.NetworkUtils;
@@ -18,11 +19,12 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent");
- if (NetworkUtils.isDownloadAllowed(context)) {
- DBTasks.refreshExpiredFeeds(context);
+ if (NetworkUtils.isDownloadAllowed()) {
+ DBTasks.refreshAllFeeds(context, null);
} else {
Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
}
+ UserPreferences.restartUpdateAlarm(false);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
index 3f2222f42..0b90cef6c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
@@ -8,12 +8,12 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
+import android.support.v4.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import java.util.Collection;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -108,7 +108,7 @@ public class GpodnetSyncService extends Service {
private synchronized void sync() {
- if (GpodnetPreferences.loggedIn() == false || NetworkUtils.networkAvailable(this) == false) {
+ if (GpodnetPreferences.loggedIn() == false || NetworkUtils.networkAvailable() == false) {
stopSelf();
return;
}
@@ -126,7 +126,7 @@ public class GpodnetSyncService extends Service {
private synchronized void syncSubscriptionChanges() {
final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
try {
- final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
+ final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls();
Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy();
Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy();
GpodnetService service = tryLogin();
@@ -226,11 +226,11 @@ public class GpodnetSyncService extends Service {
if(remoteActions.size() == 0) {
return;
}
- Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
+ Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new ArrayMap<>();
for(GpodnetEpisodeAction action : localActions) {
Pair key = new Pair(action.getPodcast(), action.getEpisode());
GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key);
- if (mostRecent == null) {
+ if (mostRecent == null || mostRecent.getTimestamp() == null) {
localMostRecentPlayAction.put(key, action);
} else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
localMostRecentPlayAction.put(key, action);
@@ -238,13 +238,13 @@ public class GpodnetSyncService extends Service {
}
// make sure more recent local actions are not overwritten by older remote actions
- Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
+ Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new ArrayMap<>();
for (GpodnetEpisodeAction action : remoteActions) {
switch (action.getAction()) {
case NEW:
- FeedItem newItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
+ FeedItem newItem = DBReader.getFeedItem(action.getPodcast(), action.getEpisode());
if(newItem != null) {
- DBWriter.markItemRead(this, newItem, false, true);
+ DBWriter.markItemPlayed(newItem, FeedItem.UNPLAYED, true);
} else {
Log.i(TAG, "Unknown feed item: " + action);
}
@@ -255,12 +255,15 @@ public class GpodnetSyncService extends Service {
Pair key = new Pair(action.getPodcast(), action.getEpisode());
GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
if(localMostRecent == null ||
+ localMostRecent.getTimestamp() == null ||
localMostRecent.getTimestamp().before(action.getTimestamp())) {
GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
- if (mostRecent == null) {
+ if (mostRecent == null || mostRecent.getTimestamp() == null) {
mostRecentPlayAction.put(key, action);
- } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
+ } else if (action.getTimestamp() != null && mostRecent.getTimestamp().before(action.getTimestamp())) {
mostRecentPlayAction.put(key, action);
+ } else {
+ Log.d(TAG, "No date information in action, skipping it");
}
}
break;
@@ -270,14 +273,14 @@ public class GpodnetSyncService extends Service {
}
}
for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
- FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
+ FeedItem playItem = DBReader.getFeedItem(action.getPodcast(), action.getEpisode());
if (playItem != null) {
FeedMedia media = playItem.getMedia();
media.setPosition(action.getPosition() * 1000);
- DBWriter.setFeedMedia(this, media);
+ DBWriter.setFeedMedia(media);
if(playItem.getMedia().hasAlmostEnded()) {
- DBWriter.markItemRead(this, playItem, true, true);
- DBWriter.addItemToPlaybackHistory(this, playItem.getMedia());
+ DBWriter.markItemPlayed(playItem, FeedItem.PLAYED, true);
+ DBWriter.addItemToPlaybackHistory(playItem.getMedia());
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/APRedirectHandler.java
deleted file mode 100644
index 3efcf4da8..000000000
--- a/core/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.core.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<CHi.length;i++)
- s = s.replaceAll(CHi[i], CHo[i]);
-
- // If anything had to be fixed, then replace the header
- if (!s.equals(h[0].getValue()))
- {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Original URL: " + h[0].getValue());
-
- response.setHeader(LOC, s);
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Fixed URL: " + s);
- }
- }
-
- // call DefaultRedirectHandler with fixed URL
- return super.getLocationURI(response, context);
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
index ec3d3e2fe..b23819ef7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
@@ -1,14 +1,29 @@
package de.danoeh.antennapod.core.service.download;
+import android.os.Build;
+import android.support.annotation.NonNull;
import android.util.Log;
import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+import com.squareup.okhttp.internal.http.StatusLine;
+import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URL;
+import java.security.GeneralSecurityException;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.BuildConfig;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import de.danoeh.antennapod.core.storage.DBWriter;
/**
* Provides access to a HttpClient singleton.
@@ -30,32 +45,74 @@ public class AntennapodHttpClient {
public static synchronized OkHttpClient getHttpClient() {
if (httpClient == null) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Creating new instance of HTTP client");
-
- System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS));
-
- OkHttpClient client = new OkHttpClient();
-
- // set cookie handler
- CookieManager cm = new CookieManager();
- cm.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
- client.setCookieHandler(cm);
-
- // set timeouts
- client.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
- client.setReadTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
- client.setWriteTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
-
- // configure redirects
- client.setFollowRedirects(true);
- client.setFollowSslRedirects(true);
-
- httpClient = client;
+ httpClient = newHttpClient();
}
return httpClient;
}
/**
+ * Creates a new HTTP client. Most users should just use
+ * getHttpClient() to get the standard AntennaPod client,
+ * but sometimes it's necessary for others to have their own
+ * copy so that the clients don't share state.
+ * @return http client
+ */
+ @NonNull
+ public static OkHttpClient newHttpClient() {
+ Log.d(TAG, "Creating new instance of HTTP client");
+
+ System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS));
+
+ OkHttpClient client = new OkHttpClient();
+
+ // detect 301 Moved permanently and 308 Permanent Redirect
+ client.networkInterceptors().add(chain -> {
+ Request request = chain.request();
+ Response response = chain.proceed(request);
+ if(response.code() == HttpURLConnection.HTTP_MOVED_PERM ||
+ response.code() == StatusLine.HTTP_PERM_REDIRECT) {
+ String location = response.header("Location");
+ if(location.startsWith("/")) { // URL is not absolute, but relative
+ URL url = request.url();
+ location = url.getProtocol() + "://" + url.getHost() + location;
+ } else if(!location.toLowerCase().startsWith("http://") &&
+ !location.toLowerCase().startsWith("https://")) {
+ // Reference is relative to current path
+ URL url = request.url();
+ String path = url.getPath();
+ String newPath = path.substring(0, path.lastIndexOf("/") + 1) + location;
+ location = url.getProtocol() + "://" + url.getHost() + newPath;
+ }
+ try {
+ DBWriter.updateFeedDownloadURL(request.urlString(), location).get();
+ } catch (Exception e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
+ }
+ return response;
+ });
+
+ // set cookie handler
+ CookieManager cm = new CookieManager();
+ cm.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
+ client.setCookieHandler(cm);
+
+ // set timeouts
+ client.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+ client.setReadTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
+ client.setWriteTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ // configure redirects
+ client.setFollowRedirects(true);
+ client.setFollowSslRedirects(true);
+
+ if(16 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 21) {
+ client.setSslSocketFactory(new CustomSslSocketFactory());
+ }
+ return client;
+ }
+
+ /**
* Closes expired connections. This method should be called by the using class once has finished its work with
* the HTTP client.
*/
@@ -64,4 +121,71 @@ public class AntennapodHttpClient {
// does nothing at the moment
}
}
+
+ private static class CustomSslSocketFactory extends SSLSocketFactory {
+
+ private SSLSocketFactory factory;
+
+ public CustomSslSocketFactory() {
+ try {
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
+ sslContext.init(null, null, null);
+ factory= sslContext.getSocketFactory();
+ } catch(GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return factory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return factory.getSupportedCipherSuites();
+ }
+
+ public Socket createSocket() throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket();
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(String var1, int var2) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(Socket var1, String var2, int var3, boolean var4) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(InetAddress var1, int var2) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(String var1, int var2, InetAddress var3, int var4) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(InetAddress var1, int var2, InetAddress var3, int var4) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
+ configureSocket(result);
+ return result;
+ }
+
+ private void configureSocket(SSLSocket s) {
+ s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" } );
+ }
+
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java
index 41bbd5ba6..bc3006eea 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java
@@ -3,8 +3,7 @@ package de.danoeh.antennapod.core.service.download;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-
-import org.apache.commons.lang3.Validate;
+import android.support.annotation.NonNull;
import de.danoeh.antennapod.core.feed.FeedFile;
import de.danoeh.antennapod.core.util.URLChecker;
@@ -27,11 +26,15 @@ public class DownloadRequest implements Parcelable {
protected long size;
protected int statusMsg;
- public DownloadRequest(String destination, String source, String title,
- long feedfileId, int feedfileType, String username, String password, boolean deleteOnFailure, Bundle arguments) {
- Validate.notNull(destination);
- Validate.notNull(source);
- Validate.notNull(title);
+ public DownloadRequest(@NonNull String destination,
+ @NonNull String source,
+ @NonNull String title,
+ long feedfileId,
+ int feedfileType,
+ String username,
+ String password,
+ boolean deleteOnFailure,
+ Bundle arguments) {
this.destination = destination;
this.source = source;
@@ -260,7 +263,7 @@ public class DownloadRequest implements Parcelable {
private int feedfileType;
private Bundle arguments;
- public Builder(String destination, FeedFile item) {
+ public Builder(@NonNull String destination, @NonNull FeedFile item) {
this.destination = destination;
this.source = URLChecker.prepareURL(item.getDownload_url());
this.title = item.getHumanReadableIdentifier();
@@ -285,9 +288,6 @@ public class DownloadRequest implements Parcelable {
}
public DownloadRequest build() {
- Validate.notNull(destination);
- Validate.notNull(source);
- Validate.notNull(title);
return new DownloadRequest(this);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index e7b226eca..d69228ceb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -14,20 +14,19 @@ import android.media.MediaMetadataRetriever;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.Pair;
+import android.text.TextUtils;
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.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -54,7 +53,8 @@ import javax.xml.parsers.ParserConfigurationException;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.event.DownloadEvent;
+import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -72,9 +72,9 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
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;
+import de.greenrobot.event.EventBus;
/**
* Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent.
@@ -103,22 +103,11 @@ public class DownloadService extends Service {
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<Long> newMediaFiles;
-
- /**
* Contains all completed downloads that have not been included in the report yet.
*/
private List<DownloadStatus> reportQueue;
@@ -136,7 +125,6 @@ public class DownloadService extends Service {
private NotificationCompat.Builder notificationCompatBuilder;
- private Notification.BigTextStyle notificationBuilder;
private int NOTIFICATION_ID = 2;
private int REPORT_ID = 3;
@@ -162,6 +150,8 @@ public class DownloadService extends Service {
private static final int SCHED_EX_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor schedExecutor;
+ private Handler postHandler = new Handler();
+
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
@@ -171,7 +161,7 @@ public class DownloadService extends Service {
}
private Thread downloadCompletionThread = new Thread() {
- private static final String TAG = "downloadCompletionThread";
+ private static final String TAG = "downloadCompletionThd";
@Override
public void run() {
@@ -187,10 +177,7 @@ public class DownloadService extends Service {
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());
+ handleCompletedFeedDownload(downloader.getDownloadRequest());
} else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest());
}
@@ -200,7 +187,7 @@ public class DownloadService extends Service {
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) {
+ && Integer.valueOf(status.getReasonDetailed()) == 416) {
Log.d(TAG, "Requested invalid range, restarting download from the beginning");
FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination()));
@@ -209,9 +196,32 @@ public class DownloadService extends Service {
Log.e(TAG, "Download failed");
saveDownloadStatus(status);
handleFailedDownload(status, downloader.getDownloadRequest());
+
+ if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ long id = status.getFeedfileId();
+ FeedMedia media = DBReader.getFeedMedia(id);
+ if(media == null || media.getItem() == null) {
+ return;
+ }
+ FeedItem item = media.getItem();
+ boolean httpNotFound = status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR
+ && String.valueOf(HttpURLConnection.HTTP_NOT_FOUND).equals(status.getReasonDetailed());
+ boolean notEnoughSpace = status.getReason() == DownloadError.ERROR_NOT_ENOUGH_SPACE;
+ if (httpNotFound || notEnoughSpace) {
+ DBWriter.saveFeedItemAutoDownloadFailed(item).get();
+ }
+ // to make lists reload the failed item, we fake an item update
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
+ }
+ }
+ } else {
+ // if FeedMedia download has been canceled, fake FeedItem update
+ // so that lists reload that it
+ if(status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId());
+ EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
}
}
- sendDownloadHandledIntent();
queryDownloadsAsync();
}
} catch (InterruptedException e) {
@@ -241,9 +251,8 @@ public class DownloadService extends Service {
Log.d(TAG, "Service started");
isRunning = true;
handler = new Handler();
- newMediaFiles = Collections.synchronizedList(new ArrayList<Long>());
reportQueue = Collections.synchronizedList(new ArrayList<DownloadStatus>());
- downloads = new ArrayList<Downloader>();
+ downloads = Collections.synchronizedList(new ArrayList<Downloader>());
numberOfDownloads = new AtomicInteger(0);
IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
@@ -309,10 +318,14 @@ public class DownloadService extends Service {
Log.d(TAG, "Service shutting down");
isRunning = false;
- if (ClientConfig.downloadServiceCallbacks.shouldCreateReport()) {
+ if (ClientConfig.downloadServiceCallbacks.shouldCreateReport() &&
+ UserPreferences.showDownloadReport()) {
updateReport();
}
+ postHandler.removeCallbacks(postDownloaderTask);
+ EventBus.getDefault().postSticky(DownloadEvent.refresh(Collections.emptyList()));
+
stopForeground(true);
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID);
@@ -324,29 +337,20 @@ public class DownloadService extends Service {
cancelNotificationUpdater();
unregisterReceiver(cancelDownloadReceiver);
- if (!newMediaFiles.isEmpty()) {
- DBTasks.autodownloadUndownloadedItems(getApplicationContext(),
- ArrayUtils.toPrimitive(newMediaFiles.toArray(new Long[newMediaFiles.size()])));
- }
+ // start auto download in case anything new has shown up
+ DBTasks.autodownloadUndownloadedItems(getApplicationContext());
}
- @SuppressLint("NewApi")
private void setupNotificationBuilders() {
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(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this)).setLargeIcon(icon)
- .setSmallIcon(R.drawable.stat_notify_sync)
- );
- } else {
notificationCompatBuilder = new NotificationCompat.Builder(this)
- .setOngoing(true).setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this))
+ .setOngoing(true)
+ .setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this))
.setLargeIcon(icon)
.setSmallIcon(R.drawable.stat_notify_sync);
- }
+
Log.d(TAG, "Notification set up");
}
@@ -354,58 +358,48 @@ public class DownloadService extends Service {
* 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);
+ downloadsLeft = getResources()
+ .getQuantityString(R.plurals.downloads_left, numDownloads, numDownloads);
} 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());
+ if (notificationCompatBuilder != 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");
}
- } else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- if (request.getTitle() != null) {
- if (i > 0) {
- bigText.append("\n");
- }
- bigText.append("\u2022 " + request.getTitle()
- + " (" + request.getProgressPercent()
- + "%)");
+ 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();
+ notificationCompatBuilder.setContentTitle(contentTitle);
+ notificationCompatBuilder.setContentText(downloadsLeft);
+ if (bigText != null) {
+ notificationCompatBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText.toString()));
}
+ return notificationCompatBuilder.build();
}
return null;
}
@@ -423,9 +417,11 @@ public class DownloadService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_DOWNLOAD)) {
+ if (TextUtils.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(url == null) {
+ throw new IllegalArgumentException("ACTION_CANCEL_DOWNLOAD intent needs download url extra");
+ }
Log.d(TAG, "Cancelling download with url " + url);
Downloader d = getDownloader(url);
@@ -434,14 +430,14 @@ public class DownloadService extends Service {
} else {
Log.e(TAG, "Could not cancel download with url " + url);
}
+ postDownloaders();
- } else if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) {
+ } else if (TextUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) {
for (Downloader d : downloads) {
d.cancel();
Log.d(TAG, "Cancelled all downloads");
}
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
-
+ postDownloaders();
}
queryDownloads();
}
@@ -460,13 +456,14 @@ public class DownloadService extends Service {
if (downloader != null) {
numberOfDownloads.incrementAndGet();
// smaller rss feeds before bigger media files
- if(request.getFeedfileId() == Feed.FEEDFILETYPE_FEED) {
+ if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
downloads.add(0, downloader);
} else {
downloads.add(downloader);
}
downloadExecutor.submit(downloader);
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+
+ postDownloaders();
}
queryDownloads();
@@ -497,7 +494,7 @@ public class DownloadService extends Service {
boolean rc = downloads.remove(d);
Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ postDownloaders();
}
});
}
@@ -510,11 +507,7 @@ public class DownloadService extends Service {
*/
private void saveDownloadStatus(DownloadStatus status) {
reportQueue.add(status);
- DBWriter.addDownloadStatus(this, status);
- }
-
- private void sendDownloadHandledIntent() {
- EventDistributor.getInstance().sendDownloadHandledBroadcast();
+ DBWriter.addDownloadStatus(status);
}
/**
@@ -633,14 +626,6 @@ public class DownloadService extends Service {
}
/**
- * Is called whenever a Feed-Image is downloaded
- */
- private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
- 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) {
@@ -774,52 +759,8 @@ public class DownloadService extends Service {
for (int i = 0; i < savedFeeds.length; i++) {
Feed savedFeed = savedFeeds[i];
- // Download Feed Image if provided and not downloaded
- if (savedFeed.getImage() != null
- && savedFeed.getImage().isDownloaded() == false) {
- 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.getPubDate() == null) {
- Log.d(TAG, item.toString());
- }
- if(item.getImage() != null && item.getImage().isDownloaded() == false) {
- item.getImage().setOwner(item);
- try {
- requester.downloadImage(DownloadService.this,
- item.getImage());
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
- }
- if (!item.isRead() && item.hasMedia() && !item.getMedia().isDownloaded()) {
- newMediaFiles.add(item.getMedia().getId());
- }
- }
// If loadAllPages=true, check if another page is available and queue it for download
-
final boolean loadAllPages = results.get(i).first.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES);
final Feed feed = results.get(i).second.feed;
if (loadAllPages && feed.getNextPageLink() != null) {
@@ -837,8 +778,6 @@ public class DownloadService extends Service {
numberOfDownloads.decrementAndGet();
}
- sendDownloadHandledIntent();
-
queryDownloadsAsync();
}
});
@@ -889,7 +828,7 @@ public class DownloadService extends Service {
feed.setFile_url(request.getDestination());
feed.setId(request.getFeedfileId());
feed.setDownloaded(true);
- feed.setPreferences(new FeedPreferences(0, true,
+ feed.setPreferences(new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL,
request.getUsername(), request.getPassword()));
feed.setPageNr(request.getArguments().getInt(DownloadRequester.REQUEST_ARG_PAGE_NR, 0));
@@ -938,7 +877,7 @@ public class DownloadService extends Service {
if (successful) {
// we create a 'successful' download log if the feed's last refresh failed
- List<DownloadStatus> log = DBReader.getFeedDownloadLog(DownloadService.this, feed);
+ List<DownloadStatus> log = DBReader.getFeedDownloadLog(feed);
if(log.size() > 0 && log.get(0).isSuccessful() == false) {
saveDownloadStatus(new DownloadStatus(feed,
feed.getHumanReadableIdentifier(), DownloadError.SUCCESS, successful,
@@ -982,7 +921,7 @@ public class DownloadService extends Service {
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())) {
+ if (TextUtils.equals(item1.getImage().getDownload_url(), item2.getImage().getDownload_url())) {
item2.setImage(null);
}
}
@@ -1061,17 +1000,17 @@ public class DownloadService extends Service {
@Override
public void run() {
if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
- DBWriter.setFeedLastUpdateFailed(DownloadService.this, request.getFeedfileId(), true);
+ DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
} else if (request.isDeleteOnFailure()) {
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());
+ FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
media.setFile_url(request.getDestination());
try {
- DBWriter.setFeedMedia(DownloadService.this, media).get();
+ DBWriter.setFeedMedia(media).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
@@ -1083,40 +1022,6 @@ public class DownloadService extends Service {
}
/**
- * 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 {
@@ -1124,25 +1029,22 @@ public class DownloadService extends Service {
private DownloadRequest request;
private DownloadStatus status;
- public MediaHandlerThread(DownloadStatus status, DownloadRequest request) {
- Validate.notNull(status);
- Validate.notNull(request);
-
+ public MediaHandlerThread(@NonNull DownloadStatus status,
+ @NonNull DownloadRequest request) {
this.status = status;
this.request = request;
}
@Override
public void run() {
- FeedMedia media = DBReader.getFeedMedia(DownloadService.this,
- request.getFeedfileId());
+ FeedMedia media = DBReader.getFeedMedia(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());
+ media.setHasEmbeddedPicture(null);
// Get duration
MediaMetadataRetriever mmr = null;
@@ -1162,20 +1064,19 @@ public class DownloadService extends Service {
}
}
- if (media.getItem().getChapters() == null) {
- ChapterUtils.loadChaptersFromFileUrl(media);
- if (media.getItem().getChapters() != null) {
- chaptersRead = true;
- }
- }
+ final FeedItem item = media.getItem();
try {
- if (chaptersRead) {
- DBWriter.setFeedItem(DownloadService.this, media.getItem()).get();
+ // we've received the media, we don't want to autodownload it again
+ if(item != null) {
+ item.setAutoDownload(false);
+ DBWriter.setFeedItem(item).get();
}
- DBWriter.setFeedMedia(DownloadService.this, media).get();
- if (!DBTasks.isInQueue(DownloadService.this, media.getItem().getId())) {
- DBWriter.addQueueItem(DownloadService.this, media.getItem().getId()).get();
+
+ DBWriter.setFeedMedia(media).get();
+
+ if (item != null && !DBTasks.isInQueue(DownloadService.this, item.getId())) {
+ DBWriter.addQueueItem(DownloadService.this, item).get();
}
} catch (ExecutionException e) {
e.printStackTrace();
@@ -1186,15 +1087,13 @@ public class DownloadService extends Service {
}
saveDownloadStatus(status);
- sendDownloadHandledIntent();
-
- if(GpodnetPreferences.loggedIn()) {
- FeedItem item = media.getItem();
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.DOWNLOAD)
- .currentDeviceId()
- .currentTimestamp()
- .build();
- GpodnetPreferences.enqueueEpisodeAction(action);
+
+ if(GpodnetPreferences.loggedIn() && item != null) {
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.DOWNLOAD)
+ .currentDeviceId()
+ .currentTimestamp()
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
}
numberOfDownloads.decrementAndGet();
@@ -1239,8 +1138,25 @@ public class DownloadService extends Service {
}
}
- public List<Downloader> getDownloads() {
- return downloads;
+
+ private long lastPost = 0;
+
+ final Runnable postDownloaderTask = new Runnable() {
+ @Override
+ public void run() {
+ List<Downloader> list = Collections.unmodifiableList(downloads);
+ EventBus.getDefault().postSticky(DownloadEvent.refresh(list));
+ postHandler.postDelayed(postDownloaderTask, 1500);
+ }
+ };
+
+ private void postDownloaders() {
+ long now = System.currentTimeMillis();
+ if(now - lastPost >= 250) {
+ postHandler.removeCallbacks(postDownloaderTask);
+ postDownloaderTask.run();
+ lastPost = now;
+ }
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java
index d05650d10..ed2b00dfe 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java
@@ -1,12 +1,14 @@
package de.danoeh.antennapod.core.service.download;
-import org.apache.commons.lang3.Validate;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+
+import java.util.Date;
import de.danoeh.antennapod.core.feed.FeedFile;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.DownloadError;
-import java.util.Date;
-
/** Contains status attributes for one download */
public class DownloadStatus {
/**
@@ -59,10 +61,8 @@ public class DownloadStatus {
this.feedfileType = feedfileType;
}
- public DownloadStatus(DownloadRequest request, DownloadError reason,
+ public DownloadStatus(@NonNull 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();
@@ -74,10 +74,8 @@ public class DownloadStatus {
}
/** Constructor for creating new completed downloads. */
- public DownloadStatus(FeedFile feedfile, String title, DownloadError reason,
- boolean successful, String reasonDetailed) {
- Validate.notNull(feedfile);
-
+ public DownloadStatus(@NonNull FeedFile feedfile, String title, DownloadError reason,
+ boolean successful, String reasonDetailed) {
this.title = title;
this.done = true;
this.feedfileId = feedfile.getId();
@@ -101,6 +99,30 @@ public class DownloadStatus {
this.reasonDetailed = reasonDetailed;
}
+ public static DownloadStatus fromCursor(Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE);
+ int indexFeedFile = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDFILE);
+ int indexFileFileType = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDFILETYPE);
+ int indexSuccessful = cursor.getColumnIndex(PodDBAdapter.KEY_SUCCESSFUL);
+ int indexReason = cursor.getColumnIndex(PodDBAdapter.KEY_REASON);
+ int indexCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_COMPLETION_DATE);
+ int indexReasonDetailed = cursor.getColumnIndex(PodDBAdapter.KEY_REASON_DETAILED);
+
+ long id = cursor.getLong(indexId);
+ String title = cursor.getString(indexTitle);
+ long feedfileId = cursor.getLong(indexFeedFile);
+ int feedfileType = cursor.getInt(indexFileFileType);
+ boolean successful = cursor.getInt(indexSuccessful) > 0;
+ int reason = cursor.getInt(indexReason);
+ Date completionDate = new Date(cursor.getLong(indexCompletionDate));
+ String reasonDetailed = cursor.getString(indexReasonDetailed);
+
+ return new DownloadStatus(id, title, feedfileId,
+ feedfileType, successful, DownloadError.fromCode(reason), completionDate,
+ reasonDetailed);
+ }
+
@Override
public String toString() {
return "DownloadStatus [id=" + id + ", title=" + title + ", reason="
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
index ac0fe8036..0b9fba6f7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
@@ -1,16 +1,16 @@
package de.danoeh.antennapod.core.service.download;
+import android.text.TextUtils;
import android.util.Log;
import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.squareup.okhttp.internal.http.HttpDate;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.http.HttpStatus;
import java.io.BufferedInputStream;
import java.io.File;
@@ -22,6 +22,7 @@ import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
+import java.util.Arrays;
import java.util.Date;
import de.danoeh.antennapod.core.ClientConfig;
@@ -84,7 +85,7 @@ public class HttpDownloader extends Downloader {
String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1");
httpReq.header("Authorization", credentials);
}
- } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
+ } else if (!TextUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
"ISO-8859-1");
httpReq.header("Authorization", credentials);
@@ -93,15 +94,29 @@ public class HttpDownloader extends Downloader {
// add range header if necessary
if (fileExists) {
request.setSoFar(destination.length());
- httpReq.addHeader("Range",
- "bytes=" + request.getSoFar() + "-");
+ httpReq.addHeader("Range", "bytes=" + request.getSoFar() + "-");
Log.d(TAG, "Adding range header: " + request.getSoFar());
}
- Response response = httpClient.newCall(httpReq.build()).execute();
+ Response response = null;
+ try {
+ response = httpClient.newCall(httpReq.build()).execute();
+ } catch(IOException e) {
+ Log.e(TAG, e.toString());
+ if(e.getMessage().contains("PROTOCOL_ERROR")) {
+ httpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
+ response = httpClient.newCall(httpReq.build()).execute();
+ }
+ else {
+ throw e;
+ }
+ }
responseBody = response.body();
String contentEncodingHeader = response.header("Content-Encoding");
- boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
+ boolean isGzip = false;
+ if(!TextUtils.isEmpty(contentEncodingHeader)) {
+ isGzip = TextUtils.equals(contentEncodingHeader.toLowerCase(), "gzip");
+ }
Log.d(TAG, "Response code is " + response.code());
@@ -113,7 +128,7 @@ public class HttpDownloader extends Downloader {
String credentials = encodeCredentials(parts[0], parts[1], "UTF-8");
httpReq.header("Authorization", credentials);
}
- } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
+ } else if (!TextUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
"UTF-8");
httpReq.header("Authorization", credentials);
@@ -121,7 +136,9 @@ public class HttpDownloader extends Downloader {
response = httpClient.newCall(httpReq.build()).execute();
responseBody = response.body();
contentEncodingHeader = response.header("Content-Encoding");
- isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
+ if(!TextUtils.isEmpty(contentEncodingHeader)) {
+ isGzip = TextUtils.equals(contentEncodingHeader.toLowerCase(), "gzip");
+ }
}
if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
@@ -144,7 +161,7 @@ public class HttpDownloader extends Downloader {
return;
}
- if (!StorageUtils.storageAvailable(ClientConfig.applicationCallbacks.getApplicationInstance())) {
+ if (!StorageUtils.storageAvailable()) {
onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
return;
}
@@ -153,8 +170,8 @@ public class HttpDownloader extends Downloader {
String contentRangeHeader = (fileExists) ? response.header("Content-Range") : null;
- if (fileExists && response.code() == HttpStatus.SC_PARTIAL_CONTENT
- && !StringUtils.isEmpty(contentRangeHeader)) {
+ if (fileExists && response.code() == HttpURLConnection.HTTP_PARTIAL
+ && !TextUtils.isEmpty(contentRangeHeader)) {
String start = contentRangeHeader.substring("bytes ".length(),
contentRangeHeader.indexOf("-"));
request.setSoFar(Long.valueOf(start));
@@ -188,13 +205,17 @@ public class HttpDownloader extends Downloader {
}
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));
+ try {
+ 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));
+ }
+ } catch(IOException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
}
if (cancelled) {
onCancelled();
@@ -210,6 +231,9 @@ public class HttpDownloader extends Downloader {
request.getSize()
);
return;
+ } else if(request.getSize() > 0 && request.getSoFar() == 0){
+ onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read");
+ return;
}
onSuccess();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/MediaButtonIntentReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/MediaButtonIntentReceiver.java
new file mode 100644
index 000000000..7d06390f2
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/MediaButtonIntentReceiver.java
@@ -0,0 +1,26 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class MediaButtonIntentReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "MediaButtonIntentRcver";
+
+ private static PlaybackServiceMediaPlayer mMediaPlayer;
+
+ public static void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer) {
+ mMediaPlayer = mediaPlayer;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive(Context, " + intent.toString() +")");
+ if (mMediaPlayer != null && Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
+ mMediaPlayer.handleMediaKey(intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 3f6769ee4..2be075a92 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -1,11 +1,11 @@
package de.danoeh.antennapod.core.service.playback;
-import android.annotation.SuppressLint;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.bluetooth.BluetoothA2dp;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -13,27 +13,22 @@ 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.os.Vibrator;
import android.preference.PreferenceManager;
-import android.support.v4.app.NotificationCompat;
+import android.support.v7.app.NotificationCompat;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.widget.Toast;
-import com.squareup.picasso.Picasso;
+import com.bumptech.glide.Glide;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.IOException;
import java.util.List;
import de.danoeh.antennapod.core.ClientConfig;
@@ -42,6 +37,7 @@ 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.glide.ApGlideSettings;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
@@ -50,6 +46,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.util.IntList;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -165,7 +162,6 @@ public class PlaybackService extends Service {
private static final int NOTIFICATION_ID = 1;
- private RemoteControlClient remoteControlClient;
private PlaybackServiceMediaPlayer mediaPlayer;
private PlaybackServiceTaskManager taskManager;
@@ -213,7 +209,6 @@ public class PlaybackService extends Service {
return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, mt);
}
- @SuppressLint("NewApi")
@Override
public void onCreate() {
super.onCreate();
@@ -224,8 +219,10 @@ public class PlaybackService extends Service {
Intent.ACTION_HEADSET_PLUG));
registerReceiver(shutdownReceiver, new IntentFilter(
ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- registerReceiver(bluetoothStateUpdated, new IntentFilter(
- AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ registerReceiver(bluetoothStateUpdated, new IntentFilter(
+ BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED));
+ }
registerReceiver(audioBecomingNoisy, new IntentFilter(
AudioManager.ACTION_AUDIO_BECOMING_NOISY));
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
@@ -234,13 +231,11 @@ public class PlaybackService extends Service {
ACTION_PAUSE_PLAY_CURRENT_EPISODE));
registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(
ACTION_RESUME_PLAY_CURRENT_EPISODE));
- remoteControlClient = setupRemoteControlClient();
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
mediaPlayer = new PlaybackServiceMediaPlayer(this, mediaPlayerCallback);
}
- @SuppressLint("NewApi")
@Override
public void onDestroy() {
super.onDestroy();
@@ -251,7 +246,9 @@ public class PlaybackService extends Service {
unregisterReceiver(headsetDisconnected);
unregisterReceiver(shutdownReceiver);
- unregisterReceiver(bluetoothStateUpdated);
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ unregisterReceiver(bluetoothStateUpdated);
+ }
unregisterReceiver(audioBecomingNoisy);
unregisterReceiver(skipCurrentEpisodeReceiver);
unregisterReceiver(pausePlayCurrentEpisodeReceiver);
@@ -345,6 +342,8 @@ public class PlaybackService extends Service {
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
+ mediaPlayer.endPlayback(true);
+ break;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000);
break;
@@ -398,11 +397,27 @@ public class PlaybackService extends Service {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+ float leftVolume = 0.1f * UserPreferences.getLeftVolume();
+ float rightVolume = 0.1f * UserPreferences.getRightVolume();
+ mediaPlayer.setVolume(leftVolume, rightVolume);
+ }
+
+ @Override
public void onSleepTimerExpired() {
mediaPlayer.pause(true, true);
+ float leftVolume = UserPreferences.getLeftVolume();
+ float rightVolume = UserPreferences.getRightVolume();
+ mediaPlayer.setVolume(leftVolume, rightVolume);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}
+ @Override
+ public void onSleepTimerReset() {
+ float leftVolume = UserPreferences.getLeftVolume();
+ float rightVolume = UserPreferences.getRightVolume();
+ mediaPlayer.setVolume(leftVolume, rightVolume);
+ }
@Override
public void onWidgetUpdaterTick() {
@@ -442,7 +457,7 @@ public class PlaybackService extends Service {
}
writePlayerStatusPlaybackPreferences();
- final Playable playable = mediaPlayer.getPSMPInfo().playable;
+ final Playable playable = newInfo.playable;
// Gpodder: send play action
if(GpodnetPreferences.loggedIn() && playable instanceof FeedMedia) {
@@ -486,7 +501,6 @@ public class PlaybackService extends Service {
// statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
sendBroadcast(statusUpdate);
updateWidget();
- refreshRemoteControlClientState(newInfo);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
}
@@ -523,9 +537,9 @@ public class PlaybackService extends Service {
@Override
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
- final String TAG = "PlaybackService.onErrorListener";
+ final String TAG = "PlaybackSvc.onErrorLtsn";
Log.w(TAG, "An error has occured: " + what + " " + extra);
- if (mediaPlayer.getPSMPInfo().playerStatus == PlayerStatus.PLAYING) {
+ if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
mediaPlayer.pause(true, false);
}
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
@@ -535,21 +549,16 @@ public class PlaybackService extends Service {
}
@Override
- public boolean endPlayback(boolean playNextEpisode) {
- PlaybackService.this.endPlayback(true);
+ public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) {
+ PlaybackService.this.endPlayback(playNextEpisode, wasSkipped);
return true;
}
-
- @Override
- public RemoteControlClient getRemoteControlClient() {
- return remoteControlClient;
- }
};
- private void endPlayback(boolean playNextEpisode) {
+ private void endPlayback(boolean playNextEpisode, boolean wasSkipped) {
Log.d(TAG, "Playback ended");
- final Playable playable = mediaPlayer.getPSMPInfo().playable;
+ final Playable playable = mediaPlayer.getPlayable();
if (playable == null) {
Log.e(TAG, "Cannot end playback: media was null");
return;
@@ -563,20 +572,28 @@ public class PlaybackService extends Service {
if (playable instanceof FeedMedia) {
FeedMedia media = (FeedMedia) playable;
FeedItem item = media.getItem();
- DBWriter.markItemRead(PlaybackService.this, item, true, true);
try {
final List<FeedItem> queue = taskManager.getQueue();
isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
- nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue);
+ nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
} catch (InterruptedException e) {
e.printStackTrace();
// isInQueue remains false
}
- if (isInQueue) {
- DBWriter.removeQueueItem(PlaybackService.this, item, true);
+
+ boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode();
+
+ if (!shouldKeep) {
+ // only mark the item as played if we're not keeping it anyways
+ DBWriter.markItemPlayed(item, FeedItem.PLAYED, true);
+
+ if (isInQueue) {
+ DBWriter.removeQueueItem(PlaybackService.this, item, true);
+ }
}
- DBWriter.addItemToPlaybackHistory(PlaybackService.this, media);
+
+ DBWriter.addItemToPlaybackHistory(media);
// auto-flattr if enabled
if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) {
@@ -584,7 +601,7 @@ public class PlaybackService extends Service {
}
// Delete episode if enabled
- if(UserPreferences.isAutoDelete()) {
+ if(item.getFeed().getPreferences().getCurrentAutoDelete() && !shouldKeep ) {
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
Log.d(TAG, "Episode Deleted");
}
@@ -634,7 +651,7 @@ public class PlaybackService extends Service {
writePlaybackPreferencesNoMediaPlaying();
if (nextMedia != null) {
- stream = !playable.localFileAvailable();
+ stream = !nextMedia.localFileAvailable();
mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
(nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
@@ -645,10 +662,9 @@ public class PlaybackService extends Service {
}
}
- public void setSleepTimer(long waitingTime) {
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
- + " milliseconds");
- taskManager.setSleepTimer(waitingTime);
+ public void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
+ Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
+ taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}
@@ -744,8 +760,7 @@ public class PlaybackService extends Service {
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext()).edit();
- PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
- int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus);
+ int playerStatus = getCurrentPlayerStatusAsInt(mediaPlayer.getPlayerStatus());
editor.putInt(
PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus);
@@ -770,151 +785,158 @@ public class PlaybackService extends Service {
/**
* Used by setupNotification to load notification data in another thread.
*/
- private AsyncTask<Void, Void, Void> notificationSetupTask;
+ private Thread notificationSetupThread;
/**
* 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);
+ if (notificationSetupThread != null) {
+ notificationSetupThread.interrupt();
}
- notificationSetupTask = new AsyncTask<Void, Void, Void>() {
+ Runnable notificationSetupTask = new Runnable() {
Bitmap icon = null;
@Override
- protected Void doInBackground(Void... params) {
+ public void run() {
Log.d(TAG, "Starting background work");
if (android.os.Build.VERSION.SDK_INT >= 11) {
if (info.playable != null) {
+ int iconSize = getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width);
try {
- int iconSize = getResources().getDimensionPixelSize(
- android.R.dimen.notification_large_icon_width);
- icon = Picasso.with(PlaybackService.this)
+ icon = Glide.with(PlaybackService.this)
.load(info.playable.getImageUri())
- .resize(iconSize, iconSize)
+ .asBitmap()
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .centerCrop()
+ .into(iconSize, iconSize)
.get();
- } catch (IOException e) {
- e.printStackTrace();
+ } catch(Throwable tr) {
+ Log.e(TAG, Log.getStackTraceString(tr));
}
}
-
}
if (icon == null) {
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext()));
}
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
if (mediaPlayer == null) {
return;
}
- PlaybackServiceMediaPlayer.PSMPInfo newInfo = mediaPlayer.getPSMPInfo();
+ PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
- if (!isCancelled() &&
- started &&
- info.playable != null) {
- String contentText = info.playable.getFeedTitle();
- String contentTitle = info.playable.getEpisodeTitle();
+ if (!Thread.currentThread().isInterrupted() && started && info.playable != null) {
+ String contentText = info.playable.getEpisodeTitle();
+ String contentTitle = info.playable.getFeedTitle();
Notification notification = null;
- if (android.os.Build.VERSION.SDK_INT >= 16) {
- Intent pauseButtonIntent = new Intent( // pause button 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);
- Intent playButtonIntent = new Intent( // play button intent
- PlaybackService.this, PlaybackService.class);
- playButtonIntent.putExtra(
- MediaButtonReceiver.EXTRA_KEYCODE,
- KeyEvent.KEYCODE_MEDIA_PLAY);
- PendingIntent playButtonPendingIntent = PendingIntent
- .getService(PlaybackService.this, 1,
- playButtonIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- Intent stopButtonIntent = new Intent( // stop button intent
- PlaybackService.this, PlaybackService.class);
- stopButtonIntent.putExtra(
- MediaButtonReceiver.EXTRA_KEYCODE,
- KeyEvent.KEYCODE_MEDIA_STOP);
- PendingIntent stopButtonPendingIntent = PendingIntent
- .getService(PlaybackService.this, 2,
- stopButtonIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- Notification.Builder notificationBuilder = new Notification.Builder(
- PlaybackService.this)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setOngoing(true)
- .setContentIntent(pIntent)
- .setLargeIcon(icon)
- .setSmallIcon(smallIcon)
- .setPriority(UserPreferences.getNotifyPriority()); // set notification priority
- if (newInfo.playerStatus == PlayerStatus.PLAYING) {
- notificationBuilder.addAction(android.R.drawable.ic_media_pause, //pause action
- getString(R.string.pause_label),
- pauseButtonPendingIntent);
- } else {
- notificationBuilder.addAction(android.R.drawable.ic_media_play, //play action
- getString(R.string.play_label),
- playButtonPendingIntent);
- }
- if (UserPreferences.isPersistNotify()) {
- notificationBuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, // stop action
- getString(R.string.stop_label),
- stopButtonPendingIntent);
- }
- if (Build.VERSION.SDK_INT >= 21) {
- notificationBuilder.setStyle(new Notification.MediaStyle()
- .setMediaSession((android.media.session.MediaSession.Token) mediaPlayer.getSessionToken().getToken())
- .setShowActionsInCompactView(0))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(Notification.COLOR_DEFAULT);
- }
+ // Builder is v7, even if some not overwritten methods return its parent's v4 interface
+ NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(
+ PlaybackService.this)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setOngoing(false)
+ .setContentIntent(pIntent)
+ .setLargeIcon(icon)
+ .setSmallIcon(smallIcon)
+ .setWhen(0) // we don't need the time
+ .setPriority(UserPreferences.getNotifyPriority()); // set notification priority
+ IntList compactActionList = new IntList();
+
+
+ int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction
+
+ // always let them rewind
+ PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction(
+ KeyEvent.KEYCODE_MEDIA_REWIND, numActions);
+ notificationBuilder.addAction(android.R.drawable.ic_media_rew,
+ getString(R.string.rewind_label),
+ rewindButtonPendingIntent);
+ numActions++;
+
+ if (playerStatus == PlayerStatus.PLAYING) {
+ PendingIntent pauseButtonPendingIntent = getPendingIntentForMediaAction(
+ KeyEvent.KEYCODE_MEDIA_PAUSE, numActions);
+ notificationBuilder.addAction(android.R.drawable.ic_media_pause, //pause action
+ getString(R.string.pause_label),
+ pauseButtonPendingIntent);
+ compactActionList.add(numActions++);
+ } else {
+ PendingIntent playButtonPendingIntent = getPendingIntentForMediaAction(
+ KeyEvent.KEYCODE_MEDIA_PLAY, numActions);
+ notificationBuilder.addAction(android.R.drawable.ic_media_play, //play action
+ getString(R.string.play_label),
+ playButtonPendingIntent);
+ compactActionList.add(numActions++);
+ }
- notification = notificationBuilder.build();
+ // ff follows play, then we have skip (if it's present)
+ PendingIntent ffButtonPendingIntent = getPendingIntentForMediaAction(
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions);
+ notificationBuilder.addAction(android.R.drawable.ic_media_ff,
+ getString(R.string.fast_forward_label),
+ ffButtonPendingIntent);
+ numActions++;
+
+ if (UserPreferences.isFollowQueue()) {
+ PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction(
+ KeyEvent.KEYCODE_MEDIA_NEXT, numActions);
+ notificationBuilder.addAction(android.R.drawable.ic_media_next,
+ getString(R.string.skip_episode_label),
+ skipButtonPendingIntent);
+ compactActionList.add(numActions++);
+ }
+
+ PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction(
+ KeyEvent.KEYCODE_MEDIA_STOP, numActions);
+ notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle()
+ .setMediaSession(mediaPlayer.getSessionToken())
+ .setShowActionsInCompactView(compactActionList.toArray())
+ .setShowCancelButton(true)
+ .setCancelButtonIntent(stopButtonPendingIntent))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setColor(Notification.COLOR_DEFAULT);
+
+ notification = notificationBuilder.build();
+
+ if (playerStatus == PlayerStatus.PLAYING ||
+ playerStatus == PlayerStatus.PREPARING ||
+ playerStatus == PlayerStatus.SEEKING) {
+ startForeground(NOTIFICATION_ID, notification);
} else {
- NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
- PlaybackService.this)
- .setContentTitle(contentTitle)
- .setContentText(contentText).setOngoing(true)
- .setContentIntent(pIntent).setLargeIcon(icon)
- .setSmallIcon(smallIcon);
- notification = notificationBuilder.build();
+ stopForeground(false);
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mNotificationManager.notify(NOTIFICATION_ID, notification);
}
- startForeground(NOTIFICATION_ID, notification);
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();
- }
+ notificationSetupThread = new Thread(notificationSetupTask);
+ notificationSetupThread.start();
+ }
+ private PendingIntent getPendingIntentForMediaAction(int keycodeValue, int requestCode) {
+ Intent intent = new Intent(
+ PlaybackService.this, PlaybackService.class);
+ intent.putExtra(
+ MediaButtonReceiver.EXTRA_KEYCODE,
+ keycodeValue);
+ return PendingIntent
+ .getService(PlaybackService.this, requestCode,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
- * Saves the current position of the media file to the DB
+ * Persists the current position and last played time of the media file.
*
* @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.
@@ -923,7 +945,7 @@ public class PlaybackService extends Service {
int position = getCurrentPosition();
int duration = getDuration();
float playbackSpeed = getCurrentPlaybackSpeed();
- final Playable playable = mediaPlayer.getPSMPInfo().playable;
+ final Playable playable = mediaPlayer.getPlayable();
if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
Log.d(TAG, "Saving current position to " + position);
if (updatePlayedDuration && playable instanceof FeedMedia) {
@@ -939,8 +961,9 @@ public class PlaybackService extends Service {
}
}
playable.saveCurrentPosition(PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext()),
- position
+ .getDefaultSharedPreferences(getApplicationContext()),
+ position,
+ System.currentTimeMillis()
);
}
}
@@ -963,74 +986,6 @@ public class PlaybackService extends Service {
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();
- }
- Log.d(TAG, "RemoteControlClient state was refreshed");
- }
- }
- }
-
private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) {
boolean isPlaying = false;
@@ -1059,14 +1014,14 @@ public class PlaybackService extends Service {
* Pauses playback when the headset is disconnected and the preference is
* set
*/
- private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() {
+ private final BroadcastReceiver headsetDisconnected = new BroadcastReceiver() {
private static final String TAG = "headsetDisconnected";
private static final int UNPLUGGED = 0;
private static final int PLUGGED = 1;
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) {
+ if (TextUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", -1);
if (state != -1) {
Log.d(TAG, "Headset plug event. State is " + state);
@@ -1075,7 +1030,7 @@ public class PlaybackService extends Service {
pauseIfPauseOnDisconnect();
} else if (state == PLUGGED) {
Log.d(TAG, "Headset was plugged in during playback.");
- unpauseIfPauseOnDisconnect();
+ unpauseIfPauseOnDisconnect(false);
}
} else {
Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent");
@@ -1084,21 +1039,22 @@ public class PlaybackService extends Service {
}
};
- private BroadcastReceiver bluetoothStateUpdated = new BroadcastReceiver() {
+ private final BroadcastReceiver bluetoothStateUpdated = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
- int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
- int prevState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, -1);
- if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
- Log.d(TAG, "Received bluetooth connection intent");
- unpauseIfPauseOnDisconnect();
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ if (TextUtils.equals(intent.getAction(), BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
+ if (state == BluetoothA2dp.STATE_CONNECTED) {
+ Log.d(TAG, "Received bluetooth connection intent");
+ unpauseIfPauseOnDisconnect(true);
+ }
}
}
}
};
- private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() {
+ private final BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1125,50 +1081,60 @@ public class PlaybackService extends Service {
}
}
- private void unpauseIfPauseOnDisconnect() {
+ /**
+ * @param bluetooth true if the event for unpausing came from bluetooth
+ */
+ private void unpauseIfPauseOnDisconnect(boolean bluetooth) {
if (transientPause) {
transientPause = false;
- if (UserPreferences.isPauseOnHeadsetDisconnect() && UserPreferences.isUnpauseOnHeadsetReconnect()) {
+ if (!bluetooth && UserPreferences.isUnpauseOnHeadsetReconnect()) {
+ mediaPlayer.resume();
+ } else if (bluetooth && UserPreferences.isUnpauseOnBluetoothReconnect()){
+ // let the user know we've started playback again...
+ Vibrator v = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
+ if(v != null) {
+ v.vibrate(500);
+ }
mediaPlayer.resume();
}
}
}
- private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
+ if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
stopSelf();
}
}
};
- private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
+ if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
- mediaPlayer.endPlayback();
+ mediaPlayer.endPlayback(true);
}
}
};
- private BroadcastReceiver pauseResumeCurrentEpisodeReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver pauseResumeCurrentEpisodeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_RESUME_PLAY_CURRENT_EPISODE)) {
+ if (TextUtils.equals(intent.getAction(), ACTION_RESUME_PLAY_CURRENT_EPISODE)) {
Log.d(TAG, "Received RESUME_PLAY_CURRENT_EPISODE intent");
mediaPlayer.resume();
}
}
};
- private BroadcastReceiver pausePlayCurrentEpisodeReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver pausePlayCurrentEpisodeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_PAUSE_PLAY_CURRENT_EPISODE)) {
+ if (TextUtils.equals(intent.getAction(), ACTION_PAUSE_PLAY_CURRENT_EPISODE)) {
Log.d(TAG, "Received PAUSE_PLAY_CURRENT_EPISODE intent");
mediaPlayer.pause(false, false);
}
@@ -1200,25 +1166,35 @@ public class PlaybackService extends Service {
}
public PlayerStatus getStatus() {
- return mediaPlayer.getPSMPInfo().playerStatus;
+ return mediaPlayer.getPlayerStatus();
}
- public Playable getPlayable() {
- return mediaPlayer.getPSMPInfo().playable;
+ public Playable getPlayable() { return mediaPlayer.getPlayable(); }
+
+ public boolean canSetSpeed() {
+ return mediaPlayer.canSetSpeed();
}
public void setSpeed(float speed) {
mediaPlayer.setSpeed(speed);
}
- public boolean canSetSpeed() {
- return mediaPlayer.canSetSpeed();
+ public void setVolume(float leftVolume, float rightVolume) {
+ mediaPlayer.setVolume(leftVolume, rightVolume);
}
public float getCurrentPlaybackSpeed() {
return mediaPlayer.getPlaybackSpeed();
}
+ public boolean canDownmix() {
+ return mediaPlayer.canDownmix();
+ }
+
+ public void setDownmix(boolean enable) {
+ mediaPlayer.setDownmix(enable);
+ }
+
public boolean isStartWhenPrepared() {
return mediaPlayer.isStartWhenPrepared();
}
@@ -1231,7 +1207,7 @@ public class PlaybackService extends Service {
public void seekTo(final int t) {
if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING
&& GpodnetPreferences.loggedIn()) {
- final Playable playable = mediaPlayer.getPSMPInfo().playable;
+ final Playable playable = mediaPlayer.getPlayable();
if (playable instanceof FeedMedia) {
FeedMedia media = (FeedMedia) playable;
FeedItem item = media.getItem();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
index 243ee78e4..a82e82506 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -1,22 +1,32 @@
package de.danoeh.antennapod.core.service.playback;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
import android.media.AudioManager;
-import android.media.RemoteControlClient;
import android.net.wifi.WifiManager;
import android.os.PowerManager;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
+import android.view.Display;
+import android.view.InputDevice;
+import android.view.KeyEvent;
import android.view.SurfaceHolder;
+import android.view.WindowManager;
-import org.apache.commons.lang3.Validate;
+import com.bumptech.glide.Glide;
import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
@@ -28,9 +38,10 @@ 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.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
import de.danoeh.antennapod.core.util.playback.AudioPlayer;
import de.danoeh.antennapod.core.util.playback.IPlayer;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -39,7 +50,7 @@ import de.danoeh.antennapod.core.util.playback.VideoPlayer;
/**
* Manages the MediaPlayer object of the PlaybackService.
*/
-public class PlaybackServiceMediaPlayer {
+public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String TAG = "PlaybackSvcMediaPlayer";
/**
@@ -69,6 +80,7 @@ public class PlaybackServiceMediaPlayer {
* have to wait until these operations have finished.
*/
private final ReentrantLock playerLock;
+ private CountDownLatch seekLatch;
private final PSMPCallback callback;
private final Context context;
@@ -80,10 +92,8 @@ public class PlaybackServiceMediaPlayer {
*/
private WifiManager.WifiLock wifiLock;
- public PlaybackServiceMediaPlayer(Context context, PSMPCallback callback) {
- Validate.notNull(context);
- Validate.notNull(callback);
-
+ public PlaybackServiceMediaPlayer(@NonNull Context context,
+ @NonNull PSMPCallback callback) {
this.context = context;
this.callback = callback;
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -98,9 +108,26 @@ public class PlaybackServiceMediaPlayer {
}
);
- mediaSession = new MediaSessionCompat(context, TAG);
- mediaSession.setCallback(sessionCallback);
- mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ MediaButtonIntentReceiver.setMediaPlayer(this);
+ ComponentName eventReceiver = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ mediaSession = new MediaSessionCompat(context, TAG, eventReceiver, buttonReceiverIntent);
+
+ try {
+ mediaSession.setCallback(sessionCallback);
+ mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mediaSession.setActive(true);
+ } catch (NullPointerException npe) {
+ // on some devices (Huawei) setting active can cause a NullPointerException
+ // even with correct use of the api.
+ // See http://stackoverflow.com/questions/31556679/android-huawei-mediassessioncompat
+ // and https://plus.google.com/+IanLake/posts/YgdTkKFxz7d
+ Log.e(TAG, "NullPointerException while setting up MediaSession");
+ npe.printStackTrace();
+ }
mediaPlayer = null;
statusBeforeSeeking = null;
@@ -108,6 +135,16 @@ public class PlaybackServiceMediaPlayer {
mediaType = MediaType.UNKNOWN;
playerStatus = PlayerStatus.STOPPED;
videoSize = null;
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if(key.equals(UserPreferences.PREF_LOCKSCREEN_BACKGROUND)) {
+ updateMediaSessionMetadata();
+ }
}
/**
@@ -136,9 +173,7 @@ public class PlaybackServiceMediaPlayer {
* 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);
-
+ public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
Log.d(TAG, "playMediaObject(...)");
executor.submit(new Runnable() {
@Override
@@ -164,8 +199,7 @@ public class PlaybackServiceMediaPlayer {
*
* @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);
+ private void playMediaObject(@NonNull final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
if (!playerLock.isHeldByCurrentThread()) {
throw new IllegalStateException("method requires playerLock");
}
@@ -193,10 +227,10 @@ public class PlaybackServiceMediaPlayer {
if(oldMedia.hasAlmostEnded()) {
Log.d(TAG, "smart mark as read");
FeedItem item = oldMedia.getItem();
- DBWriter.markItemRead(context, item, true, false);
+ DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
DBWriter.removeQueueItem(context, item, false);
- DBWriter.addItemToPlaybackHistory(context, oldMedia);
- if (UserPreferences.isAutoDelete()) {
+ DBWriter.addItemToPlaybackHistory(oldMedia);
+ if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
Log.d(TAG, "Delete " + oldMedia.toString());
DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
}
@@ -216,7 +250,7 @@ public class PlaybackServiceMediaPlayer {
setPlayerStatus(PlayerStatus.INITIALIZING, media);
try {
media.loadMetadata();
- mediaSession.setMetadata(getMediaSessionMetadata(media));
+ updateMediaSessionMetadata();
if (stream) {
mediaPlayer.setDataSource(media.getStreamUrl());
} else {
@@ -247,11 +281,38 @@ public class PlaybackServiceMediaPlayer {
}
}
- private MediaMetadataCompat getMediaSessionMetadata(Playable p) {
- MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
- builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, p.getEpisodeTitle());
- builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle());
- return builder.build();
+ private void updateMediaSessionMetadata() {
+ executor.execute(() -> {
+ final Playable p = this.media;
+ if (p == null) {
+ return;
+ }
+ MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
+ builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, p.getFeedTitle());
+ builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, p.getEpisodeTitle());
+ builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, p.getDuration());
+ builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, p.getEpisodeTitle());
+ builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle());
+
+ if (p.getImageUri() != null && UserPreferences.setLockscreenBackground()) {
+ builder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, p.getImageUri().toString());
+ try {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Bitmap art = Glide.with(context)
+ .load(p.getImageUri())
+ .asBitmap()
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .centerCrop()
+ .into(display.getWidth(), display.getHeight())
+ .get();
+ builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art);
+ } catch (Throwable tr) {
+ Log.e(TAG, Log.getStackTraceString(tr));
+ }
+ }
+ mediaSession.setMetadata(builder.build());
+ });
}
@@ -262,13 +323,10 @@ public class PlaybackServiceMediaPlayer {
* 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();
- }
+ executor.submit(() -> {
+ playerLock.lock();
+ resumeSync();
+ playerLock.unlock();
});
}
@@ -279,24 +337,26 @@ public class PlaybackServiceMediaPlayer {
AudioManager.AUDIOFOCUS_GAIN);
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
acquireWifiLockIfNecessary();
- setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
- mediaPlayer.start();
+ float speed = 1.0f;
+ try {
+ speed = Float.parseFloat(UserPreferences.getPlaybackSpeed());
+ } catch(NumberFormatException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ UserPreferences.setPlaybackSpeed(String.valueOf(speed));
+ }
+ setSpeed(speed);
+ setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume());
+
if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
- mediaPlayer.seekTo(media.getPosition());
+ int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
+ media.getPosition(),
+ media.getLastPlayedTime());
+ seekToSync(newPosition);
}
+ mediaPlayer.start();
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 {
@@ -393,7 +453,7 @@ public class PlaybackServiceMediaPlayer {
}
if (media.getPosition() > 0) {
- mediaPlayer.seekTo(media.getPosition());
+ seekToSync(media.getPosition());
}
if (media.getDuration() == 0) {
@@ -453,8 +513,20 @@ public class PlaybackServiceMediaPlayer {
statusBeforeSeeking = playerStatus;
setPlayerStatus(PlayerStatus.SEEKING, media);
}
+ if(seekLatch != null && seekLatch.getCount() > 0) {
+ try {
+ seekLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
+ }
+ seekLatch = new CountDownLatch(1);
mediaPlayer.seekTo(t);
-
+ try {
+ seekLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
} else if (playerStatus == PlayerStatus.INITIALIZED) {
media.setPosition(t);
startWhenPrepared.set(false);
@@ -503,9 +575,7 @@ public class PlaybackServiceMediaPlayer {
/**
* Seek to the start of the specified chapter.
*/
- public void seekToChapter(Chapter c) {
- Validate.notNull(c);
-
+ public void seekToChapter(@NonNull Chapter c) {
seekTo((int) c.getStart());
}
@@ -534,7 +604,9 @@ public class PlaybackServiceMediaPlayer {
* Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
*/
public int getPosition() {
- playerLock.lock();
+ if (!playerLock.tryLock()) {
+ return INVALID_TIME;
+ }
int retVal = INVALID_TIME;
if (playerStatus == PlayerStatus.PLAYING
@@ -542,6 +614,9 @@ public class PlaybackServiceMediaPlayer {
|| playerStatus == PlayerStatus.PREPARED
|| playerStatus == PlayerStatus.SEEKING) {
retVal = mediaPlayer.getCurrentPosition();
+ if(retVal <= 0 && media != null && media.getPosition() > 0) {
+ retVal = media.getPosition();
+ }
} else if (media != null && media.getPosition() > 0) {
retVal = media.getPosition();
}
@@ -617,12 +692,49 @@ public class PlaybackServiceMediaPlayer {
return retVal;
}
- public MediaType getCurrentMediaType() {
- return mediaType;
+ /**
+ * Sets the playback speed.
+ * This method is executed on an internal executor service.
+ */
+ public void setVolume(final float volumeLeft, float volumeRight) {
+ executor.submit(() -> setVolumeSync(volumeLeft, volumeRight));
}
- public PlayerStatus getPlayerStatus() {
- return playerStatus;
+ /**
+ * Sets the playback speed.
+ * This method is executed on the caller's thread.
+ */
+ private void setVolumeSync(float volumeLeft, float volumeRight) {
+ playerLock.lock();
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ mediaPlayer.setVolume(volumeLeft, volumeRight);
+ Log.d(TAG, "Media player volume was set to " + volumeLeft + " " + volumeRight);
+ }
+ playerLock.unlock();
+ }
+
+ /**
+ * Returns true if the mediaplayer can mix stereo down to mono
+ */
+ public boolean canDownmix() {
+ boolean retVal = false;
+ if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ retVal = mediaPlayer.canDownmix();
+ }
+ return retVal;
+ }
+
+ public void setDownmix(boolean enable) {
+ playerLock.lock();
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ mediaPlayer.setDownmix(enable);
+ Log.d(TAG, "Media player downmix was set to " + enable);
+ }
+ playerLock.unlock();
+ }
+
+ public MediaType getCurrentMediaType() {
+ return mediaType;
}
public boolean isStreaming() {
@@ -704,6 +816,26 @@ public class PlaybackServiceMediaPlayer {
}
/**
+ * Returns the current status, if you need the media and the player status together, you should
+ * use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition
+ * could result in nonsensical results (like a status of PLAYING, but a null playable)
+ * @return the current player status
+ */
+ public PlayerStatus getPlayerStatus() {
+ return playerStatus;
+ }
+
+ /**
+ * Returns the current media, if you need the media and the player status together, you should
+ * use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition
+ * could result in nonsensical results (like a status of PLAYING, but a null playable)
+ * @return the current media. May be null
+ */
+ public Playable getPlayable() {
+ return media;
+ }
+
+ /**
* Returns a token to this object's MediaSession. The MediaSession should only be used for notifications
* at the moment.
*
@@ -723,9 +855,7 @@ public class PlaybackServiceMediaPlayer {
* @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);
-
+ private synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
Log.d(TAG, "Setting player status to " + newStatus);
this.playerStatus = newStatus;
@@ -768,7 +898,12 @@ public class PlaybackServiceMediaPlayer {
} else {
state = PlaybackStateCompat.STATE_NONE;
}
- sessionState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, getPlaybackSpeed());
+ sessionState.setState(state, getPosition(), getPlaybackSpeed());
+ sessionState.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE
+ | PlaybackStateCompat.ACTION_REWIND
+ | PlaybackStateCompat.ACTION_FAST_FORWARD
+ | PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
+ mediaSession.setPlaybackState(sessionState.build());
callback.statusChanged(new PSMPInfo(playerStatus, media));
}
@@ -841,25 +976,24 @@ public class PlaybackServiceMediaPlayer {
};
- public void endPlayback() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
+ public void endPlayback(final boolean wasSkipped) {
+ executor.submit(() -> {
+ playerLock.lock();
+ releaseWifiLockIfNecessary();
- if (playerStatus != PlayerStatus.INDETERMINATE) {
- setPlayerStatus(PlayerStatus.INDETERMINATE, media);
- }
- if (mediaPlayer != null) {
- mediaPlayer.reset();
+ boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
- }
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- callback.endPlayback(true);
+ if (playerStatus != PlayerStatus.INDETERMINATE) {
+ setPlayerStatus(PlayerStatus.INDETERMINATE, media);
+ }
+ if (mediaPlayer != null) {
+ mediaPlayer.reset();
- playerLock.unlock();
}
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ callback.endPlayback(isPlaying, wasSkipped);
+
+ playerLock.unlock();
});
}
@@ -917,22 +1051,20 @@ public class PlaybackServiceMediaPlayer {
}
}
- public static interface PSMPCallback {
- public void statusChanged(PSMPInfo newInfo);
+ public interface PSMPCallback {
+ void statusChanged(PSMPInfo newInfo);
- public void shouldStop();
+ void shouldStop();
- public void playbackSpeedChanged(float s);
+ void playbackSpeedChanged(float s);
- public void onBufferingUpdate(int percent);
+ void onBufferingUpdate(int percent);
- public boolean onMediaPlayerInfo(int code);
+ boolean onMediaPlayerInfo(int code);
- public boolean onMediaPlayerError(Object inObj, int what, int extra);
+ boolean onMediaPlayerError(Object inObj, int what, int extra);
- public boolean endPlayback(boolean playNextEpisode);
-
- public RemoteControlClient getRemoteControlClient();
+ boolean endPlayback(boolean playNextEpisode, boolean wasSkipped);
}
private IPlayer setMediaPlayerListeners(IPlayer mp) {
@@ -960,9 +1092,9 @@ public class PlaybackServiceMediaPlayer {
return mp;
}
- private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
+ private final org.antennapod.audio.MediaPlayer.OnCompletionListener audioCompletionListener = new org.antennapod.audio.MediaPlayer.OnCompletionListener() {
@Override
- public void onCompletion(com.aocate.media.MediaPlayer mp) {
+ public void onCompletion(org.antennapod.audio.MediaPlayer mp) {
genericOnCompletion();
}
};
@@ -975,12 +1107,12 @@ public class PlaybackServiceMediaPlayer {
};
private void genericOnCompletion() {
- endPlayback();
+ endPlayback(false);
}
- private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
+ private final org.antennapod.audio.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new org.antennapod.audio.MediaPlayer.OnBufferingUpdateListener() {
@Override
- public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
+ public void onBufferingUpdate(org.antennapod.audio.MediaPlayer mp,
int percent) {
genericOnBufferingUpdate(percent);
}
@@ -997,9 +1129,9 @@ public class PlaybackServiceMediaPlayer {
callback.onBufferingUpdate(percent);
}
- private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
+ private final org.antennapod.audio.MediaPlayer.OnInfoListener audioInfoListener = new org.antennapod.audio.MediaPlayer.OnInfoListener() {
@Override
- public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
+ public boolean onInfo(org.antennapod.audio.MediaPlayer mp, int what,
int extra) {
return genericInfoListener(what);
}
@@ -1016,11 +1148,15 @@ public class PlaybackServiceMediaPlayer {
return callback.onMediaPlayerInfo(what);
}
- private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
+ private final org.antennapod.audio.MediaPlayer.OnErrorListener audioErrorListener = new org.antennapod.audio.MediaPlayer.OnErrorListener() {
@Override
- public boolean onError(com.aocate.media.MediaPlayer mp, int what,
- int extra) {
- return genericOnError(mp, what, extra);
+ public boolean onError(org.antennapod.audio.MediaPlayer mp, int what, int extra) {
+ if(mp.canFallback()) {
+ mp.fallback();
+ return true;
+ } else {
+ return genericOnError(mp, what, extra);
+ }
}
};
@@ -1035,9 +1171,9 @@ public class PlaybackServiceMediaPlayer {
return callback.onMediaPlayerError(inObj, what, extra);
}
- private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
+ private final org.antennapod.audio.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new org.antennapod.audio.MediaPlayer.OnSeekCompleteListener() {
@Override
- public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
+ public void onSeekComplete(org.antennapod.audio.MediaPlayer mp) {
genericSeekCompleteListener();
}
};
@@ -1050,65 +1186,116 @@ public class PlaybackServiceMediaPlayer {
};
private final void genericSeekCompleteListener() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- if (playerStatus == PlayerStatus.SEEKING) {
- setPlayerStatus(statusBeforeSeeking, media);
- }
- playerLock.unlock();
+ Thread t = new Thread(() -> {
+ Log.d(TAG, "genericSeekCompleteListener");
+ if(seekLatch != null) {
+ seekLatch.countDown();
+ }
+ playerLock.lock();
+ if (playerStatus == PlayerStatus.SEEKING) {
+ setPlayerStatus(statusBeforeSeeking, media);
}
+ playerLock.unlock();
});
+ t.start();
}
private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
- @Override
- public void onPlay() {
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- resume();
- } else if (playerStatus == PlayerStatus.INITIALIZED) {
- setStartWhenPrepared(true);
- prepare();
- }
- }
+ private static final String TAG = "MediaSessionCompat";
@Override
- public void onPause() {
- super.onPause();
- if (playerStatus == PlayerStatus.PLAYING) {
- pause(false, true);
+ public boolean onMediaButtonEvent(final Intent mediaButton) {
+ Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
+ if (mediaButton != null) {
+ KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT);
+ handleMediaKey(keyEvent);
}
- if (UserPreferences.isPersistNotify()) {
- pause(false, true);
- } else {
- pause(true, true);
- }
- }
-
- @Override
- public void onSkipToNext() {
- super.onSkipToNext();
- endPlayback();
- }
-
- @Override
- public void onFastForward() {
- super.onFastForward();
- seekDelta(UserPreferences.getFastFowardSecs() * 1000);
- }
-
- @Override
- public void onRewind() {
- super.onRewind();
- seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ return false;
}
+ };
- @Override
- public void onSeekTo(long pos) {
- super.onSeekTo(pos);
- seekTo((int) pos);
+ public boolean handleMediaKey(KeyEvent event) {
+ Log.d(TAG, "handleMediaKey(" + event +")");
+ if (event != null
+ && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK: {
+ Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
+ resume();
+ } else if (playerStatus == PlayerStatus.INITIALIZED) {
+ setStartWhenPrepared(true);
+ prepare();
+ } else if (playerStatus == PlayerStatus.PLAYING) {
+ pause(false, true);
+ if (UserPreferences.isPersistNotify()) {
+ pause(false, true);
+ } else {
+ pause(true, true);
+ }
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PLAY: {
+ Log.d(TAG, "Received Play event from RemoteControlClient");
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
+ resume();
+ } else if (playerStatus == PlayerStatus.INITIALIZED) {
+ setStartWhenPrepared(true);
+ prepare();
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PAUSE: {
+ Log.d(TAG, "Received Pause event from RemoteControlClient");
+ if (playerStatus == PlayerStatus.PLAYING) {
+ pause(false, true);
+ }
+ if (UserPreferences.isPersistNotify()) {
+ pause(false, true);
+ } else {
+ pause(true, true);
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_STOP: {
+ Log.d(TAG, "Received Stop event from RemoteControlClient");
+ stop();
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS: {
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_REWIND: {
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_NEXT: {
+ if(event.getSource() == InputDevice.SOURCE_CLASS_NONE ||
+ UserPreferences.shouldHardwareButtonSkip()) {
+ // assume the skip command comes from a notification or the lockscreen
+ // a >| skip button should actually skip
+ endPlayback(true);
+ } else {
+ // assume skip command comes from a (bluetooth) media button
+ // user actually wants to fast-forward
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
+ }
+ return true;
+ }
+ default:
+ Log.d(TAG, "Unhandled key code: " + event.getKeyCode());
+ break;
+ }
}
- };
+ return false;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index fc73c9446..680fb8777 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -1,10 +1,10 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
+import android.os.Vibrator;
+import android.support.annotation.NonNull;
import android.util.Log;
-import org.apache.commons.lang3.Validate;
-
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -14,12 +14,10 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.Playable;
-
import de.greenrobot.event.EventBus;
@@ -32,7 +30,7 @@ import de.greenrobot.event.EventBus;
* to notify the PlaybackService about updates from the running tasks.
*/
public class PlaybackServiceTaskManager {
- private static final String TAG = "PlaybackServiceTaskManager";
+ private static final String TAG = "PlaybackServiceTaskMgr";
/**
* Update interval of position saver in milliseconds.
@@ -41,7 +39,7 @@ public class PlaybackServiceTaskManager {
/**
* Notification interval of widget updater in milliseconds.
*/
- public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500;
+ public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1000;
private static final int SCHED_EX_POOL_SIZE = 2;
private final ScheduledThreadPoolExecutor schedExecutor;
@@ -63,10 +61,8 @@ public class PlaybackServiceTaskManager {
* @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);
-
+ public PlaybackServiceTaskManager(@NonNull Context context,
+ @NonNull PSTMCallback callback) {
this.context = context;
this.callback = callback;
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
@@ -82,6 +78,7 @@ public class PlaybackServiceTaskManager {
}
public void onEvent(QueueEvent event) {
+ Log.d(TAG, "onEvent(QueueEvent " + event +")");
cancelQueueLoader();
loadQueue();
}
@@ -101,7 +98,7 @@ public class PlaybackServiceTaskManager {
queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
@Override
public List<FeedItem> call() throws Exception {
- return DBReader.getQueue(context);
+ return DBReader.getQueue();
}
});
}
@@ -168,7 +165,7 @@ public class PlaybackServiceTaskManager {
public synchronized void cancelPositionSaver() {
if (isPositionSaverActive()) {
positionSaverFuture.cancel(false);
- if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
+ Log.d(TAG, "Cancelled PositionSaver");
}
}
@@ -186,9 +183,9 @@ public class PlaybackServiceTaskManager {
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
- if (BuildConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
+ Log.d(TAG, "Started WidgetUpdater");
} else {
- if (BuildConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
+ Log.d(TAG, "Call to startWidgetUpdater was ignored.");
}
}
@@ -199,16 +196,16 @@ public class PlaybackServiceTaskManager {
*
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
*/
- public synchronized void setSleepTimer(long waitingTime) {
- Validate.isTrue(waitingTime > 0, "Waiting time <= 0");
+ public synchronized void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
+ if(waitingTime <= 0) {
+ throw new IllegalArgumentException("Waiting time <= 0");
+ }
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
- + " milliseconds");
+ Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
if (isSleepTimerActive()) {
sleepTimerFuture.cancel(true);
}
- sleepTimer = new SleepTimer(waitingTime);
+ sleepTimer = new SleepTimer(waitingTime, shakeToReset, vibrate);
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
}
@@ -216,7 +213,11 @@ public class PlaybackServiceTaskManager {
* Returns true if the sleep timer is currently active.
*/
public synchronized boolean isSleepTimerActive() {
- return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone() && sleepTimer.isWaiting;
+ return sleepTimer != null
+ && sleepTimerFuture != null
+ && !sleepTimerFuture.isCancelled()
+ && !sleepTimerFuture.isDone()
+ && sleepTimer.getWaitingTime() > 0;
}
/**
@@ -224,8 +225,7 @@ public class PlaybackServiceTaskManager {
*/
public synchronized void disableSleepTimer() {
if (isSleepTimerActive()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Disabling sleep timer");
+ Log.d(TAG, "Disabling sleep timer");
sleepTimerFuture.cancel(true);
}
}
@@ -255,7 +255,7 @@ public class PlaybackServiceTaskManager {
public synchronized void cancelWidgetUpdater() {
if (isWidgetUpdaterActive()) {
widgetUpdaterFuture.cancel(false);
- if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
+ Log.d(TAG, "Cancelled WidgetUpdater");
}
}
@@ -274,9 +274,7 @@ public class PlaybackServiceTaskManager {
* 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);
-
+ public synchronized void startChapterLoader(@NonNull final Playable media) {
if (isChapterLoaderActive()) {
cancelChapterLoader();
}
@@ -284,16 +282,14 @@ public class PlaybackServiceTaskManager {
Runnable chapterLoader = new Runnable() {
@Override
public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Chapter loader started");
+ 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");
+ Log.d(TAG, "Chapter loader stopped");
}
};
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
@@ -324,63 +320,90 @@ public class PlaybackServiceTaskManager {
/**
* Sleeps for a given time and then pauses playback.
*/
- private class SleepTimer implements Runnable {
+ protected 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) {
+ private static final long UPDATE_INTERVAL = 1000L;
+ private static final long NOTIFICATION_THRESHOLD = 10000;
+ private long waitingTime;
+ private final boolean shakeToReset;
+ private final boolean vibrate;
+ private ShakeListener shakeListener;
+
+ public SleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
super();
this.waitingTime = waitingTime;
- isWaiting = true;
+ this.shakeToReset = shakeToReset;
+ this.vibrate = vibrate;
}
@Override
public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting");
+ Log.d(TAG, "Starting");
+ boolean notifiedAlmostExpired = false;
+ long lastTick = System.currentTimeMillis();
while (waitingTime > 0) {
try {
- Thread.sleep(UPDATE_INTERVALL);
- waitingTime -= UPDATE_INTERVALL;
-
+ Thread.sleep(UPDATE_INTERVAL);
+ long now = System.currentTimeMillis();
+ waitingTime -= now - lastTick;
+ lastTick = now;
+
+ if(waitingTime < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) {
+ Log.d(TAG, "Sleep timer is about to expire");
+ if(vibrate) {
+ Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ if(v != null) {
+ v.vibrate(500);
+ }
+ }
+ if(shakeListener == null && shakeToReset) {
+ shakeListener = new ShakeListener(context, this);
+ }
+ callback.onSleepTimerAlmostExpired();
+ notifiedAlmostExpired = true;
+ }
if (waitingTime <= 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Waiting completed");
- postExecute();
+ Log.d(TAG, "Sleep timer expired");
+ if(shakeListener != null) {
+ shakeListener.pause();
+ shakeListener = null;
+ }
if (!Thread.currentThread().isInterrupted()) {
callback.onSleepTimerExpired();
+ } else {
+ Log.d(TAG, "Sleep timer interrupted");
}
-
}
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting");
+ e.printStackTrace();
break;
}
}
- postExecute();
- }
-
- protected void postExecute() {
- isWaiting = false;
}
public long getWaitingTime() {
return waitingTime;
}
- public boolean isWaiting() {
- return isWaiting;
+ public void onShake() {
+ setSleepTimer(15 * 60 * 1000, shakeToReset, vibrate);
+ callback.onSleepTimerReset();
+ shakeListener.pause();
+ shakeListener = null;
}
}
- public static interface PSTMCallback {
+ public interface PSTMCallback {
void positionSaverTick();
+ void onSleepTimerAlmostExpired();
+
void onSleepTimerExpired();
+ void onSleepTimerReset();
+
void onWidgetUpdaterTick();
void onChapterLoaded(Playable media);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
new file mode 100644
index 000000000..fcd96826b
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
@@ -0,0 +1,64 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+public class ShakeListener implements SensorEventListener
+{
+ private static final String TAG = ShakeListener.class.getSimpleName();
+
+ private Sensor mAccelerometer;
+ private SensorManager mSensorMgr;
+ private PlaybackServiceTaskManager.SleepTimer mSleepTimer;
+ private Context mContext;
+
+ public ShakeListener(Context context, PlaybackServiceTaskManager.SleepTimer sleepTimer) {
+ mContext = context;
+ mSleepTimer = sleepTimer;
+ resume();
+ }
+
+ public void resume() {
+ // only a precaution, the user should actually not be able to activate shake to reset
+ // when the accelerometer is not available
+ mSensorMgr = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ if (mSensorMgr == null) {
+ throw new UnsupportedOperationException("Sensors not supported");
+ }
+ mAccelerometer = mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (!mSensorMgr.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI)) { // if not supported
+ mSensorMgr.unregisterListener(this);
+ throw new UnsupportedOperationException("Accelerometer not supported");
+ }
+ }
+
+ public void pause() {
+ if (mSensorMgr != null) {
+ mSensorMgr.unregisterListener(this);
+ mSensorMgr = null;
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ float gX = event.values[0] / SensorManager.GRAVITY_EARTH;
+ float gY = event.values[1] / SensorManager.GRAVITY_EARTH;
+ float gZ = event.values[2] / SensorManager.GRAVITY_EARTH;
+
+ double gForce = Math.sqrt(gX*gX + gY*gY + gZ*gZ);
+ if (gForce > 2.25) {
+ Log.d(TAG, "Detected shake " + gForce);
+ mSleepTimer.onShake();
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ return;
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
index f647fd537..0dc54fb6e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
@@ -4,54 +4,70 @@ import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.LongList;
/**
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod.
*/
-public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
+public class APCleanupAlgorithm extends EpisodeCleanupAlgorithm {
+
private static final String TAG = "APCleanupAlgorithm";
+ /** the number of days after playback to wait before an item is eligible to be cleaned up */
+ private final int numberOfDaysAfterPlayback;
+
+ public APCleanupAlgorithm(int numberOfDaysAfterPlayback) {
+ this.numberOfDaysAfterPlayback = numberOfDaysAfterPlayback;
+ }
@Override
- public int performCleanup(Context context, Integer episodeNumber) {
- List<FeedItem> candidates = new ArrayList<FeedItem>();
- List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
- LongList queue = DBReader.getQueueIDList(context);
+ public int performCleanup(Context context, int numberOfEpisodesToDelete) {
+ List<FeedItem> candidates = new ArrayList<>();
+ List<FeedItem> downloadedItems = DBReader.getDownloadedItems();
List<FeedItem> delete;
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DAY_OF_MONTH, -1 * numberOfDaysAfterPlayback);
+ Date mostRecentDateForDeletion = cal.getTime();
for (FeedItem item : downloadedItems) {
- if (item.hasMedia() && item.getMedia().isDownloaded()
- && !queue.contains(item.getId()) && item.isRead()) {
- candidates.add(item);
+ if (item.hasMedia()
+ && item.getMedia().isDownloaded()
+ && !item.isTagged(FeedItem.TAG_QUEUE)
+ && item.isPlayed()
+ && !item.isTagged(FeedItem.TAG_FAVORITE)) {
+ FeedMedia media = item.getMedia();
+ // make sure this candidate was played at least the proper amount of days prior
+ // to now
+ if (media != null
+ && media.getPlaybackCompletionDate() != null
+ && media.getPlaybackCompletionDate().before(mostRecentDateForDeletion)) {
+ candidates.add(item);
+ }
}
-
}
- Collections.sort(candidates, new Comparator<FeedItem>() {
- @Override
- public int compare(FeedItem lhs, FeedItem rhs) {
- Date l = lhs.getMedia().getPlaybackCompletionDate();
- Date r = rhs.getMedia().getPlaybackCompletionDate();
+ Collections.sort(candidates, (lhs, rhs) -> {
+ Date l = lhs.getMedia().getPlaybackCompletionDate();
+ Date r = rhs.getMedia().getPlaybackCompletionDate();
- if (l == null) {
- l = new Date();
- }
- if (r == null) {
- r = new Date();
- }
- return l.compareTo(r);
+ if (l == null) {
+ l = new Date();
}
+ if (r == null) {
+ r = new Date();
+ }
+ return l.compareTo(r);
});
- if (candidates.size() > episodeNumber) {
- delete = candidates.subList(0, episodeNumber);
+ if (candidates.size() > numberOfEpisodesToDelete) {
+ delete = candidates.subList(0, numberOfEpisodesToDelete);
} else {
delete = candidates;
}
@@ -69,35 +85,15 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
Log.i(TAG, String.format(
"Auto-delete deleted %d episodes (%d requested)", counter,
- episodeNumber));
+ numberOfEpisodesToDelete));
return counter;
}
@Override
- public Integer getDefaultCleanupParameter(Context context) {
- return getPerformAutoCleanupArgs(context, 0);
- }
-
- @Override
- public Integer getPerformCleanupParameter(Context context, List<FeedItem> items) {
- return getPerformAutoCleanupArgs(context, items.size());
+ public int getDefaultCleanupParameter() {
+ return getNumEpisodesToCleanup(0);
}
- 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;
- }
+ public int getNumberOfDaysAfterPlayback() { return numberOfDaysAfterPlayback; }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
index 92de1eee7..26dc027bf 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
@@ -7,7 +7,9 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import de.danoeh.antennapod.core.feed.FeedFilter;
import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.PowerUtils;
@@ -19,28 +21,24 @@ import de.danoeh.antennapod.core.util.PowerUtils;
public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
private static final String TAG = "APDownloadAlgorithm";
- private final APCleanupAlgorithm cleanupAlgorithm = new APCleanupAlgorithm();
-
/**
- * Looks for undownloaded episodes in the queue or list of unread items and request a download if
+ * Looks for undownloaded episodes in the queue or list of new items and request a download if
* 1. Network is available
* 2. The device is charging or the user allows auto download on battery
* 3. 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 Runnable that will be submitted to an ExecutorService.
*/
@Override
- public Runnable autoDownloadUndownloadedItems(final Context context, final long... mediaIds) {
+ public Runnable autoDownloadUndownloadedItems(final Context context) {
return new Runnable() {
@Override
public void run() {
// true if we should auto download based on network status
- boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable(context)
+ boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable()
&& UserPreferences.isEnableAutodownload();
// true if we should auto download based on power status
@@ -53,17 +51,15 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
List<FeedItem> candidates;
- if(mediaIds.length > 0) {
- candidates = DBReader.getFeedItems(context, mediaIds);
- } else {
- final List<FeedItem> queue = DBReader.getQueue(context);
- final List<FeedItem> unreadItems = DBReader.getUnreadItemsList(context);
- candidates = new ArrayList<FeedItem>(queue.size() + unreadItems.size());
- candidates.addAll(queue);
- for(FeedItem unreadItem : unreadItems) {
- if(candidates.contains(unreadItem) == false) {
- candidates.add(unreadItem);
- }
+ final List<FeedItem> queue = DBReader.getQueue();
+ final List<FeedItem> newItems = DBReader.getNewItemsList();
+ candidates = new ArrayList<FeedItem>(queue.size() + newItems.size());
+ candidates.addAll(queue);
+ for(FeedItem newItem : newItems) {
+ FeedPreferences feedPrefs = newItem.getFeed().getPreferences();
+ FeedFilter feedFilter = feedPrefs.getFilter();
+ if(candidates.contains(newItem) == false && feedFilter.shouldAutoDownload(newItem)) {
+ candidates.add(newItem);
}
}
@@ -77,9 +73,9 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
}
int autoDownloadableEpisodes = candidates.size();
- int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes(context);
- int deletedEpisodes = cleanupAlgorithm.performCleanup(context,
- APCleanupAlgorithm.getPerformAutoCleanupArgs(context, autoDownloadableEpisodes));
+ int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes();
+ int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm()
+ .makeRoomForEpisodes(context, autoDownloadableEpisodes);
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
@@ -107,5 +103,4 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
}
};
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java
new file mode 100644
index 000000000..132b61853
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java
@@ -0,0 +1,24 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * A cleanup algorithm that never removes anything
+ */
+public class APNullCleanupAlgorithm extends EpisodeCleanupAlgorithm {
+
+ private static final String TAG = "APNullCleanupAlgorithm";
+
+ @Override
+ public int performCleanup(Context context, int parameter) {
+ // never clean anything up
+ Log.i(TAG, "performCleanup: Not removing anything");
+ return 0;
+ }
+
+ @Override
+ public int getDefaultCleanupParameter() {
+ return 0;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
new file mode 100644
index 000000000..234d6162c
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
@@ -0,0 +1,81 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.util.LongList;
+
+/**
+ * A cleanup algorithm that removes any item that isn't in the queue and isn't a favorite
+ * but only if space is needed.
+ */
+public class APQueueCleanupAlgorithm extends EpisodeCleanupAlgorithm {
+
+ private static final String TAG = "APQueueCleanupAlgorithm";
+
+ @Override
+ public int performCleanup(Context context, int numberOfEpisodesToDelete) {
+ List<FeedItem> candidates = new ArrayList<>();
+ List<FeedItem> downloadedItems = DBReader.getDownloadedItems();
+ List<FeedItem> delete;
+ for (FeedItem item : downloadedItems) {
+ if (item.hasMedia()
+ && item.getMedia().isDownloaded()
+ && !item.isTagged(FeedItem.TAG_QUEUE)
+ && !item.isTagged(FeedItem.TAG_FAVORITE)) {
+ candidates.add(item);
+ }
+ }
+
+ // in the absence of better data, we'll sort by item publication date
+ Collections.sort(candidates, (lhs, rhs) -> {
+ Date l = lhs.getPubDate();
+ Date r = rhs.getPubDate();
+
+ if (l == null) {
+ l = new Date();
+ }
+ if (r == null) {
+ r = new Date();
+ }
+ return l.compareTo(r);
+ });
+
+ if (candidates.size() > numberOfEpisodesToDelete) {
+ delete = candidates.subList(0, numberOfEpisodesToDelete);
+ } else {
+ delete = candidates;
+ }
+
+ for (FeedItem item : delete) {
+ try {
+ DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ int counter = delete.size();
+
+
+ Log.i(TAG, String.format(
+ "Auto-delete deleted %d episodes (%d requested)", counter,
+ numberOfEpisodesToDelete));
+
+ return counter;
+ }
+
+ @Override
+ public int getDefaultCleanupParameter() {
+ return getNumEpisodesToCleanup(0);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java
deleted file mode 100644
index 420bbc09d..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import android.content.Context;
-import android.util.Log;
-
-import org.apache.commons.io.FileUtils;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-import de.danoeh.antennapod.core.feed.FeedItem;
-
-/**
- * Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPodSP apps.
- */
-public class APSPCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
- private static final String TAG = "APSPCleanupAlgorithm";
-
- final int numberOfNewAutomaticallyDownloadedEpisodes;
-
- public APSPCleanupAlgorithm(int numberOfNewAutomaticallyDownloadedEpisodes) {
- this.numberOfNewAutomaticallyDownloadedEpisodes = numberOfNewAutomaticallyDownloadedEpisodes;
- }
-
- /**
- * Performs an automatic cleanup. Episodes that have been downloaded first will also be deleted first.
- * The episode that is currently playing as well as the n most recent episodes (the exact value is determined
- * by AppPreferences.numberOfNewAutomaticallyDownloadedEpisodes) will never be deleted.
- *
- * @param context
- * @param episodeSize The maximum amount of space that should be freed by this method
- * @return The number of episodes that have been deleted
- */
- @Override
- public int performCleanup(Context context, Integer episodeSize) {
- Log.i(TAG, String.format("performAutoCleanup(%d)", episodeSize));
- if (episodeSize <= 0) {
- return 0;
- }
-
- List<FeedItem> candidates = getAutoCleanupCandidates(context);
- List<FeedItem> deleteList = new ArrayList<FeedItem>();
- long deletedEpisodesSize = 0;
- Collections.sort(candidates, new Comparator<FeedItem>() {
- @Override
- public int compare(FeedItem lhs, FeedItem rhs) {
- File lFile = new File(lhs.getMedia().getFile_url());
- File rFile = new File(rhs.getMedia().getFile_url());
- if (!lFile.exists() || !rFile.exists()) {
- return 0;
- }
- if (FileUtils.isFileOlder(lFile, rFile)) {
- return -1;
- } else {
- return 1;
- }
- }
- });
- // listened episodes will be deleted first
- Iterator<FeedItem> it = candidates.iterator();
- if (it.hasNext()) {
- for (FeedItem i = it.next(); it.hasNext() && deletedEpisodesSize <= episodeSize; i = it.next()) {
- if (!i.getMedia().isPlaying() && i.getMedia().getPlaybackCompletionDate() != null) {
- it.remove();
- deleteList.add(i);
- deletedEpisodesSize += i.getMedia().getSize();
- }
- }
- }
-
- // delete unlistened old episodes if necessary
- it = candidates.iterator();
- if (it.hasNext()) {
- for (FeedItem i = it.next(); it.hasNext() && deletedEpisodesSize <= episodeSize; i = it.next()) {
- if (!i.getMedia().isPlaying()) {
- it.remove();
- deleteList.add(i);
- deletedEpisodesSize += i.getMedia().getSize();
- }
- }
- }
- for (FeedItem item : deleteList) {
- try {
- DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- }
- Log.i(TAG, String.format("performAutoCleanup(%d) deleted %d episodes and freed %d bytes of memory",
- episodeSize, deleteList.size(), deletedEpisodesSize));
- return deleteList.size();
- }
-
- @Override
- public Integer getDefaultCleanupParameter(Context context) {
- return 0;
- }
-
- @Override
- public Integer getPerformCleanupParameter(Context context, List<FeedItem> items) {
- int episodeSize = 0;
- for (FeedItem item : items) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()) {
- episodeSize += item.getMedia().getSize();
- }
- }
- return episodeSize;
- }
-
- /**
- * Returns list of FeedItems that have been downloaded, but are not one of the
- * [numberOfNewAutomaticallyDownloadedEpisodes] most recent items.
- */
- private List<FeedItem> getAutoCleanupCandidates(Context context) {
- List<FeedItem> downloaded = new ArrayList<FeedItem>(DBReader.getDownloadedItems(context));
- List<FeedItem> recent = new ArrayList<FeedItem>(DBReader.getRecentlyPublishedEpisodes(context,
- numberOfNewAutomaticallyDownloadedEpisodes));
- for (FeedItem r : recent) {
- if (r.hasMedia() && r.getMedia().isDownloaded()) {
- for (int i = 0; i < downloaded.size(); i++) {
- if (downloaded.get(i).getId() == r.getId()) {
- downloaded.remove(i);
- break;
- }
- }
- }
- }
-
- return downloaded;
-
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java
deleted file mode 100644
index f760ec0ce..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import android.content.Context;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.util.NetworkUtils;
-
-/**
- * Implements the automatic download algorithm used by AntennaPodSP apps.
- */
-public class APSPDownloadAlgorithm implements AutomaticDownloadAlgorithm {
- private static final String TAG = "APSPDownloadAlgorithm";
-
- private final int numberOfNewAutomaticallyDownloadedEpisodes;
-
- public APSPDownloadAlgorithm(int numberOfNewAutomaticallyDownloadedEpisodes) {
- this.numberOfNewAutomaticallyDownloadedEpisodes = numberOfNewAutomaticallyDownloadedEpisodes;
- }
-
- /**
- * Downloads the most recent episodes automatically. The exact number of
- * episodes that will be downloaded can be set in the AppPreferences.
- *
- * @param context Used for accessing the DB.
- * @return A Runnable that will be submitted to an ExecutorService.
- */
- @Override
- public Runnable autoDownloadUndownloadedItems(final Context context, final long... mediaIds) {
- return new Runnable() {
- @Override
- public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Performing auto-dl of undownloaded episodes");
- if (NetworkUtils.autodownloadNetworkAvailable(context)
- && UserPreferences.isEnableAutodownload()) {
-
- Arrays.sort(mediaIds);
- List<FeedItem> itemsToDownload = DBReader.getRecentlyPublishedEpisodes(context,
- numberOfNewAutomaticallyDownloadedEpisodes);
- Iterator<FeedItem> it = itemsToDownload.iterator();
-
- for (FeedItem item = it.next(); it.hasNext(); item = it.next()) {
- if (!item.hasMedia()
- || item.getMedia().isDownloaded()
- || Arrays.binarySearch(mediaIds, item.getMedia().getId()) < 0) {
- it.remove();
- }
- }
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Enqueueing " + itemsToDownload.size()
- + " items for automatic download");
- if (!itemsToDownload.isEmpty()) {
- try {
- DBTasks.downloadFeedItems(false, context,
- itemsToDownload.toArray(new FeedItem[itemsToDownload
- .size()]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
- }
- }
- }
- };
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java
index 9ca9620a7..72c68ddb6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java
@@ -12,9 +12,7 @@ public interface AutomaticDownloadAlgorithm {
* 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 Runnable that will be submitted to an ExecutorService.
*/
- public Runnable autoDownloadUndownloadedItems(Context context, long... mediaIds);
+ public Runnable autoDownloadUndownloadedItems(Context context);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index cc20b3d37..0563f878f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -1,18 +1,16 @@
package de.danoeh.antennapod.core.storage;
-import android.content.Context;
import android.database.Cursor;
+import android.support.v4.util.ArrayMap;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
+import java.util.Map;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
@@ -22,14 +20,13 @@ import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.feed.ID3Chapter;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
-import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.LongList;
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;
/**
@@ -39,15 +36,16 @@ import de.danoeh.antennapod.core.util.flattr.FlattrThing;
* 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)}.
+ * Maximum size of the list returned by {@link #getPlaybackHistory()}.
*/
public static final int PLAYBACK_HISTORY_SIZE = 50;
/**
- * Maximum size of the list returned by {@link #getDownloadLog(android.content.Context)}.
+ * Maximum size of the list returned by {@link #getDownloadLog()}.
*/
public static final int DOWNLOAD_LOG_SIZE = 200;
@@ -58,16 +56,14 @@ public final class 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)}.
+ * can be loaded separately with {@link #getFeedItemList(Feed)}.
*/
- public static List<Feed> getFeedList(final Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting Feedlist");
+ public static List<Feed> getFeedList() {
+ Log.d(TAG, "Extracting Feedlist");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<Feed> result = getFeedList(adapter);
adapter.close();
@@ -75,11 +71,8 @@ public final class DBReader {
}
private static List<Feed> getFeedList(PodDBAdapter adapter) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting Feedlist");
-
Cursor feedlistCursor = adapter.getAllFeedsCursor();
- List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
+ List<Feed> feeds = new ArrayList<>(feedlistCursor.getCount());
if (feedlistCursor.moveToFirst()) {
do {
@@ -94,12 +87,11 @@ public final class DBReader {
/**
* 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<String> getFeedListDownloadUrls(final Context context) {
- PodDBAdapter adapter = new PodDBAdapter(context);
- List<String> result = new ArrayList<String>();
+ public static List<String> getFeedListDownloadUrls() {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ List<String> result = new ArrayList<>();
adapter.open();
Cursor feeds = adapter.getFeedCursorDownloadUrls();
if (feeds.moveToFirst()) {
@@ -113,34 +105,28 @@ public final class DBReader {
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)}.
+ * Loads additional data in to the feed items from other database queries
+ * @param items the FeedItems who should have other data loaded
*/
- public static List<Feed> 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();
+ public static void loadAdditionalFeedItemListData(List<FeedItem> items) {
+ loadTagsOfFeedItemList(items);
+ loadFeedDataOfFeedItemList(items);
+ }
- Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime);
- List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
+ public static void loadTagsOfFeedItemList(List<FeedItem> items) {
+ LongList favoriteIds = getFavoriteIDList();
+ LongList queueIds = getQueueIDList();
- if (feedlistCursor.moveToFirst()) {
- do {
- Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
- feeds.add(feed);
- } while (feedlistCursor.moveToNext());
+ for (FeedItem item : items) {
+ if (favoriteIds.contains(item.getId())) {
+ item.addTag(FeedItem.TAG_FAVORITE);
+ }
+ if (queueIds.contains(item.getId())) {
+ item.addTag(FeedItem.TAG_QUEUE);
+ }
}
- feedlistCursor.close();
- return feeds;
}
/**
@@ -148,12 +134,10 @@ public final class DBReader {
* 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<FeedItem> items) {
- List<Feed> feeds = getFeedList(context);
+ public static void loadFeedDataOfFeedItemList(List<FeedItem> items) {
+ List<Feed> feeds = getFeedList();
for (FeedItem item : items) {
for (Feed feed : feeds) {
if (feed.getId() == item.getFeedId()) {
@@ -169,29 +153,26 @@ public final class DBReader {
/**
* 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.
+ * used. In order to get information ABOUT the list of FeedItems, consider using {@link #getFeedStatisticsList()} 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<FeedItem> getFeedItemList(Context context,
- final Feed feed) {
- Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
+ public static List<FeedItem> getFeedItemList(final Feed feed) {
+ Log.d(TAG, "getFeedItemList() called with: " + "feed = [" + feed + "]");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
+ adapter.close();
Collections.sort(items, new FeedItemPubdateComparator());
- adapter.close();
-
for (FeedItem item : items) {
item.setFeed(feed);
}
@@ -199,200 +180,121 @@ public final class DBReader {
return items;
}
- static List<FeedItem> extractItemlistFromCursor(Context context, Cursor itemlistCursor) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static List<FeedItem> extractItemlistFromCursor(Cursor itemlistCursor) {
+ Log.d(TAG, "extractItemlistFromCursor() called with: " + "itemlistCursor = [" + itemlistCursor + "]");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor);
adapter.close();
return result;
}
- private static List<FeedItem> extractItemlistFromCursor(
- PodDBAdapter adapter, Cursor itemlistCursor) {
- ArrayList<String> itemIds = new ArrayList<String>();
- List<FeedItem> items = new ArrayList<FeedItem>(
- itemlistCursor.getCount());
+ private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter,
+ Cursor cursor) {
+ List<FeedItem> result = new ArrayList<>(cursor.getCount());
- if (itemlistCursor.moveToFirst()) {
+ LongList imageIds = new LongList(cursor.getCount());
+ LongList itemIds = new LongList(cursor.getCount());
+ if (cursor.moveToFirst()) {
do {
- long imageIndex = itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_IMAGE);
- FeedImage image = null;
- if (imageIndex != 0) {
- image = getFeedImage(adapter, imageIndex);
- }
+ int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
+ long imageId = cursor.getLong(indexImage);
+ imageIds.add(imageId);
- FeedItem item = new FeedItem(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID),
- itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_TITLE),
- itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_LINK),
- new Date(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)),
- itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK),
- itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_FEED),
- new FlattrStatus(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_FLATTR_STATUS)),
- itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0,
- image,
- (itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0),
- itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER),
- itemlistCursor.getInt(itemlistCursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD)) > 0
- );
-
- itemIds.add(String.valueOf(item.getId()));
-
- items.add(item);
- } while (itemlistCursor.moveToNext());
+ FeedItem item = FeedItem.fromCursor(cursor);
+ result.add(item);
+ itemIds.add(item.getId());
+ } while (cursor.moveToNext());
+ Map<Long,FeedImage> images = getFeedImages(adapter, imageIds.toArray());
+ Map<Long,FeedMedia> medias = getFeedMedia(adapter, itemIds.toArray());
+ for(int i=0; i < result.size(); i++) {
+ FeedItem item = result.get(i);
+ long imageId = imageIds.get(i);
+ FeedImage image = images.get(imageId);
+ item.setImage(image);
+ FeedMedia media = medias.get(item.getId());
+ item.setMedia(media);
+ if(media != null) {
+ media.setItem(item);
+ }
+ }
}
-
- extractMediafromItemlist(adapter, items, itemIds);
- return items;
+ return result;
}
- private static void extractMediafromItemlist(PodDBAdapter adapter,
- List<FeedItem> items, ArrayList<String> itemIds) {
+ private static Map<Long,FeedMedia> getFeedMedia(PodDBAdapter adapter,
+ long... itemIds) {
- List<FeedItem> itemsCopy = new ArrayList<FeedItem>(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();
+ String[] ids = new String[itemIds.length];
+ for(int i=0, len=itemIds.length; i < len; i++) {
+ ids[i] = String.valueOf(itemIds[i]);
}
- }
-
- 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);
+ Map<Long,FeedMedia> result = new ArrayMap<>(itemIds.length);
+ Cursor cursor = adapter.getFeedMediaCursor(ids);
+ try {
+ if (cursor.moveToFirst()) {
+ do {
+ int index = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
+ long itemId = cursor.getLong(index);
+ FeedMedia media = FeedMedia.fromCursor(cursor);
+ result.put(itemId, media);
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ cursor.close();
}
-
- 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));
+ return result;
}
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);
+ int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
+ long imageId = cursor.getLong(indexImage);
+ if (imageId != 0) {
+ image = getFeedImage(adapter, imageId);
} 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)),
- cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_IS_PAGED) > 0,
- cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK),
- cursor.getString(cursor.getColumnIndex(PodDBAdapter.KEY_HIDE)),
- cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED)) > 0
- );
+ Feed feed = Feed.fromCursor(cursor);
if (image != null) {
+ feed.setImage(image);
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));
-
+ FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
- return feed;
- }
-
- private static DownloadStatus extractDownloadStatusFromCursorRow(final Cursor cursor) {
- long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- long feedfileId = cursor.getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
- int feedfileType = cursor.getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
- boolean successful = cursor.getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
- int reason = cursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
- String reasonDetailed = cursor.getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
- String title = cursor.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
- Date completionDate = new Date(cursor.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
-
- return new DownloadStatus(id, title, feedfileId,
- feedfileType, successful, DownloadError.fromCode(reason), completionDate,
- reasonDetailed);
- }
-
- private static FeedItem getMatchingItemForMedia(long itemId,
- List<FeedItem> items) {
- for (FeedItem item : items) {
- if (item.getId() == itemId) {
- return item;
- }
- }
- return null;
+ return feed;
}
- static List<FeedItem> getQueue(Context context, PodDBAdapter adapter) {
+ static List<FeedItem> getQueue(PodDBAdapter adapter) {
Log.d(TAG, "getQueue()");
-
Cursor itemlistCursor = adapter.getQueueCursor();
- List<FeedItem> items = extractItemlistFromCursor(adapter,
- itemlistCursor);
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
- loadFeedDataOfFeedItemlist(context, items);
-
+ loadAdditionalFeedItemListData(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.
+ * {@link #getQueue()} 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 LongList getQueueIDList(Context context) {
- PodDBAdapter adapter = new PodDBAdapter(context);
-
+ public static LongList getQueueIDList() {
+ Log.d(TAG, "getQueueIDList() called");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
LongList result = getQueueIDList(adapter);
adapter.close();
-
return result;
}
static LongList getQueueIDList(PodDBAdapter adapter) {
- adapter.open();
Cursor queueCursor = adapter.getQueueIDCursor();
LongList queueIds = new LongList(queueCursor.getCount());
@@ -401,40 +303,23 @@ public final class DBReader {
queueIds.add(queueCursor.getLong(0));
} while (queueCursor.moveToNext());
}
+ queueCursor.close();
return queueIds;
}
-
- /**
- * Return the size of the queue.
- *
- * @param context A context that is used for opening a database connection.
- * @return Size of the queue.
- */
- public static int getQueueSize(Context context) {
- Log.d(TAG, "getQueueSize()");
-
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- int size = adapter.getQueueSize();
- adapter.close();
- return size;
- }
-
/**
* 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.
+ * {@link #getQueueIDList()} 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<FeedItem> getQueue(Context context) {
- Log.d(TAG, "getQueue()");
+ public static List<FeedItem> getQueue() {
+ Log.d(TAG, "getQueue() called with: " + "");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- List<FeedItem> items = getQueue(context, adapter);
+ List<FeedItem> items = getQueue(adapter);
adapter.close();
return items;
}
@@ -442,24 +327,23 @@ public final class DBReader {
/**
* 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<FeedItem> getDownloadedItems(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting downloaded items");
+ public static List<FeedItem> getDownloadedItems() {
+ Log.d(TAG, "getDownloadedItems() called with: " + "");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getDownloadedItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
- loadFeedDataOfFeedItemlist(context, items);
+ loadAdditionalFeedItemListData(items);
+ adapter.close();
+
Collections.sort(items, new FeedItemPubdateComparator());
- adapter.close();
return items;
}
@@ -467,23 +351,18 @@ public final class DBReader {
/**
* 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.
+ * @return A list of FeedItems whose 'read'-attribute it set to false.
*/
- public static List<FeedItem> getUnreadItemsList(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting unread items list");
+ public static List<FeedItem> getUnreadItemsList() {
+ Log.d(TAG, "getUnreadItemsList() called");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
-
Cursor itemlistCursor = adapter.getUnreadItemsCursor();
- List<FeedItem> items = extractItemlistFromCursor(adapter,
- itemlistCursor);
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
- loadFeedDataOfFeedItemlist(context, items);
+ loadAdditionalFeedItemListData(items);
adapter.close();
@@ -492,70 +371,76 @@ public final class DBReader {
/**
* Loads a list of FeedItems that are considered new.
- *
- * @param context A context that is used for opening a database connection.
+ * Excludes items from feeds that do not have keep updated enabled.
* @return A list of FeedItems that are considered new.
*/
- public static List<FeedItem> getNewItemsList(Context context) {
- Log.d(TAG, "getNewItemsList()");
+ public static List<FeedItem> getNewItemsList() {
+ Log.d(TAG, "getNewItemsList() called");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getNewItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
- loadFeedDataOfFeedItemlist(context, items);
+ loadAdditionalFeedItemListData(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 LongList getNewItemIds(Context context) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static List<FeedItem> getFavoriteItemsList() {
+ Log.d(TAG, "getFavoriteItemsList() called");
+
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- Cursor cursor = adapter.getNewItemIdsCursor();
- LongList itemIds = new LongList(cursor.getCount());
- int i = 0;
- if (cursor.moveToFirst()) {
+
+ Cursor itemlistCursor = adapter.getFavoritesCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
+ itemlistCursor.close();
+
+ loadAdditionalFeedItemListData(items);
+
+ adapter.close();
+
+ return items;
+ }
+
+ public static LongList getFavoriteIDList() {
+ Log.d(TAG, "getFavoriteIDList() called");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ Cursor favoritesCursor = adapter.getFavoritesCursor();
+
+ LongList favoriteIDs = new LongList(favoritesCursor.getCount());
+ if (favoritesCursor.moveToFirst()) {
do {
- long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- itemIds.add(id);
- i++;
- } while (cursor.moveToNext());
+ favoriteIDs.add(favoritesCursor.getLong(0));
+ } while (favoritesCursor.moveToNext());
}
- return itemIds;
+ favoritesCursor.close();
+ adapter.close();
+ return favoriteIDs;
}
-
/**
* 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<FeedItem> getRecentlyPublishedEpisodes(Context context, int limit) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting recently published items list");
+ public static List<FeedItem> getRecentlyPublishedEpisodes(int limit) {
+ Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "limit = [" + limit + "]");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor itemlistCursor = adapter.getRecentlyPublishedItemsCursor(limit);
- List<FeedItem> items = extractItemlistFromCursor(adapter,
- itemlistCursor);
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
itemlistCursor.close();
- loadFeedDataOfFeedItemlist(context, items);
+ loadAdditionalFeedItemListData(items);
adapter.close();
@@ -566,26 +451,25 @@ public final class DBReader {
* 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<FeedItem> getPlaybackHistory(final Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Loading playback history");
+ public static List<FeedItem> getPlaybackHistory() {
+ Log.d(TAG, "getPlaybackHistory() called");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
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));
+ int index = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
+ itemIds[i] = Long.toString(mediaCursor.getLong(index));
}
mediaCursor.close();
Cursor itemCursor = adapter.getFeedItemCursor(itemIds);
List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
- loadFeedDataOfFeedItemlist(context, items);
+ loadAdditionalFeedItemListData(items);
itemCursor.close();
adapter.close();
@@ -596,26 +480,25 @@ public final class DBReader {
/**
* 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<DownloadStatus> getDownloadLog(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting DownloadLog");
+ public static List<DownloadStatus> getDownloadLog() {
+ Log.d(TAG, "getDownloadLog() called");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE);
- List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
- logCursor.getCount());
+ List<DownloadStatus> downloadLog = new ArrayList<>(logCursor.getCount());
if (logCursor.moveToFirst()) {
do {
- downloadLog.add(extractDownloadStatusFromCursorRow(logCursor));
+ DownloadStatus status = DownloadStatus.fromCursor(logCursor);
+ downloadLog.add(status);
} while (logCursor.moveToNext());
}
logCursor.close();
+ adapter.close();
Collections.sort(downloadLog, new DownloadStatusComparator());
return downloadLog;
}
@@ -623,77 +506,47 @@ public final class DBReader {
/**
* Loads the download log for a particular feed from the database.
*
- * @param context A context that is used for opening a database connection.
* @param feed Feed for which the download log is loaded
* @return A list with DownloadStatus objects that represent the feed's download log,
* newest events first.
*/
- public static List<DownloadStatus> getFeedDownloadLog(Context context, Feed feed) {
- Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + feed.toString() + ")");
+ public static List<DownloadStatus> getFeedDownloadLog(Feed feed) {
+ Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feed + "]");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feed.getId());
- List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
- cursor.getCount());
-
- if (cursor.moveToFirst()) {
- do {
- downloadLog.add(extractDownloadStatusFromCursorRow(cursor));
- } while (cursor.moveToNext());
- }
- cursor.close();
- Collections.sort(downloadLog, new DownloadStatusComparator());
- return downloadLog;
- }
-
- /**
- * Loads the download log for a particular feed media from the database.
- *
- * @param context A context that is used for opening a database connection.
- * @param media Feed media for which the download log is loaded
- * @return A list with DownloadStatus objects that represent the feed media's download log,
- * newest events first.
- */
- public static List<DownloadStatus> getFeedMediaDownloadLog(Context context, FeedMedia media) {
- Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + media.toString() + ")");
-
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- Cursor cursor = adapter.getDownloadLog(FeedMedia.FEEDFILETYPE_FEEDMEDIA, media.getId());
- List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
- cursor.getCount());
+ List<DownloadStatus> downloadLog = new ArrayList<>(cursor.getCount());
if (cursor.moveToFirst()) {
do {
- downloadLog.add(extractDownloadStatusFromCursorRow(cursor));
+ DownloadStatus status = DownloadStatus.fromCursor(cursor);
+ downloadLog.add(status);
} while (cursor.moveToNext());
}
cursor.close();
+ adapter.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
+ * {@link #getFeedItemList(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<FeedItemStatistics> getFeedStatisticsList(final Context context) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static List<FeedItemStatistics> getFeedStatisticsList() {
+ Log.d(TAG, "getFeedStatisticsList() called");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- List<FeedItemStatistics> result = new ArrayList<FeedItemStatistics>();
+ List<FeedItemStatistics> 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))));
+ FeedItemStatistics fis = FeedItemStatistics.fromCursor(cursor);
+ result.add(fis);
} while (cursor.moveToNext());
}
@@ -705,28 +558,26 @@ public final class DBReader {
/**
* 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);
+ public static Feed getFeed(final long feedId) {
+ Log.d(TAG, "getFeed() called with: " + "feedId = [" + feedId + "]");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- Feed result = getFeed(context, feedId, adapter);
+ Feed result = getFeed(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);
+ static Feed getFeed(final long feedId, PodDBAdapter adapter) {
Feed feed = null;
Cursor feedCursor = adapter.getFeedCursor(feedId);
if (feedCursor.moveToFirst()) {
feed = extractFeedFromCursorRow(adapter, feedCursor);
- feed.setItems(getFeedItemList(context, feed));
+ feed.setItems(getFeedItemList(feed));
} else {
Log.e(TAG, "getFeed could not find feed with id " + feedId);
}
@@ -734,9 +585,8 @@ public final class DBReader {
return feed;
}
- static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Loading feeditem with id " + itemId);
+ static FeedItem getFeedItem(final long itemId, PodDBAdapter adapter) {
+ Log.d(TAG, "Loading feeditem with id " + itemId);
FeedItem item = null;
Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId));
@@ -744,16 +594,17 @@ public final class DBReader {
List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
if (list.size() > 0) {
item = list.get(0);
- loadFeedDataOfFeedItemlist(context, list);
+ loadAdditionalFeedItemListData(list);
if (item.hasChapters()) {
loadChaptersOfFeedItem(adapter, item);
}
}
}
+ itemCursor.close();
return item;
}
- static List<FeedItem> getFeedItems(final Context context, PodDBAdapter adapter, final long... itemIds) {
+ static List<FeedItem> getFeedItems(PodDBAdapter adapter, final long... itemIds) {
String[] ids = new String[itemIds.length];
for(int i = 0; i < itemIds.length; i++) {
@@ -766,7 +617,7 @@ public final class DBReader {
Cursor itemCursor = adapter.getFeedItemCursor(ids);
if (itemCursor.moveToFirst()) {
result = extractItemlistFromCursor(adapter, itemCursor);
- loadFeedDataOfFeedItemlist(context, result);
+ loadAdditionalFeedItemListData(result);
for(FeedItem item : result) {
if (item.hasChapters()) {
loadChaptersOfFeedItem(adapter, item);
@@ -775,6 +626,7 @@ public final class DBReader {
} else {
result = Collections.emptyList();
}
+ itemCursor.close();
return result;
}
@@ -783,23 +635,21 @@ public final class DBReader {
* Loads a specific FeedItem from the database. This method should not be used for loading more
* than one FeedItem because this method might query the database several times for each item.
*
- * @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
* as well as chapter marks 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);
+ public static FeedItem getFeedItem(final long itemId) {
+ Log.d(TAG, "getFeedItem() called with: " + "itemId = [" + itemId + "]");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- FeedItem item = getFeedItem(context, itemId, adapter);
+ FeedItem item = getFeedItem(itemId, adapter);
adapter.close();
return item;
}
- static FeedItem getFeedItem(final Context context, final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
+ static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
FeedItem item = null;
Cursor itemCursor = adapter.getFeedItemCursor(podcastUrl, episodeUrl);
@@ -807,12 +657,13 @@ public final class DBReader {
List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
if (list.size() > 0) {
item = list.get(0);
- loadFeedDataOfFeedItemlist(context, list);
+ loadAdditionalFeedItemListData(list);
if (item.hasChapters()) {
loadChaptersOfFeedItem(adapter, item);
}
}
}
+ itemCursor.close();
return item;
}
@@ -820,17 +671,15 @@ public final class DBReader {
* Loads specific FeedItems from the database. This method canbe used for loading more
* than one FeedItem
*
- * @param context A context that is used for opening a database connection.
* @param itemIds The IDs of the FeedItems
* @return The FeedItems or an empty list if none of the FeedItems could be found. All FeedComponent-attributes
* as well as chapter marks of the FeedItems will also be loaded from the database.
*/
- public static List<FeedItem> getFeedItems(final Context context, final long... itemIds) {
- Log.d(TAG, "Loading feeditem with ids: " + StringUtils.join(itemIds, ","));
-
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static List<FeedItem> getFeedItems(final long... itemIds) {
+ Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + itemIds + "]");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- List<FeedItem> items = getFeedItems(context, adapter, itemIds);
+ List<FeedItem> items = getFeedItems(adapter, itemIds);
adapter.close();
return items;
}
@@ -839,47 +688,55 @@ public final class DBReader {
/**
* Returns credentials based on image URL
*
- * @param context A context that is used for opening a database connection.
* @param imageUrl The URL of the image
* @return Credentials in format "<Username>:<Password>", empty String if no authorization given
*/
- public static String getImageAuthentication(final Context context, final String imageUrl) {
- Log.d(TAG, "Loading credentials for image with URL " + imageUrl);
+ public static String getImageAuthentication(final String imageUrl) {
+ Log.d(TAG, "getImageAuthentication() called with: " + "imageUrl = [" + imageUrl + "]");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- String credentials = getImageAuthentication(context, imageUrl, adapter);
+ String credentials = getImageAuthentication(imageUrl, adapter);
adapter.close();
return credentials;
}
- static String getImageAuthentication(final Context context, final String imageUrl, PodDBAdapter adapter) {
+ static String getImageAuthentication(final String imageUrl, PodDBAdapter adapter) {
String credentials = null;
Cursor cursor = adapter.getImageAuthenticationCursor(imageUrl);
- if (cursor.moveToFirst()) {
- String username = cursor.getString(0);
- String password = cursor.getString(1);
- return username + ":" + password;
+ try {
+ if (cursor.moveToFirst()) {
+ String username = cursor.getString(0);
+ String password = cursor.getString(1);
+ if(username != null && password != null) {
+ credentials = username + ":" + password;
+ } else {
+ credentials = "";
+ }
+ } else {
+ credentials = "";
+ }
+ } finally {
+ cursor.close();
}
- return "";
+ return credentials;
}
/**
* Loads a specific FeedItem from the database.
*
- * @param context A context that is used for opening a database connection.
* @param podcastUrl the corresponding feed's url
* @param episodeUrl the feed item's url
* @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes
* as well as chapter marks of the FeedItem will also be loaded from the database.
*/
- public static FeedItem getFeedItem(final Context context, final String podcastUrl, final String episodeUrl) {
- Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
+ public static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl) {
+ Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]");
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- FeedItem item = getFeedItem(context, podcastUrl, episodeUrl, adapter);
+ FeedItem item = getFeedItem(podcastUrl, episodeUrl, adapter);
adapter.close();
return item;
}
@@ -887,21 +744,22 @@ public final class DBReader {
/**
* 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);
+ public static void loadExtraInformationOfFeedItem(final FeedItem item) {
+ Log.d(TAG, "loadExtraInformationOfFeedItem() called with: " + "item = [" + item + "]");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
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);
+ int indexDescription = extraCursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION);
+ String description = extraCursor.getString(indexDescription);
+ int indexContentEncoded = extraCursor.getColumnIndex(PodDBAdapter.KEY_CONTENT_ENCODED);
+ String contentEncoded = extraCursor.getString(indexContentEncoded);
item.setDescription(description);
item.setContentEncoded(contentEncoded);
}
+ extraCursor.close();
adapter.close();
}
@@ -910,31 +768,31 @@ public final class DBReader {
* any chapters that this FeedItem has. If no chapters were found in the database, the chapters
* reference of the FeedItem will be set to null.
*
- * @param context A context that is used for opening a database connection.
* @param item The FeedItem
*/
- public static void loadChaptersOfFeedItem(final Context context, final FeedItem item) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static void loadChaptersOfFeedItem(final FeedItem item) {
+ Log.d(TAG, "loadChaptersOfFeedItem() called with: " + "item = [" + item + "]");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
loadChaptersOfFeedItem(adapter, item);
adapter.close();
}
static void loadChaptersOfFeedItem(PodDBAdapter adapter, FeedItem item) {
- Cursor chapterCursor = adapter
- .getSimpleChaptersOfFeedItemCursor(item);
+ Cursor chapterCursor = adapter.getSimpleChaptersOfFeedItemCursor(item);
if (chapterCursor.moveToFirst()) {
- item.setChapters(new ArrayList<Chapter>());
+ item.setChapters(new ArrayList<>());
do {
- int chapterType = chapterCursor
- .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
+ int indexType = chapterCursor.getColumnIndex(PodDBAdapter.KEY_CHAPTER_TYPE);
+ int indexStart = chapterCursor.getColumnIndex(PodDBAdapter.KEY_START);
+ int indexTitle = chapterCursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
+ int indexLink = chapterCursor.getColumnIndex(PodDBAdapter.KEY_LINK);
+
+ int chapterType = chapterCursor.getInt(indexType);
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);
+ long start = chapterCursor.getLong(indexStart);
+ String title = chapterCursor.getString(indexTitle);
+ String link = chapterCursor.getString(indexLink);
switch (chapterType) {
case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
@@ -951,8 +809,8 @@ public final class DBReader {
break;
}
if (chapter != null) {
- chapter.setId(chapterCursor
- .getLong(PodDBAdapter.KEY_ID_INDEX));
+ int indexId = chapterCursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ chapter.setId(chapterCursor.getLong(indexId));
item.getChapters().add(chapter);
}
} while (chapterCursor.moveToNext());
@@ -965,11 +823,11 @@ public final class DBReader {
/**
* 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);
+ public static int getNumberOfDownloadedEpisodes() {
+ Log.d(TAG, "getNumberOfDownloadedEpisodes() called with: " + "");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final int result = adapter.getNumberOfDownloadedEpisodes();
adapter.close();
@@ -977,29 +835,16 @@ public final class DBReader {
}
/**
- * 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 getNumberOfNewItems(final Context context) {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- final int result = adapter.getNumberOfNewItems();
- adapter.close();
- return result;
- }
-
- /**
- * Returns a map containing the number of unread items per feed
+ * Searches the DB for a FeedImage of the given id.
*
- * @param context A context that is used for opening a database connection.
- * @return The number of unread items per feed.
+ * @param imageId The id of the object
+ * @return The found object
*/
- public static LongIntMap getNumberOfUnreadFeedItems(final Context context, long... feedIds) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static FeedImage getFeedImage(final long imageId) {
+ Log.d(TAG, "getFeedImage() called with: " + "imageId = [" + imageId + "]");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds);
+ FeedImage result = getFeedImage(adapter, imageId);
adapter.close();
return result;
}
@@ -1007,60 +852,58 @@ public final class DBReader {
/**
* 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;
+ private static FeedImage getFeedImage(PodDBAdapter adapter, final long imageId) {
+ return getFeedImages(adapter, imageId).get(imageId);
}
/**
* Searches the DB for a FeedImage of the given id.
*
- * @param id The id of the object
- * @return The found object
+ * @param imageIds The ids of the images
+ * @return Map that associates the id of an image with the image itself
*/
- static FeedImage getFeedImage(PodDBAdapter adapter, final long id) {
- Cursor cursor = adapter.getImageCursor(id);
- if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
- return null;
+ private static Map<Long,FeedImage> getFeedImages(PodDBAdapter adapter, final long... imageIds) {
+ String[] ids = new String[imageIds.length];
+ for(int i=0, len=imageIds.length; i < len; i++) {
+ ids[i] = String.valueOf(imageIds[i]);
}
- 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;
+ Cursor cursor = adapter.getImageCursor(ids);
+ Map<Long, FeedImage> result = new ArrayMap<>(cursor.getCount());
+ try {
+ if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
+ return Collections.emptyMap();
+ }
+ do {
+ FeedImage image = FeedImage.fromCursor(cursor);
+ result.put(image.getId(), image);
+ } while(cursor.moveToNext());
+ } finally {
+ cursor.close();
+ }
+ return result;
}
/**
* 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);
+ public static FeedMedia getFeedMedia(final long mediaId) {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
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);
+ int indexFeedItem = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
+ final long itemId = mediaCursor.getLong(indexFeedItem);
+ media = FeedMedia.fromCursor(mediaCursor);
+ FeedItem item = getFeedItem(itemId);
if (media != null && item != null) {
media.setItem(item);
item.setMedia(media);
@@ -1076,13 +919,13 @@ public final class DBReader {
/**
* 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<FlattrThing> getFlattrQueue(Context context) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ public static List<FlattrThing> getFlattrQueue() {
+ Log.d(TAG, "getFlattrQueue() called with: " + "");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- List<FlattrThing> result = new ArrayList<FlattrThing>();
+ List<FlattrThing> result = new ArrayList<>();
// load feeds
Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor();
@@ -1103,56 +946,78 @@ public final class DBReader {
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);
+ public static NavDrawerData getNavDrawerData() {
+ Log.d(TAG, "getNavDrawerData() called with: " + "");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<Feed> feeds = getFeedList(adapter);
long[] feedIds = new long[feeds.size()];
for(int i=0; i < feeds.size(); i++) {
feedIds[i] = feeds.get(i).getId();
}
- final LongIntMap numUnreadFeedItems = adapter.getNumberOfUnreadFeedItems(feedIds);
- Collections.sort(feeds, new Comparator<Feed>() {
- @Override
- public int compare(Feed lhs, Feed rhs) {
- long numUnreadLhs = numUnreadFeedItems.get(lhs.getId());
- Log.d(TAG, "feed with id " + lhs.getId() + " has " + numUnreadLhs + " unread items");
- long numUnreadRhs = numUnreadFeedItems.get(rhs.getId());
- Log.d(TAG, "feed with id " + rhs.getId() + " has " + numUnreadRhs + " unread items");
- if(numUnreadLhs > numUnreadRhs) {
+ final LongIntMap feedCounters = adapter.getFeedCounters(feedIds);
+
+ Comparator<Feed> comparator;
+ int feedOrder = UserPreferences.getFeedOrder();
+ if(feedOrder == UserPreferences.FEED_ORDER_COUNTER) {
+ comparator = (lhs, rhs) -> {
+ long counterLhs = feedCounters.get(lhs.getId());
+ long counterRhs = feedCounters.get(rhs.getId());
+ if(counterLhs > counterRhs) {
// reverse natural order: podcast with most unplayed episodes first
return -1;
- } else if(numUnreadLhs == numUnreadRhs) {
+ } else if(counterLhs == counterRhs) {
return lhs.getTitle().compareTo(rhs.getTitle());
} else {
return 1;
}
- }
- });
+ };
+ } else if(feedOrder == UserPreferences.FEED_ORDER_ALPHABETICAL) {
+ comparator = (lhs, rhs) -> {
+ String t1 = lhs.getTitle();
+ String t2 = rhs.getTitle();
+ if(t1 == null) {
+ return 1;
+ } else if(t2 == null) {
+ return -1;
+ } else {
+ return t1.toLowerCase().compareTo(t2.toLowerCase());
+ }
+ };
+ } else {
+ comparator = (lhs, rhs) -> {
+ if(lhs.getItems() == null || lhs.getItems().size() == 0) {
+ List<FeedItem> items = DBReader.getFeedItemList(lhs);
+ lhs.setItems(items);
+ }
+ if(rhs.getItems() == null || rhs.getItems().size() == 0) {
+ List<FeedItem> items = DBReader.getFeedItemList(rhs);
+ rhs.setItems(items);
+ }
+ if(lhs.getMostRecentItem() == null) {
+ return 1;
+ } else if(rhs.getMostRecentItem() == null) {
+ return -1;
+ } else {
+ Date d1 = lhs.getMostRecentItem().getPubDate();
+ Date d2 = rhs.getMostRecentItem().getPubDate();
+ return d2.compareTo(d1);
+ }
+ };
+ }
+
+ Collections.sort(feeds, comparator);
int queueSize = adapter.getQueueSize();
int numNewItems = adapter.getNumberOfNewItems();
- NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numUnreadFeedItems);
+ int numDownloadedItems = adapter.getNumberOfDownloadedEpisodes();
+
+ NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numDownloadedItems, feedCounters);
adapter.close();
return result;
}
@@ -1161,14 +1026,19 @@ public final class DBReader {
public List<Feed> feeds;
public int queueSize;
public int numNewItems;
- public LongIntMap numUnreadFeedItems;
-
- public NavDrawerData(List<Feed> feeds, int queueSize, int numNewItems,
- LongIntMap numUnreadFeedItems) {
+ public int numDownloadedItems;
+ public LongIntMap feedCounters;
+
+ public NavDrawerData(List<Feed> feeds,
+ int queueSize,
+ int numNewItems,
+ int numDownloadedItems,
+ LongIntMap feedIndicatorValues) {
this.feeds = feeds;
this.queueSize = queueSize;
this.numNewItems = numNewItems;
- this.numUnreadFeedItems = numUnreadFeedItems;
+ this.numDownloadedItems = numDownloadedItems;
+ this.feedCounters = feedIndicatorValues;
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
index e570ee709..efc60bfc2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -6,7 +6,6 @@ import android.database.Cursor;
import android.util.Log;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
@@ -25,10 +24,9 @@ import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher;
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.preferences.UserPreferences;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
@@ -70,7 +68,7 @@ public final class DBTasks {
* @param downloadUrl URL of the feed.
*/
public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor cursor = adapter.getFeedCursorDownloadUrls();
long feedID = 0;
@@ -165,7 +163,7 @@ public final class DBTasks {
if (feeds != null) {
refreshFeeds(context, feeds);
} else {
- refreshFeeds(context, DBReader.getFeedList(context));
+ refreshFeeds(context, DBReader.getFeedList());
}
isRefreshing.set(false);
@@ -180,6 +178,7 @@ public final class DBTasks {
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
GpodnetSyncService.sendSyncIntent(context);
}
+ Log.d(TAG, "refreshAllFeeds autodownload");
autodownloadUndownloadedItems(context);
}
}.start();
@@ -189,62 +188,29 @@ public final class DBTasks {
}
/**
- * 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<Feed> getExpiredFeeds(final Context context) {
- long millis = UserPreferences.getUpdateInterval();
-
- if (millis > 0) {
-
- List<Feed> feedList = DBReader.getExpiredFeedsList(context,
- millis);
- if (feedList.size() > 0) {
- refreshFeeds(context, feedList);
- }
- return feedList;
- } else {
- return new ArrayList<Feed>();
- }
- }
-
- /**
- * 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.
+ * @param context
+ * @param feedList the list of feeds to refresh
*/
- public static void refreshExpiredFeeds(final Context context) {
- 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<Feed> 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()
- )
- );
+ FeedPreferences prefs = feed.getPreferences();
+ // feeds with !getKeepUpdated can only be refreshed
+ // directly from the FeedActivity
+ if (prefs.getKeepUpdated()) {
+ try {
+ refreshFeed(context, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(
+ new DownloadStatus(feed, feed
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR, false, e
+ .getMessage()
+ )
+ );
+ }
}
}
@@ -262,7 +228,6 @@ public final class DBTasks {
} catch (DownloadRequestException e) {
e.printStackTrace();
DBWriter.addDownloadStatus(
- context,
new DownloadStatus(feed, feed
.getHumanReadableIdentifier(),
DownloadError.ERROR_REQUEST_ERROR, false, e
@@ -302,16 +267,17 @@ public final class DBTasks {
*/
public static void refreshFeed(Context context, Feed feed)
throws DownloadRequestException {
- Log.d(TAG, "id " + feed.getId());
+ Log.d(TAG, "refreshFeed(feed.id: " + feed.getId() +")");
refreshFeed(context, feed, false);
}
private static void refreshFeed(Context context, Feed feed, boolean loadAllPages) throws DownloadRequestException {
Feed f;
+ Date lastUpdate = feed.hasLastUpdateFailed() ? new Date(0) : feed.getLastUpdate();
if (feed.getPreferences() == null) {
- f = new Feed(feed.getDownload_url(), feed.getLastUpdate(), feed.getTitle());
+ f = new Feed(feed.getDownload_url(), lastUpdate, feed.getTitle());
} else {
- f = new Feed(feed.getDownload_url(), feed.getLastUpdate(), feed.getTitle(),
+ f = new Feed(feed.getDownload_url(), lastUpdate, feed.getTitle(),
feed.getPreferences().getUsername(), feed.getPreferences().getPassword());
}
f.setId(feed.getId());
@@ -319,24 +285,6 @@ public final class DBTasks {
}
/**
- * 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.
*/
@@ -346,7 +294,7 @@ public final class DBTasks {
"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);
+ DBWriter.setFeedMedia(media);
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
@@ -358,7 +306,7 @@ public final class DBTasks {
public static void downloadAllItemsInQueue(final Context context) {
new Thread() {
public void run() {
- List<FeedItem> queue = DBReader.getQueue(context);
+ List<FeedItem> queue = DBReader.getQueue();
if (!queue.isEmpty()) {
try {
downloadFeedItems(context,
@@ -393,9 +341,7 @@ public final class DBTasks {
@Override
public void run() {
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
- .performCleanup(context,
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
- .getPerformCleanupParameter(context, Arrays.asList(items)));
+ .makeRoomForEpisodes(context, items.length);
}
}.start();
@@ -409,7 +355,7 @@ public final class DBTasks {
requester.downloadMedia(context, item.getMedia());
} catch (DownloadRequestException e) {
e.printStackTrace();
- DBWriter.addDownloadStatus(context,
+ DBWriter.addDownloadStatus(
new DownloadStatus(item.getMedia(), item
.getMedia()
.getHumanReadableIdentifier(),
@@ -433,13 +379,12 @@ public final class DBTasks {
* 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) {
+ public static Future<?> autodownloadUndownloadedItems(final Context context) {
+ Log.d(TAG, "autodownloadUndownloadedItems");
return autodownloadExec.submit(ClientConfig.dbTasksCallbacks.getAutomaticDownloadAlgorithm()
- .autoDownloadUndownloadedItems(context, mediaIds));
+ .autoDownloadUndownloadedItems(context));
}
@@ -452,24 +397,21 @@ public final class DBTasks {
* @param context Used for accessing the DB.
*/
public static void performAutoCleanup(final Context context) {
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context,
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().getDefaultCleanupParameter(context));
+ ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context);
}
/**
* 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<FeedItem> queue) {
+ public static FeedItem getQueueSuccessorOfItem(final long itemId, List<FeedItem> queue) {
FeedItem result = null;
if (queue == null) {
- queue = DBReader.getQueue(context);
+ queue = DBReader.getQueue();
}
if (queue != null) {
Iterator<FeedItem> iterator = queue.iterator();
@@ -494,19 +436,19 @@ public final class DBTasks {
* @param feedItemId ID of the FeedItem
*/
public static boolean isInQueue(Context context, final long feedItemId) {
- LongList queue = DBReader.getQueueIDList(context);
+ LongList queue = DBReader.getQueueIDList();
return queue.contains(feedItemId);
}
- private static Feed searchFeedByIdentifyingValueOrID(Context context, PodDBAdapter adapter,
+ private static Feed searchFeedByIdentifyingValueOrID(PodDBAdapter adapter,
Feed feed) {
if (feed.getId() != 0) {
- return DBReader.getFeed(context, feed.getId(), adapter);
+ return DBReader.getFeed(feed.getId(), adapter);
} else {
- List<Feed> feeds = DBReader.getFeedList(context);
+ List<Feed> feeds = DBReader.getFeedList();
for (Feed f : feeds) {
if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) {
- f.setItems(DBReader.getFeedItemList(context, f));
+ f.setItems(DBReader.getFeedItemList(f));
return f;
}
}
@@ -545,7 +487,7 @@ public final class DBTasks {
List<Feed> newFeedsList = new ArrayList<Feed>();
List<Feed> updatedFeedsList = new ArrayList<Feed>();
Feed[] resultFeeds = new Feed[newFeeds.length];
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (int feedIdx = 0; feedIdx < newFeeds.length; feedIdx++) {
@@ -553,7 +495,7 @@ public final class DBTasks {
final Feed newFeed = newFeeds[feedIdx];
// Look up feed in the feedslist
- final Feed savedFeed = searchFeedByIdentifyingValueOrID(context, adapter,
+ final Feed savedFeed = searchFeedByIdentifyingValueOrID(adapter,
newFeed);
if (savedFeed == null) {
Log.d(TAG, "Found no existing Feed with title "
@@ -563,7 +505,7 @@ public final class DBTasks {
// all new feeds will have the most recent item marked as unplayed
FeedItem mostRecent = newFeed.getMostRecentItem();
if (mostRecent != null) {
- mostRecent.setRead(false);
+ mostRecent.setNew();
}
newFeedsList.add(newFeed);
@@ -574,22 +516,27 @@ public final class DBTasks {
Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
- final boolean markNewItemsAsUnread;
if (newFeed.getPageNr() == savedFeed.getPageNr()) {
if (savedFeed.compareWithOther(newFeed)) {
Log.d(TAG, "Feed has updated attribute values. Updating old feed's attributes");
savedFeed.updateFromOther(newFeed);
}
- markNewItemsAsUnread = true;
} else {
- Log.d(TAG, "New feed has a higher page number. Merging without marking as unread");
- markNewItemsAsUnread = false;
+ Log.d(TAG, "New feed has a higher page number.");
savedFeed.setNextPageLink(newFeed.getNextPageLink());
}
if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) {
Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences");
savedFeed.getPreferences().updateFromOther(newFeed.getPreferences());
}
+
+ // get the most recent date now, before we start changing the list
+ FeedItem priorMostRecent = savedFeed.getMostRecentItem();
+ Date priorMostRecentDate = null;
+ if (priorMostRecent != null) {
+ priorMostRecentDate = priorMostRecent.getPubDate();
+ }
+
// Look for new or updated Items
for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
final FeedItem item = newFeed.getItems().get(idx);
@@ -597,12 +544,19 @@ public final class DBTasks {
item.getIdentifyingValue());
if (oldItem == null) {
// item is new
- final int i = idx;
item.setFeed(savedFeed);
item.setAutoDownload(savedFeed.getPreferences().getAutoDownload());
- savedFeed.getItems().add(i, item);
- if (markNewItemsAsUnread) {
- item.setRead(false);
+ savedFeed.getItems().add(idx, item);
+
+ // only mark the item new if it actually occurs
+ // before the most recent item (before we started adding things)
+ // (if the most recent date is null then we can assume there are no items
+ // and this is the first, hence 'new')
+ if (priorMostRecentDate == null ||
+ priorMostRecentDate.before(item.getPubDate())) {
+ Log.d(TAG, "Marking item published on " + item.getPubDate() +
+ " new, prior most recent date = " + priorMostRecentDate);
+ item.setNew();
}
} else {
oldItem.updateFromOther(item);
@@ -622,7 +576,7 @@ public final class DBTasks {
try {
DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[newFeedsList.size()])).get();
- DBWriter.setCompleteFeed(context, updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get();
+ DBWriter.setCompleteFeed(updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
@@ -650,8 +604,8 @@ public final class DBTasks {
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemTitles(feedID,
query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
- DBReader.loadFeedDataOfFeedItemlist(context, items);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
+ DBReader.loadAdditionalFeedItemListData(items);
setResult(items);
searchResult.close();
}
@@ -674,8 +628,8 @@ public final class DBTasks {
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemDescriptions(feedID,
query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
- DBReader.loadFeedDataOfFeedItemlist(context, items);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
+ DBReader.loadAdditionalFeedItemListData(items);
setResult(items);
searchResult.close();
}
@@ -698,8 +652,8 @@ public final class DBTasks {
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemContentEncoded(feedID,
query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
- DBReader.loadFeedDataOfFeedItemlist(context, items);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
+ DBReader.loadAdditionalFeedItemListData(items);
setResult(items);
searchResult.close();
}
@@ -721,8 +675,8 @@ public final class DBTasks {
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemChapters(feedID,
query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
- DBReader.loadFeedDataOfFeedItemlist(context, items);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
+ DBReader.loadAdditionalFeedItemListData(items);
setResult(items);
searchResult.close();
}
@@ -745,7 +699,7 @@ public final class DBTasks {
@Override
public T call() throws Exception {
- PodDBAdapter adapter = new PodDBAdapter(context);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
execute(adapter);
adapter.close();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index fe5d0dfd3..e728abc3b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -13,20 +13,22 @@ import org.shredzone.flattr4j.model.Flattr;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import java.util.concurrent.ThreadFactory;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
+import de.danoeh.antennapod.core.event.FavoritesEvent;
+import de.danoeh.antennapod.core.event.FeedItemEvent;
+import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
@@ -34,7 +36,6 @@ 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.feed.QueueEvent;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
@@ -56,19 +57,16 @@ import de.greenrobot.event.EventBus;
* 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;
- }
+ dbExec = Executors.newSingleThreadExecutor(r -> {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
});
}
@@ -83,62 +81,59 @@ public class DBWriter {
*/
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));
- }
+ return dbExec.submit(() -> {
+ final FeedMedia media = DBReader.getFeedMedia(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);
+ media.setHasEmbeddedPicture(false);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ 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();
}
- // Gpodder: queue delete action for synchronization
- if(GpodnetPreferences.loggedIn()) {
- FeedItem item = media.getItem();
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
- .currentDeviceId()
- .currentTimestamp()
- .build();
- GpodnetPreferences.enqueueEpisodeAction(action);
+ if (PlaybackPreferences
+ .getCurrentlyPlayingFeedMediaId() == media
+ .getId()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
}
}
- Log.d(TAG, "Deleting File. Result: " + result);
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.DELETED_MEDIA, media.getItem()));
- EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ // Gpodder: queue delete action for synchronization
+ if(GpodnetPreferences.loggedIn()) {
+ FeedItem item = media.getItem();
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
+ .currentDeviceId()
+ .currentTimestamp()
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
}
+ Log.d(TAG, "Deleting File. Result: " + result);
+ EventBus.getDefault().post(FeedItemEvent.deletedMedia(Arrays.asList(media.getItem())));
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
}
@@ -150,83 +145,91 @@ public class DBWriter {
* @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();
+ return dbExec.submit(() -> {
+ DownloadRequester requester = DownloadRequester.getInstance();
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context
+ .getApplicationContext());
+ final Feed feed = DBReader.getFeed(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<FeedItem> queue = DBReader.getQueue();
+ List<FeedItem> removed = new ArrayList<>();
+ if (feed.getItems() == null) {
+ DBReader.getFeedItemList(feed);
+ }
- // 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());
- }
+ for (FeedItem item : feed.getItems()) {
+ if(queue.remove(item)) {
+ removed.add(item);
}
- // delete stored media files and mark them as read
- List<FeedItem> queue = DBReader.getQueue(context);
- boolean queueWasModified = false;
- if (feed.getItems() == null) {
- DBReader.getFeedItemList(context, feed);
+ 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());
}
- 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());
}
-
- 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();
-
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
+ }
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ if (removed.size() > 0) {
+ adapter.setQueue(queue);
+ for(FeedItem item : removed) {
+ EventBus.getDefault().post(QueueEvent.irreversibleRemoved(item));
}
- EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ adapter.removeFeed(feed);
+ adapter.close();
- BackupManager backupManager = new BackupManager(context);
- backupManager.dataChanged();
+ if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
+ GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
}
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+
+ // we assume we also removed download log entries for the feed or its media files.
+ // especially important if download or refresh failed, as the user should not be able
+ // to retry these
+ EventDistributor.getInstance().sendDownloadLogUpdateBroadcast();
+
+ BackupManager backupManager = new BackupManager(context);
+ backupManager.dataChanged();
}
});
}
@@ -234,39 +237,27 @@ public class DBWriter {
/**
* 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();
- }
+ public static Future<?> clearPlaybackHistory() {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.clearPlaybackHistory();
+ adapter.close();
+ EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast();
});
}
/**
* Deletes the entire download log.
- *
- * @param context A context that is used for opening a database connection.
*/
- public static Future<?> clearDownloadLog(final Context context) {
- return dbExec.submit(new Runnable() {
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.clearDownloadLog();
- adapter.close();
- EventDistributor.getInstance()
- .sendDownloadLogUpdateBroadcast();
- }
+ public static Future<?> clearDownloadLog() {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.clearDownloadLog();
+ adapter.close();
+ EventDistributor.getInstance().sendDownloadLogUpdateBroadcast();
});
}
@@ -276,58 +267,36 @@ public class DBWriter {
* 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();
+ public static Future<?> addItemToPlaybackHistory(final FeedMedia media) {
+ return dbExec.submit(() -> {
+ 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 = PodDBAdapter.getInstance();
+ 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();
- }
+ public static Future<?> addDownloadStatus(final DownloadStatus status) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setDownloadStatus(status);
+ adapter.close();
+ EventDistributor.getInstance().sendDownloadLogUpdateBroadcast();
});
}
@@ -344,108 +313,118 @@ public class DBWriter {
*/
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<FeedItem> queue = DBReader.getQueue(context, adapter);
- FeedItem item = null;
-
- if (queue != null) {
- if (!itemListContains(queue, itemId)) {
- item = DBReader.getFeedItem(context, itemId);
- if (item != null) {
- queue.add(index, item);
- adapter.setQueue(queue);
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
+ FeedItem item;
+
+ if (queue != null) {
+ if (!itemListContains(queue, itemId)) {
+ item = DBReader.getFeedItem(itemId);
+ if (item != null) {
+ queue.add(index, item);
+ adapter.setQueue(queue);
+ item.addTag(FeedItem.TAG_QUEUE);
+ EventBus.getDefault().post(QueueEvent.added(item, index));
+ if (item.isNew()) {
+ DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
}
}
}
+ }
- adapter.close();
- if (performAutoDownload) {
- DBTasks.autodownloadUndownloadedItems(context);
- }
-
+ adapter.close();
+ if (performAutoDownload) {
+ DBTasks.autodownloadUndownloadedItems(context);
}
+
});
}
+ public static Future<?> addQueueItem(final Context context,
+ final FeedItem... items) {
+ LongList itemIds = new LongList(items.length);
+ for (FeedItem item : items) {
+ itemIds.add(item.getId());
+ item.addTag(FeedItem.TAG_QUEUE);
+ }
+ return addQueueItem(context, false, itemIds.toArray());
+ }
+
/**
* 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 performAutoDownload true if an auto-download process should be started after the operation.
* @param itemIds IDs of the FeedItem objects that should be added to the queue.
*/
- public static Future<?> addQueueItem(final Context context,
+ public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
final long... itemIds) {
- return dbExec.submit(new Runnable() {
+ return dbExec.submit(() -> {
+ if (itemIds.length > 0) {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
- @Override
- public void run() {
- if (itemIds.length > 0) {
- final PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(context,
- adapter);
-
- if (queue != null) {
- boolean queueModified = false;
- boolean unreadItemsModified = false;
- List<FeedItem> itemsToSave = new LinkedList<FeedItem>();
- for (int i = 0; i < itemIds.length; i++) {
- if (!itemListContains(queue, itemIds[i])) {
- final FeedItem item = DBReader.getFeedItem(
- context, itemIds[i]);
-
- if (item != null) {
- // add item to either front ot back of queue
- boolean addToFront = UserPreferences.enqueueAtFront();
-
- if(addToFront){
- queue.add(0, item);
- } else {
- queue.add(item);
- }
-
- queueModified = true;
+ if (queue != null) {
+ boolean queueModified = false;
+ LongList markAsUnplayedIds = new LongList();
+ List<QueueEvent> events = new ArrayList<QueueEvent>();
+ for (int i = 0; i < itemIds.length; i++) {
+ if (!itemListContains(queue, itemIds[i])) {
+ final FeedItem item = DBReader.getFeedItem(itemIds[i]);
+
+
+ if (item != null) {
+ // add item to either front ot back of queue
+ boolean addToFront = UserPreferences.enqueueAtFront();
+ if (addToFront) {
+ queue.add(0 + i, item);
+ events.add(QueueEvent.added(item, 0 + i));
+ } else {
+ queue.add(item);
+ events.add(QueueEvent.added(item, queue.size() - 1));
+ }
+ queueModified = true;
+ if (item.isNew()) {
+ markAsUnplayedIds.add(item.getId());
}
}
}
- if (queueModified) {
- adapter.setQueue(queue);
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ for (QueueEvent event : events) {
+ EventBus.getDefault().post(event);
+ }
+ if (markAsUnplayedIds.size() > 0) {
+ DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
}
}
- adapter.close();
+ }
+ adapter.close();
+ if (performAutoDownload) {
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();
-
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.CLEARED));
- }
+ public static Future<?> clearQueue() {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.clearQueue();
+ adapter.close();
+
+ EventBus.getDefault().post(QueueEvent.cleared());
});
}
@@ -458,79 +437,99 @@ public class DBWriter {
*/
public static Future<?> removeQueueItem(final Context context,
final FeedItem item, final boolean performAutoDownload) {
- return dbExec.submit(new Runnable() {
-
- @Override
- public void run() {
- final PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(context, adapter);
-
- if (queue != null) {
- int position = queue.indexOf(item);
- if(position >= 0) {
- queue.remove(position);
- adapter.setQueue(queue);
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.REMOVED, item, position));
- } else {
- Log.w(TAG, "Queue was not modified by call to removeQueueItem");
- }
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
+
+ if (queue != null) {
+ int position = queue.indexOf(item);
+ if (position >= 0) {
+ queue.remove(position);
+ adapter.setQueue(queue);
+ item.removeTag(FeedItem.TAG_QUEUE);
+ EventBus.getDefault().post(QueueEvent.removed(item));
} else {
- Log.e(TAG, "removeQueueItem: Could not load queue");
- }
- adapter.close();
- if (performAutoDownload) {
- DBTasks.autodownloadUndownloadedItems(context);
+ 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);
+ }
+ });
+
+ }
+
+ public static Future<?> addFavoriteItem(final FeedItem item) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
+ adapter.addFavoriteItem(item);
+ adapter.close();
+ item.addTag(FeedItem.TAG_FAVORITE);
+ EventBus.getDefault().post(FavoritesEvent.added(item));
+ });
+ }
+
+ public static Future<?> addFavoriteItemById(final long itemId) {
+ return dbExec.submit(() -> {
+ final FeedItem item = DBReader.getFeedItem(itemId);
+ if (item == null) {
+ Log.d(TAG, "Can't find item for itemId " + itemId);
+ return;
}
+ final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
+ adapter.addFavoriteItem(item);
+ adapter.close();
+ item.addTag(FeedItem.TAG_FAVORITE);
+ EventBus.getDefault().post(FavoritesEvent.added(item));
});
+ }
+ public static Future<?> removeFavoriteItem(final FeedItem item) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
+ adapter.removeFavoriteItem(item);
+ adapter.close();
+ item.removeTag(FeedItem.TAG_FAVORITE);
+ EventBus.getDefault().post(FavoritesEvent.removed(item));
+ });
}
/**
* 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 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() {
- LongList queueIdList = DBReader.getQueueIDList(context);
- int index = queueIdList.indexOf(itemId);
- if (index >=0) {
- moveQueueItemHelper(context, index, 0, broadcastUpdate);
- } else {
- Log.e(TAG, "moveQueueItemToTop: item not found");
- }
+ public static Future<?> moveQueueItemToTop(final long itemId, final boolean broadcastUpdate) {
+ return dbExec.submit(() -> {
+ LongList queueIdList = DBReader.getQueueIDList();
+ int index = queueIdList.indexOf(itemId);
+ if (index >=0) {
+ moveQueueItemHelper(index, 0, broadcastUpdate);
+ } else {
+ 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 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,
+ public static Future<?> moveQueueItemToBottom(final long itemId,
final boolean broadcastUpdate) {
- return dbExec.submit(new Runnable() {
- @Override
- public void run() {
- LongList queueIdList = DBReader.getQueueIDList(context);
- int index = queueIdList.indexOf(itemId);
- if(index >= 0) {
- moveQueueItemHelper(context, index, queueIdList.size() - 1,
- broadcastUpdate);
- } else {
- Log.e(TAG, "moveQueueItemToBottom: item not found");
- }
+ return dbExec.submit(() -> {
+ LongList queueIdList = DBReader.getQueueIDList();
+ int index = queueIdList.indexOf(itemId);
+ if (index >= 0) {
+ moveQueueItemHelper(index, queueIdList.size() - 1,
+ broadcastUpdate);
+ } else {
+ Log.e(TAG, "moveQueueItemToBottom: item not found");
}
});
}
@@ -538,21 +537,16 @@ public class DBWriter {
/**
* 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,
+ public static Future<?> moveQueueItem(final int from,
final int to, final boolean broadcastUpdate) {
- return dbExec.submit(new Runnable() {
-
- @Override
- public void run() {
- moveQueueItemHelper(context, from, to, broadcastUpdate);
- }
+ return dbExec.submit(() -> {
+ moveQueueItemHelper(from, to, broadcastUpdate);
});
}
@@ -561,32 +555,27 @@ public class DBWriter {
* <p/>
* 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,
+ private static void moveQueueItemHelper(final int from,
final int to, final boolean broadcastUpdate) {
- final PodDBAdapter adapter = new PodDBAdapter(context);
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- final List<FeedItem> queue = DBReader
- .getQueue(context, adapter);
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
if (queue != null) {
- if (from >= 0 && from < queue.size() && to >= 0
- && to < queue.size()) {
-
+ 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) {
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.MOVED, item, to));
+ EventBus.getDefault().post(QueueEvent.moved(item, to));
}
-
}
} else {
Log.e(TAG, "moveQueueItemHelper: Could not load queue");
@@ -594,183 +583,178 @@ public class DBWriter {
adapter.close();
}
- /**
- * Sets the 'read'-attribute of a FeedItem to the specified value.
+ /*
+ * Sets the 'read'-attribute of all specified FeedItems
*
* @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
+ * @param played New value of the 'read'-attribute, one of FeedItem.PLAYED, FeedItem.NEW,
+ * FeedItem.UNPLAYED
+ * @param itemIds IDs of the FeedItems.
*/
- public static Future<?> markItemRead(final Context context, final long itemId,
- final boolean read) {
- return markItemRead(context, itemId, read, 0, false);
+ public static Future<?> markItemPlayed(final int played, final long... itemIds) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedItemRead(played, itemIds);
+ adapter.close();
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ });
}
/**
* 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 item The FeedItem object
+ * @param played New value of the 'read'-attribute one of FeedItem.PLAYED,
+ * FeedItem.NEW, FeedItem.UNPLAYED
* @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) {
+ public static Future<?> markItemPlayed(FeedItem item, int played, boolean resetMediaPosition) {
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0;
- return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition);
+ return markItemPlayed(item.getId(), played, mediaId, resetMediaPosition);
}
- 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();
- }
+ private static Future<?> markItemPlayed(final long itemId,
+ final int played,
+ final long mediaId,
+ final boolean resetMediaPosition) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedItemRead(played, 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();
+ public static Future<?> markFeedSeen(final long feedId) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ Cursor itemCursor = adapter.getNewItemsIdsCursor(feedId);
+ long[] ids = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = itemCursor.getLong(0);
+ itemCursor.moveToNext();
}
- });
+ itemCursor.close();
+ adapter.setFeedItemRead(FeedItem.UNPLAYED, ids);
+ adapter.close();
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ });
}
/**
- * Sets the 'read'-attribute of all FeedItems to true.
+ * 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<?> markAllItemsRead(final Context context) {
- return dbExec.submit(new Runnable() {
+ public static Future<?> markFeedRead(final long feedId) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId);
+ long[] itemIds = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < itemIds.length; i++) {
+ int indexId = itemCursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ itemIds[i] = itemCursor.getLong(indexId);
+ itemCursor.moveToNext();
+ }
+ itemCursor.close();
+ adapter.setFeedItemRead(FeedItem.PLAYED, itemIds);
+ adapter.close();
- @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();
+ });
+ }
- EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ /**
+ * Sets the 'read'-attribute of all FeedItems to true.
+ */
+ public static Future<?> markAllItemsRead() {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ Cursor itemCursor = adapter.getUnreadItemsCursor();
+ long[] itemIds = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < itemIds.length; i++) {
+ int indexId = itemCursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ itemIds[i] = itemCursor.getLong(indexId);
+ itemCursor.moveToNext();
}
+ itemCursor.close();
+ adapter.setFeedItemRead(FeedItem.PLAYED, 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();
-
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- for (Feed feed : feeds) {
- GpodnetPreferences.addAddedFeed(feed.getDownload_url());
- }
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feeds);
+ adapter.close();
+
+ if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
+ for (Feed feed : feeds) {
+ GpodnetPreferences.addAddedFeed(feed.getDownload_url());
}
-
- BackupManager backupManager = new BackupManager(context);
- backupManager.dataChanged();
}
+
+ 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();
-
- }
+ static Future<?> setCompleteFeed(final Feed... feeds) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ 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();
- }
+ public static Future<?> setFeedMedia(final FeedMedia media) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
});
}
/**
- * Saves the 'position' and 'duration' attributes of a FeedMedia object
+ * Saves the 'position', 'duration' and 'last played time' 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();
- }
+ public static Future<?> setFeedMediaPlaybackInformation(final FeedMedia media) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedMediaPlaybackInformation(media);
+ adapter.close();
});
}
@@ -778,20 +762,15 @@ public class DBWriter {
* 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();
- }
+ public static Future<?> setFeedItem(final FeedItem item) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setSingleFeedItem(item);
+ adapter.close();
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
});
}
@@ -799,60 +778,42 @@ public class DBWriter {
* 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();
- }
+ public static Future<?> setFeedImage(final FeedImage image) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ 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
+ * Updates download URL of a feed
*/
- public static Future<?> updateFeedDownloadURLs(final Context context, final Map<String, String> 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();
- }
+ public static Future<?> updateFeedDownloadURL(final String original, final String updated) {
+ Log.d(TAG, "updateFeedDownloadURL(original: " + original + ", updated: " + updated +")");
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedDownloadUrl(original, updated);
+ 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();
- }
+ public static Future<?> setFeedPreferences(final FeedPreferences preferences) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedPreferences(preferences);
+ adapter.close();
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
});
}
@@ -873,17 +834,13 @@ public class DBWriter {
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();
- }
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedItemFlattrStatus(item);
+ adapter.close();
+ if (startFlattrClickWorker) {
+ new FlattrClickWorker(context).executeAsync();
}
});
}
@@ -896,17 +853,13 @@ public class DBWriter {
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();
- }
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedFlattrStatus(feed);
+ adapter.close();
+ if (startFlattrClickWorker) {
+ new FlattrClickWorker(context).executeAsync();
}
});
}
@@ -916,18 +869,13 @@ public class DBWriter {
*
* @param lastUpdateFailed true if last update failed
*/
- public static Future<?> setFeedLastUpdateFailed(final Context context,
- final long feedId,
- final boolean lastUpdateFailed) {
- return dbExec.submit(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
- adapter.close();
- }
+ public static Future<?> setFeedLastUpdateFailed(final long feedId,
+ final boolean lastUpdateFailed) {
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
+ adapter.close();
});
}
@@ -955,14 +903,15 @@ public class DBWriter {
*/
public static Future<?> setFlattredStatus(Context context, FlattrThing thing, boolean startFlattrClickWorker) {
// must propagate this to back db
- if (thing instanceof FeedItem)
+ if (thing instanceof FeedItem) {
return setFeedItemFlattrStatus(context, (FeedItem) thing, startFlattrClickWorker);
- else if (thing instanceof Feed)
+ } 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
+ } 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;
}
@@ -970,16 +919,13 @@ public class DBWriter {
/**
* Reset flattr status to unflattrd for all items
*/
- public static Future<?> clearAllFlattrStatus(final Context context) {
+ public static Future<?> clearAllFlattrStatus() {
Log.d(TAG, "clearAllFlattrStatus()");
- return dbExec.submit(new Runnable() {
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.clearAllFlattrStatus();
- adapter.close();
- }
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.clearAllFlattrStatus();
+ adapter.close();
});
}
@@ -987,99 +933,115 @@ public class DBWriter {
* 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<Flattr> flattrList) {
+ public static Future<?> setFlattredStatus(final List<Flattr> flattrList) {
Log.d(TAG, "setFlattredStatus to status retrieved from flattr api running with " + flattrList.size() + " items");
// clear flattr status in db
- clearAllFlattrStatus(context);
+ clearAllFlattrStatus();
// 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();
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ for (Flattr flattr : flattrList) {
+ adapter.setItemFlattrStatus(formatURIForQuery(flattr.getThing().getUrl()), new FlattrStatus(flattr.getCreated().getTime()));
}
+ adapter.close();
});
}
/**
* Sort the FeedItems in the queue with the given Comparator.
- *
- * @param context A context that is used for opening a database connection.
* @param comparator FeedItem comparator
* @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<?> sortQueue(final Context context, final Comparator<FeedItem> comparator, final boolean broadcastUpdate) {
- return dbExec.submit(new Runnable() {
- @Override
- public void run() {
- final PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(context, adapter);
-
- if (queue != null) {
- Collections.sort(queue, comparator);
- adapter.setQueue(queue);
- if (broadcastUpdate) {
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.SORTED));
- }
- } else {
- Log.e(TAG, "sortQueue: Could not load queue");
+ public static Future<?> sortQueue(final Comparator<FeedItem> comparator, final boolean broadcastUpdate) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
+
+ if (queue != null) {
+ Collections.sort(queue, comparator);
+ adapter.setQueue(queue);
+ if (broadcastUpdate) {
+ EventBus.getDefault().post(QueueEvent.sorted(queue));
}
- adapter.close();
+ } else {
+ Log.e(TAG, "sortQueue: Could not load queue");
}
+ adapter.close();
});
}
/**
* Sets the 'auto_download'-attribute of specific FeedItem.
*
- * @param context A context that is used for opening a database connection.
* @param feedItem FeedItem.
+ * @param autoDownload true enables auto download, false disables it
*/
- public static Future<?> setFeedItemAutoDownload(final Context context, final FeedItem feedItem,
+ public static Future<?> setFeedItemAutoDownload(final FeedItem feedItem,
final boolean autoDownload) {
- Log.d(TAG, "FeedItem[id=" + feedItem.getId() + "] SET auto_download " + autoDownload);
- return dbExec.submit(new Runnable() {
-
- @Override
- public void run() {
- final PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setFeedItemAutoDownload(feedItem, autoDownload);
- adapter.close();
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedItemAutoDownload(feedItem, autoDownload ? 1 : 0);
+ adapter.close();
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ });
+ }
- EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ public static Future<?> saveFeedItemAutoDownloadFailed(final FeedItem feedItem) {
+ return dbExec.submit(() -> {
+ int failedAttempts = feedItem.getFailedAutoDownloadAttempts() + 1;
+ long autoDownload;
+ if(!feedItem.getAutoDownload() || failedAttempts >= 10) {
+ autoDownload = 0; // giving up, disable auto download
+ feedItem.setAutoDownload(false);
+ } else {
+ long now = System.currentTimeMillis();
+ autoDownload = (now / 10) * 10 + failedAttempts;
}
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedItemAutoDownload(feedItem, autoDownload);
+ adapter.close();
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
});
+ }
+ /**
+ * Sets the 'auto_download'-attribute of specific FeedItem.
+ *
+ * @param feed This feed's episodes will be processed.
+ * @param autoDownload If true, auto download will be enabled for the feed's episodes. Else,
+ */
+ public static Future<?> setFeedsItemsAutoDownload(final Feed feed,
+ final boolean autoDownload) {
+ Log.d(TAG, (autoDownload ? "Enabling" : "Disabling") + " auto download for items of feed " + feed.getId());
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedsItemsAutoDownload(feed, autoDownload);
+ adapter.close();
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ });
}
+
/**
* Set filter of the feed
- *
- * @param context Used for opening a database connection.
- * @param feedId The feed's ID
+ * @param feedId The feed's ID
* @param filterValues Values that represent properties to filter by
*/
- public static Future<?> setFeedItemsFilter(final Context context, final long feedId,
- final List<String> filterValues) {
- Log.d(TAG, "setFeedFilter");
-
- return dbExec.submit(new Runnable() {
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setFeedItemFilter(feedId, filterValues);
- adapter.close();
- EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId));
- }
+ public static Future<?> setFeedItemsFilter(final long feedId,
+ final Set<String> filterValues) {
+ Log.d(TAG, "setFeedItemsFilter() called with: " + "feedId = [" + feedId + "], filterValues = [" + filterValues + "]");
+ return dbExec.submit(() -> {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setFeedItemFilter(feedId, filterValues);
+ adapter.close();
+ EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId));
});
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
index ca6aa0178..0dc1dadeb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
@@ -3,22 +3,20 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
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;
import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedFile;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
@@ -73,10 +71,8 @@ public class DownloadRequester {
* call will return false.
* @return True if the download request was accepted, false otherwise.
*/
- public synchronized boolean download(Context context, DownloadRequest request) {
- Validate.notNull(context);
- Validate.notNull(request);
-
+ public synchronized boolean download(@NonNull Context context,
+ @NonNull DownloadRequest request) {
if (downloads.containsKey(request.getSource())) {
if (BuildConfig.DEBUG) Log.i(TAG, "DownloadRequest is already stored.");
return false;
@@ -86,7 +82,7 @@ public class DownloadRequester {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
context.startService(launchIntent);
- EventDistributor.getInstance().sendDownloadQueuedBroadcast();
+
return true;
}
@@ -147,7 +143,7 @@ public class DownloadRequester {
private boolean isFilenameAvailable(String path) {
for (String key : downloads.keySet()) {
DownloadRequest r = downloads.get(key);
- if (StringUtils.equals(r.getDestination(), path)) {
+ if (TextUtils.equals(r.getDestination(), path)) {
if (BuildConfig.DEBUG)
Log.d(TAG, path
+ " is already used by another requested download");
@@ -171,7 +167,7 @@ public class DownloadRequester {
if (feedFileValid(feed)) {
String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null;
String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null;
- long ifModifiedSince = feed.getLastUpdate().getTime();
+ long ifModifiedSince = feed.isPaged() ? 0 : feed.getLastUpdate().getTime();
Bundle args = new Bundle();
args.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr());
@@ -186,15 +182,6 @@ public class DownloadRequester {
downloadFeed(context, feed, false);
}
- public synchronized void downloadImage(Context context, FeedImage image)
- throws DownloadRequestException {
- if (feedFileValid(image)) {
- FeedFile container = (image.getOwner() instanceof FeedFile) ? (FeedFile) image.getOwner() : null;
- download(context, image, container, new File(getImagefilePath(context),
- getImagefileName(image)), false, null, null, 0, false, null);
- }
- }
-
public synchronized void downloadMedia(Context context, FeedMedia feedmedia)
throws DownloadRequestException {
if (feedFileValid(feedmedia)) {
@@ -332,20 +319,6 @@ public class DownloadRequester {
return "feed-" + FileNameGenerator.generateFileName(filename);
}
- public synchronized String getImagefilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
- .toString() + "/";
- }
-
- public synchronized 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 synchronized String getMediafilePath(Context context, FeedMedia media)
throws DownloadRequestException {
File externalStorage = getExternalFilesDirOrThrowException(
@@ -359,7 +332,7 @@ public class DownloadRequester {
private File getExternalFilesDirOrThrowException(Context context,
String type) throws DownloadRequestException {
- File result = UserPreferences.getDataFolder(context, type);
+ File result = UserPreferences.getDataFolder(type);
if (result == null) {
throw new DownloadRequestException(
"Failed to access external storage");
@@ -375,7 +348,7 @@ public class DownloadRequester {
if (media.getItem() != null && media.getItem().getTitle() != null) {
String title = media.getItem().getTitle();
// Delete reserved characters
- titleBaseFilename = title.replaceAll("[\\\\/%\\?\\*:|<>\"\\p{Cntrl}]", "");
+ titleBaseFilename = title.replaceAll("[^a-zA-Z0-9 ._()-]", "");
titleBaseFilename = titleBaseFilename.trim();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
index 6a8b4a441..0f402745c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
@@ -2,35 +2,60 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
-import java.util.List;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
-public interface EpisodeCleanupAlgorithm<T> {
+public abstract class EpisodeCleanupAlgorithm {
/**
* Deletes downloaded episodes that are no longer needed. What episodes are deleted and how many
* of them depends on the implementation.
*
- * @param context Can be used for accessing the database
- * @param parameter An additional parameter. This parameter is either returned by getDefaultCleanupParameter
- * or getPerformCleanupParameter.
+ * @param context Can be used for accessing the database
+ * @param numToRemove An additional parameter. This parameter is either returned by getDefaultCleanupParameter
+ * or getPerformCleanupParameter.
* @return The number of episodes that were deleted.
*/
- public int performCleanup(Context context, T parameter);
+ public abstract int performCleanup(Context context, int numToRemove);
+
+ public int performCleanup(Context context) {
+ return performCleanup(context, getDefaultCleanupParameter());
+ }
/**
* Returns a parameter for performCleanup. The implementation of this interface should decide how much
* space to free to satisfy the episode cache conditions. If the conditions are already satisfied, this
* method should not have any effects.
*/
- public T getDefaultCleanupParameter(Context context);
+ public abstract int getDefaultCleanupParameter();
/**
- * Returns a parameter for performCleanup.
+ * Cleans up just enough episodes to make room for the requested number
*
- * @param items A list of FeedItems that are about to be downloaded. The implementation of this interface
- * should decide how much space to free to satisfy the episode cache conditions.
+ * @param context Can be used for accessing the database
+ * @param amountOfRoomNeeded the number of episodes we need space for
+ * @return The number of epiosdes that were deleted
+ */
+ public int makeRoomForEpisodes(Context context, int amountOfRoomNeeded) {
+ return performCleanup(context, getNumEpisodesToCleanup(amountOfRoomNeeded));
+ }
+
+ /**
+ * @param amountOfRoomNeeded the number of episodes we want to download
+ * @return the number of episodes to delete in order to make room
*/
- public T getPerformCleanupParameter(Context context, List<FeedItem> items);
+ protected int getNumEpisodesToCleanup(final int amountOfRoomNeeded) {
+ if (amountOfRoomNeeded >= 0
+ && UserPreferences.getEpisodeCacheSize() != UserPreferences
+ .getEpisodeCacheSizeUnlimited()) {
+ int downloadedEpisodes = DBReader
+ .getNumberOfDownloadedEpisodes();
+ if (downloadedEpisodes + amountOfRoomNeeded >= UserPreferences
+ .getEpisodeCacheSize()) {
+
+ return downloadedEpisodes + amountOfRoomNeeded
+ - UserPreferences.getEpisodeCacheSize();
+ }
+ }
+ return 0;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java
index f6a59836b..09949b87e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java
@@ -1,5 +1,7 @@
package de.danoeh.antennapod.core.storage;
+import android.database.Cursor;
+
import java.util.Date;
/**
@@ -36,6 +38,15 @@ public class FeedItemStatistics {
}
}
+ public static FeedItemStatistics fromCursor(Cursor cursor) {
+ return new FeedItemStatistics(
+ cursor.getLong(0),
+ cursor.getInt(1),
+ cursor.getInt(2),
+ cursor.getInt(4),
+ new Date(cursor.getLong(3)));
+ }
+
public long getFeedID() {
return feedID;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
index 4780098e0..85ff8fc8c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
@@ -9,16 +9,16 @@ import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
+import android.media.MediaMetadataRetriever;
+import android.text.TextUtils;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Validate;
-
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.event.ProgressEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedComponent;
@@ -26,11 +26,11 @@ 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.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
-
-;
+import de.greenrobot.event.EventBus;
// TODO Remove media column from feeditem table
@@ -38,6 +38,7 @@ import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
* Implements methods for accessing the database
*/
public class PodDBAdapter {
+
private static final String TAG = "PodDBAdapter";
public static final String DATABASE_NAME = "Antennapod.db";
@@ -51,63 +52,6 @@ public class PodDBAdapter {
*/
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;
- public static final int KEY_IS_PAGED_INDEX = 17;
- public static final int KEY_LOAD_ALL_PAGES_INDEX = 18;
- public static final int KEY_NEXT_PAGE_LINK_INDEX = 19;
- // ----------- 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";
@@ -148,6 +92,8 @@ public class PodDBAdapter {
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_KEEP_UPDATED = "keep_updated";
+ public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action";
public static final String KEY_PLAYED_DURATION = "played_duration";
public static final String KEY_USERNAME = "username";
public static final String KEY_PASSWORD = "password";
@@ -155,6 +101,10 @@ public class PodDBAdapter {
public static final String KEY_NEXT_PAGE_LINK = "next_page_link";
public static final String KEY_HIDE = "hide";
public static final String KEY_LAST_UPDATE_FAILED = "last_update_failed";
+ public static final String KEY_HAS_EMBEDDED_PICTURE = "has_embedded_picture";
+ public static final String KEY_LAST_PLAYED_TIME = "last_played_time";
+ public static final String KEY_INCLUDE_FILTER = "include_filter";
+ public static final String KEY_EXCLUDE_FILTER = "exclude_filter";
// Table names
public static final String TABLE_NAME_FEEDS = "Feeds";
@@ -164,6 +114,7 @@ public class PodDBAdapter {
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";
+ public static final String TABLE_NAME_FAVORITES = "Favorites";
// SQL Statements for creating new tables
private static final String TABLE_PRIMARY_KEY = KEY_ID
@@ -180,10 +131,14 @@ public class PodDBAdapter {
+ KEY_FLATTR_STATUS + " INTEGER,"
+ KEY_USERNAME + " TEXT,"
+ KEY_PASSWORD + " TEXT,"
+ + KEY_INCLUDE_FILTER + " TEXT DEFAULT '',"
+ + KEY_EXCLUDE_FILTER + " TEXT DEFAULT '',"
+ + KEY_KEEP_UPDATED + " INTEGER DEFAULT 1,"
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
+ KEY_NEXT_PAGE_LINK + " TEXT,"
+ KEY_HIDE + " TEXT,"
- + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0)";
+ + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0,"
+ + KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0)";
public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -209,7 +164,8 @@ public class PodDBAdapter {
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER,"
+ KEY_PLAYED_DURATION + " INTEGER,"
- + KEY_AUTO_DOWNLOAD + " INTEGER)";
+ + KEY_HAS_EMBEDDED_PICTURE + " INTEGER,"
+ + KEY_LAST_PLAYED_TIME + " INTEGER)";
public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@@ -236,6 +192,15 @@ public class PodDBAdapter {
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_IMAGE + ")";
+ public static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_PUBDATE + ")";
+
+ public static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_READ + ")";
+
+
public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ KEY_FEEDITEM + ")";
@@ -248,11 +213,10 @@ public class PodDBAdapter {
+ TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ KEY_FEEDITEM + ")";
-
- private SQLiteDatabase db;
- private final Context context;
- private PodDBHelper helper;
-
+ public static final String CREATE_TABLE_FAVORITES = "CREATE TABLE "
+ + TABLE_NAME_FAVORITES + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
+
/**
* Select all columns from the feed-table
*/
@@ -272,6 +236,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_TYPE,
TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD,
+ TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED,
TABLE_NAME_FEEDS + "." + KEY_FLATTR_STATUS,
TABLE_NAME_FEEDS + "." + KEY_IS_PAGED,
TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK,
@@ -279,30 +244,11 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_PASSWORD,
TABLE_NAME_FEEDS + "." + KEY_HIDE,
TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
+ TABLE_NAME_FEEDS + "." + KEY_AUTO_DELETE_ACTION,
+ TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER,
+ TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER
};
-
- // 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_STD_IS_PAGED = 16;
- public static final int IDX_FEED_SEL_STD_NEXT_PAGE_LINK = 17;
- public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 18;
- public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 19;
-
+
/**
* Select all columns from the feeditems-table except description and
* content-encoded.
@@ -313,7 +259,8 @@ public class PodDBAdapter {
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_PAYMENT_LINK,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_MEDIA,
TABLE_NAME_FEED_ITEMS + "." + KEY_FEED,
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
@@ -323,6 +270,20 @@ public class PodDBAdapter {
};
/**
+ * All the tables in the database
+ */
+ private static final String[] ALL_TABLES = {
+ TABLE_NAME_FEEDS,
+ TABLE_NAME_FEED_ITEMS,
+ TABLE_NAME_FEED_IMAGES,
+ TABLE_NAME_FEED_MEDIA,
+ TABLE_NAME_DOWNLOAD_LOG,
+ TABLE_NAME_QUEUE,
+ TABLE_NAME_SIMPLECHAPTERS,
+ TABLE_NAME_FAVORITES
+ };
+
+ /**
* Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries.
*/
private static final String SEL_FI_SMALL_STR;
@@ -332,74 +293,56 @@ public class PodDBAdapter {
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;
+ private static SQLiteDatabase db;
+ private static Context context;
+ private static PodDBHelper dbHelper;
+ private static int counter = 0;
- static PodDBHelper dbHelperSingleton;
+ public static void init(Context context) {
+ PodDBAdapter.context = context.getApplicationContext();
+ }
- private static synchronized PodDBHelper getDbHelperSingleton(Context appContext) {
- if (dbHelperSingleton == null) {
- dbHelperSingleton = new PodDBHelper(appContext, DATABASE_NAME, null,
- ClientConfig.storageCallbacks.getDatabaseVersion());
+ public static synchronized PodDBAdapter getInstance() {
+ if(dbHelper == null) {
+ dbHelper = new PodDBHelper(PodDBAdapter.context, DATABASE_NAME, null);
}
- return dbHelperSingleton;
+ return new PodDBAdapter();
}
- public PodDBAdapter(Context c) {
- this.context = c;
- helper = getDbHelperSingleton(c.getApplicationContext());
- }
+ private PodDBAdapter() {}
public PodDBAdapter open() {
if (db == null || !db.isOpen() || db.isReadOnly()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Opening DB");
+ Log.v(TAG, "Opening DB");
try {
- db = helper.getWritableDatabase();
+ db = dbHelper.getWritableDatabase();
} catch (SQLException ex) {
- ex.printStackTrace();
- db = helper.getReadableDatabase();
+ Log.e(TAG, Log.getStackTraceString(ex));
+ db = dbHelper.getReadableDatabase();
}
}
return this;
}
public void close() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Closing DB");
- //db.close();
+ // do nothing
}
- public static boolean deleteDatabase(Context context) {
- Log.w(TAG, "Deleting database");
- dbHelperSingleton.close();
- dbHelperSingleton = null;
- return context.deleteDatabase(DATABASE_NAME);
+ public static boolean deleteDatabase() {
+ PodDBAdapter adapter = getInstance();
+ adapter.open();
+ for (String tableName : ALL_TABLES) {
+ db.delete(tableName, "1", null);
+ }
+ adapter.close();
+ return true;
}
/**
@@ -435,7 +378,7 @@ public class PodDBAdapter {
values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink());
if(feed.getItemFilter() != null && feed.getItemFilter().getValues().length > 0) {
- values.put(KEY_HIDE, StringUtils.join(feed.getItemFilter().getValues(), ","));
+ values.put(KEY_HIDE, TextUtils.join( ",", feed.getItemFilter().getValues()));
} else {
values.put(KEY_HIDE, "");
}
@@ -458,15 +401,20 @@ public class PodDBAdapter {
}
ContentValues values = new ContentValues();
values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload());
+ values.put(KEY_KEEP_UPDATED, prefs.getKeepUpdated());
+ values.put(KEY_AUTO_DELETE_ACTION,prefs.getAutoDeleteAction().ordinal());
values.put(KEY_USERNAME, prefs.getUsername());
values.put(KEY_PASSWORD, prefs.getPassword());
+ values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter());
+ values.put(KEY_EXCLUDE_FILTER, prefs.getFilter().getExcludeFilter());
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())});
}
- public void setFeedItemFilter(long feedId, List<String> filterValues) {
+ public void setFeedItemFilter(long feedId, Set<String> filterValues) {
+ Log.d(TAG, "setFeedItemFilter() called with: " + "feedId = [" + feedId + "], " +
+ "filterValues = [" + TextUtils.join(",", filterValues) + "]");
ContentValues values = new ContentValues();
- values.put(KEY_HIDE, StringUtils.join(filterValues, ","));
- Log.d(TAG, StringUtils.join(filterValues, ","));
+ values.put(KEY_HIDE, TextUtils.join(",", filterValues));
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
}
@@ -476,7 +424,12 @@ public class PodDBAdapter {
* @return the id of the entry
*/
public long setImage(FeedImage image) {
- db.beginTransaction();
+ boolean startedTransaction = false;
+ if(false == db.inTransaction()) {
+ db.beginTransaction();
+ startedTransaction = true;
+ }
+
ContentValues values = new ContentValues();
values.put(KEY_TITLE, image.getTitle());
values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
@@ -497,8 +450,10 @@ public class PodDBAdapter {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())});
}
}
- db.setTransactionSuccessful();
- db.endTransaction();
+ if(startedTransaction) {
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
return image.getId();
}
@@ -516,10 +471,11 @@ public class PodDBAdapter {
values.put(KEY_DOWNLOAD_URL, media.getDownload_url());
values.put(KEY_DOWNLOADED, media.isDownloaded());
values.put(KEY_FILE_URL, media.getFile_url());
+ values.put(KEY_HAS_EMBEDDED_PICTURE, media.hasEmbeddedPicture());
+ values.put(KEY_LAST_PLAYED_TIME, media.getLastPlayedTime());
if (media.getPlaybackCompletionDate() != null) {
- values.put(KEY_PLAYBACK_COMPLETION_DATE, media
- .getPlaybackCompletionDate().getTime());
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime());
} else {
values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
}
@@ -541,6 +497,7 @@ public class PodDBAdapter {
values.put(KEY_POSITION, media.getPosition());
values.put(KEY_DURATION, media.getDuration());
values.put(KEY_PLAYED_DURATION, media.getPlayedDuration());
+ values.put(KEY_LAST_PLAYED_TIME, media.getLastPlayedTime());
db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
new String[]{String.valueOf(media.getId())});
} else {
@@ -736,7 +693,13 @@ public class PodDBAdapter {
setFeed(item.getFeed());
}
values.put(KEY_FEED, item.getFeed().getId());
- values.put(KEY_READ, item.isRead());
+ if(item.isNew()) {
+ values.put(KEY_READ, FeedItem.NEW);
+ } else if(item.isPlayed()) {
+ values.put(KEY_READ, FeedItem.PLAYED);
+ } else {
+ values.put(KEY_READ, FeedItem.UNPLAYED);
+ }
values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters());
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
@@ -763,12 +726,12 @@ public class PodDBAdapter {
return item.getId();
}
- public void setFeedItemRead(boolean read, long itemId, long mediaId,
+ public void setFeedItemRead(int played, long itemId, long mediaId,
boolean resetMediaPosition) {
db.beginTransaction();
ContentValues values = new ContentValues();
- values.put(KEY_READ, read);
+ values.put(KEY_READ, played);
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)});
if (resetMediaPosition) {
@@ -781,7 +744,12 @@ public class PodDBAdapter {
db.endTransaction();
}
- public void setFeedItemRead(boolean read, long... itemIds) {
+ /**
+ * Sets the 'read' attribute of the item.
+ * @param read must be one of FeedItem.PLAYED, FeedItem.NEW, FeedItem.UNPLAYED
+ * @param itemIds items to change the value of
+ */
+ public void setFeedItemRead(int read, long... itemIds) {
db.beginTransaction();
ContentValues values = new ContentValues();
for (long id : itemIds) {
@@ -802,8 +770,7 @@ public class PodDBAdapter {
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));
+ chapter.setId(db.insert(TABLE_NAME_SIMPLECHAPTERS, null, values));
} else {
db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?",
new String[]{String.valueOf(chapter.getId())});
@@ -839,11 +806,65 @@ public class PodDBAdapter {
return status.getId();
}
- public void setFeedItemAutoDownload(FeedItem feedItem, boolean autoDownload) {
+ public void setFeedItemAutoDownload(FeedItem feedItem, long autoDownload) {
ContentValues values = new ContentValues();
values.put(KEY_AUTO_DOWNLOAD, autoDownload);
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
- new String[] { String.valueOf(feedItem.getId()) } );
+ new String[]{String.valueOf(feedItem.getId())});
+ }
+
+ public void setFeedsItemsAutoDownload(Feed feed, boolean autoDownload) {
+ final String sql = "UPDATE " + TABLE_NAME_FEED_ITEMS
+ + " SET " + KEY_AUTO_DOWNLOAD + "="+ (autoDownload ? "1" : "0")
+ + " WHERE " + KEY_FEED + "=" + feed.getId();
+ db.execSQL(sql);
+ }
+
+ public void setFavorites(List<FeedItem> favorites) {
+ ContentValues values = new ContentValues();
+ db.beginTransaction();
+ db.delete(TABLE_NAME_FAVORITES, null, null);
+ for (int i = 0; i < favorites.size(); i++) {
+ FeedItem item = favorites.get(i);
+ values.put(KEY_ID, i);
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_FEED, item.getFeed().getId());
+ db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ /**
+ * Adds the item to favorites
+ */
+ public void addFavoriteItem(FeedItem item) {
+ // don't add an item that's already there...
+ if (isItemInFavorites(item)) {
+ Log.d(TAG, "item already in favorites");
+ return;
+ }
+ ContentValues values = new ContentValues();
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_FEED, item.getFeedId());
+ db.insert(TABLE_NAME_FAVORITES, null, values);
+ }
+
+ public void removeFavoriteItem(FeedItem item) {
+ String deleteClause = String.format("DELETE FROM %s WHERE %s=%s AND %s=%s",
+ TABLE_NAME_FAVORITES,
+ KEY_FEEDITEM, item.getId(),
+ KEY_FEED, item.getFeedId());
+ db.execSQL(deleteClause);
+ }
+
+ public boolean isItemInFavorites(FeedItem item) {
+ String query = String.format("SELECT %s from %s WHERE %s=%d",
+ KEY_ID, TABLE_NAME_FAVORITES, KEY_FEEDITEM, item.getId());
+ Cursor c = db.rawQuery(query, null);
+ int count = c.getCount();
+ c.close();
+ return count > 0;
}
public long getDownloadLogSize() {
@@ -857,14 +878,6 @@ public class PodDBAdapter {
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<FeedItem> queue) {
ContentValues values = new ContentValues();
db.beginTransaction();
@@ -874,8 +887,7 @@ public class PodDBAdapter {
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.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
db.setTransactionSuccessful();
db.endTransaction();
@@ -886,6 +898,10 @@ public class PodDBAdapter {
}
public void removeFeedMedia(FeedMedia media) {
+ // delete download log entries for feed media
+ db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE +"=?",
+ new String[] { String.valueOf(media.getId()), String.valueOf(FeedMedia.FEEDFILETYPE_FEEDMEDIA) });
+
db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?",
new String[]{String.valueOf(media.getId())});
}
@@ -930,6 +946,9 @@ public class PodDBAdapter {
removeFeedItem(item);
}
}
+ // delete download log entries for feed
+ db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE +"=?",
+ new String[] { String.valueOf(feed.getId()), String.valueOf(Feed.FEEDFILETYPE_FEED) });
db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())});
@@ -937,11 +956,6 @@ public class PodDBAdapter {
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);
@@ -967,13 +981,6 @@ public class PodDBAdapter {
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
*
@@ -1017,15 +1024,44 @@ public class PodDBAdapter {
}
/**
- * Returns a cursor for a DB query in the FeedImages table for a given ID.
+ * Returns a cursor for a DB query in the FeedImages table for given IDs.
*
- * @param id ID of the FeedImage
+ * @param imageIds IDs of the images
* @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 getImageCursor(String... imageIds) {
+ int length = imageIds.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;
+ String[] parts;
+ final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
+
+ if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
+ neededLength = IN_OPERATOR_MAXIMUM;
+ parts = Arrays.copyOfRange(imageIds, i
+ * IN_OPERATOR_MAXIMUM, (i + 1)
+ * IN_OPERATOR_MAXIMUM);
+ } else {
+ neededLength = elementsLeft;
+ parts = Arrays.copyOfRange(imageIds, i
+ * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
+ + neededLength);
+ }
+
+ cursors[i] = db.rawQuery("SELECT * FROM "
+ + TABLE_NAME_FEED_IMAGES + " WHERE " + KEY_ID + " IN "
+ + buildInOperator(neededLength), parts);
+ }
+ return new MergeCursor(cursors);
+ } else {
+ return db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + " IN "
+ + buildInOperator(length), imageIds, null, null, null);
+ }
}
public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
@@ -1053,23 +1089,17 @@ public class PodDBAdapter {
/**
* Returns a cursor which contains all feed items in the queue. The returned
* cursor uses the FEEDITEM_SEL_FI_SMALL selection.
+ * 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,
+ Object[] args = new String[] {
+ SEL_FI_SMALL_STR,
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);
+ 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;
}
@@ -1078,51 +1108,58 @@ public class PodDBAdapter {
return c;
}
+
+ public final Cursor getFavoritesCursor() {
+ Object[] args = new String[] {
+ SEL_FI_SMALL_STR,
+ TABLE_NAME_FEED_ITEMS, TABLE_NAME_FAVORITES,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
+ TABLE_NAME_FAVORITES + "." + KEY_FEEDITEM,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE };
+ String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s DESC", args);
+ Cursor c = db.rawQuery(query, 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");
+ + "<" + FeedItem.PLAYED, null, null, null, KEY_PUBDATE + " DESC");
return c;
}
- public final Cursor getNewItemIdsCursor() {
- final String query = "SELECT " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ /**
+ * Returns a cursor which contains all items of a feed that are considered new.
+ * The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
+ */
+ public final Cursor getNewItemsIdsCursor(long feedId) {
+ final String query = "SELECT " + KEY_ID
+ " FROM " + TABLE_NAME_FEED_ITEMS
- + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
- + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
- + " WHERE "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
- + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
- + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
- + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
- return db.rawQuery(query, null);
+ + " WHERE " + KEY_FEED + "=" + feedId
+ + " AND " + KEY_READ + "=" + FeedItem.NEW
+ + " ORDER BY " + KEY_PUBDATE + " DESC";
+ Cursor c = db.rawQuery(query, null);
+ return c;
}
/**
* Returns a cursor which contains all feed items that are considered new.
+ * Excludes those feeds that do not have 'Keep Updated' enabled.
* The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
*/
public final Cursor getNewItemsCursor() {
- 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
- + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
- + " WHERE "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
- + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
- + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
- + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL" // not in queue
- + " ORDER BY " + KEY_PUBDATE + " DESC";
+ String[] args = new String[] {
+ SEL_FI_SMALL_STR,
+ TABLE_NAME_FEED_ITEMS,
+ TABLE_NAME_FEEDS,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.NEW + " AND " + TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED + " > 0",
+ KEY_PUBDATE + " DESC"
+ };
+ final String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s WHERE %s ORDER BY %s", args);
Cursor c = db.rawQuery(query, null);
return c;
}
@@ -1133,11 +1170,11 @@ public class PodDBAdapter {
}
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";
+ 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;
}
@@ -1151,7 +1188,9 @@ public class PodDBAdapter {
* @throws IllegalArgumentException if limit < 0
*/
public final Cursor getCompletedMediaCursor(int limit) {
- Validate.isTrue(limit >= 0, "Limit must be >= 0");
+ if(limit < 0) {
+ throw new IllegalArgumentException("Limit must be >= 0");
+ }
Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null,
KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
@@ -1163,26 +1202,26 @@ public class PodDBAdapter {
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;
+ public final Cursor getFeedMediaCursor(String... itemIds) {
+ int length = itemIds.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;
+ int neededLength;
+ String[] parts;
final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
neededLength = IN_OPERATOR_MAXIMUM;
- parts = Arrays.copyOfRange(mediaIds, i
+ parts = Arrays.copyOfRange(itemIds, i
* IN_OPERATOR_MAXIMUM, (i + 1)
* IN_OPERATOR_MAXIMUM);
} else {
neededLength = elementsLeft;
- parts = Arrays.copyOfRange(mediaIds, i
+ parts = Arrays.copyOfRange(itemIds, i
* IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
+ neededLength);
}
@@ -1194,7 +1233,7 @@ public class PodDBAdapter {
return new MergeCursor(cursors);
} else {
return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN "
- + buildInOperator(length), mediaIds, null, null, null);
+ + buildInOperator(length), itemIds, null, null, null);
}
}
@@ -1237,25 +1276,31 @@ public class PodDBAdapter {
}
public final Cursor getFeedItemCursor(final String podcastUrl, final String episodeUrl) {
- final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
- + " INNER JOIN " +
- TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" +
- TABLE_NAME_FEEDS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + "='" +
- episodeUrl + "' AND " + TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL + "='" + podcastUrl + "'";
+ String downloadUrl = DatabaseUtils.sqlEscapeString(podcastUrl);
+ String itemIdentifier = DatabaseUtils.sqlEscapeString(episodeUrl);
+ final String query = ""
+ + "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEEDS
+ + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
+ + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + "=" + itemIdentifier
+ + " AND " + TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl;
return db.rawQuery(query, null);
}
public Cursor getImageAuthenticationCursor(final String imageUrl) {
- final String query = "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM "
- + TABLE_NAME_FEED_IMAGES + " INNER JOIN " + TABLE_NAME_FEEDS + " ON " +
- TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEEDS + "." + KEY_IMAGE + " WHERE "
- + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "='" + imageUrl + "' UNION SELECT "
- + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_IMAGES + " INNER JOIN "
- + TABLE_NAME_FEED_ITEMS + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" +
- TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE + " INNER JOIN " + TABLE_NAME_FEEDS + " ON "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID + " WHERE "
- + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "='" + imageUrl + "'";
- Log.d(TAG, "Query: " + query);
+ String downloadUrl = DatabaseUtils.sqlEscapeString(imageUrl);
+ final String query = ""
+ + "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_IMAGES
+ + " INNER JOIN " + TABLE_NAME_FEEDS
+ + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEEDS + "." + KEY_IMAGE
+ + " WHERE " + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl
+ + " UNION SELECT " + KEY_USERNAME + "," + KEY_PASSWORD
+ + " FROM " + TABLE_NAME_FEED_IMAGES
+ + " INNER JOIN " + TABLE_NAME_FEED_ITEMS
+ + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE
+ + " INNER JOIN " + TABLE_NAME_FEEDS
+ + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
+ + " WHERE " + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl;
return db.rawQuery(query, null);
}
@@ -1271,19 +1316,9 @@ public class PodDBAdapter {
}
public final int getNumberOfNewItems() {
- final String query = "SELECT COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ")"
- +" FROM " + TABLE_NAME_FEED_ITEMS
- + " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
- + " LEFT JOIN " + TABLE_NAME_QUEUE + " ON "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
- + " WHERE "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
- + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
- + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
- + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
+ final String query = "SELECT COUNT(" + KEY_ID + ")"
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " WHERE " + KEY_READ + "=" + FeedItem.NEW;
Cursor c = db.rawQuery(query, null);
int result = 0;
if (c.moveToFirst()) {
@@ -1293,12 +1328,37 @@ public class PodDBAdapter {
return result;
}
- public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) {
+ public final LongIntMap getFeedCounters(long... feedIds) {
+ int setting = UserPreferences.getFeedCounterSetting();
+ String whereRead;
+ if(setting == UserPreferences.FEED_COUNTER_SHOW_NEW_UNPLAYED_SUM) {
+ whereRead = "(" + KEY_READ + "=" + FeedItem.NEW
+ + " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")";
+ } else if(setting == UserPreferences.FEED_COUNTER_SHOW_NEW) {
+ whereRead = KEY_READ + "=" + FeedItem.NEW;
+ } else if(setting == UserPreferences.FEED_COUNTER_SHOW_UNPLAYED) {
+ whereRead = KEY_READ + "=" + FeedItem.UNPLAYED;
+ } else { // NONE
+ return new LongIntMap(0);
+ }
+
+ // work around TextUtils.join wanting only boxed items
+ // and StringUtils.join() causing NoSuchMethodErrors on MIUI
+ StringBuilder builder = new StringBuilder();
+ for (long id : feedIds) {
+ builder.append(id);
+ builder.append(',');
+ }
+ if (feedIds.length > 0) {
+ // there's an extra ',', get rid of it
+ builder.deleteCharAt(builder.length() - 1);
+ }
+
final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count "
+ " FROM " + TABLE_NAME_FEED_ITEMS
- + " WHERE " + KEY_FEED + " IN (" + StringUtils.join(feedIds, ',') + ") "
- + " AND " + KEY_READ + " = 0"
- + " GROUP BY " + KEY_FEED;
+ + " WHERE " + KEY_FEED + " IN (" + builder.toString() + ") "
+ + " AND " + whereRead + " GROUP BY " + KEY_FEED;
+
Cursor c = db.rawQuery(query, null);
LongIntMap result = new LongIntMap(c.getCount());
if (c.moveToFirst()) {
@@ -1451,17 +1511,22 @@ public class PodDBAdapter {
* Helper class for opening the Antennapod database.
*/
private static class PodDBHelper extends SQLiteOpenHelper {
+
+ private final static int VERSION = 1050003;
+
+ private Context context;
+
/**
* 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);
+ final CursorFactory factory) {
+ super(context, name, factory, VERSION);
+ this.context = context;
}
@Override
@@ -1473,9 +1538,12 @@ public class PodDBAdapter {
db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
db.execSQL(CREATE_TABLE_QUEUE);
db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
+ db.execSQL(CREATE_TABLE_FAVORITES);
db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
+ db.execSQL(CREATE_INDEX_FEEDITEMS_PUBDATE);
+ db.execSQL(CREATE_INDEX_FEEDITEMS_READ);
db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
@@ -1485,7 +1553,259 @@ public class PodDBAdapter {
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
- ClientConfig.storageCallbacks.onUpgrade(db, oldVersion, newVersion);
+ EventBus.getDefault().post(ProgressEvent.start(context.getString(R.string.progress_upgrading_database)));
+ Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
+ + newVersion + ".");
+ if (oldVersion <= 1) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ + KEY_TYPE + " TEXT");
+ }
+ if (oldVersion <= 2) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + KEY_LINK + " TEXT");
+ }
+ if (oldVersion <= 3) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 4) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ + KEY_FEED_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 5) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
+ }
+ if (oldVersion <= 6) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
+ }
+ if (oldVersion <= 7) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.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 " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_FEEDITEM
+ + " INTEGER");
+ Cursor feeditemCursor = db.query(PodDBAdapter.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(PodDBAdapter.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 " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_AUTO_DOWNLOAD
+ + " INTEGER DEFAULT 1");
+ }
+ if (oldVersion <= 10) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_FLATTR_STATUS
+ + " INTEGER");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + KEY_FLATTR_STATUS
+ + " INTEGER");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_PLAYED_DURATION
+ + " INTEGER");
+ }
+ if (oldVersion <= 11) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_USERNAME
+ + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_PASSWORD
+ + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + KEY_IMAGE
+ + " INTEGER");
+ }
+ if (oldVersion <= 12) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_IS_PAGED + " INTEGER DEFAULT 0");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_NEXT_PAGE_LINK + " TEXT");
+ }
+ if (oldVersion <= 13) {
+ // remove duplicate rows in "Chapters" table that were created because of a bug.
+ db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
+ "(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
+ PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
+ KEY_ID,
+ KEY_ID,
+ KEY_ID,
+ PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
+ KEY_TITLE,
+ KEY_START,
+ KEY_FEEDITEM,
+ KEY_LINK,
+ KEY_CHAPTER_TYPE));
+ }
+ if(oldVersion <= 14) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + KEY_AUTO_DOWNLOAD + " INTEGER");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + KEY_AUTO_DOWNLOAD + " = "
+ + "(SELECT " + KEY_AUTO_DOWNLOAD
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + ")");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_HIDE + " TEXT");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
+
+ // create indexes
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_IMAGE);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
+ }
+ if(oldVersion <= 15) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_HAS_EMBEDDED_PICTURE + " INTEGER DEFAULT -1");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + KEY_HAS_EMBEDDED_PICTURE + "=0"
+ + " WHERE " + KEY_DOWNLOADED + "=0");
+ Cursor c = db.rawQuery("SELECT " + KEY_FILE_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " WHERE " + KEY_DOWNLOADED + "=1 "
+ + " AND " + KEY_HAS_EMBEDDED_PICTURE + "=-1", null);
+ if(c.moveToFirst()) {
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ do {
+ String fileUrl = c.getString(0);
+ try {
+ mmr.setDataSource(fileUrl);
+ byte[] image = mmr.getEmbeddedPicture();
+ if (image != null) {
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + KEY_HAS_EMBEDDED_PICTURE + "=1"
+ + " WHERE " + KEY_FILE_URL + "='"+ fileUrl + "'");
+ } else {
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + KEY_HAS_EMBEDDED_PICTURE + "=0"
+ + " WHERE " + KEY_FILE_URL + "='"+ fileUrl + "'");
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ } while(c.moveToNext());
+ }
+ c.close();
+ }
+ if(oldVersion <= 16) {
+ String selectNew = "SELECT " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ON "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + PodDBAdapter.TABLE_NAME_QUEUE + " ON "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + PodDBAdapter.TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + PodDBAdapter.TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
+ String sql = "UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + KEY_READ + "=" + FeedItem.NEW
+ + " WHERE " + KEY_ID + " IN (" + selectNew + ")";
+ Log.d("Migration", "SQL: " + sql);
+ db.execSQL(sql);
+ }
+ if(oldVersion <= 17) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0");
+ }
+ if(oldVersion < 1030005) {
+ db.execSQL("UPDATE FeedItems SET auto_download=0 WHERE " +
+ "(read=1 OR id IN (SELECT feeditem FROM FeedMedia WHERE position>0 OR downloaded=1)) " +
+ "AND id NOT IN (SELECT feeditem FROM Queue)");
+ }
+ if(oldVersion < 1040001) {
+ db.execSQL(CREATE_TABLE_FAVORITES);
+ }
+ if (oldVersion < 1040002) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
+ }
+ if(oldVersion < 1040013) {
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
+ }
+
+ if (oldVersion < 1050003) {
+ // Migrates feed list filter data
+
+ db.beginTransaction();
+
+ // Change to intermediate values to avoid overwriting in the following find/replace
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'unplayed', 'noplay')");
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'not_queued', 'noqueue')");
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'not_downloaded', 'nodl')");
+
+ // Replace played, queued, and downloaded with their opposites
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'played', 'unplayed')");
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'queued', 'not_queued')");
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'downloaded', 'not_downloaded')");
+
+ // Now replace intermediates for unplayed, not queued, etc. with their opposites
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'noplay', 'played')");
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'noqueue', 'queued')");
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'nodl', 'downloaded')");
+
+ // Paused doesn't have an opposite, so unplayed is the next best option
+ db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
+ "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'paused', 'unplayed')");
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ // and now get ready for autodownload filters
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_INCLUDE_FILTER + " TEXT DEFAULT ''");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''");
+
+ // and now auto refresh
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_KEEP_UPDATED + " INTEGER DEFAULT 1");
+ }
+
+ EventBus.getDefault().post(ProgressEvent.end());
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
index 413a11f8e..9280db8a3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
@@ -1,8 +1,8 @@
package de.danoeh.antennapod.core.syndication.handler;
+import android.support.v4.util.ArrayMap;
+
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;
@@ -32,7 +32,7 @@ public class HandlerState {
/**
* Namespaces that have been defined so far.
*/
- protected HashMap<String, Namespace> namespaces;
+ protected Map<String, Namespace> namespaces;
protected Stack<Namespace> defaultNamespaces;
/**
* Buffer for saving characters.
@@ -42,16 +42,16 @@ public class HandlerState {
/**
* Temporarily saved objects.
*/
- protected HashMap<String, Object> tempObjects;
+ protected Map<String, Object> tempObjects;
public HandlerState(Feed feed) {
this.feed = feed;
- alternateUrls = new LinkedHashMap<String, String>();
+ alternateUrls = new ArrayMap<>();
items = new ArrayList<FeedItem>();
tagstack = new Stack<SyndElement>();
- namespaces = new HashMap<String, Namespace>();
+ namespaces = new ArrayMap<>();
defaultNamespaces = new Stack<Namespace>();
- tempObjects = new HashMap<String, Object>();
+ tempObjects = new ArrayMap<>();
}
public Feed getFeed() {
@@ -105,7 +105,7 @@ public class HandlerState {
alternateUrls.put(url, title);
}
- public HashMap<String, Object> getTempObjects() {
+ public Map<String, Object> getTempObjects() {
return tempObjects;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java
index 32cd538d5..4d56e1365 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java
@@ -1,8 +1,7 @@
package de.danoeh.antennapod.core.syndication.handler;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.Feed;
+
import org.apache.commons.io.input.XmlStreamReader;
import org.jsoup.Jsoup;
import org.xmlpull.v1.XmlPullParser;
@@ -14,6 +13,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
+import de.danoeh.antennapod.core.feed.Feed;
+
/** Gets the type of a specific feed by reading the root element. */
public class TypeGetter {
private static final String TAG = "TypeGetter";
@@ -28,11 +29,13 @@ public class TypeGetter {
public Type getType(Feed feed) throws UnsupportedFeedtypeException {
XmlPullParserFactory factory;
if (feed.getFile_url() != null) {
+ Reader reader = null;
try {
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
- xpp.setInput(createReader(feed));
+ reader = createReader(feed);
+ xpp.setInput(reader);
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
@@ -40,38 +43,30 @@ public class TypeGetter {
String tag = xpp.getName();
if (tag.equals(ATOM_ROOT)) {
feed.setType(Feed.TYPE_ATOM1);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Recognized type Atom");
+ Log.d(TAG, "Recognized type Atom");
return Type.ATOM;
} else if (tag.equals(RSS_ROOT)) {
- String strVersion = xpp.getAttributeValue(null,
- "version");
+ 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");
+ 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");
+ 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");
+ 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
@@ -88,10 +83,17 @@ public class TypeGetter {
} catch (IOException e) {
e.printStackTrace();
+ } finally {
+ if(reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
}
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Type is invalid");
+ Log.d(TAG, "Type is invalid");
throw new UnsupportedFeedtypeException(Type.INVALID);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java
index ff9828eba..99c4cd67a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java
@@ -1,11 +1,14 @@
package de.danoeh.antennapod.core.syndication.namespace;
-import de.danoeh.antennapod.core.feed.FeedImage;
-import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+import android.text.TextUtils;
+
import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
+import de.danoeh.antennapod.core.feed.FeedImage;
+import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+
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";
@@ -16,6 +19,8 @@ public class NSITunes extends Namespace {
private static final String AUTHOR = "author";
public static final String DURATION = "duration";
+ public static final String SUBTITLE = "subtitle";
+ public static final String SUMMARY = "summary";
@Override
@@ -34,7 +39,8 @@ public class NSITunes extends Namespace {
} else {
// this is the feed image
- if (state.getFeed().getImage() == null) {
+ // prefer to all other images
+ if(!TextUtils.isEmpty(image.getDownload_url())) {
image.setOwner(state.getFeed());
state.getFeed().setImage(image);
}
@@ -63,13 +69,28 @@ public class NSITunes extends Namespace {
} else {
return;
}
-
state.getTempObjects().put(DURATION, duration);
} catch (NumberFormatException e) {
e.printStackTrace();
}
+ } else if (localName.equals(SUBTITLE)) {
+ String subtitle = state.getContentBuf().toString();
+ if (state.getCurrentItem() != null) {
+ if (TextUtils.isEmpty(state.getCurrentItem().getDescription())) {
+ state.getCurrentItem().setDescription(subtitle);
+ }
+ } else {
+ if (TextUtils.isEmpty(state.getFeed().getDescription())) {
+ state.getFeed().setDescription(subtitle);
+ }
+ }
+ } else if (localName.equals(SUMMARY)) {
+ String summary = state.getContentBuf().toString();
+ if (state.getCurrentItem() != null) {
+ state.getCurrentItem().setDescription(summary);
+ } else {
+ state.getFeed().setDescription(summary);
+ }
}
-
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
index 6455332be..7e19213be 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
@@ -5,6 +5,7 @@ import android.util.Log;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.BuildConfig;
+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;
@@ -57,6 +58,10 @@ public class NSRSS20 extends Namespace {
long size = 0;
try {
size = Long.parseLong(attributes.getValue(ENC_LEN));
+ if(size < 16384) {
+ // less than 16kb is suspicious, check manually
+ size = 0;
+ }
} catch (NumberFormatException e) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Length attribute could not be parsed.");
@@ -69,8 +74,11 @@ public class NSRSS20 extends Namespace {
if (state.getTagstack().size() >= 1) {
String parent = state.getTagstack().peek().getName();
if (parent.equals(CHANNEL)) {
- state.getFeed().setImage(new FeedImage());
- state.getFeed().getImage().setOwner(state.getFeed());
+ Feed feed = state.getFeed();
+ if(feed.getImage() == null) {
+ feed.setImage(new FeedImage());
+ feed.getImage().setOwner(state.getFeed());
+ }
}
}
}
@@ -115,13 +123,16 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setItemIdentifier(content);
}
} else if (top.equals(TITLE)) {
+ String title = content.trim();
if (second.equals(ITEM)) {
- state.getCurrentItem().setTitle(content);
+ state.getCurrentItem().setTitle(title);
} else if (second.equals(CHANNEL)) {
- state.getFeed().setTitle(content);
+ state.getFeed().setTitle(title);
} else if (second.equals(IMAGE) && third != null
&& third.equals(CHANNEL)) {
- state.getFeed().getImage().setTitle(content);
+ if(state.getFeed().getImage().getTitle() == null) {
+ state.getFeed().getImage().setTitle(title);
+ }
}
} else if (top.equals(LINK)) {
if (second.equals(CHANNEL)) {
@@ -134,7 +145,9 @@ public class NSRSS20 extends Namespace {
DateUtils.parse(content));
} else if (top.equals(URL) && second.equals(IMAGE) && third != null
&& third.equals(CHANNEL)) {
- state.getFeed().getImage().setDownload_url(content);
+ if(state.getFeed().getImage().getDownload_url() == null) { // prefer itunes:image
+ state.getFeed().getImage().setDownload_url(content);
+ }
} else if (localName.equals(DESCR)) {
if (second.equals(CHANNEL)) {
state.getFeed().setDescription(content);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
index abff5b2db..b23a142af 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
@@ -4,7 +4,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -88,16 +87,15 @@ public class NSAtom extends Namespace {
size = Long.parseLong(strSize);
}
} catch (NumberFormatException e) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Length attribute could not be parsed.");
+ 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)
- );
+ || (type = SyndTypeUtils.getValidMimeTypeFromUrl(href)) != null) {
+ FeedItem currItem = state.getCurrentItem();
+ if(!currItem.hasMedia()) {
+ currItem.setMedia(new FeedMedia(currItem, href, size, type));
+ }
}
} else if (rel.equals(LINK_REL_PAYMENT)) {
state.getCurrentItem().setPaymentLink(href);
@@ -111,9 +109,11 @@ public class NSAtom extends Namespace {
* 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)))) {
+ || (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))) {
+ } 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) {
@@ -199,7 +199,9 @@ public class NSAtom extends Namespace {
DateUtils.parse(content));
}
} else if (top.equals(IMAGE)) {
- state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ if(state.getFeed().getImage() == null) {
+ state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ }
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
index a0b514bd6..1b929b214 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
@@ -1,7 +1,10 @@
package de.danoeh.antennapod.core.util;
+import android.content.Context;
import android.util.Log;
+import de.danoeh.antennapod.core.R;
+
/** Provides methods for converting various units. */
public final class Converter {
/** Class shall not be instantiated. */
@@ -23,7 +26,7 @@ public final class Converter {
/** Determines the length of the number for best readability.*/
private static final int NUM_LENGTH = 1024;
-
+ private static final int DAYS_MIL = 86400000;
private static final int HOURS_MIL = 3600000;
private static final int MINUTES_MIL = 60000;
private static final int SECONDS_MIL = 1000;
@@ -99,5 +102,21 @@ public final class Converter {
return Integer.valueOf(parts[0]) * 3600 * 1000 +
Integer.valueOf(parts[1]) * 1000 * 60;
}
+
+ /** Converts milliseconds to a localized string containing hours and minutes */
+ public static String getDurationStringLocalized(Context context, long duration) {
+ int h = (int)(duration / HOURS_MIL);
+ int rest = (int)(duration - h * HOURS_MIL);
+ int m = rest / MINUTES_MIL;
+
+ String result = "";
+ if(h > 0) {
+ String hours = context.getResources().getQuantityString(R.plurals.time_hours_quantified, h, h);
+ result += hours + " ";
+ }
+ String minutes = context.getResources().getQuantityString(R.plurals.time_minutes_quantified, m, m);
+ result += minutes;
+ return result;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
index b6df2dc85..4b4201b50 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.util;
+import android.content.Context;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
@@ -7,7 +8,9 @@ import org.apache.commons.lang3.StringUtils;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.GregorianCalendar;
import java.util.Locale;
+import java.util.TimeZone;
/**
* Parses several date formats.
@@ -16,60 +19,88 @@ public class DateUtils {
private static final String TAG = "DateUtils";
+ private static final SimpleDateFormat parser = new SimpleDateFormat("", Locale.US);
+ private static final TimeZone defaultTimezone = TimeZone.getTimeZone("GMT");
+
+ static {
+ parser.setLenient(false);
+ parser.setTimeZone(defaultTimezone);
+ }
+
public static Date parse(final String input) {
if(input == null) {
- throw new IllegalArgumentException("Date most not be null");
+ throw new IllegalArgumentException("Date must not be null");
}
- String date = input.replace('/', '-');
+ String date = input.trim().replace('/', '-').replaceAll("( ){2,}+", " ");
+
+ // if datetime is more precise than seconds, make sure the value is in ms
if(date.contains(".")) {
int start = date.indexOf('.');
int current = start+1;
while(current < date.length() && Character.isDigit(date.charAt(current))) {
current++;
}
+ // even more precise than microseconds: discard further decimal places
if(current - start > 4) {
if(current < date.length()-1) {
date = date.substring(0, start + 4) + date.substring(current);
} else {
date = date.substring(0, start + 4);
}
+ // less than 4 decimal places: pad to have a consistent format for the parser
} else if(current - start < 4) {
if(current < date.length()-1) {
date = date.substring(0, current) + StringUtils.repeat("0", 4-(current-start)) + date.substring(current);
} else {
date = date.substring(0, current) + StringUtils.repeat("0", 4-(current-start));
}
-
}
}
String[] patterns = {
"dd MMM yy HH:mm:ss Z",
"dd MMM yy HH:mm Z",
"EEE, dd MMM yyyy HH:mm:ss Z",
+ "EEE, dd MMM yyyy HH:mm:ss",
"EEE, dd MMMM yyyy HH:mm:ss Z",
+ "EEE, dd MMMM yyyy HH:mm:ss",
+ "EEEE, dd MMM yyyy HH:mm:ss Z",
"EEEE, dd MMM yy HH:mm:ss Z",
+ "EEEE, dd MMM yyyy HH:mm:ss",
+ "EEEE, dd MMM yy HH:mm:ss",
"EEE MMM d HH:mm:ss yyyy",
+ "EEE, dd MMM yyyy HH:mm Z",
+ "EEE, dd MMM yyyy HH:mm",
+ "EEE, dd MMMM yyyy HH:mm Z",
+ "EEE, dd MMMM yyyy HH:mm",
+ "EEEE, dd MMM yyyy HH:mm Z",
+ "EEEE, dd MMM yy HH:mm Z",
+ "EEEE, dd MMM yyyy HH:mm",
+ "EEEE, dd MMM yy HH:mm",
+ "EEE MMM d HH:mm yyyy",
"yyyy-MM-dd'T'HH:mm:ss",
- "yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd'T'HH:mm:ss.SSS Z",
+ "yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd'T'HH:mm:ssZ",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-ddZ",
"yyyy-MM-dd"
};
- SimpleDateFormat parser = new SimpleDateFormat("", Locale.US);
- parser.setLenient(false);
+
ParsePosition pos = new ParsePosition(0);
for(String pattern : patterns) {
parser.applyPattern(pattern);
pos.setIndex(0);
- Date result = parser.parse(date, pos);
- if(result != null && pos.getIndex() == date.length()) {
- return result;
+ try {
+ Date result = parser.parse(date, pos);
+ if (result != null && pos.getIndex() == date.length()) {
+ return result;
+ }
+ } catch(Exception e) {
+ Log.e(TAG, Log.getStackTraceString(e));
}
}
- Log.d(TAG, "Could not parse date string \"" + input + "\"");
+ Log.d(TAG, "Could not parse date string \"" + input + "\" [" + date + "]");
return null;
}
@@ -109,6 +140,20 @@ public class DateUtils {
public static String formatRFC3339UTC(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ format.setTimeZone(defaultTimezone);
return format.format(date);
}
+
+ public static String formatAbbrev(final Context context, final Date date) {
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.add(GregorianCalendar.YEAR, -1);
+ // some padding, because no one really remembers what day of the month it is
+ cal.add(GregorianCalendar.DAY_OF_MONTH, 10);
+ boolean withinLastYear = date.after(cal.getTime());
+ int format = android.text.format.DateUtils.FORMAT_ABBREV_ALL;
+ if(withinLastYear) {
+ format |= android.text.format.DateUtils.FORMAT_NO_YEAR;
+ }
+ return android.text.format.DateUtils.formatDateTime(context, date.getTime(), format);
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
new file mode 100644
index 000000000..892e5ff38
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
@@ -0,0 +1,78 @@
+package de.danoeh.antennapod.core.util;
+
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+public class FeedItemUtil {
+
+ public static int indexOfItemWithDownloadUrl(List<FeedItem> items, String downloadUrl) {
+ if(items == null) {
+ return -1;
+ }
+ for(int i=0; i < items.size(); i++) {
+ FeedItem item = items.get(i);
+ if(item.hasMedia() && item.getMedia().getDownload_url().equals(downloadUrl)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static int indexOfItemWithId(List<FeedItem> items, long id) {
+ for(int i=0; i < items.size(); i++) {
+ FeedItem item = items.get(i);
+ if(item != null && item.getId() == id) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static int indexOfItemWithMediaId(List<FeedItem> items, long mediaId) {
+ for(int i=0; i < items.size(); i++) {
+ FeedItem item = items.get(i);
+ if(item != null && item.getMedia() != null && item.getMedia().getId() == mediaId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static long[] getIds(FeedItem... items) {
+ if(items == null || items.length == 0) {
+ return new long[0];
+ }
+ long[] result = new long[items.length];
+ for(int i=0; i < items.length; i++) {
+ result[i] = items[i].getId();
+ }
+ return result;
+ }
+
+ public static long[] getIds(List<FeedItem> items) {
+ if(items == null || items.size() == 0) {
+ return new long[0];
+ }
+ long[] result = new long[items.size()];
+ for(int i=0; i < items.size(); i++) {
+ result[i] = items.get(i).getId();
+ }
+ return result;
+ }
+
+ public static boolean containsAnyId(List<FeedItem> items, long[] ids) {
+ if(items == null || items.size() == 0) {
+ return false;
+ }
+ for(FeedItem item : items) {
+ for(long id : ids) {
+ if(item.getId() == id) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
new file mode 100644
index 000000000..2d5a6e5a1
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
@@ -0,0 +1,18 @@
+package de.danoeh.antennapod.core.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import java.util.List;
+
+public class IntentUtils {
+
+ public static boolean isCallable(final Context context, final Intent intent) {
+ List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return list.size() > 0;
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
index 07432d28a..287ec4d0c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
@@ -1,14 +1,15 @@
package de.danoeh.antennapod.core.util;
+import android.support.v4.util.ArrayMap;
+
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<String, String> languages;
+ private static ArrayMap<String, String> languages;
static {
- languages = new HashMap<String, String>();
+ languages = new ArrayMap<>();
languages.put("af", "Afrikaans");
languages.put("sq", "Albanian");
languages.put("sq", "Albanian");
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
index 8934f3272..6ed8b820e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
@@ -30,6 +30,17 @@ public final class LongList {
size = 0;
}
+ public static LongList of(long... values) {
+ if(values == null || values.length == 0) {
+ return new LongList(0);
+ }
+ LongList result = new LongList(values.length);
+ for(long value : values) {
+ result.add(value);
+ }
+ return result;
+ }
+
@Override
public int hashCode() {
int hashCode = 1;
@@ -166,6 +177,28 @@ public final class LongList {
}
/**
+ * Removes values from this list.
+ *
+ * @param values values to remove
+ */
+ public void removeAll(long[] values) {
+ for(long value : values) {
+ remove(value);
+ }
+ }
+
+ /**
+ * Removes values from this list.
+ *
+ * @param list List with values to remove
+ */
+ public void removeAll(LongList list) {
+ for(long value : list.values) {
+ remove(value);
+ }
+ }
+
+ /**
* Removes an element at a given index, shifting elements at greater
* indicies down one.
*
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
index 3a349e221..c2cd273b8 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
@@ -5,19 +5,35 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.text.TextUtils;
import android.util.Log;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+
+import java.io.File;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
-import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import rx.Observable;
+import rx.Subscriber;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
public class NetworkUtils {
- private static final String TAG = "NetworkUtils";
- private NetworkUtils() {
+ private static final String TAG = NetworkUtils.class.getSimpleName();
+
+ private static Context context;
+ public static void init(Context context) {
+ NetworkUtils.context = context;
}
/**
@@ -26,18 +42,16 @@ public class NetworkUtils {
* 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) {
+ public static boolean autodownloadNetworkAvailable() {
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");
+ 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");
+ Log.d(TAG, "Auto-dl filter is disabled");
return true;
} else {
WifiManager wm = (WifiManager) context
@@ -48,31 +62,28 @@ public class NetworkUtils {
.getAutodownloadSelectedNetworks());
if (selectedNetworks.contains(Integer.toString(wifiInfo
.getNetworkId()))) {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Current network is on the selected networks list");
+ 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");
+ Log.d(TAG, "Network for auto-dl is not available");
return false;
}
- public static boolean networkAvailable(Context context) {
+ public static boolean networkAvailable() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
return info != null && info.isConnected();
}
- public static boolean isDownloadAllowed(Context context) {
- return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi(context);
+ public static boolean isDownloadAllowed() {
+ return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi();
}
- public static boolean connectedToWifi(Context context) {
+ public static boolean connectedToWifi() {
ConnectivityManager connManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager
@@ -81,4 +92,67 @@ public class NetworkUtils {
return mWifi.isConnected();
}
+ public static Observable<Long> getFeedMediaSizeObservable(FeedMedia media) {
+ return Observable.create(new Observable.OnSubscribe<Long>() {
+ @Override
+ public void call(Subscriber<? super Long> subscriber) {
+ if (false == NetworkUtils.isDownloadAllowed()) {
+ subscriber.onNext(0L);
+ subscriber.onCompleted();
+ return;
+ }
+ long size = Integer.MIN_VALUE;
+ if (media.isDownloaded()) {
+ File mediaFile = new File(media.getLocalMediaUrl());
+ if (mediaFile.exists()) {
+ size = mediaFile.length();
+ }
+ } else if (false == media.checkedOnSizeButUnknown()) {
+ // only query the network if we haven't already checked
+
+ String url = media.getDownload_url();
+ if(TextUtils.isEmpty(url)) {
+ subscriber.onNext(0L);
+ subscriber.onCompleted();
+ return;
+ }
+
+ OkHttpClient client = AntennapodHttpClient.getHttpClient();
+ Request.Builder httpReq = new Request.Builder()
+ .url(url)
+ .header("Accept-Encoding", "identity")
+ .head();
+ try {
+ Response response = client.newCall(httpReq.build()).execute();
+ if (response.isSuccessful()) {
+ String contentLength = response.header("Content-Length");
+ try {
+ size = Integer.parseInt(contentLength);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
+ }
+ } catch (IOException e) {
+ subscriber.onNext(0L);
+ subscriber.onCompleted();
+ Log.e(TAG, Log.getStackTraceString(e));
+ return; // better luck next time
+ }
+ }
+ Log.d(TAG, "new size: " + size);
+ if (size <= 0) {
+ // they didn't tell us the size, but we don't want to keep querying on it
+ media.setCheckedOnSizeButUnknown();
+ } else {
+ media.setSize(size);
+ }
+ subscriber.onNext(size);
+ subscriber.onCompleted();
+ DBWriter.setFeedMedia(media);
+ }
+ })
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread());
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java
index 9a1496b75..71d6040ba 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java
@@ -83,7 +83,7 @@ public class QueueSorter {
}
if (comparator != null) {
- DBWriter.sortQueue(context, comparator, broadcastUpdate);
+ DBWriter.sortQueue(comparator, broadcastUpdate);
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java
new file mode 100644
index 000000000..ee306a401
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java
@@ -0,0 +1,47 @@
+package de.danoeh.antennapod.core.util;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class calculates the proper rewind time after the pause and resume.
+ * <p>
+ * User might loose context if he/she pauses and resumes the media after longer time.
+ * Media file should be "rewinded" x seconds after user resumes the playback.
+ */
+public class RewindAfterPauseUtils {
+
+ public static final long ELAPSED_TIME_FOR_SHORT_REWIND = TimeUnit.MINUTES.toMillis(1);
+ public static final long ELAPSED_TIME_FOR_MEDIUM_REWIND = TimeUnit.HOURS.toMillis(1);
+ public static final long ELAPSED_TIME_FOR_LONG_REWIND = TimeUnit.DAYS.toMillis(1);
+
+ public static final long SHORT_REWIND = TimeUnit.SECONDS.toMillis(3);
+ public static final long MEDIUM_REWIND = TimeUnit.SECONDS.toMillis(10);
+ public static final long LONG_REWIND = TimeUnit.SECONDS.toMillis(20);
+
+ /**
+ * @param currentPosition current position in a media file in ms
+ * @param lastPlayedTime timestamp when was media paused
+ * @return new rewinded position for playback in milliseconds
+ */
+ public static int calculatePositionWithRewind(int currentPosition, long lastPlayedTime) {
+ if (currentPosition > 0 && lastPlayedTime > 0) {
+ long elapsedTime = System.currentTimeMillis() - lastPlayedTime;
+ long rewindTime = 0;
+
+ if (elapsedTime > ELAPSED_TIME_FOR_LONG_REWIND) {
+ rewindTime = LONG_REWIND;
+ } else if (elapsedTime > ELAPSED_TIME_FOR_MEDIUM_REWIND) {
+ rewindTime = MEDIUM_REWIND;
+ } else if (elapsedTime > ELAPSED_TIME_FOR_SHORT_REWIND) {
+ rewindTime = SHORT_REWIND;
+ }
+
+ int newPosition = currentPosition - (int) rewindTime;
+
+ return newPosition > 0 ? newPosition : 0;
+ }
+ else {
+ return currentPosition;
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
index 85f32ed50..35916a604 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
@@ -2,6 +2,8 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.content.Intent;
+
+import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -11,24 +13,49 @@ public class ShareUtils {
private ShareUtils() {}
- public static void shareLink(Context context, String link) {
+ public static void shareLink(Context context, String text) {
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"));
+ i.putExtra(Intent.EXTRA_TEXT, text);
+ context.startActivity(Intent.createChooser(i, context.getString(R.string.share_url_label)));
}
-
- public static void shareFeedItemLink(Context context, FeedItem item) {
- shareLink(context, item.getLink());
+
+ public static void shareFeedlink(Context context, Feed feed) {
+ shareLink(context, feed.getTitle() + ": " + feed.getLink());
}
public static void shareFeedDownloadLink(Context context, Feed feed) {
- shareLink(context, feed.getDownload_url());
+ shareLink(context, feed.getTitle() + ": " + feed.getDownload_url());
}
-
- public static void shareFeedlink(Context context, Feed feed) {
- shareLink(context, feed.getLink());
+
+ public static void shareFeedItemLink(Context context, FeedItem item) {
+ shareFeedItemLink(context, item, false);
+ }
+
+ public static void shareFeedItemDownloadLink(Context context, FeedItem item) {
+ shareFeedItemDownloadLink(context, item, false);
+ }
+
+ private static String getItemShareText(FeedItem item) {
+ return item.getFeed().getTitle() + ": " + item.getTitle();
+ }
+
+ public static void shareFeedItemLink(Context context, FeedItem item, boolean withPosition) {
+ String text = getItemShareText(item) + " " + item.getLink();
+ if(withPosition) {
+ int pos = item.getMedia().getPosition();
+ text = item.getLink() + " [" + Converter.getDurationStringLong(pos) + "]";
+ }
+ shareLink(context, text);
+ }
+
+ public static void shareFeedItemDownloadLink(Context context, FeedItem item, boolean withPosition) {
+ String text = getItemShareText(item) + " " + item.getMedia().getDownload_url();
+ if(withPosition) {
+ int pos = item.getMedia().getPosition();
+ text += " [" + Converter.getDurationStringLong(pos) + "]";
+ }
+ shareLink(context, text);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java
index dea380937..1ef81bf64 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java
@@ -1,14 +1,12 @@
package de.danoeh.antennapod.core.util;
import android.app.Activity;
-import android.content.Context;
import android.os.Build;
import android.os.StatFs;
import android.util.Log;
import java.io.File;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -18,13 +16,12 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
public class StorageUtils {
private static final String TAG = "StorageUtils";
- public static boolean storageAvailable(Context context) {
- File dir = UserPreferences.getDataFolder(context, null);
+ public static boolean storageAvailable() {
+ File dir = UserPreferences.getDataFolder(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");
+ Log.d(TAG, "Storage not available: data folder is null");
return false;
}
}
@@ -39,7 +36,7 @@ public class StorageUtils {
* @return true if external storage is available
*/
public static boolean checkStorageAvailability(Activity activity) {
- boolean storageAvailable = storageAvailable(activity);
+ boolean storageAvailable = storageAvailable();
if (!storageAvailable) {
activity.finish();
activity.startActivity(ClientConfig.applicationCallbacks.getStorageErrorActivity(activity));
@@ -51,8 +48,19 @@ public class StorageUtils {
* Get the number of free bytes that are available on the external storage.
*/
public static long getFreeSpaceAvailable() {
- StatFs stat = new StatFs(UserPreferences.getDataFolder(
- ClientConfig.applicationCallbacks.getApplicationInstance(), null).getAbsolutePath());
+ File dataFolder = UserPreferences.getDataFolder(null);
+ if (dataFolder != null) {
+ return getFreeSpaceAvailable(dataFolder.getAbsolutePath());
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Get the number of free bytes that are available on the external storage.
+ */
+ public static long getFreeSpaceAvailable(String path) {
+ StatFs stat = new StatFs(path);
long availableBlocks;
long blockSize;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
index 4300556d2..415a1d3a2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
@@ -3,8 +3,6 @@ package de.danoeh.antennapod.core.util;
import android.net.Uri;
import android.util.Log;
-import org.apache.commons.lang3.StringUtils;
-
import de.danoeh.antennapod.core.BuildConfig;
/**
@@ -32,19 +30,19 @@ public final class URLChecker {
* @return The prepared url
*/
public static String prepareURL(String url) {
- url = StringUtils.trim(url);
+ url = url.trim();
if (url.startsWith("feed://")) {
if (BuildConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://");
return url.replaceFirst("feed://", "http://");
} else if (url.startsWith("pcast://")) {
if (BuildConfig.DEBUG) Log.d(TAG, "Removing pcast://");
- return prepareURL(StringUtils.removeStart(url, "pcast://"));
+ return prepareURL(url.substring("pcast://".length()));
} else if (url.startsWith("itpc")) {
if (BuildConfig.DEBUG) Log.d(TAG, "Replacing itpc:// with http://");
return url.replaceFirst("itpc://", "http://");
} else if (url.startsWith(AP_SUBSCRIBE)) {
if (BuildConfig.DEBUG) Log.d(TAG, "Removing antennapod-subscribe://");
- return prepareURL(StringUtils.removeStart(url, AP_SUBSCRIBE));
+ return prepareURL(url.substring(AP_SUBSCRIBE.length()));
} else if (!(url.startsWith("http://") || url.startsWith("https://"))) {
if (BuildConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL");
return "http://" + url;
@@ -66,7 +64,7 @@ public final class URLChecker {
if (base == null) {
return prepareURL(url);
}
- url = StringUtils.trim(url);
+ url = url.trim();
base = prepareURL(base);
Uri urlUri = Uri.parse(url);
Uri baseUri = Uri.parse(base);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
index 50792ae26..6ddfb0366 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.util.flattr;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
@@ -8,9 +7,10 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
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;
@@ -42,12 +42,6 @@ public class FlattrUtils {
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() {
@@ -84,8 +78,8 @@ public class FlattrUtils {
* 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(ClientConfig.flattrCallbacks.getFlattrAppKey())
- && StringUtils.isNotEmpty(ClientConfig.flattrCallbacks.getFlattrAppSecret());
+ return !TextUtils.isEmpty(ClientConfig.flattrCallbacks.getFlattrAppKey())
+ && !TextUtils.isEmpty(ClientConfig.flattrCallbacks.getFlattrAppSecret());
}
public static boolean hasToken() {
@@ -110,18 +104,6 @@ public class FlattrUtils {
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()) {
@@ -185,7 +167,7 @@ public class FlattrUtils {
deleteToken();
FlattrServiceCreator.deleteFlattrService();
showRevokeDialog(context);
- DBWriter.clearAllFlattrStatus(context);
+ DBWriter.clearAllFlattrStatus();
}
// ------------------------------------------------ DIALOGS
@@ -245,37 +227,6 @@ public class FlattrUtils {
}
}
- 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(
- ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context));
- }
-
- }
- );
- 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);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
deleted file mode 100644
index 26c712af3..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package de.danoeh.antennapod.core.util.gui;
-
-import android.os.Handler;
-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.core.R;
-
-import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
-
-public class UndoBarController<T> {
- private View mBarView;
- private TextView mMessageView;
- private ViewPropertyAnimator mBarAnimator;
- private Handler mHideHandler = new Handler();
-
- private UndoListener<T> mUndoListener;
-
- // State objects
- private T mUndoToken;
- private CharSequence mUndoMessage;
-
- public interface UndoListener<T> {
- /**
- * This callback function is called when the undo button is pressed
- *
- * @param token
- */
- void onUndo(T token);
-
- /**
- *
- * This callback function is called when the bar fades out without button press
- *
- * @param token
- */
- void onHide(T token);
- }
-
- public UndoBarController(View undoBarView, UndoListener<T> 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, T 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 boolean isShowing() {
- return mBarView.getVisibility() == View.VISIBLE;
- }
-
- public void close() {
- hideUndoBar(true);
- mUndoListener.onHide(mUndoToken);
- }
-
- public void hideUndoBar(boolean immediate) {
- mHideHandler.removeCallbacks(mHideRunnable);
- if (immediate) {
- mBarView.setVisibility(View.GONE);
- ViewHelper.setAlpha(mBarView, 0);
- mUndoMessage = 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;
- }
- });
- }
- }
-
- private Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- hideUndoBar(false);
- mUndoListener.onHide(mUndoToken);
- }
- };
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
index aafcea307..f0850e6df 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
@@ -3,7 +3,9 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.util.Log;
import android.view.SurfaceHolder;
-import com.aocate.media.MediaPlayer;
+import org.antennapod.audio.MediaPlayer;
+
+import de.danoeh.antennapod.core.preferences.UserPreferences;
public class AudioPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "AudioPlayer";
@@ -16,7 +18,6 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
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
@@ -31,4 +32,14 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
public void setVideoScalingMode(int mode) {
throw new UnsupportedOperationException("Setting scaling mode is not supported in Audio Player");
}
+
+ @Override
+ protected boolean useSonic() {
+ return UserPreferences.useSonic();
+ }
+
+ @Override
+ protected boolean downmix() {
+ return UserPreferences.stereoToMono();
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
index 49769f4f0..ec50dce7c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
@@ -20,6 +20,7 @@ public class ExternalMedia implements Playable {
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";
+ public static final String PREF_LAST_PLAYED_TIME = "ExternalMedia.PrefLastPlayedTime";
private String source;
@@ -29,6 +30,7 @@ public class ExternalMedia implements Playable {
private List<Chapter> chapters;
private int duration;
private int position;
+ private long lastPlayedTime;
public ExternalMedia(String source, MediaType mediaType) {
super();
@@ -36,9 +38,10 @@ public class ExternalMedia implements Playable {
this.mediaType = mediaType;
}
- public ExternalMedia(String source, MediaType mediaType, int position) {
+ public ExternalMedia(String source, MediaType mediaType, int position, long lastPlayedTime) {
this(source, mediaType);
this.position = position;
+ this.lastPlayedTime = lastPlayedTime;
}
@Override
@@ -51,6 +54,7 @@ public class ExternalMedia implements Playable {
dest.writeString(source);
dest.writeString(mediaType.toString());
dest.writeInt(position);
+ dest.writeLong(lastPlayedTime);
}
@Override
@@ -58,6 +62,7 @@ public class ExternalMedia implements Playable {
prefEditor.putString(PREF_SOURCE_URL, source);
prefEditor.putString(PREF_MEDIA_TYPE, mediaType.toString());
prefEditor.putInt(PREF_POSITION, position);
+ prefEditor.putLong(PREF_LAST_PLAYED_TIME, lastPlayedTime);
}
@Override
@@ -145,6 +150,11 @@ public class ExternalMedia implements Playable {
}
@Override
+ public long getLastPlayedTime() {
+ return lastPlayedTime;
+ }
+
+ @Override
public MediaType getMediaType() {
return mediaType;
}
@@ -170,10 +180,12 @@ public class ExternalMedia implements Playable {
}
@Override
- public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp) {
SharedPreferences.Editor editor = pref.edit();
editor.putInt(PREF_POSITION, newPosition);
+ editor.putLong(PREF_LAST_PLAYED_TIME, timestamp);
position = newPosition;
+ lastPlayedTime = timestamp;
editor.commit();
}
@@ -188,6 +200,11 @@ public class ExternalMedia implements Playable {
}
@Override
+ public void setLastPlayedTime(long lastPlayedTime) {
+ this.lastPlayedTime = lastPlayedTime;
+ }
+
+ @Override
public void onPlaybackStart() {
}
@@ -215,8 +232,12 @@ public class ExternalMedia implements Playable {
if (in.dataAvail() > 0) {
position = in.readInt();
}
- ExternalMedia extMedia = new ExternalMedia(source, type, position);
- return extMedia;
+ long lastPlayedTime = 0;
+ if (in.dataAvail() > 0) {
+ lastPlayedTime = in.readLong();
+ }
+
+ return new ExternalMedia(source, type, position, lastPlayedTime);
}
public ExternalMedia[] newArray(int size) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java
index 147c7848d..d67153a4e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java
@@ -10,6 +10,8 @@ public interface IPlayer {
boolean canSetSpeed();
+ boolean canDownmix();
+
float getCurrentPitchStepsAdjustment();
int getCurrentPosition();
@@ -57,6 +59,8 @@ public interface IPlayer {
void setPlaybackSpeed(float f);
+ void setDownmix(boolean enable);
+
void setVolume(float left, float right);
void start();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
index 7ebd580f7..86ec4fbd0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
@@ -7,7 +7,7 @@ import android.util.Log;
import java.util.List;
-import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
+import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
@@ -18,7 +18,7 @@ import de.danoeh.antennapod.core.util.ShownotesProvider;
* Interface for objects that can be played by the PlaybackService.
*/
public interface Playable extends Parcelable,
- ShownotesProvider, PicassoImageResource {
+ ShownotesProvider, ImageResource {
/**
* Save information about the playable in a preference so that it can be
@@ -82,6 +82,12 @@ public interface Playable extends Parcelable,
public int getPosition();
/**
+ * Returns last time (in ms) when this playable was played or 0
+ * if last played time is unknown.
+ */
+ public long getLastPlayedTime();
+
+ /**
* Returns the type of media. This method should return the correct value
* BEFORE loadMetadata() is called.
*/
@@ -115,14 +121,23 @@ public interface Playable extends Parcelable,
* Saves the current position of this object. Implementations can use the
* provided SharedPreference to save this information and retrieve it later
* via PlayableUtils.createInstanceFromPreferences.
+ *
+ * @param pref shared prefs that might be used to store this object
+ * @param newPosition new playback position in ms
+ * @param timestamp current time in ms
*/
- public void saveCurrentPosition(SharedPreferences pref, int newPosition);
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp);
public void setPosition(int newPosition);
public void setDuration(int newDuration);
/**
+ * @param lastPlayedTimestamp timestamp in ms
+ */
+ public void setLastPlayedTime(long lastPlayedTimestamp);
+
+ /**
* Is called by the PlaybackService when playback starts.
*/
public void onPlaybackStart();
@@ -159,28 +174,42 @@ public interface Playable extends Parcelable,
*/
public static Playable createInstanceFromPreferences(Context context, int type,
SharedPreferences pref) {
+ Playable result = null;
// 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);
- }
+ result = createFeedMediaInstance(pref);
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);
- }
+ result = createExternalMediaInstance(pref);
break;
}
- Log.e(TAG, "Could not restore Playable object from preferences");
- return null;
+ if (result == null) {
+ Log.e(TAG, "Could not restore Playable object from preferences");
+ }
+ return result;
+ }
+
+ private static Playable createFeedMediaInstance(SharedPreferences pref) {
+ Playable result = null;
+ long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
+ if (mediaId != -1) {
+ result = DBReader.getFeedMedia(mediaId);
+ }
+ return result;
+ }
+
+ private static Playable createExternalMediaInstance(SharedPreferences pref) {
+ Playable result = null;
+ 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);
+ long lastPlayedTime = pref.getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, 0);
+ result = new ExternalMedia(source, MediaType.valueOf(mediaType),
+ position, lastPlayedTime);
+ }
+ return result;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
index a0d12d3e7..27935978c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
@@ -11,24 +11,22 @@ import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.media.MediaPlayer;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
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 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;
@@ -37,6 +35,7 @@ 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;
@@ -49,6 +48,7 @@ import de.danoeh.antennapod.core.util.playback.Playable.PlayableUtils;
* 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;
@@ -56,7 +56,7 @@ public abstract class PlaybackController {
private final Activity activity;
private PlaybackService playbackService;
- private Playable media;
+ protected Playable media;
private PlayerStatus status;
private ScheduledThreadPoolExecutor schedExecutor;
@@ -74,22 +74,16 @@ public abstract class PlaybackController {
*/
private boolean reinitOnPause;
- public PlaybackController(Activity activity, boolean reinitOnPause) {
- Validate.notNull(activity);
+ public PlaybackController(@NonNull Activity activity, boolean reinitOnPause) {
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;
- }
+ r -> {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
}, new RejectedExecutionHandler() {
-
@Override
public void rejectedExecution(Runnable r,
ThreadPoolExecutor executor) {
@@ -106,10 +100,10 @@ public abstract class PlaybackController {
*/
public void init() {
activity.registerReceiver(statusUpdate, new IntentFilter(
- PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
+ PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
activity.registerReceiver(notificationReceiver, new IntentFilter(
- PlaybackService.ACTION_PLAYER_NOTIFICATION));
+ PlaybackService.ACTION_PLAYER_NOTIFICATION));
activity.registerReceiver(shutdownReceiver, new IntentFilter(
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
@@ -120,6 +114,7 @@ public abstract class PlaybackController {
throw new IllegalStateException(
"Can't call init() after release() has been called");
}
+ checkMediaInfoLoaded();
}
/**
@@ -240,7 +235,7 @@ public abstract class PlaybackController {
return null;
}
- public abstract void setupGUI();
+
private void setupPositionObserver() {
if ((positionObserverFuture != null && positionObserverFuture
@@ -264,8 +259,6 @@ public abstract class PlaybackController {
}
}
- public abstract void onPositionObserverUpdate();
-
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
playbackService = ((PlaybackService.LocalBinder) service)
@@ -359,7 +352,7 @@ public abstract class PlaybackController {
@Override
public void onReceive(Context context, Intent intent) {
if (isConnectedToPlaybackService()) {
- if (StringUtils.equals(intent.getAction(),
+ if (TextUtils.equals(intent.getAction(),
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
release();
onShutdownNotification();
@@ -368,26 +361,31 @@ public abstract class PlaybackController {
}
};
- public abstract void onPlaybackSpeedChange();
+ public void setupGUI() {};
+
+ public void onPositionObserverUpdate() {};
+
- public abstract void onShutdownNotification();
+ public void onPlaybackSpeedChange() {};
+
+ public void onShutdownNotification() {};
/**
* Called when the currently displayed information should be refreshed.
*/
- public abstract void onReloadNotification(int code);
+ public void onReloadNotification(int code) {};
- public abstract void onBufferStart();
+ public void onBufferStart() {};
- public abstract void onBufferEnd();
+ public void onBufferEnd() {};
- public abstract void onBufferUpdate(float progress);
+ public void onBufferUpdate(float progress) {};
- public abstract void onSleepTimerUpdate();
+ public void onSleepTimerUpdate() {};
- public abstract void handleError(int code);
+ public void handleError(int code) {};
- public abstract void onPlaybackEnd();
+ public void onPlaybackEnd() {};
public void repeatHandleStatus() {
if (status != null && playbackService != null) {
@@ -418,7 +416,6 @@ public abstract class PlaybackController {
Log.d(TAG, "status: " + status.toString());
switch (status) {
-
case ERROR:
postStatusMsg(R.string.player_error_msg);
handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN);
@@ -479,19 +476,25 @@ public abstract class PlaybackController {
private void updatePlayButtonAppearance(int resource, CharSequence contentDescription) {
ImageButton butPlay = getPlayButton();
- butPlay.setImageResource(resource);
- butPlay.setContentDescription(contentDescription);
+ if(butPlay != null) {
+ butPlay.setImageResource(resource);
+ butPlay.setContentDescription(contentDescription);
+ }
}
- public abstract ImageButton getPlayButton();
+ public ImageButton getPlayButton() {
+ return null;
+ };
- public abstract void postStatusMsg(int msg);
+ public void postStatusMsg(int msg) {};
- public abstract void clearStatusMsg();
+ public void clearStatusMsg() {};
- public abstract boolean loadMediaInfo();
+ public boolean loadMediaInfo() {
+ return false;
+ };
- public abstract void onAwaitingVideoSurface();
+ public void onAwaitingVideoSurface() {};
/**
* Called when connection to playback service has been established or
@@ -525,7 +528,7 @@ public abstract class PlaybackController {
}
}
- public abstract void onServiceQueried();
+ public void onServiceQueried() {};
/**
* Should be used by classes which implement the OnSeekBarChanged interface.
@@ -555,7 +558,7 @@ public abstract class PlaybackController {
* Should be used by classes which implement the OnSeekBarChanged interface.
*/
public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) {
- if (playbackService != null) {
+ if (playbackService != null && media != null) {
playbackService.seekTo((int) (prog * media.getDuration()));
setupPositionObserver();
}
@@ -572,37 +575,32 @@ public abstract class PlaybackController {
}
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;
+ return v -> {
+ if (playbackService == null) {
+ Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!");
+ return;
+ }
+ 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();
}
- } else {
- Log.w(TAG,
- "Play/Pause button was pressed, but playbackservice was null!");
- }
+ break;
+ case INITIALIZED:
+ playbackService.setStartWhenPrepared(true);
+ playbackService.prepare();
+ break;
}
-
};
}
@@ -652,9 +650,9 @@ public abstract class PlaybackController {
}
}
- public void setSleepTimer(long time) {
+ public void setSleepTimer(long time, boolean shakeToReset, boolean vibrate) {
if (playbackService != null) {
- playbackService.setSleepTimer(time);
+ playbackService.setSleepTimer(time, shakeToReset, vibrate);
}
}
@@ -681,6 +679,11 @@ public abstract class PlaybackController {
}
public boolean canSetPlaybackSpeed() {
+ if (org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(activity.getApplicationContext())
+ || UserPreferences.useSonic()
+ || Build.VERSION.SDK_INT >= 23) {
+ return true;
+ }
return playbackService != null && playbackService.canSetSpeed();
}
@@ -690,6 +693,12 @@ public abstract class PlaybackController {
}
}
+ public void setVolume(float leftVolume, float rightVolume) {
+ if (playbackService != null) {
+ playbackService.setVolume(leftVolume, rightVolume);
+ }
+ }
+
public float getCurrentPlaybackSpeedMultiplier() {
if (canSetPlaybackSpeed()) {
return playbackService.getCurrentPlaybackSpeed();
@@ -698,6 +707,16 @@ public abstract class PlaybackController {
}
}
+ public boolean canDownmix() {
+ return playbackService != null && playbackService.canDownmix();
+ }
+
+ public void setDownmix(boolean enable) {
+ if(playbackService != null) {
+ playbackService.setDownmix(enable);
+ }
+ }
+
public boolean isPlayingVideo() {
if (playbackService != null) {
return PlaybackService.getCurrentMediaType() == MediaType.VIDEO;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
index f31297b41..2eee1ac87 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
@@ -2,10 +2,10 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
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;
@@ -14,7 +14,6 @@ import org.jsoup.select.Elements;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@@ -59,6 +58,8 @@ public class Timeline {
private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))");
private static final String TIMECODE_LINK = "<a class=\"timecode\" href=\"antennapod://timecode/%d\">%s</a>";
private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(([0-9][0-9])):))?(([0-9][0-9])):(([0-9][0-9]))\\b");
+ private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br *\\/?>");
+
/**
* Applies an app-specific CSS stylesheet and adds timecode links (optional).
@@ -82,11 +83,15 @@ public class Timeline {
return null;
}
if (shownotes == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "shownotesProvider contained no shownotes. Returning empty string");
+ Log.d(TAG, "shownotesProvider contained no shownotes. Returning empty string");
return "";
}
+ // replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already
+ if(!LINE_BREAK_REGEX.matcher(shownotes).find() && !shownotes.contains("<p>")) {
+ shownotes = shownotes.replace("\n", "<br />");
+ }
+
Document document = Jsoup.parse(shownotes);
// apply style
@@ -97,10 +102,9 @@ public class Timeline {
// apply timecode links
if (addTimecodes) {
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
+ Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
for (Element element : elementsWithTimeCodes) {
- Matcher matcherLong = TIMECODE_REGEX.matcher(element.text());
+ Matcher matcherLong = TIMECODE_REGEX.matcher(element.html());
StringBuffer buffer = new StringBuffer();
while (matcherLong.find()) {
String h = matcherLong.group(1);
@@ -154,8 +158,7 @@ public class Timeline {
}
- public void setShownotesProvider(ShownotesProvider shownotesProvider) {
- Validate.notNull(shownotesProvider);
+ public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
this.shownotesProvider = shownotesProvider;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java
index dc5270d8f..368379509 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java
@@ -17,6 +17,11 @@ public class VideoPlayer extends MediaPlayer implements IPlayer {
}
@Override
+ public boolean canDownmix() {
+ return false;
+ }
+
+ @Override
public float getCurrentPitchStepsAdjustment() {
return 1;
}
@@ -60,6 +65,12 @@ public class VideoPlayer extends MediaPlayer implements IPlayer {
throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
}
+ @Override
+ public void setDownmix(boolean b) {
+ Log.e(TAG, "Setting downmix unsupported in video player");
+ throw new UnsupportedOperationException("Setting downmix unsupported in video player");
+ }
+
@Override
public void setVideoScalingMode(int mode) {
super.setVideoScalingMode(mode);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java
index 9588265b8..13cb9f002 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java
@@ -1,7 +1,9 @@
package de.danoeh.antennapod.core.util.syndication;
import android.net.Uri;
-import org.apache.commons.lang3.StringUtils;
+import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
+
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@@ -9,7 +11,6 @@ import org.jsoup.select.Elements;
import java.io.File;
import java.io.IOException;
-import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -45,12 +46,12 @@ public class FeedDiscoverer {
}
private Map<String, String> findLinks(Document document, String baseUrl) {
- Map<String, String> res = new LinkedHashMap<String, String>();
+ Map<String, String> res = new ArrayMap<>();
Elements links = document.head().getElementsByTag("link");
for (Element link : links) {
String rel = link.attr("rel");
String href = link.attr("href");
- if (!StringUtils.isEmpty(href) &&
+ if (!TextUtils.isEmpty(href) &&
(rel.equals("alternate") || rel.equals("feed"))) {
String type = link.attr("type");
if (type.equals(MIME_RSS) || type.equals(MIME_ATOM)) {
@@ -58,7 +59,7 @@ public class FeedDiscoverer {
String processedUrl = processURL(baseUrl, href);
if (processedUrl != null) {
res.put(processedUrl,
- (StringUtils.isEmpty(title)) ? href : title);
+ (TextUtils.isEmpty(title)) ? href : title);
}
}
}
diff --git a/core/src/main/res/drawable-hdpi-v11/stat_notify_sync.png b/core/src/main/res/drawable-hdpi-v11/stat_notify_sync.png
deleted file mode 100644
index 90b39c958..000000000
--- a/core/src/main/res/drawable-hdpi-v11/stat_notify_sync.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi-v11/stat_notify_sync_error.png b/core/src/main/res/drawable-hdpi-v11/stat_notify_sync_error.png
deleted file mode 100644
index 074cdee27..000000000
--- a/core/src/main/res/drawable-hdpi-v11/stat_notify_sync_error.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_av_fast_forward_80dp.png b/core/src/main/res/drawable-hdpi/ic_av_fast_forward_80dp.png
new file mode 100755
index 000000000..a32968a19
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_av_fast_forward_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_av_rewind_80dp.png b/core/src/main/res/drawable-hdpi/ic_av_rewind_80dp.png
new file mode 100755
index 000000000..e39de4dcf
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_av_rewind_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_check_box_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_check_box_grey600_24dp.png
new file mode 100644
index 000000000..e4e833242
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_grey600_24dp.png
new file mode 100644
index 000000000..4f1a804b3
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..6dbf83c58
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_check_box_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..d3f636f13
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_drag_handle.9.png b/core/src/main/res/drawable-hdpi/ic_drag_handle.9.png
deleted file mode 100644
index 939454989..000000000
--- a/core/src/main/res/drawable-hdpi/ic_drag_handle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_drag_handle_dark.9.png b/core/src/main/res/drawable-hdpi/ic_drag_handle_dark.9.png
deleted file mode 100644
index 65b9ec1fa..000000000
--- a/core/src/main/res/drawable-hdpi/ic_drag_handle_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_drag_vertical_grey600_48dp.9.png b/core/src/main/res/drawable-hdpi/ic_drag_vertical_grey600_48dp.9.png
new file mode 100644
index 000000000..f2d9906c6
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_drag_vertical_grey600_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_drag_vertical_white_48dp.9.png b/core/src/main/res/drawable-hdpi/ic_drag_vertical_white_48dp.9.png
new file mode 100644
index 000000000..a2a91938d
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_drag_vertical_white_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
index 0c3bb0757..d1ad6629d 100755
--- a/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
index 667300129..9ff662fca 100755
--- a/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_grey600_24dp.png
new file mode 100644
index 000000000..62dd8ef48
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_white_24dp.png
new file mode 100644
index 000000000..5bb43b464
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_skip_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_skip_grey600_36dp.png
new file mode 100644
index 000000000..edbc95b05
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_skip_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_skip_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_skip_white_36dp.png
new file mode 100644
index 000000000..cbfb262d8
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_skip_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sleep_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_sleep_grey600_24dp.png
new file mode 100644
index 000000000..809066499
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_sleep_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sleep_off_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_sleep_off_grey600_24dp.png
new file mode 100644
index 000000000..4496a320d
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_sleep_off_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sleep_off_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_sleep_off_white_24dp.png
new file mode 100644
index 000000000..79684ab65
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_sleep_off_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sleep_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_sleep_white_24dp.png
new file mode 100644
index 000000000..f0df6032c
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_sleep_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sort_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_sort_grey600_24dp.png
new file mode 100644
index 000000000..0a52de9fe
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_sort_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sort_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_sort_white_24dp.png
new file mode 100644
index 000000000..26014a542
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_star_border_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_star_border_grey600_24dp.png
new file mode 100644
index 000000000..006410bc3
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_star_border_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_star_border_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_star_border_white_24dp.png
new file mode 100644
index 000000000..27831192f
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_star_border_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png
new file mode 100644
index 000000000..93f70a024
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_star_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_star_white_24dp.png
new file mode 100644
index 000000000..e8619b780
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_widget_preview.png b/core/src/main/res/drawable-hdpi/ic_widget_preview.png
new file mode 100644
index 000000000..8b583b285
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_widget_preview.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/stat_notify_sync.png b/core/src/main/res/drawable-hdpi/stat_notify_sync.png
index bfb8110fe..ca6c68e4e 100644
--- a/core/src/main/res/drawable-hdpi/stat_notify_sync.png
+++ b/core/src/main/res/drawable-hdpi/stat_notify_sync.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/stat_notify_sync_error.png b/core/src/main/res/drawable-hdpi/stat_notify_sync_error.png
index b340a313e..84f19cd4e 100644
--- a/core/src/main/res/drawable-hdpi/stat_notify_sync_error.png
+++ b/core/src/main/res/drawable-hdpi/stat_notify_sync_error.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi-v11/stat_notify_sync.png b/core/src/main/res/drawable-mdpi-v11/stat_notify_sync.png
deleted file mode 100644
index 1be8677f1..000000000
--- a/core/src/main/res/drawable-mdpi-v11/stat_notify_sync.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi-v11/stat_notify_sync_error.png b/core/src/main/res/drawable-mdpi-v11/stat_notify_sync_error.png
deleted file mode 100644
index 30658c583..000000000
--- a/core/src/main/res/drawable-mdpi-v11/stat_notify_sync_error.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_av_fast_forward_80dp.png b/core/src/main/res/drawable-mdpi/ic_av_fast_forward_80dp.png
new file mode 100755
index 000000000..69b81c10d
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_av_fast_forward_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_av_rewind_80dp.png b/core/src/main/res/drawable-mdpi/ic_av_rewind_80dp.png
new file mode 100755
index 000000000..5355abfd6
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_av_rewind_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_check_box_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_check_box_grey600_24dp.png
new file mode 100644
index 000000000..a6bdce736
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_grey600_24dp.png
new file mode 100644
index 000000000..cd90223d1
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..8a014bda1
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_check_box_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..0811a3493
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_drag_handle.9.png b/core/src/main/res/drawable-mdpi/ic_drag_handle.9.png
deleted file mode 100644
index 8de13a08b..000000000
--- a/core/src/main/res/drawable-mdpi/ic_drag_handle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_drag_handle_dark.9.png b/core/src/main/res/drawable-mdpi/ic_drag_handle_dark.9.png
deleted file mode 100644
index e24681d12..000000000
--- a/core/src/main/res/drawable-mdpi/ic_drag_handle_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_drag_vertical_grey600_48dp.9.png b/core/src/main/res/drawable-mdpi/ic_drag_vertical_grey600_48dp.9.png
new file mode 100644
index 000000000..0d8277204
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_drag_vertical_grey600_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_drag_vertical_white_48dp.9.png b/core/src/main/res/drawable-mdpi/ic_drag_vertical_white_48dp.9.png
new file mode 100644
index 000000000..15d016e89
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_drag_vertical_white_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
index d46b325d8..3932b6eef 100755
--- a/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
index ac94476c2..9c2a1eb6e 100755
--- a/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_grey600_24dp.png
new file mode 100644
index 000000000..52812c42f
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_white_24dp.png
new file mode 100644
index 000000000..20a9fe5d7
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_skip_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_skip_grey600_36dp.png
new file mode 100644
index 000000000..be0fcc765
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_skip_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_skip_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_skip_white_36dp.png
new file mode 100644
index 000000000..893cf2c64
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_skip_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sleep_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_sleep_grey600_24dp.png
new file mode 100644
index 000000000..ea511bf2a
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_sleep_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sleep_off_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_sleep_off_grey600_24dp.png
new file mode 100644
index 000000000..7f631ad86
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_sleep_off_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sleep_off_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_sleep_off_white_24dp.png
new file mode 100644
index 000000000..795e318e3
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_sleep_off_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sleep_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_sleep_white_24dp.png
new file mode 100644
index 000000000..4304a6bca
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_sleep_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sort_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_sort_grey600_24dp.png
new file mode 100644
index 000000000..f527d0094
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_sort_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sort_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_sort_white_24dp.png
new file mode 100644
index 000000000..e28dd4592
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_star_border_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_star_border_grey600_24dp.png
new file mode 100644
index 000000000..dd9d11ba0
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_star_border_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_star_border_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_star_border_white_24dp.png
new file mode 100644
index 000000000..104fb3c9d
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_star_border_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png
new file mode 100644
index 000000000..af84b71f2
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_star_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_star_white_24dp.png
new file mode 100644
index 000000000..0ccebc7c8
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/stat_notify_sync.png b/core/src/main/res/drawable-mdpi/stat_notify_sync.png
index 03ce57a47..516b65bd9 100644
--- a/core/src/main/res/drawable-mdpi/stat_notify_sync.png
+++ b/core/src/main/res/drawable-mdpi/stat_notify_sync.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/stat_notify_sync_error.png b/core/src/main/res/drawable-mdpi/stat_notify_sync_error.png
index f849b5040..c3ed306cf 100644
--- a/core/src/main/res/drawable-mdpi/stat_notify_sync_error.png
+++ b/core/src/main/res/drawable-mdpi/stat_notify_sync_error.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi-v11/stat_notify_sync.png b/core/src/main/res/drawable-xhdpi-v11/stat_notify_sync.png
deleted file mode 100644
index b3bf21ffe..000000000
--- a/core/src/main/res/drawable-xhdpi-v11/stat_notify_sync.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi-v11/stat_notify_sync_error.png b/core/src/main/res/drawable-xhdpi-v11/stat_notify_sync_error.png
deleted file mode 100644
index 33582ef10..000000000
--- a/core/src/main/res/drawable-xhdpi-v11/stat_notify_sync_error.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_av_fast_forward_80dp.png b/core/src/main/res/drawable-xhdpi/ic_av_fast_forward_80dp.png
new file mode 100755
index 000000000..ed34e22a1
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_av_fast_forward_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_av_rewind_80dp.png b/core/src/main/res/drawable-xhdpi/ic_av_rewind_80dp.png
new file mode 100755
index 000000000..3dc7bf5cb
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_av_rewind_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_check_box_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_check_box_grey600_24dp.png
new file mode 100644
index 000000000..f7c205dd2
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_grey600_24dp.png
new file mode 100644
index 000000000..ea2ff8671
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..a615ee436
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..946cfea57
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_drag_handle.9.png b/core/src/main/res/drawable-xhdpi/ic_drag_handle.9.png
deleted file mode 100644
index 46b8a5ad8..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_drag_handle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_drag_handle_dark.9.png b/core/src/main/res/drawable-xhdpi/ic_drag_handle_dark.9.png
deleted file mode 100644
index 864fae9e8..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_drag_handle_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_drag_vertical_grey600_48dp.9.png b/core/src/main/res/drawable-xhdpi/ic_drag_vertical_grey600_48dp.9.png
new file mode 100644
index 000000000..8f335e274
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_drag_vertical_grey600_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_drag_vertical_white_48dp.9.png b/core/src/main/res/drawable-xhdpi/ic_drag_vertical_white_48dp.9.png
new file mode 100644
index 000000000..ae9972926
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_drag_vertical_white_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
index b25d64863..995aafb5c 100755
--- a/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
index 3c3e74c1d..1495c4fa6 100755
--- a/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_grey600_24dp.png
new file mode 100644
index 000000000..e67d41cc1
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_white_24dp.png
new file mode 100644
index 000000000..48e52d596
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_skip_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_skip_grey600_36dp.png
new file mode 100644
index 000000000..2e291dd19
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_skip_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_skip_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_skip_white_36dp.png
new file mode 100644
index 000000000..fa85f1899
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_skip_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sleep_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_sleep_grey600_24dp.png
new file mode 100644
index 000000000..ae0787a26
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_sleep_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sleep_off_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_sleep_off_grey600_24dp.png
new file mode 100644
index 000000000..026224c5e
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_sleep_off_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sleep_off_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_sleep_off_white_24dp.png
new file mode 100644
index 000000000..ef39d7279
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_sleep_off_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sleep_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_sleep_white_24dp.png
new file mode 100644
index 000000000..07e156172
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_sleep_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sort_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_sort_grey600_24dp.png
new file mode 100644
index 000000000..f2ef499ef
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_sort_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png
new file mode 100644
index 000000000..68b0b7ad3
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_star_border_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_star_border_grey600_24dp.png
new file mode 100644
index 000000000..5160319b5
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_star_border_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_star_border_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_star_border_white_24dp.png
new file mode 100644
index 000000000..33f9727d3
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_star_border_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png
new file mode 100644
index 000000000..7a09ebc33
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_star_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_star_white_24dp.png
new file mode 100644
index 000000000..288799f93
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/stat_notify_sync.png b/core/src/main/res/drawable-xhdpi/stat_notify_sync.png
new file mode 100644
index 000000000..ab02927fb
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/stat_notify_sync.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/stat_notify_sync_error.png b/core/src/main/res/drawable-xhdpi/stat_notify_sync_error.png
new file mode 100644
index 000000000..c1f483f71
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/stat_notify_sync_error.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_av_fast_forward_80dp.png b/core/src/main/res/drawable-xxhdpi/ic_av_fast_forward_80dp.png
new file mode 100755
index 000000000..f90617f45
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_av_fast_forward_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_av_rewind_80dp.png b/core/src/main/res/drawable-xxhdpi/ic_av_rewind_80dp.png
new file mode 100755
index 000000000..81709e0ae
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_av_rewind_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_check_box_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_check_box_grey600_24dp.png
new file mode 100644
index 000000000..5e52fa65e
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_grey600_24dp.png
new file mode 100644
index 000000000..9c8615618
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.png
new file mode 100644
index 000000000..565a755f3
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.png
new file mode 100644
index 000000000..7b1d9ea34
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_drag_handle.9.png b/core/src/main/res/drawable-xxhdpi/ic_drag_handle.9.png
deleted file mode 100644
index 0e99bde9e..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_drag_handle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_drag_handle_dark.9.png b/core/src/main/res/drawable-xxhdpi/ic_drag_handle_dark.9.png
deleted file mode 100644
index 0da191a69..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_drag_handle_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_drag_vertical_grey600_48dp.9.png b/core/src/main/res/drawable-xxhdpi/ic_drag_vertical_grey600_48dp.9.png
new file mode 100644
index 000000000..88a800f24
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_drag_vertical_grey600_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_drag_vertical_white_48dp.9.png b/core/src/main/res/drawable-xxhdpi/ic_drag_vertical_white_48dp.9.png
new file mode 100644
index 000000000..148891e6c
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_drag_vertical_white_48dp.9.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
index aacf24d28..ddfe32b53 100755
--- a/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
index 625dbaa1f..4929a6a11 100755
--- a/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_grey600_24dp.png
new file mode 100644
index 000000000..c2300b53b
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_white_24dp.png
new file mode 100644
index 000000000..66a710d8c
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_skip_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_skip_grey600_36dp.png
new file mode 100644
index 000000000..00a55a0f8
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_skip_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_skip_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_skip_white_36dp.png
new file mode 100644
index 000000000..ac38e6d42
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_skip_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sleep_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_sleep_grey600_24dp.png
new file mode 100644
index 000000000..f4bd9e94d
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_sleep_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sleep_off_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_sleep_off_grey600_24dp.png
new file mode 100644
index 000000000..6c42d6051
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_sleep_off_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sleep_off_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_sleep_off_white_24dp.png
new file mode 100644
index 000000000..b8e06f9b3
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_sleep_off_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sleep_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_sleep_white_24dp.png
new file mode 100644
index 000000000..e2249c357
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_sleep_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sort_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_sort_grey600_24dp.png
new file mode 100644
index 000000000..6cdc649ea
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_sort_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png
new file mode 100644
index 000000000..56ea13fe2
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_star_border_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_star_border_grey600_24dp.png
new file mode 100644
index 000000000..6348e1997
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_star_border_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_star_border_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_star_border_white_24dp.png
new file mode 100644
index 000000000..aab4831ff
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_star_border_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png
new file mode 100644
index 000000000..ef0294931
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png
new file mode 100644
index 000000000..de4b7b29d
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/stat_notify_sync.png b/core/src/main/res/drawable-xxhdpi/stat_notify_sync.png
new file mode 100644
index 000000000..9cd2a53b4
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/stat_notify_sync.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/stat_notify_sync_error.png b/core/src/main/res/drawable-xxhdpi/stat_notify_sync_error.png
new file mode 100644
index 000000000..bb76c2756
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/stat_notify_sync_error.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_av_fast_forward_80dp.png b/core/src/main/res/drawable-xxxhdpi/ic_av_fast_forward_80dp.png
new file mode 100755
index 000000000..c9b68abf0
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_av_fast_forward_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_av_rewind_80dp.png b/core/src/main/res/drawable-xxxhdpi/ic_av_rewind_80dp.png
new file mode 100755
index 000000000..87b0756eb
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_av_rewind_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_skip_grey600_36dp.png b/core/src/main/res/drawable-xxxhdpi/ic_skip_grey600_36dp.png
new file mode 100644
index 000000000..94836e0c8
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_skip_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_skip_white_36dp.png b/core/src/main/res/drawable-xxxhdpi/ic_skip_white_36dp.png
new file mode 100644
index 000000000..a84f34228
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_skip_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_sleep_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_sleep_grey600_24dp.png
new file mode 100644
index 000000000..9c0116c60
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_sleep_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_sleep_off_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_sleep_off_grey600_24dp.png
new file mode 100644
index 000000000..e8141d0df
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_sleep_off_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_sleep_off_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_sleep_off_white_24dp.png
new file mode 100644
index 000000000..ca41ad5e6
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_sleep_off_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_sleep_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_sleep_white_24dp.png
new file mode 100644
index 000000000..29782e155
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_sleep_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_star_border_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_star_border_grey600_24dp.png
new file mode 100644
index 000000000..1109e95c8
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_star_border_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_star_border_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_star_border_white_24dp.png
new file mode 100644
index 000000000..086cb677c
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_star_border_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png
new file mode 100644
index 000000000..dda3262ed
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png
new file mode 100644
index 000000000..ba29292b0
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable/progress_bar_horizontal_dark.xml b/core/src/main/res/drawable/progress_bar_horizontal_dark.xml
new file mode 100644
index 000000000..73ad5b37c
--- /dev/null
+++ b/core/src/main/res/drawable/progress_bar_horizontal_dark.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background">
+ <shape>
+ <solid android:color="#33FFFFFF"/>
+ </shape>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape>
+ <solid android:color="@color/holo_blue_dark"/>
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/core/src/main/res/drawable/progress_bar_horizontal_light.xml b/core/src/main/res/drawable/progress_bar_horizontal_light.xml
new file mode 100644
index 000000000..a1a7129e9
--- /dev/null
+++ b/core/src/main/res/drawable/progress_bar_horizontal_light.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background">
+ <shape>
+ <solid android:color="#33212121"/>
+ </shape>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape>
+ <solid android:color="@color/holo_blue_light"/>
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/core/src/main/res/values-az/strings.xml b/core/src/main/res/values-az/strings.xml
index b52ecf4a4..d305e8127 100644
--- a/core/src/main/res/values-az/strings.xml
+++ b/core/src/main/res/values-az/strings.xml
@@ -4,7 +4,6 @@
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Kanallar</string>
<string name="podcasts_label">PODKASTLAR</string>
- <string name="episodes_label">EPIZODLAR</string>
<string name="new_label">Yeni</string>
<string name="waiting_list_label">Gözləmədə</string>
<string name="settings_label">Parametrlər</string>
@@ -38,7 +37,6 @@
<string name="length_prefix">Müddət:\u0020</string>
<string name="size_prefix">Ölçü:\u0020</string>
<string name="processing_label">Hazırlaşma</string>
- <string name="loading_label">Yükləmə...</string>
<string name="close_label">Bağla</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Kanalın URLı</string>
@@ -46,7 +44,6 @@
<string name="mark_all_read_label">Hamısını oxunmuş kimi işarələ</string>
<string name="show_info_label">Məlumatı göstər</string>
<string name="share_link_label">Web-səhifəyi paylaş</string>
- <string name="share_source_label">Kanalı paylaş</string>
<string name="feed_delete_confirmation_msg">Bütün kanallar və epizodlar silinəçək.</string>
<!--actions on feeditems-->
<string name="download_label">Yüklə</string>
@@ -81,7 +78,6 @@
<string name="download_error_malformed_url">Yanlış URL</string>
<string name="download_error_io_error">IO xətasi</string>
<string name="download_error_request_error">Tələbin xətası</string>
- <string name="downloads_left">\u0020yükləmə galdı</string>
<string name="download_notification_title">Podkast məlumatların yüklənişi</string>
<string name="download_report_content">%1$d yükləmə uğurludur, %2$d uğursuzdur</string>
<string name="download_log_title_unknown">Naməlum başliğ</string>
@@ -130,12 +126,9 @@
<string name="about_pref">Proqram haqqinda</string>
<string name="queue_label">Növbə</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Qulaqliqı ayiranda oynatma dayanacağ</string>
<string name="pref_followQueue_sum">Oynatma başa çatanda növbədə irəlidəki epizodu oynat</string>
<string name="playback_pref">Oynatma</string>
<string name="network_pref">Şəbəkə</string>
- <string name="pref_autoUpdateIntervall_title">Təzələmə intervalı</string>
- <string name="pref_autoUpdateIntervall_sum">Kanalın avtomatik təzələməsinin intervalını seç ya da keçir onu</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Təkçə Wi-Fi vasitəsiilə yüklə</string>
<string name="pref_followQueue_title">Fasiləsiz oynatma</string>
<string name="pref_downloadMediaOnWifiOnly_title">Wi-Fi vasitəsiilə yükləmə</string>
@@ -182,7 +175,6 @@
<string name="select_all_label">Hamısını seç</string>
<string name="deselect_all_label">Seçimi ləğv et</string>
<string name="opml_export_label">OPML ixraçı</string>
- <string name="exporting_label">İxrac...</string>
<string name="export_error_label">İxracın xətası</string>
<string name="opml_export_success_sum">OPML fayl:\u0020 yazılıb</string>
<!--Sleep timer-->
@@ -207,8 +199,10 @@
<string name="folder_not_empty_dialog_msg">Seçilən qovluq boş deyil. Mediya yükləmələr və başka fayllar bu qovluqa yazılacaqlar. Necə olsa davam olsunmu?</string>
<string name="set_to_default_folder">Başlanğıc qovluqu seç</string>
<!--Online feed view-->
- <string name="downloading_label">Yükləmə...</string>
<!--Content descriptions for image buttons-->
<!--Feed information screen-->
+ <!--Progress information-->
<!--AntennaPodSP-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-ca/strings.xml b/core/src/main/res/values-ca/strings.xml
index be7a73e6d..290c16176 100644
--- a/core/src/main/res/values-ca/strings.xml
+++ b/core/src/main/res/values-ca/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Canals</string>
<string name="add_feed_label">Afegeix podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISODIS</string>
+ <string name="episodes_label">Episodis</string>
<string name="new_episodes_label">Episodis nous</string>
<string name="all_episodes_label">Tots els episodis</string>
+ <string name="all_episodes_short_label">Tot</string>
+ <string name="favorite_episodes_label">Favorits</string>
<string name="new_label">Nous</string>
<string name="waiting_list_label">Llista d\'espera</string>
<string name="settings_label">Configuració</string>
@@ -26,6 +28,14 @@
<!--Main activity-->
<string name="drawer_open">Obre menú</string>
<string name="drawer_close">Tanca menú</string>
+ <string name="drawer_preferences">Calaix de preferències</string>
+ <string name="drawer_feed_order_unplayed_episodes">Ordre per durada</string>
+ <string name="drawer_feed_order_alphabetical">Ordre alfabètic</string>
+ <string name="drawer_feed_order_last_update">Ordre per data de publicació</string>
+ <string name="drawer_feed_counter_new_unplayed">Número d\'episodis nous i per reproduir</string>
+ <string name="drawer_feed_counter_new">Número de episodis nous</string>
+ <string name="drawer_feed_counter_unplayed">Número de episodis per reproduir</string>
+ <string name="drawer_feed_counter_none">Cap</string>
<!--Webview actions-->
<string name="open_in_browser_label">Obre en un navegador</string>
<string name="copy_url_label">Copia l\'enllaç</string>
@@ -37,8 +47,11 @@
<!--Other-->
<string name="confirm_label">D\'acord</string>
<string name="cancel_label">Cancel·la</string>
+ <string name="yes">Sí</string>
+ <string name="no">No</string>
<string name="author_label">Autor</string>
<string name="language_label">Llengua</string>
+ <string name="url_label">Adreça web</string>
<string name="podcast_settings_label">Configuració</string>
<string name="cover_label">Imatge</string>
<string name="error_label">Error</string>
@@ -53,11 +66,24 @@
<string name="length_prefix">Durada:\u0020</string>
<string name="size_prefix">Mida:\u0020</string>
<string name="processing_label">S\'està processant</string>
- <string name="loading_label">S\'està carregant...</string>
<string name="save_username_password_label">Desa nom d\'usuari i contrasenya</string>
<string name="close_label">Tanca</string>
<string name="retry_label">Reintenta</string>
<string name="auto_download_label">Inclou a baixades automàtiques</string>
+ <string name="auto_download_apply_to_items_title">Aplica als episodis previs</string>
+ <string name="auto_download_apply_to_items_message">El nou ajustament de <i>baixada automàtica</i> s\'aplicarà als nous episodis.\nVols que també sigui aplicat als episodis publicats prèviament?</string>
+ <string name="auto_delete_label">Esborra episodis automàticament\n(anula la preferència global)</string>
+ <string name="parallel_downloads_suffix">\u0020baixades paral·leles</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Sempre</string>
+ <string name="feed_auto_download_never">Mai</string>
+ <string name="episode_cleanup_never">Mai</string>
+ <string name="episode_cleanup_queue_removal">Quan no està a la cua</string>
+ <string name="episode_cleanup_after_listening">Després d\'acabar</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dia després d\'acabar</item>
+ <item quantity="other">%d dies després d\'acabar</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Enllaç del canal</string>
<string name="etxtFeedurlHint">URL, canal o lloc web</string>
@@ -68,13 +94,30 @@
<!--Actions on feeds-->
<string name="mark_all_read_label">Marca-ho tot com a llegit</string>
<string name="mark_all_read_msg">S\'han marcat tots els episodis com a llegits</string>
+ <string name="mark_all_read_confirmation_msg">Si us plau confirma que vols marcar tots els episodis com reproduits.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Si us plau confirma que vols marcar tots els episodis d\'aquest canal com reproduits.</string>
+ <string name="mark_all_seen_label">Marca tot com a llegit</string>
<string name="show_info_label">Mostra informació</string>
<string name="remove_feed_label">Esborra podcast</string>
- <string name="share_link_label">Comparteix l\'enllaç de la plana</string>
- <string name="share_source_label">Comparteix l\'enllaç del canal</string>
+ <string name="share_link_label">Comparteix l\'enllaç</string>
+ <string name="share_link_with_position_label">Comparteix enllaç amb posició</string>
+ <string name="share_feed_url_label">Comparteix adreça del canal</string>
+ <string name="share_item_url_label">Comparteix adreça d\'Episodi</string>
+ <string name="share_item_url_with_position_label">Comparteix adreça d\'Episodi amb posició</string>
<string name="feed_delete_confirmation_msg">Confirmeu que, efectivament, voleu suprimir aquest canal i tots els episodis que us n\'heu baixat.</string>
<string name="feed_remover_msg">S\'està esborrant el canal</string>
<string name="load_complete_feed">S\'ha actualitzat el canal</string>
+ <string name="hide_episodes_title">Amaga Episodis</string>
+ <string name="episode_actions">Aplica accions</string>
+ <string name="hide_unplayed_episodes_label">Per reproduir</string>
+ <string name="hide_paused_episodes_label">Pausat</string>
+ <string name="hide_played_episodes_label">Reproduit</string>
+ <string name="hide_queued_episodes_label">En cua</string>
+ <string name="hide_not_queued_episodes_label">No en cua</string>
+ <string name="hide_downloaded_episodes_label">Baixat</string>
+ <string name="hide_not_downloaded_episodes_label">No baixat</string>
+ <string name="filtered_label">Filtrat</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Darrera actualització fallida</string>
<!--actions on feeditems-->
<string name="download_label">Baixa</string>
<string name="play_label">Reprodueix</string>
@@ -84,14 +127,22 @@
<string name="remove_label">Suprimeix</string>
<string name="remove_episode_lable">Esborra episodi</string>
<string name="mark_read_label">Marca com a llegit</string>
+ <string name="marked_as_read_label">Marcats com llegits</string>
<string name="mark_unread_label">Marca com a pendent</string>
<string name="add_to_queue_label">Afegeix a la cua</string>
+ <string name="added_to_queue_label">Afegit a la cua</string>
<string name="remove_from_queue_label">Suprimeix de la cua</string>
+ <string name="add_to_favorite_label">Afegit a Favorits</string>
+ <string name="remove_from_favorite_label">Suprimeix de Favorits</string>
<string name="visit_website_label">Visita el lloc web</string>
<string name="support_label">Comparteix amb Flattr</string>
<string name="enqueue_all_new">Posa-ho tot a la cua</string>
<string name="download_all">Baixa-ho tot</string>
<string name="skip_episode_label">Omet l\'episodi</string>
+ <string name="activate_auto_download">Activa baixades automàtiques</string>
+ <string name="deactivate_auto_download">Desactiva les baixades automàtiques</string>
+ <string name="reset_position">Restablir posició de reproducció</string>
+ <string name="removed_item">Element eliminat</string>
<!--Download messages and labels-->
<string name="download_successful">ha funcionat</string>
<string name="download_failed">ha fallat</string>
@@ -109,12 +160,13 @@
<string name="download_error_unauthorized">Error d\'autenticació</string>
<string name="cancel_all_downloads_label">Cancel·la totes les baixades</string>
<string name="download_canceled_msg">S\'ha cancel·lat la baixada</string>
- <string name="download_report_title">Baixades completades</string>
+ <string name="download_canceled_autodownload_enabled_msg">Baixada cancel·lada\nDesactivada les <i>baixades automàtiques</i> per aquest element</string>
+ <string name="download_report_title">Baixades completades amb error(s)</string>
+ <string name="download_report_content_title">Registre de baixades</string>
<string name="download_error_malformed_url">URL mal formatada</string>
<string name="download_error_io_error">Error d\'E/S</string>
<string name="download_error_request_error">Error de petició</string>
<string name="download_error_db_access">Error d\'accés a la base de dades</string>
- <string name="downloads_left">\u0020Baixades pendents</string>
<string name="downloads_processing">S\'estan processant les baixades</string>
<string name="download_notification_title">S\'estan baixant les dades del podcast</string>
<string name="download_report_content">%1$d baixades finalitzades, %2$d fallides</string>
@@ -125,6 +177,11 @@
<string name="download_request_error_dialog_message_prefix">S\'ha produït un error en intentar baixar el fitxer:\u0020</string>
<string name="authentication_notification_title">Cal autenticar-se</string>
<string name="authentication_notification_msg">Es necessita un usuari i una contrasenya per accedir al recurs</string>
+ <string name="confirm_mobile_download_dialog_title">Confirma baixada mòvil</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">La baixada amb conexió de dades mòvils està desactivada.\n\nPots escollir ja sigui només afegir l\'episodi a la cua o pot permetre la descàrrega de forma temporal.\n\n<small>La seva elecció serà recordada durant 10 minuts.</small></string>
+ <string name="confirm_mobile_download_dialog_message">La baixada amb conexió de dades mòvils està desactivada.\n\nVols permetre la descàrrega de forma temporal?\n\n<small>La seva elecció serà recordada durant 10 minuts.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">En cua</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Permetre temporalment</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Error</string>
<string name="player_stopped_msg">No s\'està reproduint res</string>
@@ -139,6 +196,8 @@
<string name="playbackservice_notification_title">Podcast en reproducció</string>
<string name="unknown_media_key">AntennaPod - Control desconegut: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Bloqueja la cua</string>
+ <string name="unlock_queue">Desbloqueja la cua</string>
<string name="clear_queue_label">Buida la cua</string>
<string name="undo">Desfés</string>
<string name="removed_from_queue">Ítem esborrat</string>
@@ -150,6 +209,7 @@
<string name="duration">Durada</string>
<string name="ascending">Ascendent</string>
<string name="descending">Descendent</string>
+ <string name="clear_queue_confirmation_msg">Si us plau confirma que vols suprimir tots els episodis de la cua</string>
<!--Flattr-->
<string name="flattr_auth_label">Inici de sessió a Flattr</string>
<string name="flattr_auth_explanation">Premeu el botó per iniciar el procés d\'autenticació. Quan s\'obri la pantalla d\'inici de sessió de Flattr al vostre navegador, introduïu les vostres credencials i concediu a AntennaPod els permisos de compartir mitjançant Flattr. En finalitzar el procés, tornareu automàticament a aquesta pantalla.</string>
@@ -179,8 +239,8 @@
<!--Variable Speed-->
<string name="download_plugin_label">Baixa el connector</string>
<string name="no_playback_plugin_title">Connector no instal·lat</string>
- <string name="no_playback_plugin_msg">Per a què funcioni la velocitat de reproducció variable, cal instal·lar una biblioteca addicional.\n\nFeu un toc a «Baixa el connector» per baixar-vos el connector gratuït des de la Play Store.\n\nQualsevol problema que sorgeixi en utilitzar aquest connector no és culpa de l\'AntennaPod. Cal informar-ne, doncs, al propietari del connector.</string>
<string name="set_playback_speed_label">Velocitats de reproducció</string>
+ <string name="enable_sonic">Activa Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">No hi ha elements a la llista.</string>
<string name="no_feeds_label">No us heu subscrit a cap canal.</string>
@@ -190,15 +250,21 @@
<string name="queue_label">Cua</string>
<string name="services_label">Serveis</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pausa la reproducció en desconnectar els auriculars.</string>
+ <string name="pref_episode_cleanup_title">Neteja l\'episodi</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Continua la reproducció en connectar novament els auriculars</string>
<string name="pref_followQueue_sum">Salta al següent element de la cua en acabar la reproducció</string>
<string name="pref_auto_delete_sum">Suprimeix l\'episodi quan s\'acabi de reproduir</string>
<string name="pref_auto_delete_title">Esborrat automàtic</string>
+ <string name="pref_smart_mark_as_played_sum">Marca episodis com com reproduits, encara que quedi menys d\'una certa quantitat de segons de temps</string>
+ <string name="pref_smart_mark_as_played_title">Marcació intel·ligent com a reproduit</string>
<string name="playback_pref">Reproducció</string>
<string name="network_pref">Xarxa</string>
- <string name="pref_autoUpdateIntervall_title">Interval d\'actualització</string>
- <string name="pref_autoUpdateIntervall_sum">Especifiqueu l\'interval en què els canals s\'actualitzen de forma automàtica, o deshabiliteu la funcionalitat.</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Actualitza interval o horari del dia</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Especifica un interval o una hora específica del dia per refrescar els canals de forma automàtica</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Pots establir un <i>interval</i> com \"cada 2 hores\", establir una <i>hora del dia</i> com \"7:00 AM\" o <i>disactiva</i> l\'actualització automàtica del conjunt.\n\n<small>Pren nota: Les actualitzacions de vegades són inexactes. Es pot trobar una breu demora.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Desactivar</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Establir interval</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Establir hora del dia</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Només baixa fitxers a través d\'una xarxa sense fils</string>
<string name="pref_followQueue_title">Reproducció continuada</string>
<string name="pref_downloadMediaOnWifiOnly_title">Baixa a través de xarxes sense fils</string>
@@ -218,6 +284,14 @@
<string name="pref_auto_flattr_sum">Configura la compartició automàtica per Flattr</string>
<string name="user_interface_label">Interfície d\'usuari</string>
<string name="pref_set_theme_title">Selecció de tema</string>
+ <string name="pref_nav_drawer_title">Personalitzar navegació del calaix</string>
+ <string name="pref_nav_drawer_sum">Personalitza l\'aparença del calaix de navegació.</string>
+ <string name="pref_nav_drawer_items_title">Establir els elements del calaix de navegació</string>
+ <string name="pref_nav_drawer_items_sum">Canviar els elements que apareixen al calaix de navegació.</string>
+ <string name="pref_nav_drawer_feed_order_title">Establir ordre de subscripció</string>
+ <string name="pref_nav_drawer_feed_order_sum">Canviar l\'ordre de les subscripcions</string>
+ <string name="pref_nav_drawer_feed_counter_title">Establir contador de subscripció</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Canviar la informació mostrada pel comptador de subscripció</string>
<string name="pref_set_theme_sum">Canvieu l\'aparença d\'AntennaPod.</string>
<string name="pref_automatic_download_title">Baixada automàtica</string>
<string name="pref_automatic_download_sum">Configureu la baixada automàtica d\'episodis.</string>
@@ -225,6 +299,7 @@
<string name="pref_autodl_wifi_filter_sum">Permet les baixades automàtiques només per a les xarxes sense fils seleccionades.</string>
<string name="pref_automatic_download_on_battery_title">Baixa mentre no es carrega</string>
<string name="pref_automatic_download_on_battery_sum">Permet les baixades automàtiques mentre la bateria no es carrega</string>
+ <string name="pref_parallel_downloads_title">Baixades paral·leles</string>
<string name="pref_episode_cache_title">Memòria d\'episodis</string>
<string name="pref_theme_title_light">Clar</string>
<string name="pref_theme_title_dark">Fosc</string>
@@ -240,15 +315,26 @@
<string name="pref_gpodnet_setlogin_information_sum">Canvia les dades d\'inici de sessió del vostre compte de gpodder.net</string>
<string name="pref_playback_speed_title">Velocitats de reproducció</string>
<string name="pref_playback_speed_sum">Personalitzeu les velocitats disponibles per a una velocitat de reproducció d\'àudio variable</string>
- <string name="pref_seek_delta_title">Salta a l\'instant</string>
- <string name="pref_seek_delta_sum">Salta aquesta quantitat de segons en rebobinar o en avançar ràpidament</string>
+ <string name="pref_fast_forward">Temps d\'avançada ràpida</string>
+ <string name="pref_rewind">Temps de rebobinat</string>
<string name="pref_gpodnet_sethostname_title">Definex nom del servidor</string>
<string name="pref_gpodnet_sethostname_use_default_host">Utilitza el servidor per defecte</string>
<string name="pref_expandNotify_title">Amplia la notificació</string>
<string name="pref_expandNotify_sum">Amplia sempre les notificacions per mostrar els botons de reproducció.</string>
<string name="pref_persistNotify_title">Botons de reproducció persistents</string>
<string name="pref_persistNotify_sum">Mantén els controls a l\'àrea de notificacions i pantalla de bloqueig quan la reproducció estigui aturada</string>
+ <string name="pref_lockscreen_background_title">Establir fons de la pantalla de bloqueig</string>
+ <string name="pref_lockscreen_background_sum">Establir el fons de pantalla de bloqueig a la imatge de l\'episodi actual. Com efecte secundari, això també mostrarà la imatge en aplicacions de tercers.</string>
+ <string name="pref_showDownloadReport_title">Mostra informació de baixades</string>
+ <string name="pref_showDownloadReport_sum">Si les descàrregues fallen, genera un informe que mostra els detalls de la falla.</string>
<string name="pref_expand_notify_unsupport_toast">Les versions d\'Android anteriors a la 4.1 no suporten les notificacions ampliades.</string>
+ <string name="pref_queueAddToFront_sum">Afegir nous episodis a la part davantera de la cua.</string>
+ <string name="pref_queueAddToFront_title">Cua al davant</string>
+ <string name="pref_smart_mark_as_played_disabled">Desactivar</string>
+ <string name="pref_image_cache_size_title">Mida de la memòria cau de les imatges</string>
+ <string name="pref_image_cache_size_sum">Mida de la memòria cau en el disc de les imatges.</string>
+ <string name="experimental_pref">Experimental</string>
+ <string name="pref_sonic_title">Reproductor multimèdia Sonic</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Activa la compartició automàtica per Flattr</string>
<string name="auto_flattr_after_percent">Comparteix per Flattr l\'episodi en haver-ne reproduït el %d per cent</string>
@@ -263,6 +349,10 @@
<string name="found_in_title_label">Trobat al títol</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Els fitxers OPML us permeten moure els podcasts d\'un gestor de podcasts a un altre.</string>
+ <string name="opml_import_option">Opció %1$d</string>
+ <string name="opml_import_explanation_1">Trieu una ruta de fitxer específic del sistema de fitxers local.</string>
+ <string name="opml_import_explanation_2">Utilitzeu una aplicació externa com Dropbox, Google Drive o el seu gestor de fitxers preferit per obrir un fitxer OPML.</string>
+ <string name="opml_import_explanation_3">Algunes aplicacions com Google Mail, Dropbox, Google Drive i la majoria dels administradors d\'arxius pot <i>obrir</i> arxius OPML <i>amb</i> AntennaPod.</string>
<string name="start_import_label">Inicia la importació</string>
<string name="opml_import_label">Importació OPML</string>
<string name="opml_directory_error">Error!</string>
@@ -271,8 +361,9 @@
<string name="opml_import_error_dir_empty">El directori d\'importacions és buit.</string>
<string name="select_all_label">Selecciona-ho tot</string>
<string name="deselect_all_label">Deselecciona-ho tot</string>
+ <string name="choose_file_from_filesystem">Des de sistema d\'arxius local</string>
+ <string name="choose_file_from_external_application">Utilitza aplicació externa</string>
<string name="opml_export_label">Exportació OPML</string>
- <string name="exporting_label">S\'està exportant...</string>
<string name="export_error_label">Error d\'exportació</string>
<string name="opml_export_success_title">S\'ha exportat l\'OPML correctament.</string>
<string name="opml_export_success_sum">El fitxer OPML s\'ha escrit a:\u0020</string>
@@ -283,9 +374,24 @@
<string name="sleep_timer_label">Temporitzador</string>
<string name="time_left_label">Temps restant:\u0020</string>
<string name="time_dialog_invalid_input">L\'entrada no és vàlida, ja que el temps ha de ser un nombre i no ho és</string>
- <string name="time_unit_seconds">segons</string>
- <string name="time_unit_minutes">minuts</string>
- <string name="time_unit_hours">hores</string>
+ <string name="timer_about_to_expire_label"><b>Quan el temporitzador està a punt d\'expirar:</b></string>
+ <string name="shake_to_reset_label">Agitar per reiniciar el temporitzador</string>
+ <string name="timer_vibration_label">Vibrar</string>
+ <string name="time_seconds">segons</string>
+ <string name="time_minutes">minuts</string>
+ <string name="time_hours">hores</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 segon</item>
+ <item quantity="other">%d segons</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minut</item>
+ <item quantity="other">%d minuts</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 hora</item>
+ <item quantity="other">%d hores</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORIES</string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
@@ -328,10 +434,12 @@
<string name="set_to_default_folder">Selecciona la carpeta per defecte</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pausa la reproducció en lloc de baixar el volum quan una altra app necessiti reproduir sons</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pausa en interrompre</string>
+ <string name="pref_resumeAfterCall_sum">Reprendre la reproducció després d\'una trucada telefònica</string>
+ <string name="pref_resumeAfterCall_title">Reprendre després d\'una trucada</string>
+ <string name="pref_restart_required">AntennaPod s\'ha de reiniciar perquè aquest canvi tingui efecte.</string>
<!--Online feed view-->
<string name="subscribe_label">Subscriu</string>
<string name="subscribed_label">Subscrit</string>
- <string name="downloading_label">S\'està baixant...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Mostra els capítols</string>
<string name="show_shownotes_label">Mostra les notes del programa</string>
@@ -354,6 +462,29 @@
<!--Feed information screen-->
<string name="authentication_label">Autenticació</string>
<string name="authentication_descr">Canvieu el nom d\'usuari i contrasenya per a aquest podcast i els seus episodis.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Actualització de la base de dades</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">S\'estan important les subscripcions des de les apps de propòsit únic...</string>
+ <string name="search_itunes_label">Cerca a iTunes</string>
+ <string name="all_label">Tot</string>
+ <string name="selected_all_label">Selecciona tots els episodis</string>
+ <string name="none_label">Cap</string>
+ <string name="deselected_all_label">Deselecciona tots els episodis</string>
+ <string name="played_label">Reproduit</string>
+ <string name="selected_played_label">Selecciona episodis reproduits</string>
+ <string name="unplayed_label">Per reproduir</string>
+ <string name="selected_unplayed_label">Selecciona episodis sense reproduir</string>
+ <string name="downloaded_label">Baixat</string>
+ <string name="selected_downloaded_label">Selecciona episodis descarregats</string>
+ <string name="not_downloaded_label">No baixat</string>
+ <string name="selected_not_downloaded_label">Selecciona episodis sense descarregar</string>
+ <string name="sort_title_a_z">Títol (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Títol (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Data (Nou \u2192 Antic)</string>
+ <string name="sort_date_old_new">Data (Antic \u2192 Nou)</string>
+ <string name="sort_duration_short_long">Duració (Curt \u2192 Llarg)</string>
+ <string name="sort_duration_long_short">Duration (Llarg \u2192 Curt)</string>
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-cs-rCZ/strings.xml b/core/src/main/res/values-cs-rCZ/strings.xml
index 661faefbe..335232675 100644
--- a/core/src/main/res/values-cs-rCZ/strings.xml
+++ b/core/src/main/res/values-cs-rCZ/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Kanály</string>
<string name="add_feed_label">Přidat podcast</string>
<string name="podcasts_label">PODCASTY</string>
- <string name="episodes_label">EPIZODY</string>
+ <string name="episodes_label">Epizody</string>
<string name="new_episodes_label">Nové epizody</string>
<string name="all_episodes_label">Všechny epizody</string>
+ <string name="all_episodes_short_label">Vše</string>
+ <string name="favorite_episodes_label">Oblíbené</string>
<string name="new_label">Nový</string>
<string name="waiting_list_label">Seznam nepřečtených</string>
<string name="settings_label">Nastavení</string>
@@ -20,12 +22,23 @@
<string name="playback_history_label">Historie přehrávání</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">Login pro gpodder.net</string>
+ <string name="free_space_label">%1$s volné</string>
+ <string name="episode_cache_full_title">Odkládací prostor pro epizody je plný</string>
+ <string name="episode_cache_full_message">Došlo k zaplnění limitu odkládacího prostoru pro epizody. Můžete navýšit vyhrazený prostor v Nastavení.</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Nedávno zveřejněné</string>
<string name="episode_filter_label">Zobrazit pouze nové epizody</string>
<!--Main activity-->
<string name="drawer_open">Otevřít menu</string>
<string name="drawer_close">Zavřít menu</string>
+ <string name="drawer_preferences">Nastavení panelu</string>
+ <string name="drawer_feed_order_unplayed_episodes">Řadit dle počtu</string>
+ <string name="drawer_feed_order_alphabetical">Řadit abecedně</string>
+ <string name="drawer_feed_order_last_update">Řadit dle data zveřejnění</string>
+ <string name="drawer_feed_counter_new_unplayed">Počet nových a nepřehraných epizod</string>
+ <string name="drawer_feed_counter_new">Počet nových epizod</string>
+ <string name="drawer_feed_counter_unplayed">Počet nepřehraných epizod</string>
+ <string name="drawer_feed_counter_none">Žádné</string>
<!--Webview actions-->
<string name="open_in_browser_label">Otevřít v prohlížeči</string>
<string name="copy_url_label">Kopírovat URL</string>
@@ -37,8 +50,12 @@
<!--Other-->
<string name="confirm_label">Potvrdit</string>
<string name="cancel_label">Zrušit</string>
+ <string name="yes">Ano</string>
+ <string name="no">Ne
+</string>
<string name="author_label">Autor</string>
<string name="language_label">Jazyk</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Nastavení</string>
<string name="cover_label">Obrázek</string>
<string name="error_label">Chyba</string>
@@ -53,11 +70,27 @@
<string name="length_prefix">Délka:\u0020</string>
<string name="size_prefix">Velikost:\u0020</string>
<string name="processing_label">Zpracovávám</string>
- <string name="loading_label">Načítám...</string>
+ <string name="loading_label">Načítání</string>
<string name="save_username_password_label">Uložit uživatelské jméno a heslo</string>
<string name="close_label">Zavřít</string>
<string name="retry_label">Zkusit znovu</string>
<string name="auto_download_label">Zahrnout do automaticky stahovaných</string>
+ <string name="auto_download_apply_to_items_title">Použít na předchozí epizody</string>
+ <string name="auto_download_apply_to_items_message">Nové nastavení <i>automatického stahování</i> bude použito pro nové epizody.\nChcete ho použít také na epizody zveřejněné dříve?</string>
+ <string name="auto_delete_label">Automaticky smazat epizodu\n(přeskočit globální nastavení)</string>
+ <string name="parallel_downloads_suffix">\u0020paralelních stahování</string>
+ <string name="feed_auto_download_global">Globální</string>
+ <string name="feed_auto_download_always">Vždy</string>
+ <string name="feed_auto_download_never">Nikdy</string>
+ <string name="send_label">Odeslat</string>
+ <string name="episode_cleanup_never">Nikdy</string>
+ <string name="episode_cleanup_queue_removal">Pokud není ve frontě</string>
+ <string name="episode_cleanup_after_listening">Po dokončení</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 den po dokončení</item>
+ <item quantity="few">%d dny po dokončení</item>
+ <item quantity="other">%d dnů po dokončení</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL kanálu</string>
<string name="etxtFeedurlHint">URL nebo webová stránka kanálu</string>
@@ -66,15 +99,33 @@
<string name="podcastdirectories_descr">Můžete vyhledávat nové podcasty podle jména, kategorie nebo popularity v seznamu gpodder.net.</string>
<string name="browse_gpoddernet_label">Prohledávat gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Označit vše jako přečtené</string>
- <string name="mark_all_read_msg">Označit všechny epizody jako přečtené</string>
+ <string name="mark_all_read_label">Označit vše jako poslechnuté</string>
+ <string name="mark_all_read_msg">Všechny epizody označeny jako poslechnuté</string>
+ <string name="mark_all_read_confirmation_msg">Prosím potvrďte, že chcete označit všechny vybrané epizody jako poslechnuté.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Prosím potvrďte, že chcete označit všechny epizody z tohoto zdroje jako poslechnuté.</string>
+ <string name="mark_all_seen_label">Označit vše jako zobrazené</string>
<string name="show_info_label">Informace o zdroji</string>
<string name="remove_feed_label">Odstranit podcast</string>
+ <string name="share_label">Sdílet</string>
<string name="share_link_label">Sdílet odkaz</string>
- <string name="share_source_label">Sdílet adresu kanálu</string>
+ <string name="share_link_with_position_label">Sdílet odkaz s pozicí</string>
+ <string name="share_feed_url_label">Sdílet URL kanálu</string>
+ <string name="share_item_url_label">Sdílet URL epizody</string>
+ <string name="share_item_url_with_position_label">Sdílet URL epizody s pozicí</string>
<string name="feed_delete_confirmation_msg">Prosím potvrďte, že chcete smazat tento kanál včetně všech stažených epizod.</string>
<string name="feed_remover_msg">Odstranit kanál</string>
<string name="load_complete_feed">Obnovit kompletní kanál</string>
+ <string name="hide_episodes_title">Skrýt epizody</string>
+ <string name="episode_actions">Provést akce</string>
+ <string name="hide_unplayed_episodes_label">Neposlechnuté</string>
+ <string name="hide_paused_episodes_label">Pozastavené</string>
+ <string name="hide_played_episodes_label">Poslechnuté</string>
+ <string name="hide_queued_episodes_label">Ve frontě</string>
+ <string name="hide_not_queued_episodes_label">Mimo frontu</string>
+ <string name="hide_downloaded_episodes_label">Stažené</string>
+ <string name="hide_not_downloaded_episodes_label">Nestažené</string>
+ <string name="filtered_label">Filtrované</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Poslední aktualizace selhala</string>
<!--actions on feeditems-->
<string name="download_label">Stáhnout</string>
<string name="play_label">Přehrát</string>
@@ -83,15 +134,25 @@
<string name="stream_label">Vysílat</string>
<string name="remove_label">Odstranit</string>
<string name="remove_episode_lable">Odstranit epizodu</string>
- <string name="mark_read_label">Označit jako přečtené</string>
- <string name="mark_unread_label">Označit jako nepřečtené</string>
+ <string name="mark_read_label">Označit jako poslechnuté</string>
+ <string name="marked_as_read_label">Označeno jako poslechnuté</string>
+ <string name="mark_unread_label">Označit jako neposlechnuté</string>
<string name="add_to_queue_label">Přidat do fronty</string>
+ <string name="added_to_queue_label">Přidáno do fronty</string>
<string name="remove_from_queue_label">Odebrat z fronty</string>
+ <string name="add_to_favorite_label">Přidat k oblíbeným</string>
+ <string name="added_to_favorites">Přidáno k oblíbeným</string>
+ <string name="remove_from_favorite_label">Odebrat z obíbených</string>
+ <string name="removed_from_favorites">Odebráno z oblíbených</string>
<string name="visit_website_label">Navštívit stránku</string>
<string name="support_label">Flattrovat</string>
<string name="enqueue_all_new">Vše do fronty</string>
<string name="download_all">Stáhnout vše</string>
<string name="skip_episode_label">Přeskočit epizodu</string>
+ <string name="activate_auto_download">Aktivovat automatické stahování</string>
+ <string name="deactivate_auto_download">Deaktivovat automatické stahování</string>
+ <string name="reset_position">Vymazat pozici přehrávání</string>
+ <string name="removed_item">Položka odebrána</string>
<!--Download messages and labels-->
<string name="download_successful">úspěšné</string>
<string name="download_failed">selhalo</string>
@@ -109,12 +170,18 @@
<string name="download_error_unauthorized">Chyba přihlášení</string>
<string name="cancel_all_downloads_label">Zrušit všechna stahování</string>
<string name="download_canceled_msg">Stahování zrušeno</string>
- <string name="download_report_title">Všechna stahování dokončena</string>
+ <string name="download_canceled_autodownload_enabled_msg">Stahování zrušeno\nVypnuto <i>automatické stahování</i> této položky</string>
+ <string name="download_report_title">Stahování dokončeno s chybou</string>
+ <string name="download_report_content_title">Report stahování</string>
<string name="download_error_malformed_url">Chybné URL</string>
<string name="download_error_io_error">IO chyba</string>
<string name="download_error_request_error">Chyba požadavku</string>
<string name="download_error_db_access">Chyba přístupu do databáze</string>
- <string name="downloads_left">\u0020Stahování zbývá</string>
+ <plurals name="downloads_left">
+ <item quantity="one">%d čekající na stažení</item>
+ <item quantity="few">%d čekající na stažení</item>
+ <item quantity="other">%d čekajících na stažení</item>
+ </plurals>
<string name="downloads_processing">Probíhá stahování</string>
<string name="download_notification_title">Stahuji podcast data</string>
<string name="download_report_content">%1$d úspěšných stahování, %2$d selhalo</string>
@@ -125,6 +192,11 @@
<string name="download_request_error_dialog_message_prefix">Nastala chyba při pokusu o stažení souboru:\u0020</string>
<string name="authentication_notification_title">Vyžadováno ověření</string>
<string name="authentication_notification_msg">Zdroj který jste vybrali vyžaduje zadání uživatelského jména a hesla</string>
+ <string name="confirm_mobile_download_dialog_title">Potvrdit mobilní stahování</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Stahování dat přes mobilní připojení je v nastavení vypnuto.\n\nDočasně povolit nebo pouze přidat do fronty?\n\n<small>Tato volba bude platná po dalších 10 minut.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Stahování dat přes mobilní připojení je v nastavení vypnuto.\n\nDočasně povolit?\n\n<small>Tato volba bude platná po dalších 10 minut.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Pouze přidat do fronty</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Dočasně povolit</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Chyba!</string>
<string name="player_stopped_msg">Žádné probíhající přehrávání</string>
@@ -139,6 +211,10 @@
<string name="playbackservice_notification_title">Přehrávaný podcast</string>
<string name="unknown_media_key">AntennaPod - Neznámý klíč médií: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Zamknout frontu</string>
+ <string name="unlock_queue">Odemknout frontu</string>
+ <string name="queue_locked">Fronta zamknuta</string>
+ <string name="queue_unlocked">Fronta odemknuta</string>
<string name="clear_queue_label">Vyprázdnit frontu</string>
<string name="undo">Zpět</string>
<string name="removed_from_queue">Položka odebrána</string>
@@ -150,6 +226,7 @@
<string name="duration">Délka</string>
<string name="ascending">Vzestupně</string>
<string name="descending">Sestupně</string>
+ <string name="clear_queue_confirmation_msg">Prosím potvrďte, že chcete vyčistit tuto frontu a VŠECHNY v ní obsažené epizody</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr přihlášení</string>
<string name="flattr_auth_explanation">Stiskněte následující tlačítko pro spuštění autentizačního procesu. Budete přesměrováni na přihlašovací obrazovku flattru a vyzváni k potvrzení udělení práv pro použití flattru aplikací AntennaPod. Po udělení práv se automaticky vrátíte na tuto obrazovku.</string>
@@ -165,38 +242,63 @@
<string name="access_revoked_title">Přístup ukončen</string>
<string name="access_revoked_info">Úspěšně revokován přístup AntennPodu k vašemu účtu. Pro dokončení tohoto procesu je ještě zapotřebí na stránkách flattru odebrat z vašeho účtu AntennaPod ze seznamu povolených aplikací.</string>
<!--Flattr-->
+ <string name="flattr_click_success">Flattrován jeden příspěvek!</string>
+ <string name="flattr_click_success_count">Flattrováno %d příspěvků!</string>
+ <string name="flattr_click_success_queue">Flattrován: %s.</string>
+ <string name="flattr_click_failure_count">Selhalo flattrování %d příspěvků!</string>
+ <string name="flattr_click_failure">Neflattrováno: %s.</string>
+ <string name="flattr_click_enqueued">Příspěvek bude flattrován později</string>
<string name="flattring_thing">Flattruji %s</string>
<string name="flattring_label">AntennaPod flattruje</string>
<string name="flattrd_label">AntennaPod flattroval</string>
<string name="flattrd_failed_label">AntennaPod flattr selhal</string>
+ <string name="flattr_retrieving_status">Získávání flattrovaných příspěvků</string>
<!--Variable Speed-->
- <string name="download_plugin_label">Stáhnout plugin</string>
- <string name="no_playback_plugin_title">Plugin není nainstalován</string>
- <string name="no_playback_plugin_msg">Pro možnost měnit rychlost přehrávání musí být nainstalovaná knihovna třetí strany.\n\nKlikněte na \"Stáhnout Plugin\" ke stažení pluginu z Obchodu Play.\n\nAntennaPod nenese žádnou odpovědnost za jakékoliv problémy způsobené tímto pluginem.</string>
+ <string name="download_plugin_label">Stáhnout modul</string>
+ <string name="no_playback_plugin_title">Modul není nainstalován</string>
+ <string name="no_playback_plugin_or_sonic_msg">Pro fungující proměnné rychlosti přehrávání doporučujeme povolit zabudovaný Sonic mediaplayer [Android 4.1+].\n\nAlternativně je možné z Obchodu Play stáhnout zásuvný modul třetí strany <i>Prestissimo</i>.\nAntennaPod nenese zodpovědnost za jakékoliv problémy s modulem Prestissimo a ty by měly být hlášeny jeho vývojářům.</string>
<string name="set_playback_speed_label">Rychlosti přehrávání</string>
+ <string name="enable_sonic">Povolit Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Žádné položky v seznamu.</string>
<string name="no_feeds_label">Zatím nebyly přidány žádné kanály.</string>
+ <string name="no_chapters_label">Tato epizoda nemá žádné kapitoly.</string>
<!--Preferences-->
<string name="other_pref">Ostatní</string>
<string name="about_pref">O aplikaci</string>
<string name="queue_label">Fronta</string>
<string name="services_label">Služby</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Při odpojení sluchátek pozastavit přehrávání.</string>
+ <string name="pref_episode_cleanup_title">Vyčistit epizody</string>
+ <string name="pref_episode_cleanup_summary">Epizody, které nejsou ve frontě a nejsou označeny za oblíbené by mělo být možné smazat, pokud bude funkce automatického stahování potřebovat místo pro nové epizody</string>
+ <string name="pref_pauseOnDisconnect_sum">Při odpojení sluchátek nebo bluetooth připojení pozastavit přehrávání.</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Pokračovat v přehrávání po připojení sluchátek</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Pokračovat v přehrávání po připojení bluetooth</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Přeskočit tlačítkem vpřed</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Po stlačení hardwarového tlačítka pro posun vpřed místo přetočení vpřed přeskočit na další epizodu</string>
<string name="pref_followQueue_sum">Po přehrání položky z fronty přejít automaticky na další</string>
<string name="pref_auto_delete_sum">Smazat díl po jeho přehrání</string>
<string name="pref_auto_delete_title">Automatické mazání</string>
+ <string name="pref_smart_mark_as_played_sum">Označit epizody jako poslechnuté i pokud ještě zbývá určitý počet sekund přehrávání do jejich konce</string>
+ <string name="pref_smart_mark_as_played_title">Chytré označování jako poslechnuté</string>
+ <string name="pref_skip_keeps_episodes_sum">Neodstraňovat epizody při jejich přeskočení</string>
+ <string name="pref_skip_keeps_episodes_title">Nemazat přeskočené epizody</string>
<string name="playback_pref">Přehrávání</string>
<string name="network_pref">Síť</string>
- <string name="pref_autoUpdateIntervall_title">Interval aktualizace zdrojů</string>
- <string name="pref_autoUpdateIntervall_sum">Udává interval, ve kterém se kanály automaticky aktualizují nebo tuto funkci deaktivuje</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Aktualizovat interval nebo čas v průběhu dne</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Udat interval nebo přesný čas v průběhu dne pro automatickou aktualizaci kanálů</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Můžete nastavit <i>interval</i> jako třeba \"každé 2 hodiny\", nastavit specifický <i>čas v průběhu dne</i> jako \"7:00\" nebo úplně <i>vypnout</i> automatické aktualizace.\n\n<small>Mějte na paměti: Časy aktualizací nejsou přesné. Možná zaznamenáte krátká zpoždění.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Vypnout</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Nastavit interval</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Nastavit čas v průběhu dne</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">každých %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">v %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Stahovat soubory pouze pomocí WiFi</string>
<string name="pref_followQueue_title">Kontinuální přehrávání</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi stahování</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Sluchátka odpojena</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Sluchátka připojena</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth připojeno</string>
<string name="pref_mobileUpdate_title">Mobilní aktualizace</string>
<string name="pref_mobileUpdate_sum">Povolit aktualizace pomocí mobilního připojení</string>
<string name="refreshing_label">Obnovuji</string>
@@ -211,6 +313,14 @@
<string name="pref_auto_flattr_sum">Nastavit automatické flattrování</string>
<string name="user_interface_label">Uživatelské rozhraní</string>
<string name="pref_set_theme_title">Vybrat motiv</string>
+ <string name="pref_nav_drawer_title">Upravit navigační panel</string>
+ <string name="pref_nav_drawer_sum">Upravit vzhled navigačního panelu.</string>
+ <string name="pref_nav_drawer_items_title">Změnit navigační panel</string>
+ <string name="pref_nav_drawer_items_sum">Upravit zobrazení položek v navigačním panelu.</string>
+ <string name="pref_nav_drawer_feed_order_title">Nastavit pořadí sbírek</string>
+ <string name="pref_nav_drawer_feed_order_sum">Upravit pořadí vašich sbírek</string>
+ <string name="pref_nav_drawer_feed_counter_title">Nastavit počítadlo sbírek</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Upravit informaci zobrazovanou počítadlem sbírek</string>
<string name="pref_set_theme_sum">Změnit vzhled AntennaPod.</string>
<string name="pref_automatic_download_title">Automatické stahování</string>
<string name="pref_automatic_download_sum">Nastavení automatického stahování epizod.</string>
@@ -218,6 +328,7 @@
<string name="pref_autodl_wifi_filter_sum">Povolit automatické stahování pouze pomocí vybraných Wi-Fi sítí.</string>
<string name="pref_automatic_download_on_battery_title">Stahovat, pokud neprobíhá nabíjení</string>
<string name="pref_automatic_download_on_battery_sum">Povolit automatické stahování i pokud není baterie nabíjena</string>
+ <string name="pref_parallel_downloads_title">Paralelní stahování</string>
<string name="pref_episode_cache_title">Historie epizod</string>
<string name="pref_theme_title_light">Světlý</string>
<string name="pref_theme_title_dark">Tmavý</string>
@@ -233,15 +344,31 @@
<string name="pref_gpodnet_setlogin_information_sum">Změní přihlašovací údaje k vašemu gpodder.net účtu.</string>
<string name="pref_playback_speed_title">Rychlosti přehrávání</string>
<string name="pref_playback_speed_sum">Přizpůsobení rychlosti je dostupné pro přehrávání zvuku různými rychlostmi</string>
- <string name="pref_seek_delta_title">Čas poskočení</string>
- <string name="pref_seek_delta_sum">Poskočí o vybraný počet sekund při posunu zpět nebo vpřed</string>
+ <string name="pref_fast_forward">Čas rychlého posunu</string>
+ <string name="pref_rewind">Čas přetočení</string>
<string name="pref_gpodnet_sethostname_title">Nastavit hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Použít přednastaveného hosta</string>
<string name="pref_expandNotify_title">Rozšířené upozornění</string>
<string name="pref_expandNotify_sum">Vždy zobrazovat tlačítka pro přehrávání v upozornění.</string>
<string name="pref_persistNotify_title">Pevné ovládání přehrávání</string>
<string name="pref_persistNotify_sum">Zachovat upozornění a ovládání na obrazovce uzamčení i při pozastaveném přehrávání.</string>
+ <string name="pref_lockscreen_background_title">Nastavit pozadí uzamčené obrazovky</string>
+ <string name="pref_lockscreen_background_sum">Nastavit pozadí uzamčené obrazovky na obrázek aktuální epizody. Jako vedlejší efekt zobrazí toto nastavení obrázek i v aplikacích třetích stran. </string>
+ <string name="pref_showDownloadReport_title">Zobrazit report stahování</string>
+ <string name="pref_showDownloadReport_sum">Pokud selže stahování, vygenerovat report zobrazující detaily o chybě.</string>
<string name="pref_expand_notify_unsupport_toast">Verze Androidu nižší než 4.1 nepodporují rozšířená upozornění.</string>
+ <string name="pref_queueAddToFront_sum">Přidávat nové epizody na začátek fronty.</string>
+ <string name="pref_queueAddToFront_title">Přidat na začátek.</string>
+ <string name="pref_smart_mark_as_played_disabled">Vypnuto</string>
+ <string name="pref_image_cache_size_title">Velikost odkládací paměti obrázků</string>
+ <string name="pref_image_cache_size_sum">Velikost diskové paměti pro obrázky.</string>
+ <string name="crash_report_title">Hlášení pádů</string>
+ <string name="crash_report_sum">Odesílat hlášení o posledním pádu aplikace emailem</string>
+ <string name="send_email">Poslat email</string>
+ <string name="experimental_pref">Experimentální</string>
+ <string name="pref_sonic_title">Přehrávač médií Sonic</string>
+ <string name="pref_sonic_message">Použít připojený sonic media player jako náhradu za výchozí přehrávač médií pro Android a Prestissimo</string>
+ <string name="pref_current_value">Aktuální hodnota: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Povolit automatické flattrování</string>
<string name="auto_flattr_after_percent">Flattrovat díl jakmile bude odehráno %d procent</string>
@@ -256,6 +383,10 @@
<string name="found_in_title_label">Nalezeno v názvu</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML soubory umožňují přenést vaše podcasty z jednoho přehrávače na jiný.</string>
+ <string name="opml_import_option">Možnost %1$d</string>
+ <string name="opml_import_explanation_1">Vybrat umístění v místním souborovém systému.</string>
+ <string name="opml_import_explanation_2">Použít externí aplikaci jako třeba Dropbox, Google Drive nebo oblíbeného správce souborů k otevření OPML souboru.</string>
+ <string name="opml_import_explanation_3">Mnoho aplikací jako třeba Google Mail, Dropbox, Google Drive a většina správců souborů umí <i>otevřít</i> OPML soubory <i>v aplikaci</i> AntennaPod.</string>
<string name="start_import_label">Importovat</string>
<string name="opml_import_label">OPML import</string>
<string name="opml_directory_error">CHYBA!</string>
@@ -264,8 +395,11 @@
<string name="opml_import_error_dir_empty">Adresář importu je prázdný.</string>
<string name="select_all_label">Označit vše</string>
<string name="deselect_all_label">Zrušit výběr</string>
+ <string name="select_options_label">Vybrat</string>
+ <string name="choose_file_from_filesystem">Z místního souborového systému</string>
+ <string name="choose_file_from_external_application">Použít externí aplikaci</string>
<string name="opml_export_label">OPML export</string>
- <string name="exporting_label">Exportuji...</string>
+ <string name="exporting_label">Export</string>
<string name="export_error_label">Chyba exportu</string>
<string name="opml_export_success_title">OPML export byl úspěšný.</string>
<string name="opml_export_success_sum">OPML soubor byl zapsán do:\u0020</string>
@@ -276,14 +410,32 @@
<string name="sleep_timer_label">Časovač vypnutí</string>
<string name="time_left_label">Zbývající čas:\u0020</string>
<string name="time_dialog_invalid_input">Neplatný vstup, musí být zadáno celé číslo</string>
- <string name="time_unit_seconds">sekund</string>
- <string name="time_unit_minutes">minut</string>
- <string name="time_unit_hours">hodin</string>
+ <string name="timer_about_to_expire_label"><b>Těsně před vypršením časovače:</b></string>
+ <string name="shake_to_reset_label">Potřesením vynulovat časovač</string>
+ <string name="timer_vibration_label">Vibrovat</string>
+ <string name="time_seconds">sekund</string>
+ <string name="time_minutes">minut</string>
+ <string name="time_hours">hodin</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 sekunda</item>
+ <item quantity="few">%d sekundy</item>
+ <item quantity="other">%d sekund</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minuta</item>
+ <item quantity="few">%d minuty</item>
+ <item quantity="other">%d minut</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 hodina</item>
+ <item quantity="few">%d hodiny</item>
+ <item quantity="other">%d hodin</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORIE</string>
<string name="gpodnet_toplist_header">TOP PODCASTY</string>
<string name="gpodnet_suggestions_header">DOPORUČENÉ</string>
- <string name="gpodnet_search_hint">Vyhledat na gpodder.net</string>
+ <string name="gpodnet_search_hint">Prohledat gpodder.net</string>
<string name="gpodnetauth_login_title">Přihlásit</string>
<string name="gpodnetauth_login_descr">Vítejte do průvodce přihlášením ke gpodder.net účtu. Zadejte vaše přihlašovací údaje:</string>
<string name="gpodnetauth_login_butLabel">Přihlásit</string>
@@ -311,20 +463,27 @@
<string name="selected_folder_label">Vybraný adresář:</string>
<string name="create_folder_label">Vytvořit adresář</string>
<string name="choose_data_directory">Vybrat umístění dat</string>
+ <string name="choose_data_directory_message">Vyberte prosím váš výchozí datový adresář. AntennaPod vytvoří všechny potřebné podadresáře.</string>
<string name="create_folder_msg">Vytvořit adresář \"%1$s\"?</string>
<string name="create_folder_success">Nový adresář vytvořen</string>
<string name="create_folder_error_no_write_access">Nelze zapisovat do adresáře</string>
<string name="create_folder_error_already_exists">Adresář již existuje</string>
<string name="create_folder_error">Nelze vytvořit adresář</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" neexistuje</string>
+ <string name="folder_not_readable_error">\"%1$s\" nelze číst</string>
+ <string name="folder_not_writable_error">\"%1$s\" nelze zapisovat</string>
<string name="folder_not_empty_dialog_title">Adresář není prázdný</string>
<string name="folder_not_empty_dialog_msg">Vybraný adresář není prázdný. Stažená media a ostatní soubory budou umístěny přímo do tohoto adresáře. Přesto pokračovat?</string>
<string name="set_to_default_folder">Vybrat hlavní adresář</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Místo snížení hlasitosti pozastavit přehrávání v případě, že jiná aplikace přehrává zvuk.</string>
<string name="pref_pausePlaybackForFocusLoss_title">Automatické pozastavení přehrávání</string>
+ <string name="pref_resumeAfterCall_sum">Pokračovat v přehrávání po ukončení telefonního hovoru</string>
+ <string name="pref_resumeAfterCall_title">Pokračovat po telefonátu</string>
+ <string name="pref_restart_required">Pro aktivování změn nastavení bylo třeba restartovat aplikaci AntennaPod.</string>
<!--Online feed view-->
<string name="subscribe_label">Odebírat</string>
<string name="subscribed_label">Odebíráno</string>
- <string name="downloading_label">Stahuji...</string>
+ <string name="downloading_label">Stahování</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Zobrazit kapitoly</string>
<string name="show_shownotes_label">Zobrazit poznámky o pořadu</string>
@@ -347,6 +506,45 @@
<!--Feed information screen-->
<string name="authentication_label">Ověření</string>
<string name="authentication_descr">Změnit uživatelské jméno a heslo pro tento podcast a jeho epizody.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Probíhá aktualizace databáze</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importuji odběry z jednoúčelových aplikací...</string>
+ <string name="search_itunes_label">Prohledat iTunes</string>
+ <string name="select_label"><b>Vybrat…</b></string>
+ <string name="filter">Filtr</string>
+ <string name="all_label">Vše</string>
+ <string name="selected_all_label">Vybrány všechny epizody</string>
+ <string name="none_label">Žádné</string>
+ <string name="deselected_all_label">Odebrány všechny epizody</string>
+ <string name="played_label">Přehrány</string>
+ <string name="selected_played_label">Vybrány přehrané epizody</string>
+ <string name="unplayed_label">Nepřehrány</string>
+ <string name="selected_unplayed_label">Odebrány nepřehrané epizody</string>
+ <string name="downloaded_label">Stažené</string>
+ <string name="selected_downloaded_label">Vybrány stažené epizody</string>
+ <string name="not_downloaded_label">Nestažené</string>
+ <string name="selected_not_downloaded_label">Vybrány nestažené epizody</string>
+ <string name="sort_title"><b>Řadit dle</b></string>
+ <string name="sort_title_a_z">Názvu (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Názvu (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Data (Nové \u2192 Staré)</string>
+ <string name="sort_date_old_new">Data (Staré \u2192 Nové)</string>
+ <string name="sort_duration_short_long">Délka (Krátké \u2192 Dlouhé)</string>
+ <string name="sort_duration_long_short">Délka (Dlouhé \u2192 Krátké)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Líbí se vám AntennaPod?</string>
+ <string name="rating_message">Oceníme, pokud věnujete chvíli času ohodnocení AntennaPod.</string>
+ <string name="rating_never_label">Neobtěžuj mě</string>
+ <string name="rating_later_label">Upozornit později</string>
+ <string name="rating_now_label">Jasně, s radostí!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Audio ovládání</string>
+ <string name="playback_speed">Rychlost přehrávání</string>
+ <string name="volume">Hlasitost</string>
+ <string name="left_short">L</string>
+ <string name="right_short">P</string>
+ <string name="audio_effects">Audio efekty</string>
+ <string name="stereo_to_mono">Downmix: Stereo na mono</string>
+ <string name="sonic_only">Pouze Sonic</string>
</resources>
diff --git a/core/src/main/res/values-da/strings.xml b/core/src/main/res/values-da/strings.xml
index ba7fafca6..13f576bfe 100644
--- a/core/src/main/res/values-da/strings.xml
+++ b/core/src/main/res/values-da/strings.xml
@@ -5,7 +5,6 @@
<string name="feeds_label">Feeds</string>
<string name="add_feed_label">Tilføj podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISODER</string>
<string name="new_episodes_label">Nye episoder</string>
<string name="all_episodes_label">Alle episoder</string>
<string name="new_label">Nye</string>
@@ -53,7 +52,6 @@
<string name="length_prefix">Længde:\u0020</string>
<string name="size_prefix">Størrelse:\u0020</string>
<string name="processing_label">Behandler</string>
- <string name="loading_label">Indlæser...</string>
<string name="save_username_password_label">Gem brugernavn og kodeord</string>
<string name="close_label">Luk</string>
<string name="retry_label">Prøv igen</string>
@@ -71,7 +69,6 @@
<string name="show_info_label">Vis information</string>
<string name="remove_feed_label">Fjern podcast</string>
<string name="share_link_label">Del webside link</string>
- <string name="share_source_label">Del feed link</string>
<string name="feed_delete_confirmation_msg">Bekræft venligst at du vil fjerne dette feed og ALLE episoder du har downloadet fra dette feed.</string>
<string name="feed_remover_msg">Fjerner feed</string>
<string name="load_complete_feed">Opdater hele feed\'et</string>
@@ -114,7 +111,6 @@
<string name="download_error_io_error">IO fejl</string>
<string name="download_error_request_error">Anmode fejl</string>
<string name="download_error_db_access">Adgangsfejl i database</string>
- <string name="downloads_left">\u0020Downloads tilbage</string>
<string name="downloads_processing">Bearbejder downloads</string>
<string name="download_notification_title">Downloader podcast data</string>
<string name="download_report_content">%1$d downloads lykkedes, %2$d fejlet</string>
@@ -179,7 +175,6 @@
<!--Variable Speed-->
<string name="download_plugin_label">Hent Plugin</string>
<string name="no_playback_plugin_title">Plugin er ikke installeret</string>
- <string name="no_playback_plugin_msg">For at variabel afspilningshastighed virker skal der installeres et 3. part bibliotek.\n\nTap \'Download Plugin\' for at downloade et gratis plugin fra Play Store\n\nHvis der findes nogen problemer med dette plugin, så er det ikke AntennaPods ansvar, og problemet bør reporteres til ejeren af pluginet. </string>
<string name="set_playback_speed_label">Afspilningshastigheder</string>
<!--Empty list labels-->
<string name="no_items_label">Der er ingen emner i denne liste.</string>
@@ -190,15 +185,12 @@
<string name="queue_label">Kø</string>
<string name="services_label">Tjenester</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Sæt afspilning på pause når hovedtelefoner afbrydes</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Genoptag afspilning når hovedtelefoner tilsluttes igen</string>
<string name="pref_followQueue_sum">Hop til næste medie i køen når afspilning er færdig</string>
<string name="pref_auto_delete_sum">Slet episode når afspilningen er færdig</string>
<string name="pref_auto_delete_title">Slet Automatisk</string>
<string name="playback_pref">Afspilning</string>
<string name="network_pref">Netværk</string>
- <string name="pref_autoUpdateIntervall_title">Opdaterings interval</string>
- <string name="pref_autoUpdateIntervall_sum">Specificer et interval indenfor hvilket feeds opdaterer automatisk eller deaktiver det</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Download kun medie filer over WiFi</string>
<string name="pref_followQueue_title">Kontinuerlig afspilning</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi medie download</string>
@@ -240,8 +232,6 @@
<string name="pref_gpodnet_setlogin_information_sum">Skift din gpodder.net kontos login information.</string>
<string name="pref_playback_speed_title">Afspilningshastigheder</string>
<string name="pref_playback_speed_sum">Tilpas tilgængelige hastigheder for variabelt afspilningshastigheds plugin</string>
- <string name="pref_seek_delta_title">Søg tid</string>
- <string name="pref_seek_delta_sum">Søg så mange sekunder når der spoles tilbage eller frem</string>
<string name="pref_gpodnet_sethostname_title">Indstil værtsnavn</string>
<string name="pref_gpodnet_sethostname_use_default_host">Brug standard vært</string>
<string name="pref_expandNotify_title">Udvid notifikation</string>
@@ -272,7 +262,6 @@
<string name="select_all_label">Vælg alt</string>
<string name="deselect_all_label">Fravælg alt</string>
<string name="opml_export_label">OPML eksport</string>
- <string name="exporting_label">Eksporterer...</string>
<string name="export_error_label">Eksport fejl</string>
<string name="opml_export_success_title">Opml eksport lykkedes.</string>
<string name="opml_export_success_sum">.opml filen var skrevet til:\u0020</string>
@@ -283,9 +272,6 @@
<string name="sleep_timer_label">Søvn timer</string>
<string name="time_left_label">Tid tilbage:\u0020</string>
<string name="time_dialog_invalid_input">Ugyldig indtastning, tid skal være et heltal</string>
- <string name="time_unit_seconds">sekunder</string>
- <string name="time_unit_minutes">minutter</string>
- <string name="time_unit_hours">timer</string>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORIER </string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
@@ -331,7 +317,6 @@
<!--Online feed view-->
<string name="subscribe_label">Abonner</string>
<string name="subscribed_label">Abonneret</string>
- <string name="downloading_label">Downloader...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Vis kapitler</string>
<string name="show_shownotes_label">Vis shownoter</string>
@@ -354,6 +339,9 @@
<!--Feed information screen-->
<string name="authentication_label">Godkendelse</string>
<string name="authentication_descr">Skift dit brugernavn og kodeord for denne podcast og dets episoder.</string>
+ <!--Progress information-->
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importerer abonnementer fra single-purpose apps…</string>
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml
index f2d0a66ad..7432f5537 100644
--- a/core/src/main/res/values-de/strings.xml
+++ b/core/src/main/res/values-de/strings.xml
@@ -5,27 +5,40 @@
<string name="feeds_label">Feeds</string>
<string name="add_feed_label">Podcast hinzufügen</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISODEN</string>
+ <string name="episodes_label">Episoden</string>
<string name="new_episodes_label">Neue Episoden</string>
<string name="all_episodes_label">Alle Episoden</string>
+ <string name="all_episodes_short_label">Alle</string>
+ <string name="favorite_episodes_label">Favoriten</string>
<string name="new_label">Neu</string>
<string name="waiting_list_label">Warteliste</string>
<string name="settings_label">Einstellungen</string>
<string name="add_new_feed_label">Podcast hinzufügen</string>
<string name="downloads_label">Downloads</string>
<string name="downloads_running_label">Aktiv</string>
- <string name="downloads_completed_label">Abgeschlossen</string>
+ <string name="downloads_completed_label">Beendet</string>
<string name="downloads_log_label">Log</string>
<string name="cancel_download_label">Download abbrechen</string>
<string name="playback_history_label">Zuletzt gespielt</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net Anmeldung</string>
+ <string name="free_space_label">%1$s frei</string>
+ <string name="episode_cache_full_title">Episodenspeicher voll</string>
+ <string name="episode_cache_full_message">Der Episodenspeicher ist voll. Du kannst die Größe des Episodenspeichers in den Einstellungen erhöhen.</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Zuletzt veröffentlicht</string>
<string name="episode_filter_label">Nur neue Episoden anzeigen</string>
<!--Main activity-->
<string name="drawer_open">Menü öffnen</string>
<string name="drawer_close">Menü schließen</string>
+ <string name="drawer_preferences">Seitenleisten-Einstellungen</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sortieren nach Zähler</string>
+ <string name="drawer_feed_order_alphabetical">Alphabetisch sortieren</string>
+ <string name="drawer_feed_order_last_update">Sortiere nach Veröffentlichungsdatum</string>
+ <string name="drawer_feed_counter_new_unplayed">Anzahl neuer und ungespielter Episoden</string>
+ <string name="drawer_feed_counter_new">Anzahl neuer Episoden</string>
+ <string name="drawer_feed_counter_unplayed">Anzahl ungespielter Episoden</string>
+ <string name="drawer_feed_counter_none">Keine</string>
<!--Webview actions-->
<string name="open_in_browser_label">Im Browser öffnen</string>
<string name="copy_url_label">URL kopieren</string>
@@ -37,8 +50,11 @@
<!--Other-->
<string name="confirm_label">Bestätigen</string>
<string name="cancel_label">Abbrechen</string>
+ <string name="yes">Ja</string>
+ <string name="no">Nein</string>
<string name="author_label">Autor</string>
<string name="language_label">Sprache</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Einstellungen</string>
<string name="cover_label">Bild</string>
<string name="error_label">Fehler</string>
@@ -53,31 +69,61 @@
<string name="length_prefix">Länge:\u0020</string>
<string name="size_prefix">Größe:\u0020</string>
<string name="processing_label">Verarbeite</string>
- <string name="loading_label">Lade ...</string>
+ <string name="loading_label">Wird geladen…</string>
<string name="save_username_password_label">Benutzername und Password merken</string>
<string name="close_label">Schließen</string>
<string name="retry_label">Erneut versuchen</string>
<string name="auto_download_label">Automatisch herunterladen</string>
+ <string name="auto_download_apply_to_items_title">Auf bisherige Episoden anwenden</string>
+ <string name="auto_download_apply_to_items_message">Die neue <i>Auto-Download</i>-Einstellung wird automatisch auf neue Episoden angewandt.\nMöchtest du sie auch auf schon veröffentlichte Episoden anwenden?</string>
+ <string name="auto_delete_label">Episoden automatisch löschen\n(überschreibt globale Vorgabe)</string>
<string name="parallel_downloads_suffix">\u0020gleichzeitige Downloads</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Immer</string>
+ <string name="feed_auto_download_never">Nie</string>
+ <string name="send_label">Senden…</string>
+ <string name="episode_cleanup_never">Nie</string>
+ <string name="episode_cleanup_queue_removal">Wenn nicht in der Abspielliste</string>
+ <string name="episode_cleanup_after_listening">wenn fertig gespielt gespielt</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 Tag nachdem fertig gespielt</item>
+ <item quantity="other">%d Tage nachdem fertig gespielt</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Feed URL</string>
<string name="etxtFeedurlHint">URL des Feeds oder der Webseite</string>
<string name="txtvfeedurl_label">Podcast per URL hinzufügen</string>
<string name="podcastdirectories_label">Podcast in Verzeichnis finden</string>
- <string name="podcastdirectories_descr">Bei gpodder.net kannst du neue Podcasts nach Name, Kategorie oder Popularität suchen.</string>
+ <string name="podcastdirectories_descr">Bei gpodder.net kannst du neue Podcasts nach Name, Kategorie oder Popularität suchen. Oder suche bei iTunes.</string>
<string name="browse_gpoddernet_label">gpodder.net durchsuchen</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Alle als gespielt markieren</string>
<string name="mark_all_read_msg">Alle Episoden als gespielt markieren</string>
<string name="mark_all_read_confirmation_msg">Bitte bestätige, dass alle Episoden als gespielt markiert werden sollen.</string>
<string name="mark_all_read_feed_confirmation_msg">Bitte bestätige, dass alle Episoden in diesem Feed als gespielt markiert werden sollen.</string>
+ <string name="mark_all_seen_label">Alle als gesehen markieren</string>
<string name="show_info_label">Informationen anzeigen</string>
<string name="remove_feed_label">Podcast entfernen</string>
+ <string name="share_label">Teilen…</string>
<string name="share_link_label">Webseiten-Link teilen</string>
- <string name="share_source_label">Feed-Link teilen</string>
+ <string name="share_link_with_position_label">Teile Link mit Zeitmarke</string>
+ <string name="share_feed_url_label">Teile URL des Podcasts</string>
+ <string name="share_item_url_label">Teile URL der Episode</string>
+ <string name="share_item_url_with_position_label">Teile URL der Episode mit Zeitmarke</string>
<string name="feed_delete_confirmation_msg">Bitte bestätige, dass du diesen Feed und ALLE heruntergeladenen Episoden dieses Feeds entfernen möchtest.</string>
<string name="feed_remover_msg">Entferne Feed</string>
<string name="load_complete_feed">Kompletten Feed aktualisieren</string>
+ <string name="hide_episodes_title">Episoden verbergen</string>
+ <string name="episode_actions">Aktionen anwenden</string>
+ <string name="hide_unplayed_episodes_label">Ungespielt</string>
+ <string name="hide_paused_episodes_label">Pausiert</string>
+ <string name="hide_played_episodes_label">Gespielt</string>
+ <string name="hide_queued_episodes_label">In Abspielliste</string>
+ <string name="hide_not_queued_episodes_label">Nicht in Abspielliste</string>
+ <string name="hide_downloaded_episodes_label">Heruntergeladen</string>
+ <string name="hide_not_downloaded_episodes_label">Nicht heruntergeladen</string>
+ <string name="filtered_label">Gefiltert</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Aktualisierung fehlgeschlagen</string>
<!--actions on feeditems-->
<string name="download_label">Herunterladen</string>
<string name="play_label">Abspielen</string>
@@ -87,15 +133,24 @@
<string name="remove_label">Entfernen</string>
<string name="remove_episode_lable">Episode entfernen</string>
<string name="mark_read_label">Als gespielt markieren</string>
- <string name="mark_unread_label">Als ungespielt markieren</string>
<string name="marked_as_read_label">Als gespielt markiert</string>
+ <string name="mark_unread_label">Als ungespielt markieren</string>
<string name="add_to_queue_label">Zur Abspielliste hinzufügen</string>
+ <string name="added_to_queue_label">Zur Abspielliste hinzugefügt</string>
<string name="remove_from_queue_label">Aus der Abspielliste entfernen</string>
+ <string name="add_to_favorite_label">Zu Favoriten hinzufügen</string>
+ <string name="added_to_favorites">Zu Favoriten hinzugefügt</string>
+ <string name="remove_from_favorite_label">Aus Favoriten entfernen</string>
+ <string name="removed_from_favorites">Aus Favoriten entfernt</string>
<string name="visit_website_label">Webseite besuchen</string>
<string name="support_label">Flattrn</string>
<string name="enqueue_all_new">Alle zur Abspielliste hinzufügen</string>
<string name="download_all">Alle herunterladen</string>
<string name="skip_episode_label">Episode überspringen</string>
+ <string name="activate_auto_download">Automatischen Download aktivieren</string>
+ <string name="deactivate_auto_download">Automatischen Download deaktivieren</string>
+ <string name="reset_position">Wiedergabe-Position zurücksetzen</string>
+ <string name="removed_item">Element entfernt</string>
<!--Download messages and labels-->
<string name="download_successful">erfolgreich</string>
<string name="download_failed">fehlgeschlagen</string>
@@ -113,12 +168,17 @@
<string name="download_error_unauthorized">Authentifizierungsfehler</string>
<string name="cancel_all_downloads_label">Alle Downloads abbrechen</string>
<string name="download_canceled_msg">Download abgebrochen</string>
- <string name="download_report_title">Download abgeschlossen</string>
+ <string name="download_canceled_autodownload_enabled_msg">Download abgebrochen\n<i>Automatischen Download</i> für diese Episode deaktiviert</string>
+ <string name="download_report_title">Downloads endeten mit Fehler(n)</string>
+ <string name="download_report_content_title">Download-Bericht</string>
<string name="download_error_malformed_url">Fehler in URL</string>
<string name="download_error_io_error">E/A Error</string>
<string name="download_error_request_error">Anfragefehler</string>
<string name="download_error_db_access">Datenbankzugriffsfehler</string>
- <string name="downloads_left">\u0020Downloads übrig</string>
+ <plurals name="downloads_left">
+ <item quantity="one">%d Download übrig</item>
+ <item quantity="other">%d Downloads übrig</item>
+ </plurals>
<string name="downloads_processing">Verarbeite Downloads</string>
<string name="download_notification_title">Lade Podcast-Daten</string>
<string name="download_report_content">%1$d Downloads erfolgreich, %2$d fehlgeschlagen</string>
@@ -129,6 +189,11 @@
<string name="download_request_error_dialog_message_prefix">Beim Herunterladen der Datei ist ein Fehler aufgetreten:\u0020</string>
<string name="authentication_notification_title">Authentifizierung erforderlich</string>
<string name="authentication_notification_msg">Die angeforderte Quelle erfordert einen Benutzernamen und ein Passwort</string>
+ <string name="confirm_mobile_download_dialog_title">Mobilen Download bestätigen</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Das Herunterladen über die mobile Datenverbindung ist in den Einstellungen deaktiviert.\n\nVorübergehend erlauben oder nur zur Abspielliste hinzufügen?\n\n<small>Deine Entscheidung wird für 10 Minuten gespeichert.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Das Herunterladen über die mobile Datenverbindung ist in den Einstellungen deaktiviert.\n\nVorübergehend erlauben?\n\n<small>Deine Entscheidung wird für 10 Minuten gespeichert.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Zur Abspielliste hinzufügen</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Vorübergehend erlauben</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Fehler!</string>
<string name="player_stopped_msg">Keine Medienwiedergabe</string>
@@ -143,6 +208,10 @@
<string name="playbackservice_notification_title">Spiele Podcast ab</string>
<string name="unknown_media_key">AntennaPod - Unbekannte Medientaste: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Abspielliste sperren</string>
+ <string name="unlock_queue">Abspielliste entsperren</string>
+ <string name="queue_locked">Abspielliste gesperrt</string>
+ <string name="queue_unlocked">Abspielliste entsperrt</string>
<string name="clear_queue_label">Abspielliste leeren</string>
<string name="undo">Rückgängig</string>
<string name="removed_from_queue">Element entfernt</string>
@@ -184,31 +253,49 @@
<!--Variable Speed-->
<string name="download_plugin_label">Plugin herunterladen</string>
<string name="no_playback_plugin_title">Plugin nicht installiert</string>
- <string name="no_playback_plugin_msg">Um die Wiedergabegeschwindigkeit zu verändern, muss eine Drittanbieter-Bibliothek heruntegeladen werden. Drücke auf \"Plugin herunterladen\", um ein kostenloses Plugin aus dem Play Store zu installieren. Probleme, die bei der Benutzung des Plugins auftreten, sollten dem Entwickler des Plugins gemeldet werden.</string>
+ <string name="no_playback_plugin_or_sonic_msg">Um variable Wiedergabegeschwindigkeit benutzen zu können, empfehlen wir, den integrierten Sonic Mediaplayer [Android 4.1+] zu aktivieren.\n\nAlternativ kannst du das kostenlose Plugin <i>Prestissimo</i> eines Drittanbieters aus dem Play Store herunterladen.\nProbleme mit Prestissimo liegen nicht im Verantwortungsbereich von AntennaPod und sollten dem Entwickler des Plugins gemeldet werden.</string>
<string name="set_playback_speed_label">Wiedergabegeschwindigkeiten</string>
+ <string name="enable_sonic">Sonic aktivieren</string>
<!--Empty list labels-->
<string name="no_items_label">Es sind keine Einträge in dieser Liste.</string>
<string name="no_feeds_label">Du hast noch keine Feeds abonniert.</string>
+ <string name="no_chapters_label">Diese Episode hat keine Kapitel.</string>
<!--Preferences-->
<string name="other_pref">Anderes</string>
<string name="about_pref">Über</string>
<string name="queue_label">Abspielliste</string>
<string name="services_label">Dienste</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pausiere die Wiedergabe, wenn der Kopfhörer entfernt worden ist.</string>
- <string name="pref_unpauseOnHeadsetReconnect_sum">Wiedergabe fortsetzen, wenn Kopfhörer wieder reingesteckt werden</string>
+ <string name="pref_episode_cleanup_title">Automatisches Löschen</string>
+ <string name="pref_episode_cleanup_summary">Episoden, die weder in Abspielliste noch favorisiert sind, können gelöscht werden, wenn beim automatischen Herunterladen Speicherplatz für neue Episoden gebraucht wird</string>
+ <string name="pref_pauseOnDisconnect_sum">Wiedergabe pausieren, wenn Kopfhörer ausgesteckt oder Bluetooth getrennt wird</string>
+ <string name="pref_unpauseOnHeadsetReconnect_sum">Wiedergabe fortsetzen, wenn Kopfhörer wieder eingesteckt werden</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Wiedergabe fortsetzen, wenn Bluetooth wieder verbunden ist</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Nächster-Taste überspringt</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Springe zur nächsten Episode (statt Vorspulen), wenn die \"Nächster\"-Taste gedrückt wird</string>
<string name="pref_followQueue_sum">Springe zur nächsten Episode, wenn die vorherige Episode endet</string>
<string name="pref_auto_delete_sum">Episode löschen, wenn die Wiedergabe endet</string>
<string name="pref_auto_delete_title">Automatisches Löschen</string>
+ <string name="pref_smart_mark_as_played_sum">Episoden werden als gespielt markiert, wenn weniger als eine bestimmte Anzahl Sekunden Restspielzeit übrig sind</string>
+ <string name="pref_smart_mark_as_played_title">Schlaues als gespielt markieren</string>
+ <string name="pref_skip_keeps_episodes_sum">Behalte Episoden beim Überspringen</string>
+ <string name="pref_skip_keeps_episodes_title">Behalte übersprungene Episoden</string>
<string name="playback_pref">Wiedergabe</string>
<string name="network_pref">Netzwerk</string>
- <string name="pref_autoUpdateIntervall_title">Aktualisierungsintervall</string>
- <string name="pref_autoUpdateIntervall_sum">Lege ein Intervall fest, in dem Feeds automatisch aktualisiert werden oder deaktiviere es</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Aktualisierungsintervall oder -tageszeit</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Lege ein Intervall oder eine Tageszeit zur automatischen Aktualisierung der Podcasts fest</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Du kannst ein festes <i>Intervall</i> wie \"alle 2 Stunden\", eine bestimmte <i>Tageszeit</i> wie \"7 Uhr morgens\" festlegen oder die automatische Aktualisierung komplett <i>deaktivieren</i>.\n\n<small>Bitte beachte: Der Zeitpunkt der Aktualisierung ist ungenau. Du wirst vielleicht eine kurze Verzögerung bemerken.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Deaktivieren</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Intervall einstellen</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Tageszeit festlegen</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">jede %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">um %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Lade Mediendateien nur über WiFi</string>
<string name="pref_followQueue_title">Durchgehendes Abspielen</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi Medien-Download</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Kopfhörer-Trennung</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Kopfhörer wieder eingesteckt</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth wieder verbunden</string>
<string name="pref_mobileUpdate_title">Mobile Aktualisierungen</string>
<string name="pref_mobileUpdate_sum">Erlaube Aktualisierungen über die mobile Datenverbindung</string>
<string name="refreshing_label">Aktualisiere</string>
@@ -223,6 +310,14 @@
<string name="pref_auto_flattr_sum">Automatisches Flattrn konfigurieren</string>
<string name="user_interface_label">Benutzeroberfläche</string>
<string name="pref_set_theme_title">Theme auswählen</string>
+ <string name="pref_nav_drawer_title">Seitenleiste anpassen</string>
+ <string name="pref_nav_drawer_sum">Passe das Aussehen der Seitenleiste an.</string>
+ <string name="pref_nav_drawer_items_title">Seitenleiste ändern</string>
+ <string name="pref_nav_drawer_items_sum">Ändere, welche Listen in der Seitenleiste erscheinen</string>
+ <string name="pref_nav_drawer_feed_order_title">Reihenfolge der Abonnements einstellen</string>
+ <string name="pref_nav_drawer_feed_order_sum">Ändere die Reihenfolge deiner Abonnements</string>
+ <string name="pref_nav_drawer_feed_counter_title">Abonnement-Zähler einstellen</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Ändere, welche Information der Abonnement-Zähler anzeigt</string>
<string name="pref_set_theme_sum">Ändere das Aussehen von AntennaPod.</string>
<string name="pref_automatic_download_title">Automatisches Herunterladen</string>
<string name="pref_automatic_download_sum">Konfiguriere das automatische Herunterladen von Episoden.</string>
@@ -246,17 +341,31 @@
<string name="pref_gpodnet_setlogin_information_sum">Ändere die Anmeldeinformationen deines gpodder.net Profils</string>
<string name="pref_playback_speed_title">Wiedergabegeschwindigkeiten</string>
<string name="pref_playback_speed_sum">Lege die verfügbaren Werte für die Veränderung der Wiedergabeschwindigkeit fest</string>
- <string name="pref_seek_delta_title">Spul-Zeit</string>
- <string name="pref_seek_delta_sum">Spule so viele Sekunden vor oder zurück</string>
+ <string name="pref_fast_forward">Vorspulzeit</string>
+ <string name="pref_rewind">Rückspulzeit</string>
<string name="pref_gpodnet_sethostname_title">Hostname ändern</string>
<string name="pref_gpodnet_sethostname_use_default_host">Standard-Host verwenden</string>
<string name="pref_expandNotify_title">Benachrichtigung erweitern</string>
<string name="pref_expandNotify_sum">Erweiterte Wiedergabebenachrichtigung mit Abspiel-, Pause- und Stop-Knöpfen anzeigen.</string>
- <string name="pref_persistNotify_title">Persistente Wiedergabesteurung</string>
+ <string name="pref_persistNotify_title">Persistente Wiedergabesteuerung</string>
<string name="pref_persistNotify_sum">Zeige Wiedergabebedienelemente in der Benachrichtigung und im Lockscreen an, während die Wiedergabe pausiert ist.</string>
+ <string name="pref_lockscreen_background_title">Lockscreen-Hintergrund einstellen</string>
+ <string name="pref_lockscreen_background_sum">Verwende das aktuelle Episodenbild als Lockscreen-Hintergrund. Es wird als Nebeneffekt auch in anderen Apps gezeigt.</string>
+ <string name="pref_showDownloadReport_title">Zeige Download-Bericht</string>
+ <string name="pref_showDownloadReport_sum">Wenn Downloads fehlschlagen, erstelle einen Bericht, der die Details des Fehlschlages beschreibt.</string>
<string name="pref_expand_notify_unsupport_toast">Android-Versionen vor 4.1 unterstützen keine erweiterten Benachrichtigungen.</string>
<string name="pref_queueAddToFront_sum">Fügen Sie neue Folgen auf den Anfang der Warteschlange.</string>
<string name="pref_queueAddToFront_title">Vorne in Abspielliste einreihen</string>
+ <string name="pref_smart_mark_as_played_disabled">Deaktiviert</string>
+ <string name="pref_image_cache_size_title">Größe des Bilder-Zwischenspeichers</string>
+ <string name="pref_image_cache_size_sum">Größe des Zwischenspeichers für Bilder</string>
+ <string name="crash_report_title">Absturzbericht</string>
+ <string name="crash_report_sum">Sende den aktuellen Absturzbericht per E-Mail</string>
+ <string name="send_email">E-Mail senden</string>
+ <string name="experimental_pref">Experimentell</string>
+ <string name="pref_sonic_title">Sonic Mediaplayer</string>
+ <string name="pref_sonic_message">Benutze den integrierten Sonic Mediaplayer als Ersatz für Androids eigenen Mediaplayer und Prestissimo</string>
+ <string name="pref_current_value">Aktueller Wert: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Automatisches Flattrn aktivieren</string>
<string name="auto_flattr_after_percent">Flattr eine Episode, sobald %d Prozent gespielt worden sind</string>
@@ -271,9 +380,10 @@
<string name="found_in_title_label">In Titel gefunden</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Mit OPML Dateien kannst du deine Podcasts von einem Podcatcher auf einen anderen übertragen</string>
+ <string name="opml_import_option">Option %1$d</string>
<string name="opml_import_explanation_1">Wähle einen bestimmten Dateipfad aus dem lokalen Dateisystem.</string>
<string name="opml_import_explanation_2">Verwende externe Anwendungen wie Dropbox, Google Drive oder deinen Lieblingsdateimanager, um eine OPML-Datei zu öffnen.</string>
- <string name="opml_import_explanation_3">Viele Anwendungen wie Google Mail, Dropbox, Google Drive und die meisten Dateimanager können OPML-Dateien &lt;i&gt;mit&lt;/ i&gt; AntennaPod &lt;i&gt;öffnen&lt;/i&gt;.</string>
+ <string name="opml_import_explanation_3">Viele Anwendungen wie Google Mail, Dropbox, Google Drive und die meisten Dateimanager können OPML-Dateien <i>mit</i> AntennaPod <i>öffnen</i>.</string>
<string name="start_import_label">Import starten</string>
<string name="opml_import_label">OPML Import</string>
<string name="opml_directory_error">FEHLER!</string>
@@ -282,10 +392,11 @@
<string name="opml_import_error_dir_empty">Der Import-Ordner ist leer.</string>
<string name="select_all_label">Alle auswählen</string>
<string name="deselect_all_label">Auswahl zurücksetzen</string>
+ <string name="select_options_label">Auswählen…</string>
<string name="choose_file_from_filesystem">Vom lokalen Dateisystem</string>
<string name="choose_file_from_external_application">Verwende externe Anwendung</string>
<string name="opml_export_label">OPML Export</string>
- <string name="exporting_label">Exportiere...</string>
+ <string name="exporting_label">Exportiere…</string>
<string name="export_error_label">Exportfehler</string>
<string name="opml_export_success_title">OPML Export erfolgreich</string>
<string name="opml_export_success_sum">Die OPML Datei wurde unter dem folgenden Pfad gespeichert:\u0020</string>
@@ -296,9 +407,24 @@
<string name="sleep_timer_label">Schlummerfunktion</string>
<string name="time_left_label">Zeit übrig:\u0020</string>
<string name="time_dialog_invalid_input">Ungültige Eingabe, Zeit muss eine Ganzzahl sein</string>
- <string name="time_unit_seconds">Sekunden</string>
- <string name="time_unit_minutes">Minuten</string>
- <string name="time_unit_hours">Stunden</string>
+ <string name="timer_about_to_expire_label"><b>Wenn der Timer gleich abläuft:</b></string>
+ <string name="shake_to_reset_label">Timer durch Schütteln zurücksetzen</string>
+ <string name="timer_vibration_label">Vibriere</string>
+ <string name="time_seconds">Sekunden</string>
+ <string name="time_minutes">Minuten</string>
+ <string name="time_hours">Stunden</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 Sekunde</item>
+ <item quantity="other">%d Sekunden</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 Minute</item>
+ <item quantity="other">%d Minuten</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 Stunde</item>
+ <item quantity="other">%d Stunden</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORIEN</string>
<string name="gpodnet_toplist_header">BESTE PODCASTS</string>
@@ -331,20 +457,27 @@
<string name="selected_folder_label">Ausgewählter Ordner</string>
<string name="create_folder_label">Neuer Ordner</string>
<string name="choose_data_directory">Datenordner auswählen</string>
+ <string name="choose_data_directory_message">Bitte wähle eine Basis für deinen Datenordner. AntennaPod erstellt automatisch die richtigen Unterverzeichnisse.</string>
<string name="create_folder_msg">Neuen Ordner mit Namen \"%1$s\" erstellen?</string>
<string name="create_folder_success">Neuer Ordner angelegt</string>
<string name="create_folder_error_no_write_access">Kann in diesem Ordner nicht schreiben</string>
<string name="create_folder_error_already_exists">Ordner existiert bereits</string>
<string name="create_folder_error">Konnte Datenordner nicht erstellen</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" existiert nicht</string>
+ <string name="folder_not_readable_error">\"%1$s\" ist nicht lesbar</string>
+ <string name="folder_not_writable_error">\"%1$s\" ist nicht beschreibbar</string>
<string name="folder_not_empty_dialog_title">Ordner ist nicht leer</string>
<string name="folder_not_empty_dialog_msg">Der ausgewählte Ordner ist nicht leer. Medien-Downloads und andere Daten werden direkt in diesem Ordner gespeichert. Trotzdem fortfahren?</string>
<string name="set_to_default_folder">Standardordner auswählen</string>
- <string name="pref_pausePlaybackForFocusLoss_sum">Pausiere die Wiedergabe statt die Lautstärke zu reduzieren, wenn eine andere Anwendung Töne abspielt</string>
+ <string name="pref_pausePlaybackForFocusLoss_sum">Wiedergabe pausieren statt die Lautstärke zu reduzieren, wenn eine andere Anwendung Töne abspielt</string>
<string name="pref_pausePlaybackForFocusLoss_title">Bei Unterbrechungen pausieren</string>
+ <string name="pref_resumeAfterCall_sum">Wiedergabe fortsetzen, wenn Anruf beendet ist</string>
+ <string name="pref_resumeAfterCall_title">Nach Anruf fortsetzen</string>
+ <string name="pref_restart_required">AntennaPod muss neu gestartet werden, damit die Änderungen wirksam werden.</string>
<!--Online feed view-->
<string name="subscribe_label">Abonnieren</string>
<string name="subscribed_label">Abonniert</string>
- <string name="downloading_label">Lade herunter...</string>
+ <string name="downloading_label">Lade herunter…</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Kapitel anzeigen</string>
<string name="show_shownotes_label">Sendungsnotizen anzeigen</string>
@@ -367,7 +500,45 @@
<!--Feed information screen-->
<string name="authentication_label">Authentifizierung</string>
<string name="authentication_descr">Ändere den Benutzernamen und das Passwort für diesen Podcast und dessen Episoden.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Datenbank wird aktualisiert</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importiere Abonnements aus Single-Purpose Apps</string>
- <string name="search_itunes_label">iTunes suchen</string>
+ <string name="search_itunes_label">iTunes durchsuchen</string>
+ <string name="select_label"><b>Wähle aus…</b></string>
+ <string name="filter">Filtern</string>
+ <string name="all_label">Alle</string>
+ <string name="selected_all_label">Alle Episoden ausgewählt</string>
+ <string name="none_label">Keine</string>
+ <string name="deselected_all_label">Alle Episoden abgewählt</string>
+ <string name="played_label">Gespielt</string>
+ <string name="selected_played_label">Gespielte Episoden ausgewählt</string>
+ <string name="unplayed_label">Ungespielt</string>
+ <string name="selected_unplayed_label">Ungespielte Episoden ausgewählt</string>
+ <string name="downloaded_label">Heruntergeladen</string>
+ <string name="selected_downloaded_label">Heruntergeladene Episoden ausgewählt</string>
+ <string name="not_downloaded_label">Nicht heruntergeladen</string>
+ <string name="selected_not_downloaded_label">Nicht heruntergeladene Episoden ausgewählt</string>
+ <string name="sort_title"><b>Sortiere nach…</b></string>
+ <string name="sort_title_a_z">Titel (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Titel (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Datum (neu \u2192 alt)</string>
+ <string name="sort_date_old_new">Datum (alt \u2192 neu)</string>
+ <string name="sort_duration_short_long">Dauer (kurz \u2192 lang)</string>
+ <string name="sort_duration_long_short">Dauer (lang \u2192 kurz)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Gefällt dir AntennaPod?</string>
+ <string name="rating_message">Wir würden uns freuen, wenn du dir kurz die Zeit nimmst, AntennaPod zu bewerten.</string>
+ <string name="rating_never_label">Lass mich in Ruhe</string>
+ <string name="rating_later_label">Erinnere mich später</string>
+ <string name="rating_now_label">Sicher, los geht\'s!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Audioregler</string>
+ <string name="playback_speed">Abspielgeschwindigkeit</string>
+ <string name="volume">Lautstärke</string>
+ <string name="left_short">L</string>
+ <string name="right_short">R</string>
+ <string name="audio_effects">Audioeffekte</string>
+ <string name="stereo_to_mono">Heruntermischen: Stereo zu Mono</string>
+ <string name="sonic_only">nur Sonic</string>
</resources>
diff --git a/core/src/main/res/values-es-rES/strings.xml b/core/src/main/res/values-es-rES/strings.xml
index d05c34876..7c851db62 100644
--- a/core/src/main/res/values-es-rES/strings.xml
+++ b/core/src/main/res/values-es-rES/strings.xml
@@ -3,46 +3,91 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Canales</string>
+ <string name="add_feed_label">Añadir podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISODIOS</string>
+ <string name="episodes_label">Episodios</string>
+ <string name="new_episodes_label">Nuevos episodios</string>
+ <string name="all_episodes_label">Todos los episodios</string>
+ <string name="all_episodes_short_label">Todos</string>
+ <string name="favorite_episodes_label">Favoritos</string>
<string name="new_label">Nuevos</string>
<string name="waiting_list_label">Lista de espera</string>
<string name="settings_label">Ajustes</string>
+ <string name="add_new_feed_label">Añadir podcast</string>
<string name="downloads_label">Descargas</string>
<string name="cancel_download_label">Cancelar descarga</string>
<string name="playback_history_label">Historial de reproducción</string>
+ <string name="gpodnet_main_label">gpodder.net</string>
+ <string name="gpodnet_auth_label">gpodder.net Login</string>
+ <string name="free_space_label">%1$s disponible</string>
<!--New episodes fragment-->
+ <string name="recently_published_episodes_label">Publicado recientemente</string>
+ <string name="episode_filter_label">Mostrar solo episodios nuevos</string>
<!--Main activity-->
+ <string name="drawer_open">Abrir menu</string>
+ <string name="drawer_close">Cerrar menu</string>
+ <string name="drawer_feed_order_unplayed_episodes">Ordenar por contador</string>
+ <string name="drawer_feed_order_alphabetical">Ordenar alfabéticamente</string>
+ <string name="drawer_feed_order_last_update">Ordenar por fecha de publicación</string>
+ <string name="drawer_feed_counter_new_unplayed">Numero de episodios nuevos y no escuchados</string>
+ <string name="drawer_feed_counter_new">Numero de nuevos episodios</string>
+ <string name="drawer_feed_counter_unplayed">Numero de episodios no escuchados</string>
+ <string name="drawer_feed_counter_none">Ningun</string>
<!--Webview actions-->
<string name="open_in_browser_label">Abrir en el navegador</string>
<string name="copy_url_label">Copiar URL</string>
<string name="share_url_label">Compartir URL</string>
<string name="copied_url_msg">URL copiada al portapapeles.</string>
+ <string name="go_to_position_label">Ir a esta posición</string>
<!--Playback history-->
<string name="clear_history_label">Limpiar el historial</string>
<!--Other-->
<string name="confirm_label">Confirmar</string>
<string name="cancel_label">Cancelar</string>
+ <string name="yes">Si</string>
+ <string name="no">No</string>
<string name="author_label">Autor</string>
<string name="language_label">Idioma</string>
+ <string name="url_label">URL</string>
<string name="error_label">Error</string>
<string name="error_msg_prefix">Ha ocurrido un error:</string>
<string name="refresh_label">Actualizar</string>
<string name="external_storage_error_msg">No se encuentra un almacenamiento externo. Asegúrese de que su almacenamiento externo esté montado para que la aplicación funcione correctamente.</string>
<string name="chapters_label">Capítulos</string>
<string name="shownotes_label">Notas del programa</string>
+ <string name="description_label">Descripción</string>
+ <string name="most_recent_prefix">Episodio mas recién:\u0020</string>
<string name="episodes_suffix">\u0020episodios</string>
<string name="length_prefix">Duración:\u0020</string>
<string name="size_prefix">Tamaño:\u0020</string>
<string name="processing_label">Procesando</string>
- <string name="loading_label">Cargando...</string>
+ <string name="save_username_password_label">Guardar nombre de usuario y contraseña</string>
+ <string name="close_label">Cerrar</string>
+ <string name="retry_label">Intentar de nuevo</string>
+ <string name="auto_download_label">Incluir en descargas automaticas</string>
+ <string name="auto_download_apply_to_items_title">Aplicar a los previos episodios</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Siempre</string>
+ <string name="feed_auto_download_never">Nunca</string>
+ <string name="episode_cleanup_never">Nunca</string>
+ <string name="episode_cleanup_queue_removal">Cuando no en la lista</string>
+ <string name="episode_cleanup_after_listening">Despues de terminar</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 día después de terminar</item>
+ <item quantity="other">%d días después de terminar</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL del canal</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
+ <string name="txtvfeedurl_label">Añadir podcast por URL</string>
+ <string name="podcastdirectories_label">Buscar podcast en directorio</string>
+ <string name="browse_gpoddernet_label">Navegar gpodder.net</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Marcar todo como leído</string>
+ <string name="mark_all_seen_label">Marcar todos como visto</string>
<string name="show_info_label">Información del programa</string>
+ <string name="remove_feed_label">Eliminar podcast</string>
<string name="share_link_label">Compartir el enlace de la web</string>
- <string name="share_source_label">Compartir el enlace del canal</string>
<string name="feed_delete_confirmation_msg">Confirme que quiere eliminar este canal y TODOS los episodios descargados del mismo.</string>
<!--actions on feeditems-->
<string name="download_label">Descargar</string>
@@ -77,7 +122,6 @@
<string name="download_error_malformed_url">URL malformada</string>
<string name="download_error_io_error">Error de E/S</string>
<string name="download_error_request_error">Error de petición</string>
- <string name="downloads_left">\u0020descargas restantes</string>
<string name="download_notification_title">Descargando datos del podcast</string>
<string name="download_report_content">%1$d descargas exitosas, %2$d fallidas</string>
<string name="download_log_title_unknown">Título desconocido</string>
@@ -121,12 +165,9 @@
<string name="other_pref">Otros</string>
<string name="about_pref">Acerca de</string>
<string name="queue_label">Cola</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pausar la reproducción al desconectar los auriculares</string>
<string name="pref_followQueue_sum">Saltar al siguiente elemento de la cola al acabar la reproducción</string>
<string name="playback_pref">Reproducción</string>
<string name="network_pref">Red</string>
- <string name="pref_autoUpdateIntervall_title">Intervalo de actualización</string>
- <string name="pref_autoUpdateIntervall_sum">Especificar el intervalo en que se actualizarán automáticamente los canales, o desactivarlo</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Solo descargar los contenidos por WiFi</string>
<string name="pref_followQueue_title">Reproducción continua</string>
<string name="pref_downloadMediaOnWifiOnly_title">Descarga de contenidos por WiFi</string>
@@ -167,7 +208,6 @@
<string name="select_all_label">Seleccionar todo</string>
<string name="deselect_all_label">Deseleccionar todo</string>
<string name="opml_export_label">Exportar a OPML</string>
- <string name="exporting_label">Exportando...</string>
<string name="export_error_label">Error en la exportación</string>
<string name="opml_export_success_sum">El archivo OPML se ha escrito en:\u0020</string>
<!--Sleep timer-->
@@ -192,6 +232,12 @@
<string name="set_to_default_folder">Elegir carpeta predeterminada</string>
<!--Online feed view-->
<!--Content descriptions for image buttons-->
+ <string name="new_episodes_count_label">Numero de nuevos episodios</string>
<!--Feed information screen-->
+ <!--Progress information-->
<!--AntennaPodSP-->
+ <string name="all_label">Todos</string>
+ <string name="none_label">Ningun</string>
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-es/strings.xml b/core/src/main/res/values-es/strings.xml
index eb1d1631b..a2abd5d85 100644
--- a/core/src/main/res/values-es/strings.xml
+++ b/core/src/main/res/values-es/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Canales</string>
<string name="add_feed_label">Añadir podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISODIOS</string>
+ <string name="episodes_label">Episodios</string>
<string name="new_episodes_label">Episodios nuevos</string>
<string name="all_episodes_label">Todos los episodios</string>
+ <string name="all_episodes_short_label">Todos</string>
+ <string name="favorite_episodes_label">Favoritos</string>
<string name="new_label">Nuevos</string>
<string name="waiting_list_label">Lista de espera</string>
<string name="settings_label">Ajustes</string>
@@ -16,29 +18,41 @@
<string name="downloads_running_label">En curso</string>
<string name="downloads_completed_label">Completadas</string>
<string name="downloads_log_label">Registro</string>
- <string name="cancel_download_label">Cancelar descarga</string>
+ <string name="cancel_download_label">Cancelar\ndescarga</string>
<string name="playback_history_label">Historial de reproducciones</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">Iniciar sesión en gpodder.net</string>
+ <string name="free_space_label">%1$s libre</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Publicados recientemente</string>
<string name="episode_filter_label">Mostrar solo episodios nuevos</string>
<!--Main activity-->
<string name="drawer_open">Abrir menú</string>
<string name="drawer_close">Cerrar menú</string>
+ <string name="drawer_preferences">Preferencias del cajón</string>
+ <string name="drawer_feed_order_unplayed_episodes">Ordenar por cuenta</string>
+ <string name="drawer_feed_order_alphabetical">Ordenar alfabéticamente</string>
+ <string name="drawer_feed_order_last_update">Ordenar por fecha de publicación</string>
+ <string name="drawer_feed_counter_new_unplayed">Cantidad de episodios nuevos y no escuchados</string>
+ <string name="drawer_feed_counter_new">Cantidad de episodios nuevos</string>
+ <string name="drawer_feed_counter_unplayed">Cantidad de episodios no escuchados</string>
+ <string name="drawer_feed_counter_none">Ninguno</string>
<!--Webview actions-->
<string name="open_in_browser_label">Abrir en el navegador</string>
<string name="copy_url_label">Copiar URL</string>
<string name="share_url_label">Compartir URL</string>
- <string name="copied_url_msg">URL copiada en el portapapeles.</string>
+ <string name="copied_url_msg">URL copiado en el portapapeles</string>
<string name="go_to_position_label">Ir a esta posición</string>
<!--Playback history-->
<string name="clear_history_label">Vaciar el historial</string>
<!--Other-->
<string name="confirm_label">Confirmar</string>
<string name="cancel_label">Cancelar</string>
+ <string name="yes">Sí</string>
+ <string name="no">No</string>
<string name="author_label">Autor</string>
<string name="language_label">Idioma</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Ajustes</string>
<string name="cover_label">Imagen</string>
<string name="error_label">Error</string>
@@ -53,31 +67,61 @@
<string name="length_prefix">Duración:\u0020</string>
<string name="size_prefix">Tamaño:\u0020</string>
<string name="processing_label">Procesando</string>
- <string name="loading_label">Cargando...</string>
+ <string name="loading_label">Cargando…</string>
<string name="save_username_password_label">Guardar usuario y contraseña</string>
<string name="close_label">Cerrar</string>
<string name="retry_label">Reintentar</string>
<string name="auto_download_label">Incluir en descargas automáticas</string>
+ <string name="auto_download_apply_to_items_title">Aplicar a episodios anteriores</string>
+ <string name="auto_download_apply_to_items_message">La nueva opción <i>Auto Descarga</i> se aplicará automáticamente a episodios nuevos.\n¿También desea aplicarlo a episodios anteriores?</string>
+ <string name="auto_delete_label">Eliminar episodio automáticamente\n(ignorar config. global)</string>
<string name="parallel_downloads_suffix">\u0020descargas paralelas</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Siempre</string>
+ <string name="feed_auto_download_never">Nunca</string>
+ <string name="send_label">Enviar…</string>
+ <string name="episode_cleanup_never">Nunca</string>
+ <string name="episode_cleanup_queue_removal">Cuando no esté en cola</string>
+ <string name="episode_cleanup_after_listening">Después de acabar</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 día después de acabar</item>
+ <item quantity="other">%d días después de acabar</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL del canal</string>
- <string name="etxtFeedurlHint">URL del canal o del sitio web</string>
+ <string name="etxtFeedurlHint">www.ejemplo.com/feed</string>
<string name="txtvfeedurl_label">Añadir podcast por URL</string>
<string name="podcastdirectories_label">Buscar podcast en directorio</string>
<string name="podcastdirectories_descr">Es posible buscar podcasts nuevos por nombre, categoría o popularidad en el directorio de gpodder.net.</string>
<string name="browse_gpoddernet_label">Explorar gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Marcar todo como leído</string>
- <string name="mark_all_read_msg">Se marcaron todos los episodios como leídos</string>
- <string name="mark_all_read_confirmation_msg">Por favor, confirme que desea marcar todos los episodios como leídos.</string>
- <string name="mark_all_read_feed_confirmation_msg">Por favor, confirme que desea marcar todos los episodios de este feed como leídos.</string>
+ <string name="mark_all_read_label">Marcar todos como escuchado</string>
+ <string name="mark_all_read_msg">Se marcaron todos los episodios como escuchados</string>
+ <string name="mark_all_read_confirmation_msg">Confirme que quiere marcar todos los episodios como escuchados.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Confirme que quiere marcar todos los episodios de este canal como escuchados.</string>
+ <string name="mark_all_seen_label">Marcar todos como vistos</string>
<string name="show_info_label">Información del programa</string>
<string name="remove_feed_label">Eliminar podcast</string>
+ <string name="share_label">Compartir…</string>
<string name="share_link_label">Compartir el enlace de la web</string>
- <string name="share_source_label">Compartir el enlace del canal</string>
+ <string name="share_link_with_position_label">Compartir enlace con posición</string>
+ <string name="share_feed_url_label">Compartir URL del canal</string>
+ <string name="share_item_url_label">Compartir URL del episodio</string>
+ <string name="share_item_url_with_position_label">Compartir URL del episodio con posición</string>
<string name="feed_delete_confirmation_msg">Confirme que quiere eliminar este canal y TODOS los episodios descargados del mismo.</string>
<string name="feed_remover_msg">Quitando el canal</string>
<string name="load_complete_feed">Actualizar el canal completo</string>
+ <string name="hide_episodes_title">Ocultar episodios</string>
+ <string name="episode_actions">Aplicar acciones</string>
+ <string name="hide_unplayed_episodes_label">No escuchados</string>
+ <string name="hide_paused_episodes_label">Pausados</string>
+ <string name="hide_played_episodes_label">Escuchados</string>
+ <string name="hide_queued_episodes_label">En cola</string>
+ <string name="hide_not_queued_episodes_label">No en cola</string>
+ <string name="hide_downloaded_episodes_label">Descargados</string>
+ <string name="hide_not_downloaded_episodes_label">No descargados</string>
+ <string name="filtered_label">Filtrados</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Error en última actualización</string>
<!--actions on feeditems-->
<string name="download_label">Descargar</string>
<string name="play_label">Reproducir</string>
@@ -86,16 +130,25 @@
<string name="stream_label">Transmitir</string>
<string name="remove_label">Quitar</string>
<string name="remove_episode_lable">Quitar episodio</string>
- <string name="mark_read_label">Marcar como leído</string>
- <string name="mark_unread_label">Marcar como no leído</string>
- <string name="marked_as_read_label">Marcado como leído</string>
+ <string name="mark_read_label">Marcar como escuchado</string>
+ <string name="marked_as_read_label">Marcado como escuchado</string>
+ <string name="mark_unread_label">Marcar como no escuchado</string>
<string name="add_to_queue_label">Añadir a la cola</string>
+ <string name="added_to_queue_label">Añadido a la cola</string>
<string name="remove_from_queue_label">Quitar de la cola</string>
+ <string name="add_to_favorite_label">Añadir a Favoritos</string>
+ <string name="added_to_favorites">Añadido a Favoritos</string>
+ <string name="remove_from_favorite_label">Eliminar de Favoritos</string>
+ <string name="removed_from_favorites">Quitado de Favoritos</string>
<string name="visit_website_label">Visitar el sitio web</string>
<string name="support_label">Añadir a Flattr</string>
<string name="enqueue_all_new">Ponerlos todos en cola</string>
<string name="download_all">Descargarlos todos</string>
<string name="skip_episode_label">Omitir episodio</string>
+ <string name="activate_auto_download">Activar descarga automática</string>
+ <string name="deactivate_auto_download">Desactivar descarga automática</string>
+ <string name="reset_position">Resetear posición de reproducción</string>
+ <string name="removed_item">Elemento elminado</string>
<!--Download messages and labels-->
<string name="download_successful">exitoso</string>
<string name="download_failed">fallido</string>
@@ -113,12 +166,17 @@
<string name="download_error_unauthorized">Error de autenticación</string>
<string name="cancel_all_downloads_label">Cancelar todas las descargas</string>
<string name="download_canceled_msg">Descarga cancelada</string>
- <string name="download_report_title">Descargas completadas</string>
+ <string name="download_canceled_autodownload_enabled_msg">Descarga cancelada\nSe desactivó <i>Descarga automática</i> en este elemento</string>
+ <string name="download_report_title">Descargas completadas con error(es)</string>
+ <string name="download_report_content_title">Informe de descarga</string>
<string name="download_error_malformed_url">URL con formato incorrecto</string>
<string name="download_error_io_error">Error de E/S</string>
<string name="download_error_request_error">Error de solicitud</string>
<string name="download_error_db_access">Error de acceso a la base de datos</string>
- <string name="downloads_left">\u0020descargas restantes</string>
+ <plurals name="downloads_left">
+ <item quantity="one">Queda %d descarga</item>
+ <item quantity="other">Quedan %d descargas</item>
+ </plurals>
<string name="downloads_processing">Procesando descargas</string>
<string name="download_notification_title">Descargando datos del podcast</string>
<string name="download_report_content">%1$d descargas exitosas, %2$d fallidas</string>
@@ -129,6 +187,11 @@
<string name="download_request_error_dialog_message_prefix">Ha ocurrido un error al intentar descargar el archivo:\u0020</string>
<string name="authentication_notification_title">Se necesita autenticación</string>
<string name="authentication_notification_msg">Para acceder al recurso solicitado debe proporcionar un usuario y contraseña</string>
+ <string name="confirm_mobile_download_dialog_title">Confirmar descarga por red móvil</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Se desactivaron las descargas por red de datos móviles en la configuración.\n\nPuede elegir entre añadir el episodio a la cola o permitir las descargas temporalmente.\n\n<small>Se recordará la elección por 10 minutos.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Se desactivaron las descargas por red de datos móviles en la configuración.\n\n¿Quiere permitir las descargas temporalmente?\n\n<small>Se recordará la elección por 10 minutos.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Añadir a la cola</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Permitir temporalmente</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Error</string>
<string name="player_stopped_msg">No hay medios en reproducción</string>
@@ -143,9 +206,13 @@
<string name="playbackservice_notification_title">Reproduciendo el podcast</string>
<string name="unknown_media_key">AntennaPod - Tecla multimedia desconocida: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Bloquear cola</string>
+ <string name="unlock_queue">Desbloquear cola</string>
+ <string name="queue_locked">Cola bloqueada</string>
+ <string name="queue_unlocked">Cola desbloqueada</string>
<string name="clear_queue_label">Vaciar la cola</string>
<string name="undo">Deshacer</string>
- <string name="removed_from_queue">Artículo eliminado</string>
+ <string name="removed_from_queue">Se quitó el elemento</string>
<string name="move_to_top_label">Mover al principio</string>
<string name="move_to_bottom_label">Mover al final</string>
<string name="sort">Ordenar</string>
@@ -154,7 +221,7 @@
<string name="duration">Duración</string>
<string name="ascending">Ascendente</string>
<string name="descending">Descendente</string>
- <string name="clear_queue_confirmation_msg">Por favor, confirme que desea borrar TODOS los episodios de la cola</string>
+ <string name="clear_queue_confirmation_msg">Confirme que quiere borrar TODOS los episodios de la cola</string>
<!--Flattr-->
<string name="flattr_auth_label">Identificarse en Flattr</string>
<string name="flattr_auth_explanation">Pulse el botón inferior para comenzar la autenticación. Su navegador abrirá la pantalla de identificación de Flattr y le preguntará si quiere conceder permiso a AntennaPod para valorar cosas. Tras concederlo, volverá a esta pantalla automáticamente.</string>
@@ -162,7 +229,7 @@
<string name="return_home_label">Volver a la pantalla principal</string>
<string name="flattr_auth_success">Autentificación exitosa. Ya puede valorar cosas en Flattr desde la aplicación.</string>
<string name="no_flattr_token_title">No se ha encontrado un token de Flattr</string>
- <string name="no_flattr_token_notification_msg">Tu cuenta Flatter parece no estar conectada con AntennaPod. Pulsa aquí para autenticar.</string>
+ <string name="no_flattr_token_notification_msg">Parece que su cuenta de Flattr no está conectada con AntennaPod. Toque aquí para autenticarse.</string>
<string name="no_flattr_token_msg">Su cuenta de Flattr no está conectada con AntennaPod. Puede conectarla o puede visitar la página web de cada cosa para valorarla desde allí.</string>
<string name="authenticate_now_label">Autenticarse</string>
<string name="action_forbidden_title">Acción prohibida</string>
@@ -170,10 +237,10 @@
<string name="access_revoked_title">Acceso revocado</string>
<string name="access_revoked_info">Ha revocado el token de acceso de AntennaPod a su cuenta. Para completar el proceso debe eliminar esta aplicación de la lista de aplicaciones aprobadas, en los ajustes de Flattr.</string>
<!--Flattr-->
- <string name="flattr_click_success">¡Flattr una cosa!</string>
- <string name="flattr_click_success_count">¡Flattr %d cosas!</string>
+ <string name="flattr_click_success">Ha hecho Flattr en un elemento.</string>
+ <string name="flattr_click_success_count">Ha hecho Flattr en %d elementos.</string>
<string name="flattr_click_success_queue">Flattr: %s.</string>
- <string name="flattr_click_failure_count">¡Falló Flattr de %d cosas!</string>
+ <string name="flattr_click_failure_count">No se pudo hacer Flattr en %d elementos.</string>
<string name="flattr_click_failure">No se hizo Flattr: %s.</string>
<string name="flattr_click_enqueued">Se hará Flattr de esta cosa más tarde</string>
<string name="flattring_thing">Haciendo Flattr de %s</string>
@@ -184,31 +251,48 @@
<!--Variable Speed-->
<string name="download_plugin_label">Descargar complemento</string>
<string name="no_playback_plugin_title">Complemento no instalado</string>
- <string name="no_playback_plugin_msg">Para que funcione la reproducción a velocidad variable, es necesaria una librería de terceros.\n\nPulsa «Descargar plugin» para descargar un plugin gratuito desde el Play Store\n\nCualquier problema relacionado con este plugin no es responsabilidad de AntennaPod y deberían ser reportados al autor del plugin.</string>
<string name="set_playback_speed_label">Velocidades de reproducción</string>
+ <string name="enable_sonic">Activar Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Esta lista no tiene elementos.</string>
<string name="no_feeds_label">No se ha suscrito a ningún canal.</string>
+ <string name="no_chapters_label">Este episodio no tiene capítulos.</string>
<!--Preferences-->
<string name="other_pref">Otros</string>
<string name="about_pref">Acerca de</string>
<string name="queue_label">Cola</string>
<string name="services_label">Servicios</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pausar la reproducción al desconectar los auriculares</string>
+ <string name="pref_episode_cleanup_title">Limpieza de episodios</string>
+ <string name="pref_episode_cleanup_summary">Los episodios que no estén en la cola ni en Favoritos pueden eliminarse si Descarga automática necesita espacio para episodios nuevos</string>
+ <string name="pref_pauseOnDisconnect_sum">Pausar la reproducción al desconectar los auriculares o el bluetooth</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Reanudar reproducción cuando se reconecten los auriculares</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Reanudar reproducción cuando se reconecte el bluetooth</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Saltar episodio con botón</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Al pulsar el botón físico de avanzar se saltará al siguiente episodio en lugar de sólo avanzar</string>
<string name="pref_followQueue_sum">Saltar al siguiente elemento de la cola al acabar la reproducción</string>
<string name="pref_auto_delete_sum">Borrar episodio cuando finalice la reproducción</string>
<string name="pref_auto_delete_title">Eliminar automáticamente</string>
+ <string name="pref_smart_mark_as_played_sum">Marcar episodios como escuchados incluso si todavía quedan unos segundos por escuchar</string>
+ <string name="pref_smart_mark_as_played_title">Marcar como escuchado inteligente</string>
+ <string name="pref_skip_keeps_episodes_sum">Conservar episodios al saltarlos</string>
+ <string name="pref_skip_keeps_episodes_title">Conservar episodios saltados</string>
<string name="playback_pref">Reproducción</string>
<string name="network_pref">Red</string>
- <string name="pref_autoUpdateIntervall_title">Intervalo de actualización</string>
- <string name="pref_autoUpdateIntervall_sum">Especificar el intervalo en que se actualizarán automáticamente los canales, o desactivarlo</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Intervalo de actualización u hora del día</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Especificar el intervalo o la hora del día en que se actualizarán automáticamente los canales</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Se puede ajustar un <i>intervalo</i> como \"cada 2 horas\", especificar una <i>hora del día</i> como \"7:00 AM\" o <i>deshabilitar</i> las actualizaciones automáticas.\n\n<small>Nota: Las horas de actualización no son exactas. Puede haber un ligero retraso.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Deshabilitar</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Ajustar intervalo</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Ajustar hora del día</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">todos los %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">a las %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Solo descargar los contenidos por WiFi</string>
<string name="pref_followQueue_title">Reproducción continua</string>
<string name="pref_downloadMediaOnWifiOnly_title">Descarga de contenidos por WiFi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Desconexión de los cascos</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Reconectar auriculares</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Reconectar Bluetooth</string>
<string name="pref_mobileUpdate_title">Actualizaciones por red móvil</string>
<string name="pref_mobileUpdate_sum">Permitir actualizaciones por red de datos móvil</string>
<string name="refreshing_label">Actualizando</string>
@@ -223,6 +307,14 @@
<string name="pref_auto_flattr_sum">Configurar flattr automático</string>
<string name="user_interface_label">Interfaz de usuario</string>
<string name="pref_set_theme_title">Elegir un tema</string>
+ <string name="pref_nav_drawer_title">Personalizar el cajón de navegación</string>
+ <string name="pref_nav_drawer_sum">Personalizar la apariencia del cajón de navegación</string>
+ <string name="pref_nav_drawer_items_title">Cambiar el cajón de navegación</string>
+ <string name="pref_nav_drawer_items_sum">Cambiar los ítems que aparecen en el cajón de navegación</string>
+ <string name="pref_nav_drawer_feed_order_title">Ajustar orden de suscripción</string>
+ <string name="pref_nav_drawer_feed_order_sum">Cambiar el orden de las suscripciones</string>
+ <string name="pref_nav_drawer_feed_counter_title">Ajustar contador de suscripción</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Cambiar la información mostrada en el contador de suscripción</string>
<string name="pref_set_theme_sum">Cambiar la apariencia de AntennaPod.</string>
<string name="pref_automatic_download_title">Descarga automática</string>
<string name="pref_automatic_download_sum">Configurar la descarga automática de episodios.</string>
@@ -246,17 +338,30 @@
<string name="pref_gpodnet_setlogin_information_sum">Modificar datos de inicio de sesión en gpodder.net.</string>
<string name="pref_playback_speed_title">Velocidades de reproducción</string>
<string name="pref_playback_speed_sum">Personalice las velocidades disponibles para la reproducción de audio a velocidad variable</string>
- <string name="pref_seek_delta_title">Intervalo de búsqueda</string>
- <string name="pref_seek_delta_sum">Avanzar o retroceder esta cantidad de segundos</string>
+ <string name="pref_fast_forward">Intervalo de avance</string>
+ <string name="pref_rewind">Intervalo de retroceso</string>
<string name="pref_gpodnet_sethostname_title">Definir nombre de equipo</string>
<string name="pref_gpodnet_sethostname_use_default_host">Usar nombre de equipo por defecto</string>
<string name="pref_expandNotify_title">Expandir Notificación</string>
<string name="pref_expandNotify_sum">Expandir siempre la notificación para mostrar los botones de reproducción</string>
<string name="pref_persistNotify_title">Controles de reproducción persistentes</string>
<string name="pref_persistNotify_sum">Mantener la notificación y controles en pantalla de bloqueo cuando se pausa.</string>
+ <string name="pref_lockscreen_background_title">Establecer fondo de pantalla de bloqueo</string>
+ <string name="pref_lockscreen_background_sum">Establecer el fondo de pantalla de bloqueo desde la imagen del episodio. Como efecto lateral, esto también mostrará la imagen en aplicaciones de terceros.</string>
+ <string name="pref_showDownloadReport_title">Mostrar informe de descarga</string>
+ <string name="pref_showDownloadReport_sum">Si la descarga falla, generar un informe con los detalles del fallo</string>
<string name="pref_expand_notify_unsupport_toast">Las versiones de Android anteriores a la 4.1 no soportan notificaciones expandidas</string>
<string name="pref_queueAddToFront_sum">Agregar nuevos episodios al principio de la cola.</string>
<string name="pref_queueAddToFront_title">Poner al principio de la cola.</string>
+ <string name="pref_smart_mark_as_played_disabled">Deshabilitado</string>
+ <string name="pref_image_cache_size_title">Tamaño de la caché de imágenes</string>
+ <string name="pref_image_cache_size_sum">Tamaño de la caché en disco para imágenes.</string>
+ <string name="crash_report_title">Informe de fallo</string>
+ <string name="crash_report_sum">Enviar el último informe de fallo por e-mail</string>
+ <string name="send_email">Enviar e-mail</string>
+ <string name="experimental_pref">Experimental</string>
+ <string name="pref_sonic_title">Sonic media player</string>
+ <string name="pref_current_value">Valor actual: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Habilitar Flattr automático</string>
<string name="auto_flattr_after_percent">Hacer Flattr del episodio en cuanto se haya reproducido el %d por ciento</string>
@@ -271,6 +376,7 @@
<string name="found_in_title_label">Encontrado en el título</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Los archivos OPML le permiten migrar sus podcasts de una aplicación a otra.</string>
+ <string name="opml_import_option">Opción %1$d</string>
<string name="opml_import_explanation_1">Elegir un una ruta del sistema de ficheros local.</string>
<string name="opml_import_explanation_2">Usar una aplicación externa tipo Dropbox, Google Drive or su gestor de ficheros favorito para abrir un archivo OPML.</string>
<string name="opml_import_explanation_3">Muchas aplicaciones como Google Mail, Dropbox, Google Drive y la mayoría de gestores de ficheros pueden <i>abrir</i> archivos OPML <i>con</i> AntennaPod.</string>
@@ -282,10 +388,11 @@
<string name="opml_import_error_dir_empty">El directorio de importación está vacío.</string>
<string name="select_all_label">Seleccionar todo</string>
<string name="deselect_all_label">Deseleccionar todo</string>
+ <string name="select_options_label">Seleccionar…</string>
<string name="choose_file_from_filesystem">Desde el sistema de ficheros local</string>
<string name="choose_file_from_external_application">Usar aplicación externa</string>
<string name="opml_export_label">Exportar a OPML</string>
- <string name="exporting_label">Exportando...</string>
+ <string name="exporting_label">Exportando…</string>
<string name="export_error_label">Error en la exportación</string>
<string name="opml_export_success_title">Exportación a OPML exitosa</string>
<string name="opml_export_success_sum">El archivo OPML se ha escrito en:\u0020</string>
@@ -296,9 +403,24 @@
<string name="sleep_timer_label">Temporizador</string>
<string name="time_left_label">Tiempo restante:\u0020</string>
<string name="time_dialog_invalid_input">Entrada no válida, el tiempo debe ser un entero</string>
- <string name="time_unit_seconds">segundos</string>
- <string name="time_unit_minutes">minutos</string>
- <string name="time_unit_hours">horas</string>
+ <string name="timer_about_to_expire_label"><b>Cuando el temporizador vaya a expirara:</b></string>
+ <string name="shake_to_reset_label">Agitar para reiniciar temporizador</string>
+ <string name="timer_vibration_label">Vibrar</string>
+ <string name="time_seconds">segundos</string>
+ <string name="time_minutes">minutos</string>
+ <string name="time_hours">horas</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 segundo</item>
+ <item quantity="other">%d segundos</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minuto</item>
+ <item quantity="other">%d minutos</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 hora</item>
+ <item quantity="other">%d horas</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORÍAS</string>
<string name="gpodnet_toplist_header">MEJORES PODCASTS</string>
@@ -331,16 +453,23 @@
<string name="selected_folder_label">Carpeta seleccionada</string>
<string name="create_folder_label">Crear carpeta</string>
<string name="choose_data_directory">Elegir carpeta de datos</string>
+ <string name="choose_data_directory_message">Por favor elige la raíz de la carpeta de datos. AntennaPod creará los subdirectorios apropiados.</string>
<string name="create_folder_msg">¿Crear carpeta con nombre «%1$s»?</string>
<string name="create_folder_success">Carpeta creada</string>
<string name="create_folder_error_no_write_access">No se puede escribir a esta carpeta</string>
<string name="create_folder_error_already_exists">Ya existe la carpeta</string>
<string name="create_folder_error">No se ha podido crear la carpeta</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" no existe</string>
+ <string name="folder_not_readable_error">\"%1$s\" no se puede leer</string>
+ <string name="folder_not_writable_error">\"%1$s\" no se puede modificar</string>
<string name="folder_not_empty_dialog_title">La carpeta no está vacía</string>
<string name="folder_not_empty_dialog_msg">La carpeta elegida no está vacía. Las descargas y otros archivos se copiarán directamente en esta carpeta. ¿Continuar igualmente?</string>
<string name="set_to_default_folder">Elegir carpeta predeterminada</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pausar la reproducción en lugar de bajar el volumen cuando otra aplicación reproduzca sonidos</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pausar durante las interrupciones</string>
+ <string name="pref_resumeAfterCall_sum">Reanudar reproducción tras una llamada</string>
+ <string name="pref_resumeAfterCall_title">Reanudar tras una llamada</string>
+ <string name="pref_restart_required">Es necesario reiniciar AntennaPod para aplicar los cambios.</string>
<!--Online feed view-->
<string name="subscribe_label">Suscribirse</string>
<string name="subscribed_label">Suscrito</string>
@@ -367,7 +496,43 @@
<!--Feed information screen-->
<string name="authentication_label">Autenticación</string>
<string name="authentication_descr">Cambiar nombre y contraseña de este podcast y sus episodios</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Actualizando la base de datos</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importando subscripciones de aplicaciones de uso específico...</string>
<string name="search_itunes_label">Buscar en iTunes</string>
+ <string name="select_label"><b>Seleccionar…</b></string>
+ <string name="filter">Filtro</string>
+ <string name="all_label">Todo</string>
+ <string name="selected_all_label">Seleccionados todos los episodios</string>
+ <string name="none_label">Ninguno</string>
+ <string name="deselected_all_label">Deseleccionados todos los episodios</string>
+ <string name="played_label">Reproducido</string>
+ <string name="selected_played_label">Seleccionados episodios reproducidos</string>
+ <string name="unplayed_label">No reproducidos</string>
+ <string name="selected_unplayed_label">Seleccionados episodios no reproducidos</string>
+ <string name="downloaded_label">Descargado</string>
+ <string name="selected_downloaded_label">Seleccionados episodios descargados</string>
+ <string name="not_downloaded_label">No descargado</string>
+ <string name="selected_not_downloaded_label">Seleccionados episodios no descargados</string>
+ <string name="sort_title"><b>Ordenar por…</b></string>
+ <string name="sort_title_a_z">Título (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Título (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Fecha (Nuevo \u2192 Antiguo)</string>
+ <string name="sort_date_old_new">Fecha (Antiguo \u2192 Nuevo)</string>
+ <string name="sort_duration_short_long">Duración (Corto \u2192 Largo)</string>
+ <string name="sort_duration_long_short">Duración (Largo \u2192 Corto)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">¿Te gusta AntennaPod?</string>
+ <string name="rating_message">Estaríamos muy agradecidos si nos dedicas un tiempo para puntuar AntennaPod</string>
+ <string name="rating_never_label">Déjame en paz</string>
+ <string name="rating_later_label">Recuérdamelo después</string>
+ <string name="rating_now_label">¡Venga, hagámoslo!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Controles de audio</string>
+ <string name="playback_speed">Velocidad de reproducción</string>
+ <string name="volume">Volumen</string>
+ <string name="left_short">I</string>
+ <string name="right_short">D</string>
+ <string name="audio_effects">Efectos de audio</string>
</resources>
diff --git a/core/src/main/res/values-fr/strings.xml b/core/src/main/res/values-fr/strings.xml
index 141b84bf7..38eef41df 100644
--- a/core/src/main/res/values-fr/strings.xml
+++ b/core/src/main/res/values-fr/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Flux</string>
<string name="add_feed_label">Ajouter un podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">ÉPISODES</string>
+ <string name="episodes_label">Épisodes</string>
<string name="new_episodes_label">Nouveaux épisodes</string>
<string name="all_episodes_label">Tous les épisodes</string>
+ <string name="all_episodes_short_label">Tout</string>
+ <string name="favorite_episodes_label">Favoris</string>
<string name="new_label">Nouveau</string>
<string name="waiting_list_label">Liste d\'attente</string>
<string name="settings_label">Préférences</string>
@@ -19,13 +21,24 @@
<string name="cancel_download_label">Annuler les téléchargements</string>
<string name="playback_history_label">Journal des lectures</string>
<string name="gpodnet_main_label">gpodder.net</string>
- <string name="gpodnet_auth_label">identifiants gpodder.net</string>
+ <string name="gpodnet_auth_label">Identifiants gpodder.net</string>
+ <string name="free_space_label">%1$s d\'espace libre</string>
+ <string name="episode_cache_full_title">L\'emplacement pour stocker les épisodes est plein</string>
+ <string name="episode_cache_full_message">Le volume maximum d\'épisode à stocker a été atteint. Vous pouvez augmenter la taille du cache dans les paramètres.</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Publié récemment</string>
<string name="episode_filter_label">N\'afficher que les nouveaux épisodes</string>
<!--Main activity-->
<string name="drawer_open">Ouvrir le menu</string>
<string name="drawer_close">Fermer le menu</string>
+ <string name="drawer_preferences">Préférences du volet</string>
+ <string name="drawer_feed_order_unplayed_episodes">Trier par compteur</string>
+ <string name="drawer_feed_order_alphabetical">Trier alphabétiquement</string>
+ <string name="drawer_feed_order_last_update">Trier par date de publication</string>
+ <string name="drawer_feed_counter_new_unplayed">Nombre de nouveaux épisodes non-lus</string>
+ <string name="drawer_feed_counter_new">Nombre de nouveaux épisodes</string>
+ <string name="drawer_feed_counter_unplayed">Nombre d\'épisodes non-lus</string>
+ <string name="drawer_feed_counter_none">Aucun</string>
<!--Webview actions-->
<string name="open_in_browser_label">Ouvrir dans le navigateur</string>
<string name="copy_url_label">Copier l\'URL</string>
@@ -37,8 +50,11 @@
<!--Other-->
<string name="confirm_label">Confirmer</string>
<string name="cancel_label">Annuler</string>
+ <string name="yes">Oui</string>
+ <string name="no">Non</string>
<string name="author_label">Auteur</string>
<string name="language_label">Langue</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Préférences</string>
<string name="cover_label">Image</string>
<string name="error_label">Erreur</string>
@@ -53,31 +69,61 @@
<string name="length_prefix">Durée :\u0020</string>
<string name="size_prefix">Taille :\u0020</string>
<string name="processing_label">Traitement en cours</string>
- <string name="loading_label">En chargement...</string>
+ <string name="loading_label">Chargement...</string>
<string name="save_username_password_label">Sauvegarder votre identifiant et votre mot de passe</string>
<string name="close_label">Fermer</string>
<string name="retry_label">Réessayer</string>
<string name="auto_download_label">Télécharger automatiquement à l\'avenir</string>
+ <string name="auto_download_apply_to_items_title">Appliquer aux épisodes précédents</string>
+ <string name="auto_download_apply_to_items_message">Le nouveau paramètre <i>Téléchargement Automatique</i> sera automatiquement appliqué sur chaque nouvel épisode.\nVoulez-vous faire de même avec les épisodes précédents ?</string>
+ <string name="auto_delete_label">Supprimer Épisode Automatiquement\n(redéfini les paramètres par défaut)</string>
<string name="parallel_downloads_suffix">\u0020téléchargements parallèles</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Toujours</string>
+ <string name="feed_auto_download_never">Jamais</string>
+ <string name="send_label">Envoyer...</string>
+ <string name="episode_cleanup_never">Jamais</string>
+ <string name="episode_cleanup_queue_removal">Quand l’épisode n\'est pas dans la liste</string>
+ <string name="episode_cleanup_after_listening">Après avoir terminé</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 jour après avoir été écouté</item>
+ <item quantity="other">%d jours après avoir été écouté</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL du flux</string>
- <string name="etxtFeedurlHint">URL ou flux ou site web</string>
+ <string name="etxtFeedurlHint">URL du flux</string>
<string name="txtvfeedurl_label">Ajouter un podcast par son URL</string>
<string name="podcastdirectories_label">Trouver le podcast dans la bibliothèque</string>
- <string name="podcastdirectories_descr">Vous pouvez chercher de nouveaux podcasts en filtrant par nom, catégorie ou popularité dans la bibliothèque gpodder.net</string>
+ <string name="podcastdirectories_descr">Vous pouvez chercher de nouveaux podcasts en filtrant par nom, catégorie ou popularité dans la bibliothèque gpodder.net, ou sur l\'iTunes Store.</string>
<string name="browse_gpoddernet_label">Chercher sur gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Tous marquer comme lus</string>
+ <string name="mark_all_read_label">Marquer tous les épisodes comme lus</string>
<string name="mark_all_read_msg">Tous les épisodes ont été marqués comme lus</string>
- <string name="mark_all_read_confirmation_msg">Veuillez confirmer que vous voulez bien marquer tous les épisodes comme lus</string>
- <string name="mark_all_read_feed_confirmation_msg">Veuillez confirmer que vous voulez bien marquer tous les épisode de ce flux comme lus</string>
+ <string name="mark_all_read_confirmation_msg">Confirmer le marquage de tous les épisode comme lus</string>
+ <string name="mark_all_read_feed_confirmation_msg">Confirmer le marquage de tous les épisode de ce flux comme lus</string>
+ <string name="mark_all_seen_label">Marquer tout les épisodes comme vus</string>
<string name="show_info_label">Voir les détails</string>
<string name="remove_feed_label">Supprimer le podcast</string>
+ <string name="share_label">Partager...</string>
<string name="share_link_label">Partager un lien vers le site</string>
- <string name="share_source_label">Partager le flux</string>
+ <string name="share_link_with_position_label">Partager lien avec position</string>
+ <string name="share_feed_url_label">Partager lien du flux</string>
+ <string name="share_item_url_label">Partager lien de l\'épisode</string>
+ <string name="share_item_url_with_position_label">Partager lien de l\'épisode avec position</string>
<string name="feed_delete_confirmation_msg">Veuillez confirmer que vous voulez bien supprimer ce flux et TOUS ses épisodes que vous avez téléchargés.</string>
<string name="feed_remover_msg">Flux en cours de suppression</string>
<string name="load_complete_feed">Mettre à jour tout le flux</string>
+ <string name="hide_episodes_title">Cacher épisodes</string>
+ <string name="episode_actions">Appliquer les actions</string>
+ <string name="hide_unplayed_episodes_label">Non joués</string>
+ <string name="hide_paused_episodes_label">En pause</string>
+ <string name="hide_played_episodes_label">Joués</string>
+ <string name="hide_queued_episodes_label">Rajouté à la liste d\'attente</string>
+ <string name="hide_not_queued_episodes_label">Pas rajouté à la liste d\'attente</string>
+ <string name="hide_downloaded_episodes_label">Téléchargé</string>
+ <string name="hide_not_downloaded_episodes_label">Non téléchargé</string>
+ <string name="filtered_label">Filtré</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} La dernière mise à jour a échoué</string>
<!--actions on feeditems-->
<string name="download_label">Télécharger</string>
<string name="play_label">Lire</string>
@@ -87,15 +133,24 @@
<string name="remove_label">Supprimer</string>
<string name="remove_episode_lable">Supprimer cet épisode</string>
<string name="mark_read_label">Marquer comme lu</string>
- <string name="mark_unread_label">Marquer comme non lu</string>
<string name="marked_as_read_label">Les épisodes ont été marqués comme lus</string>
+ <string name="mark_unread_label">Marquer comme non lu</string>
<string name="add_to_queue_label">Ajouter à la liste</string>
+ <string name="added_to_queue_label">Ajouté à la liste</string>
<string name="remove_from_queue_label">Supprimer de la liste</string>
+ <string name="add_to_favorite_label">Ajouter aux Favoris</string>
+ <string name="added_to_favorites">Ajouter aux Favoris</string>
+ <string name="remove_from_favorite_label">Supprimer des Favoris</string>
+ <string name="removed_from_favorites">Supprimer des Favoris</string>
<string name="visit_website_label">Visiter le site</string>
<string name="support_label">Flattr ça!</string>
<string name="enqueue_all_new">Ajouter tous à la liste</string>
<string name="download_all">Tous télécharger</string>
<string name="skip_episode_label">Passer cet épisode</string>
+ <string name="activate_auto_download">Activer téléchargement automatique</string>
+ <string name="deactivate_auto_download">Désactiver téléchargement automatique</string>
+ <string name="reset_position">Remettre a zéro la position de lecture actuelle</string>
+ <string name="removed_item">Élément retiré</string>
<!--Download messages and labels-->
<string name="download_successful">terminé</string>
<string name="download_failed">échoué</string>
@@ -113,12 +168,17 @@
<string name="download_error_unauthorized">Erreur d\'authentification</string>
<string name="cancel_all_downloads_label">Annuler tous les téléchargements</string>
<string name="download_canceled_msg">Téléchargement annulé</string>
- <string name="download_report_title">Téléchargements terminés</string>
+ <string name="download_canceled_autodownload_enabled_msg">Téléchargement annulé\n <i>Téléchargement Automatique</i> désactivé pour cet élément</string>
+ <string name="download_report_title">Téléchargements terminés avec des erreurs</string>
+ <string name="download_report_content_title">Rapport des téléchargements</string>
<string name="download_error_malformed_url">URL incorrecte</string>
<string name="download_error_io_error">Erreur d\'E/S</string>
<string name="download_error_request_error">Erreur de requête</string>
- <string name="download_error_db_access">Problème dans l\'accès à la base de données</string>
- <string name="downloads_left">\u0020téléchargements restants</string>
+ <string name="download_error_db_access">Problème d\'accès à la base de données</string>
+ <plurals name="downloads_left">
+ <item quantity="one">%d téléchargement restant</item>
+ <item quantity="other">%d téléchargements restants</item>
+ </plurals>
<string name="downloads_processing">Traitement des téléchargements</string>
<string name="download_notification_title">Téléchargement des données du podcast</string>
<string name="download_report_content">%1$d téléchargements réussis, %2$d échoués</string>
@@ -129,6 +189,11 @@
<string name="download_request_error_dialog_message_prefix">Une erreur s\'est produite durant le téléchargement du fichier :\u0020</string>
<string name="authentication_notification_title">Authentification requise</string>
<string name="authentication_notification_msg">La ressource que vous avez demandé nécessite un nom d\'utilisateur et un mot de passe</string>
+ <string name="confirm_mobile_download_dialog_title">Confirmer le téléchargement mobile</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Le téléchargement sur la connexion mobile est désactivé dans les options.\n\nVous pouvez choisir d\'ajouter seulement l\'épisode à la liste ou vous pouvez autoriser temporairement le téléchargement.\n\n<small>Votre choix sera retenu pour les 10 prochaines minutes.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Le téléchargement sur la connexion mobile est désactivé dans les options.\n\nVoulez-vous autoriser temporairement le téléchargement?\n\n<small>Votre choix sera retenu pour les 10 prochaines minutes.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Rajouter à la liste</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Autoriser temporairement</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Erreur !</string>
<string name="player_stopped_msg">Pas de lecture en cours</string>
@@ -143,10 +208,14 @@
<string name="playbackservice_notification_title">Lecture de podcast en cours</string>
<string name="unknown_media_key">AntennaPod - Touche média inconnue : %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Bloquer la liste</string>
+ <string name="unlock_queue">Débloquer la liste</string>
+ <string name="queue_locked">Liste de lecture verrouillée</string>
+ <string name="queue_unlocked">Liste de lecture déverrouillée</string>
<string name="clear_queue_label">Effacer la liste</string>
<string name="undo">Annuler</string>
<string name="removed_from_queue">Élément retiré</string>
- <string name="move_to_top_label">Déplacer vers le haut de haut de la liste</string>
+ <string name="move_to_top_label">Déplacer vers le haut de la liste</string>
<string name="move_to_bottom_label">Déplacer vers le bas de la liste</string>
<string name="sort">Trier</string>
<string name="alpha">Ordre alphabétique</string>
@@ -157,16 +226,16 @@
<string name="clear_queue_confirmation_msg">Veuillez confirmer que vous voulez bien supprimer TOUS les épisodes de la file d\'attente</string>
<!--Flattr-->
<string name="flattr_auth_label">Connecter à Flattr</string>
- <string name="flattr_auth_explanation">Appuyez sur le bouton ci-dessous pour vous authentifier. Vous serez envoyés à l\'écran de connexion Flattr dans le navigateur, et il vous sera demandé de donner à AntennaPod la permission de flattr. Une fois ceci fait, vous reviendrez automatiquement à cet écran.</string>
+ <string name="flattr_auth_explanation">Appuyez sur le bouton ci-dessous pour vous authentifier. Voter navigateur va s\'ouvrir et vous serez envoyés vers l\'écran de connexion Flattr afin de donner à AntennaPod la permission de flattr. Une fois fait, vous reviendrez automatiquement à cet écran.</string>
<string name="authenticate_label">S\'authentifier</string>
- <string name="return_home_label">Revenir au départ</string>
+ <string name="return_home_label">Retourner à l\'écran d\'accueil</string>
<string name="flattr_auth_success">L\'authentification a réussi. Vous pouvez maintenant flattr depuis cette application.</string>
<string name="no_flattr_token_title">Aucun jeton Flattr trouvé.</string>
- <string name="no_flattr_token_notification_msg">Votre compte flattr semble ne pas être connecté à AntennaPod. Touchez ici pour vous connecter.</string>
+ <string name="no_flattr_token_notification_msg">Votre compte flattr ne semble pas être connecté à AntennaPod. Cliquer ici pour vous connecter.</string>
<string name="no_flattr_token_msg">Votre compte Flattr se semble pas être connecté à AntennaPod. Vous pouvez soit connecter votre compte Flattr à AntennaPod pour pouvoir flattr depuis l\'application, ou vous pouvez aller sur le site de ce que vous voulez flattr.</string>
<string name="authenticate_now_label">S\'authentifier</string>
<string name="action_forbidden_title">Action interdite</string>
- <string name="action_forbidden_msg">AntennaPod n\'a pas la permission pour cette action. Il est possible que l\'accès à votre compte depuis AntennaPod ait été révoqué. Vous pouvez vous authentifier à nouveau, ou bien visiter le site à flattr directement.</string>
+ <string name="action_forbidden_msg">AntennaPod n\'a pas la permission pour cette action. Il est possible que l\'accès à votre compte depuis AntennaPod ait été révoqué. Vous pouvez vous authentifier à nouveau, ou bien visiter le site à Flattrer directement.</string>
<string name="access_revoked_title">Accès révoqué</string>
<string name="access_revoked_info">Vous avez révoqué le jeton d\'accès d\'AntennaPod à votre compte. Pour terminer cette opération, vous devez retirer AntennaPod de la liste des applications autorisées sur le site web de Flattr.</string>
<!--Flattr-->
@@ -179,37 +248,55 @@
<string name="flattring_thing">En train de Flattrer %s</string>
<string name="flattring_label">AntennaPod est en train de Flattrer</string>
<string name="flattrd_label">AntennaPod a Flattré</string>
- <string name="flattrd_failed_label">Flattr d\'AntennaPod a échoué</string>
+ <string name="flattrd_failed_label">Le Flattr d\'AntennaPod a échoué</string>
<string name="flattr_retrieving_status">Obtention de la liste des choses Flattrées</string>
<!--Variable Speed-->
<string name="download_plugin_label">Télécharger une extension</string>
<string name="no_playback_plugin_title">Extension non installée</string>
- <string name="no_playback_plugin_msg">Pour pouvoir changer la vitesse de lecture il est nécessaire d\'installer une librairie tierce.\n\nSélectionnez \"Télécharger une extension\" pour télécharger une extension gratuite depuis le Play Store\n\nLes problèmes concernant les extensions sont de la responsabilité de leur créateur et non d\'AntennaPod. Veillez à notifier le créateur de l\'extension de tout problème.</string>
+ <string name="no_playback_plugin_or_sonic_msg">Pour pouvoir changer la vitesse de lecture il est recommandé d\'activer le lecteur interne Sonic [Android 4.1+].\n\nSinon vous pouvez télécharger l\'extension <i>Prestissimo</i> depuis le Play Store.\nLes problème rencontrés avec Prestissimo ne sont pas de la responsabilité d\'AntennaPod et sont à communiquer au créateur de l\'extension.</string>
<string name="set_playback_speed_label">Vitesses de lecture</string>
+ <string name="enable_sonic">Activer Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Cette liste est vide.</string>
<string name="no_feeds_label">Vous n\'êtes encore abonné à aucun flux.</string>
+ <string name="no_chapters_label">Cet épisode n\'a pas de chapitres.</string>
<!--Preferences-->
<string name="other_pref">Autres</string>
<string name="about_pref">À propos</string>
<string name="queue_label">Liste</string>
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Interrompre la lecture lorsque le casque est débranché</string>
+ <string name="pref_episode_cleanup_title">Nettoyage des épisodes</string>
+ <string name="pref_episode_cleanup_summary">Les épisodes qui ne sont pas dans la liste de lecture et qui ne sont pas marqués comme favoris peuvent être supprimés si l\'espace est insuffisant pour le téléchargement automatique de nouveaux épisodes</string>
+ <string name="pref_pauseOnDisconnect_sum">Interrompre la lecture lorsque le casque ou le bluetooth sont déconnectés</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Reprendre la lecture quand les écouteurs sont reconnectés</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Reprendre la lecture quand le Bluetooth se reconnecte</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Le bouton piste suivante saute l\'épisode</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Passer à l\'épisode suivant au lieu de faire un saut avant quand il est pressé un bouton physique pour avancer à la piste suivante</string>
<string name="pref_followQueue_sum">Après la fin d\'un épisode, passer au suivant</string>
<string name="pref_auto_delete_sum">Supprimer l\'épisode quand la lecture est finie</string>
- <string name="pref_auto_delete_title">Supression automatique</string>
+ <string name="pref_auto_delete_title">Suppression automatique</string>
+ <string name="pref_smart_mark_as_played_sum">Marquer les épisodes comme lus même s\'il reste encore un certain nombre de secondes à jouer</string>
+ <string name="pref_smart_mark_as_played_title">Marquer comme lu de manière intelligente</string>
+ <string name="pref_skip_keeps_episodes_sum">Garder les épisodes quand ils sont passés</string>
+ <string name="pref_skip_keeps_episodes_title">Garder les épisodes passés</string>
<string name="playback_pref">Lecture</string>
<string name="network_pref">Réseau</string>
- <string name="pref_autoUpdateIntervall_title">Intervalle de mise à jour</string>
- <string name="pref_autoUpdateIntervall_sum">Indiquer un intervalle de mise à jour automatique des flux, ou le désactiver</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Mettre à jour l’intervalle ou l\'heure</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Indiquer un intervalle ou une heure spécifique de mise à jour des flux</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Vous pouvez mettre en place un <i>intervalle</i> comme \"toutes les 2 heures\", une <i>heure précise</i> comme \"7:00\" ou désactiver les mises à jours automatique.\n\n<small>Note: Il est possible qu\'il y ait un délai car l\'heure de mise à jour peut être inexacte.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Désactiver</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Définir intervalle</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Régler l\'heure</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">toutes les %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">à %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Ne télécharger les épisodes que par Wi-Fi</string>
<string name="pref_followQueue_title">Lecture continue</string>
<string name="pref_downloadMediaOnWifiOnly_title">Téléchargement en Wi-Fi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Déconnexion du casque</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Reconnexion du casque</string>
- <string name="pref_mobileUpdate_title">Mise à jour mobile</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Reconnexion Bluetooth</string>
+ <string name="pref_mobileUpdate_title">Mises à jour mobile</string>
<string name="pref_mobileUpdate_sum">Autoriser les mises à jour à travers la connexion de données mobile</string>
<string name="refreshing_label">Mise à jour en cours</string>
<string name="flattr_settings_label">Paramètres Flattr</string>
@@ -223,6 +310,14 @@
<string name="pref_auto_flattr_sum">Configurer les paiements flattr automatiques</string>
<string name="user_interface_label">Interface utilisateur</string>
<string name="pref_set_theme_title">Choisir un thème</string>
+ <string name="pref_nav_drawer_title">Personnaliser le volet de navigation</string>
+ <string name="pref_nav_drawer_sum">Personnaliser l\'apparence du volet de navigation</string>
+ <string name="pref_nav_drawer_items_title">Changer les éléments du volet de navigation</string>
+ <string name="pref_nav_drawer_items_sum">Choisir quels éléments apparaissent dans le volet de navigation</string>
+ <string name="pref_nav_drawer_feed_order_title">Définir l\'ordre des abonnements</string>
+ <string name="pref_nav_drawer_feed_order_sum">Change l\'ordre de vos abonnements</string>
+ <string name="pref_nav_drawer_feed_counter_title">Définir le compteur d\'abonnements</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Changer l\'information affichée par le compteur d\'abonnements</string>
<string name="pref_set_theme_sum">Modifier l\'apparence d\'AntennaPod.</string>
<string name="pref_automatic_download_title">Téléchargement automatique</string>
<string name="pref_automatic_download_sum">Configurer le téléchargement automatique des épisodes.</string>
@@ -246,17 +341,31 @@
<string name="pref_gpodnet_setlogin_information_sum">Modifier les information de connexion pour votre compte gpodder.net</string>
<string name="pref_playback_speed_title">Vitesses de lecture</string>
<string name="pref_playback_speed_sum">Modifier la liste des vitesses disponibles pour la lecture audio</string>
- <string name="pref_seek_delta_title">Chercher un moment spécifique</string>
- <string name="pref_seek_delta_sum">Bouger d\'autant de secondes en rembobinant ou en faisant une avance rapide </string>
+ <string name="pref_fast_forward">Avance rapide</string>
+ <string name="pref_rewind">Retour en arrière</string>
<string name="pref_gpodnet_sethostname_title">Choisir un nom de domaine</string>
<string name="pref_gpodnet_sethostname_use_default_host">Utiliser le nom de domaine par défaut</string>
<string name="pref_expandNotify_title">Etendre la notification</string>
<string name="pref_expandNotify_sum">Toujours étendre les notifications pour montrer les boutons de lecture</string>
<string name="pref_persistNotify_title">Boutons de lecture permanents</string>
<string name="pref_persistNotify_sum">Garder les notifications et les boutons de lecture sur l\'écran de verouillage quand la lecture est en pause</string>
+ <string name="pref_lockscreen_background_title">Changer l’arrière plan de l\'écran de déverrouillage</string>
+ <string name="pref_lockscreen_background_sum">Placer l\'image de l’épisode en arrière plan de l\'écran de déverrouillage. Cela a pour effet secondaire de montrer l\'image dans les apps tierces.</string>
+ <string name="pref_showDownloadReport_title">Afficher le rapport de téléchargements</string>
+ <string name="pref_showDownloadReport_sum">Si les téléchargements échouent, générer un rapport des détails des échecs.</string>
<string name="pref_expand_notify_unsupport_toast">Les versions d\'Android antérieures à 4.1 ne sont pas compatibles avec les notifications élargies</string>
<string name="pref_queueAddToFront_sum">Ajouter de nouveaux épisodes en tête de file</string>
<string name="pref_queueAddToFront_title">Mettre au début de la file d\'attente</string>
+ <string name="pref_smart_mark_as_played_disabled">Désactivé</string>
+ <string name="pref_image_cache_size_title">Taille du cache de l\'image</string>
+ <string name="pref_image_cache_size_sum">Taille de l’espace de stockage temporaire des images.</string>
+ <string name="crash_report_title">Rapport de crash</string>
+ <string name="crash_report_sum">Envoyer le dernier rapport de crash par e-mail</string>
+ <string name="send_email">Envoyer e-mail</string>
+ <string name="experimental_pref">Expérimental</string>
+ <string name="pref_sonic_title">Lecteur multimédia Sonic</string>
+ <string name="pref_sonic_message">Utiliser le lecteur interne Sonic au lieu du lecteur natif d\'Android et de Prestissimo</string>
+ <string name="pref_current_value">Valeur actuelle : %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Activer le paiement flattr automatique</string>
<string name="auto_flattr_after_percent">Lancer un paiement flattr pour un épisode dès que %d de l\'épisode a été joué</string>
@@ -270,7 +379,8 @@
<string name="search_label">Recherche</string>
<string name="found_in_title_label">Trouvé dans le titre</string>
<!--OPML import and export-->
- <string name="opml_import_txtv_button_lable">Les fichiers OPML vous permettent de bouger vos podcasts d\'un logiciel à un autre.</string>
+ <string name="opml_import_txtv_button_lable">Les fichiers OPML vous permettent d\'exporter vos podcasts d\'un logiciel à un autre.</string>
+ <string name="opml_import_option">Option %1$d</string>
<string name="opml_import_explanation_1">Choisir un chemin de fichier spécifique dans le système de fichiers local</string>
<string name="opml_import_explanation_2">Utiliser une application tierce comme Dropbox, Google Drive ou votre gestionnaire de fichier favori pour ouvrir un fichier OPML</string>
<string name="opml_import_explanation_3">De nombreuses applications comme Google Mail, Dropbox ou Google Drive et la plupart des gestionnaires de fichiers peuvent <i>ouvrir</i> les fichiers OPML <i>avec</i> AntennaPod.</string>
@@ -282,10 +392,11 @@
<string name="opml_import_error_dir_empty">Le répertoire d\'importation est vide.</string>
<string name="select_all_label">Tout choisir</string>
<string name="deselect_all_label">Ne rien choisir</string>
+ <string name="select_options_label">Choisir...</string>
<string name="choose_file_from_filesystem">Depuis le système de fichier local</string>
<string name="choose_file_from_external_application">Utiliser une application tierce</string>
<string name="opml_export_label">Exportation OPML</string>
- <string name="exporting_label">Exportation en cours...</string>
+ <string name="exporting_label">Export en cours...</string>
<string name="export_error_label">Erreur d\'exportation</string>
<string name="opml_export_success_title">Exportation OPML réussie.</string>
<string name="opml_export_success_sum">Le fichier .opml a été écrit ici :\u0020</string>
@@ -293,12 +404,27 @@
<string name="set_sleeptimer_label">Définir le minuteur d\'arrêt automatique</string>
<string name="disable_sleeptimer_label">Désactiver le minuteur d\'arrêt automatique</string>
<string name="enter_time_here_label">Entrer l\'heure</string>
- <string name="sleep_timer_label">Arrêt automatique</string>
+ <string name="sleep_timer_label">Minuteur de mise en veille</string>
<string name="time_left_label">Durée restante :\u0020</string>
<string name="time_dialog_invalid_input">Entrée invalide, la durée doit être un nombre entier</string>
- <string name="time_unit_seconds">secondes</string>
- <string name="time_unit_minutes">minutes</string>
- <string name="time_unit_hours">heures</string>
+ <string name="timer_about_to_expire_label"><b>Quand le minuteur est sur le point de se terminer:</b></string>
+ <string name="shake_to_reset_label">Secouer afin de remettre le minuteur a zéro</string>
+ <string name="timer_vibration_label">Vibrer</string>
+ <string name="time_seconds">secondes</string>
+ <string name="time_minutes">minutes</string>
+ <string name="time_hours">heures</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 seconde</item>
+ <item quantity="other">%d secondes</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minute</item>
+ <item quantity="other">%d minutes</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 heure</item>
+ <item quantity="other">%d heures</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORIES</string>
<string name="gpodnet_toplist_header">PODCASTS POPULAIRES</string>
@@ -311,7 +437,7 @@
<string name="username_label">Identifiant</string>
<string name="password_label">Mot de passe</string>
<string name="gpodnetauth_device_title">Choix de l\'appareil</string>
- <string name="gpodnetauth_device_descr">Créez un nouvel appareil à utiliser pour votre compte gpodder.net ou choisissez un appareil existant :</string>
+ <string name="gpodnetauth_device_descr">Créer un nouvel appareil pour votre compte gpodder.net ou choisir un appareil existant :</string>
<string name="gpodnetauth_device_deviceID">ID de l\'appareil :\u0020</string>
<string name="gpodnetauth_device_caption">Légende</string>
<string name="gpodnetauth_device_butCreateNewDevice">Créer un nouvel appareil</string>
@@ -331,20 +457,27 @@
<string name="selected_folder_label">Répertoire choisi :</string>
<string name="create_folder_label">Créer répertoire</string>
<string name="choose_data_directory">Choisir le répertoire</string>
+ <string name="choose_data_directory_message">Choisissez le répertoire où enregistrer les données. AntennaPod créera automatiquement les sous-répertoires nécessaires.</string>
<string name="create_folder_msg">Créer un répertoire nommé \"%1$s\" ?</string>
<string name="create_folder_success">Répertoire créé</string>
<string name="create_folder_error_no_write_access">Impossible d\'écrire dans ce répertoire</string>
<string name="create_folder_error_already_exists">Le répertoire existe déjà</string>
<string name="create_folder_error">Impossible de créer le répertoire</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" n\'existe pas</string>
+ <string name="folder_not_readable_error">\"%1$s\" n\'est pas lisible</string>
+ <string name="folder_not_writable_error">\"%1$s\" est en lecture seul</string>
<string name="folder_not_empty_dialog_title">Le répertoire n\'est pas vide</string>
<string name="folder_not_empty_dialog_msg">Le répertoire que vous avez choisi n\'est pas vide. Les fichiers téléchargés seront ajoutés à ce répertoire. Continuer malgré tout ?</string>
<string name="set_to_default_folder">Choisir le répertoire par défaut</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Mettre la lecture en pause au lieu de baisser le volume quand une autre application veut jouer un son</string>
- <string name="pref_pausePlaybackForFocusLoss_title">Mettre en pause lors des interruptions</string>
+ <string name="pref_pausePlaybackForFocusLoss_title">Mettre en pause lors d\'interruptions</string>
+ <string name="pref_resumeAfterCall_sum">Reprendre la lecture après un appel téléphonique</string>
+ <string name="pref_resumeAfterCall_title">Reprendre après appel</string>
+ <string name="pref_restart_required">AntennaPod doit être redémarré afin que ce changement prenne effet</string>
<!--Online feed view-->
<string name="subscribe_label">S\'abonner</string>
<string name="subscribed_label">Abonné</string>
- <string name="downloading_label">Téléchargement en cours</string>
+ <string name="downloading_label">En cours de téléchargement...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Afficher chapitres</string>
<string name="show_shownotes_label">Afficher notes d\'épisode</string>
@@ -367,7 +500,45 @@
<!--Feed information screen-->
<string name="authentication_label">Authentification</string>
<string name="authentication_descr">Modifier votre identifiant et mot de passe pour ce podcast et tous ses épisodes</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Mise à jour de la base de données</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importation des abonnements à partir d\'applications à usage unique...</string>
<string name="search_itunes_label">Chercher sur iTunes</string>
+ <string name="select_label"><b>Sélectionner…</b></string>
+ <string name="filter">Filtrer</string>
+ <string name="all_label">Tout</string>
+ <string name="selected_all_label">Tous les épisodes ont été sélectionné</string>
+ <string name="none_label">Aucun</string>
+ <string name="deselected_all_label">Tous les épisodes ont été désélectionné</string>
+ <string name="played_label">Joués</string>
+ <string name="selected_played_label">Episodes joués sélectionnés</string>
+ <string name="unplayed_label">Non joués</string>
+ <string name="selected_unplayed_label">Episodes non joués sélectionnés</string>
+ <string name="downloaded_label">Téléchargés</string>
+ <string name="selected_downloaded_label">Episodes téléchargés sélectionnés</string>
+ <string name="not_downloaded_label">Non téléchargés</string>
+ <string name="selected_not_downloaded_label">Épisodes non téléchargés sélectionnés</string>
+ <string name="sort_title"><b>Trier par...</b></string>
+ <string name="sort_title_a_z">Titre (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Titre (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Date (Nouveau \u2192 Ancien)</string>
+ <string name="sort_date_old_new">Date (Ancien \u2192 Nouveau)</string>
+ <string name="sort_duration_short_long">Durée (Courte \u2192 Longue)</string>
+ <string name="sort_duration_long_short">Durée (Longue \u2192 Courte)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Vous aimez AntennaPod ?</string>
+ <string name="rating_message">Nous vous serions reconnaissant de prendre un peu de temps pour noter AntennaPod.</string>
+ <string name="rating_never_label">Laissez moi tranquille</string>
+ <string name="rating_later_label">Rappelez le moi plus tard</string>
+ <string name="rating_now_label">Okay, c\'est parti !</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Contrôles audio</string>
+ <string name="playback_speed">Vitesse de lecture</string>
+ <string name="volume">Volume</string>
+ <string name="left_short">G</string>
+ <string name="right_short">D</string>
+ <string name="audio_effects">Effets audio</string>
+ <string name="stereo_to_mono">Mixage : stéréo à mono</string>
+ <string name="sonic_only">Sonic seulement</string>
</resources>
diff --git a/core/src/main/res/values-hi-rIN/strings.xml b/core/src/main/res/values-hi-rIN/strings.xml
index f32c7c02f..87aa77caf 100644
--- a/core/src/main/res/values-hi-rIN/strings.xml
+++ b/core/src/main/res/values-hi-rIN/strings.xml
@@ -4,7 +4,6 @@
<string name="app_name"> ऐन्टेनापॉड</string>
<string name="feeds_label">फिड्स</string>
<string name="podcasts_label">पॉडकास्ट</string>
- <string name="episodes_label">एपिसोड</string>
<string name="new_label">नया</string>
<string name="waiting_list_label">वेटिंग लिस्ट</string>
<string name="settings_label">सेटिंग्स</string>
@@ -42,7 +41,6 @@
<string name="length_prefix">लंबाई:\u0020</string>
<string name="size_prefix">साइज:\u0020</string>
<string name="processing_label">प्रसंस्करण</string>
- <string name="loading_label">लोड हो रहा है ...</string>
<string name="save_username_password_label">यूज़रनेम और पासवर्ड सहेजें</string>
<string name="close_label">बंद करें</string>
<string name="retry_label">पुन: प्रयास</string>
@@ -57,7 +55,6 @@
<string name="remove_feed_label">पॉडकास्ट हटाएँ
</string>
<string name="share_link_label">शेयर वेबसाइट लिंक</string>
- <string name="share_source_label">शेयर फ़ीड लिंक</string>
<string name="feed_delete_confirmation_msg">इसकी पुष्टि करें कि आप इस फ़ीड और इस फ़ीड के सभी प्रकरणों को हटाना चाहते हैं जिन्हें आपने डाउनलोड किया है.</string>
<string name="feed_remover_msg">फ़ीड निकाल रहा है</string>
<!--actions on feeditems-->
@@ -97,7 +94,6 @@
<string name="download_error_io_error">आईओ त्रुटि</string>
<string name="download_error_request_error">अनुरोध त्रुटि</string>
<string name="download_error_db_access">डेटाबेस का उपयोग त्रुटि</string>
- <string name="downloads_left">\u0020Downloads छोड़ा</string>
<string name="download_notification_title">पॉडकास्ट डेटा डाउनलोड करें</string>
<string name="download_report_content">%1$d डाउनलोड सफल रहा, %2$d में विफल रहा है</string>
<string name="download_log_title_unknown">अज्ञात शीर्षक</string>
@@ -154,12 +150,9 @@
<string name="queue_label">पंक्ति</string>
<string name="services_label">सेवाएं</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum"> प्लेबैक रोकें जब हेडफोन काट रहे हैं</string>
<string name="pref_followQueue_sum">प्लेबैक के पूरा होने पर अगली पंक्ति आइटम के लिए जाएँ</string>
<string name="playback_pref">प्लेबैक</string>
<string name="network_pref">संजाल</string>
- <string name="pref_autoUpdateIntervall_title">अंतराल अद्यतन</string>
- <string name="pref_autoUpdateIntervall_sum">फ़ीड स्वचालित रूप से ताजा कर रहे हैं जिसमें एक अंतराल निर्दिष्ट करें या उसे निष्क्रिय करें </string>
<string name="pref_downloadMediaOnWifiOnly_sum">केवल वाईफ़ाई पर मीडिया फ़ाइलें डाउनलोड करें</string>
<string name="pref_followQueue_title">सतत प्लेबैक</string>
<string name="pref_downloadMediaOnWifiOnly_title">वाईफाई मीडिया डाउनलोड करें</string>
@@ -217,7 +210,6 @@
<string name="select_all_label">सभी का चयन करें</string>
<string name="deselect_all_label">सभी का चयन रद्द करें</string>
<string name="opml_export_label">OPML निर्यात</string>
- <string name="exporting_label">निर्यात ...</string>
<string name="export_error_label">निर्यात त्रुटि</string>
<string name="opml_export_success_sum">.ompl फ़ाइल लिखा गया था:\u0020</string>
<!--Sleep timer-->
@@ -271,8 +263,10 @@
<!--Online feed view-->
<string name="subscribe_label">सदस्यता लें</string>
<string name="subscribed_label">सदस्यता ली गई</string>
- <string name="downloading_label">डाउनलोड कर रहा है ...</string>
<!--Content descriptions for image buttons-->
<!--Feed information screen-->
+ <!--Progress information-->
<!--AntennaPodSP-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-it-rIT/strings.xml b/core/src/main/res/values-it-rIT/strings.xml
index 361657b6b..d7aff71cf 100644
--- a/core/src/main/res/values-it-rIT/strings.xml
+++ b/core/src/main/res/values-it-rIT/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Feed</string>
<string name="add_feed_label">Aggiungi un podcast</string>
<string name="podcasts_label">PODCAST</string>
- <string name="episodes_label">EPISODI</string>
+ <string name="episodes_label">Episodi</string>
<string name="new_episodes_label">Episodi nuovi</string>
<string name="all_episodes_label">Tutti gli episodi</string>
+ <string name="all_episodes_short_label">Tutti</string>
+ <string name="favorite_episodes_label">Preferiti</string>
<string name="new_label">Nuovo</string>
<string name="waiting_list_label">Lista d\'attesa</string>
<string name="settings_label">Impostazioni</string>
@@ -26,6 +28,11 @@
<!--Main activity-->
<string name="drawer_open">Apri il menù</string>
<string name="drawer_close">Chiudi il menù</string>
+ <string name="drawer_feed_order_alphabetical">Ordina alfabeticamente</string>
+ <string name="drawer_feed_counter_new_unplayed">Numero di episodi nuovi e non riprodotti</string>
+ <string name="drawer_feed_counter_new">Numero di episodi nuovi</string>
+ <string name="drawer_feed_counter_unplayed">Numero di episodi non riprodotti</string>
+ <string name="drawer_feed_counter_none">Nessuno</string>
<!--Webview actions-->
<string name="open_in_browser_label">Apri nel browser</string>
<string name="copy_url_label">Copia URL</string>
@@ -37,8 +44,11 @@
<!--Other-->
<string name="confirm_label">Conferma</string>
<string name="cancel_label">Annulla</string>
+ <string name="yes">Sì</string>
+ <string name="no">No</string>
<string name="author_label">Autore</string>
<string name="language_label">Lingua</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Impostazioni</string>
<string name="cover_label">Immagine</string>
<string name="error_label">Errore</string>
@@ -53,11 +63,20 @@
<string name="length_prefix">Durata:\u0020</string>
<string name="size_prefix">Dimensione:\u0020</string>
<string name="processing_label">Elaborazione in corso</string>
- <string name="loading_label">Caricamento in corso...</string>
<string name="save_username_password_label">Salva nome utente e password</string>
<string name="close_label">Chiudi</string>
<string name="retry_label">Riprova</string>
<string name="auto_download_label">Includi nei download automatici</string>
+ <string name="auto_download_apply_to_items_title">Applica ai Precedenti Episodi</string>
+ <string name="parallel_downloads_suffix">\u0020download paralleli</string>
+ <string name="feed_auto_download_global">Globale</string>
+ <string name="feed_auto_download_always">Sempre</string>
+ <string name="feed_auto_download_never">Mai</string>
+ <string name="episode_cleanup_never">Mai</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 giorno dopo il completamento</item>
+ <item quantity="other">%d giorni dopo il completamento</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL del feed</string>
<string name="etxtFeedurlHint">www.example.com/feed</string>
@@ -66,15 +85,29 @@
<string name="podcastdirectories_descr">Puoi cercare dei nuovi podcast in base al nome, alla categoria o alla popolarità nella directory di gpodder.net.</string>
<string name="browse_gpoddernet_label">Esplora gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Segna tutti come letti</string>
- <string name="mark_all_read_msg">Segnati tutti gli episodi come letti</string>
+ <string name="mark_all_read_label">Segna tutti come riprodotti</string>
+ <string name="mark_all_read_msg">Segnati tutti gli episodi come riprodotti</string>
+ <string name="mark_all_seen_label">Segna tutti come visti</string>
<string name="show_info_label">Informazioni</string>
<string name="remove_feed_label">Rimuovi un podcast</string>
<string name="share_link_label">Condividi il link al sito</string>
- <string name="share_source_label">Condividi il link al feed</string>
+ <string name="share_link_with_position_label">Condividi il Link con la Posizione</string>
+ <string name="share_feed_url_label">Condividi URL del Feed</string>
+ <string name="share_item_url_label">Condividi URL dell\'Episodio</string>
+ <string name="share_item_url_with_position_label">Condividi l\'URL dell\'Episodio con la Posizione</string>
<string name="feed_delete_confirmation_msg">Per favore conferma la cancellazione di questo feed e di TUTTI gli episodi collegati che sono stati precedentemente scaricati.</string>
<string name="feed_remover_msg">Rimozione feed</string>
<string name="load_complete_feed">Ricarica il feed completo</string>
+ <string name="hide_episodes_title">Nascondi gli episodi</string>
+ <string name="hide_unplayed_episodes_label">Non riprodotti</string>
+ <string name="hide_paused_episodes_label">In pausa</string>
+ <string name="hide_played_episodes_label">Riprodotti</string>
+ <string name="hide_queued_episodes_label">In coda</string>
+ <string name="hide_not_queued_episodes_label">Non in coda</string>
+ <string name="hide_downloaded_episodes_label">Scaricati</string>
+ <string name="hide_not_downloaded_episodes_label">Non scaricati</string>
+ <string name="filtered_label">Filtrati</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Ultimo aggiornamento fallito</string>
<!--actions on feeditems-->
<string name="download_label">Download</string>
<string name="play_label">Riproduci</string>
@@ -83,16 +116,23 @@
<string name="stream_label">Stream</string>
<string name="remove_label">Rimuovi</string>
<string name="remove_episode_lable">Rimuovi l\'episodio</string>
- <string name="mark_read_label">Segna come letto</string>
- <string name="mark_unread_label">Segna come non letto</string>
- <string name="marked_as_read_label">Segnato come letto</string>
+ <string name="mark_read_label">Segna come riprodotto</string>
+ <string name="marked_as_read_label">Segnato come riprodotto</string>
+ <string name="mark_unread_label">Segna come non riprodotto</string>
<string name="add_to_queue_label">Aggiungi alla coda</string>
+ <string name="added_to_queue_label">Aggiunto alla coda</string>
<string name="remove_from_queue_label">Rimuovi dalla coda</string>
+ <string name="add_to_favorite_label">Aggiungi ai preferiti</string>
+ <string name="remove_from_favorite_label">Rimuovi dai preferiti</string>
<string name="visit_website_label">Visita il sito</string>
<string name="support_label">Carica questo su Flattr</string>
<string name="enqueue_all_new">Accoda tutti</string>
<string name="download_all">Scarica tutti</string>
<string name="skip_episode_label">Salta l\'episodio</string>
+ <string name="activate_auto_download">Attiva il download automatico</string>
+ <string name="deactivate_auto_download">Disattiva il download automatico</string>
+ <string name="reset_position">Azzera la posizione della riproduzione</string>
+ <string name="removed_item">Oggetto rimosso</string>
<!--Download messages and labels-->
<string name="download_successful">successo</string>
<string name="download_failed">fallito</string>
@@ -110,12 +150,12 @@
<string name="download_error_unauthorized">Errore di autenticazione</string>
<string name="cancel_all_downloads_label">Annulla tutti i download</string>
<string name="download_canceled_msg">Download annullato</string>
- <string name="download_report_title">Download completati</string>
+ <string name="download_report_title">Download completato con un errore (o errori)</string>
+ <string name="download_report_content_title">Rapporto del downoad</string>
<string name="download_error_malformed_url">URL malformato</string>
<string name="download_error_io_error">Errore IO</string>
<string name="download_error_request_error">Errore della richiesta</string>
<string name="download_error_db_access">Errore di accesso al database</string>
- <string name="downloads_left">\u0020Download rimasti</string>
<string name="downloads_processing">Elaborazione dei download in corso</string>
<string name="download_notification_title">Download podcast in corso</string>
<string name="download_report_content">%1$d download con successo, %2$d falliti</string>
@@ -126,6 +166,9 @@
<string name="download_request_error_dialog_message_prefix">Rilevato errore durante il download del file:\u0020</string>
<string name="authentication_notification_title">Autenticazione richiesta</string>
<string name="authentication_notification_msg">La risorsa che hai richiesto richiede un nome utente e una password</string>
+ <string name="confirm_mobile_download_dialog_title">Conferma il download su cellulare</string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Aggiungi solo alla coda</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Abilita temporaneamente</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Errore!</string>
<string name="player_stopped_msg">Nessun media in riproduzione</string>
@@ -140,6 +183,8 @@
<string name="playbackservice_notification_title">Riproduzione del podcast in corso</string>
<string name="unknown_media_key">AntennaPod - Chiave dell\'elemento multimediale sconosciuta: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Blocca la coda</string>
+ <string name="unlock_queue">Sblocca la coda</string>
<string name="clear_queue_label">Svuota la coda</string>
<string name="undo">Undo</string>
<string name="removed_from_queue">Oggetto rimosso</string>
@@ -180,7 +225,6 @@
<!--Variable Speed-->
<string name="download_plugin_label">Scarica plugin</string>
<string name="no_playback_plugin_title">Plugin non installato</string>
- <string name="no_playback_plugin_msg">Perché la riproduzione a velocità variabile funzioni, deve venire installata una libreria di terze parti.\n\nPremi \'Scarica un plugin\' scaricare un plugin gratuito dal Play Store.\n\nTutti i problemi rilevati con questo plugin non sono responsabilità di AntennaPod, e devono essere segnalati al proprietario del plugin.</string>
<string name="set_playback_speed_label">Velocità di riproduzione</string>
<!--Empty list labels-->
<string name="no_items_label">Non ci sono oggetti in questo elenco.</string>
@@ -191,15 +235,14 @@
<string name="queue_label">Coda</string>
<string name="services_label">Servizi</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Metti in pausa quanto le cuffie vengono disconnesse</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Riprendi la riproduzione quando vengono riconnesse le cuffie</string>
<string name="pref_followQueue_sum">Passa al prossimo episodio in coda quanto si completa una riproduzione</string>
<string name="pref_auto_delete_sum">Elimina l\'episodio quando viene completata la riproduzione</string>
<string name="pref_auto_delete_title">Elimina automaticamente</string>
<string name="playback_pref">Riproduzione</string>
<string name="network_pref">Rete</string>
- <string name="pref_autoUpdateIntervall_title">Intervallo di update</string>
- <string name="pref_autoUpdateIntervall_sum">Specifica un intervallo per l\'aggiornamento automatico dei feed o disabilitalo</string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Disabilita</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Imposta Intervallo</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Abilita il download dei media solo tramite WiFi</string>
<string name="pref_followQueue_title">Playback continuo</string>
<string name="pref_downloadMediaOnWifiOnly_title">Download dei media su WiFi</string>
@@ -242,15 +285,15 @@
<string name="pref_gpodnet_setlogin_information_sum">Cambia le informazioni di login per il tuo account gpodder.net.</string>
<string name="pref_playback_speed_title">Velocità di riproduzione</string>
<string name="pref_playback_speed_sum">Personalizza le velocità disponibili per la riproduzione audio a velocità variabile</string>
- <string name="pref_seek_delta_title">Tempo di ricerca</string>
- <string name="pref_seek_delta_sum">Cerca tutti questi secondi quando si riavvolge o si va avanti velocemente</string>
<string name="pref_gpodnet_sethostname_title">Imposta l\'hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Usa l\'host di default</string>
<string name="pref_expandNotify_title">Espandi le notifiche</string>
<string name="pref_expandNotify_sum">Espandi sempre le notifiche per mostrare i pulsanti di riproduzione.</string>
<string name="pref_persistNotify_title">Controlli di riproduzione persistenti</string>
<string name="pref_persistNotify_sum">Mantieni le notifiche e i controlli del blocco dello schermo quando la riproduzione è in pausa.</string>
+ <string name="pref_showDownloadReport_title">Mostra il Rapporto del Download</string>
<string name="pref_expand_notify_unsupport_toast">Le versioni di Android prima della 4.1 non supportano le notifiche estese.</string>
+ <string name="pref_smart_mark_as_played_disabled">Disabilitato</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Abilita l\'esecuzione automatica di Flattr</string>
<string name="auto_flattr_after_percent">Carica l\'episodio su Flattr appena è stato riprodotto al %d percento</string>
@@ -273,8 +316,9 @@
<string name="opml_import_error_dir_empty">La directory di importazione è vuota.</string>
<string name="select_all_label">Seleziona tutti</string>
<string name="deselect_all_label">Deseleziona tutti</string>
+ <string name="choose_file_from_filesystem">Dal filesystem locale</string>
+ <string name="choose_file_from_external_application">Utilizza un\'applicazione esterna</string>
<string name="opml_export_label">Esportazione su OPML</string>
- <string name="exporting_label">Esportazione in corso...</string>
<string name="export_error_label">Errore di esportazione</string>
<string name="opml_export_success_title">Esportazione OPML avvenuta con successo.</string>
<string name="opml_export_success_sum">Il file .opml è stato scritto su:\u0020</string>
@@ -285,9 +329,21 @@
<string name="sleep_timer_label">Timer di spegnimento</string>
<string name="time_left_label">Tempo residuo:\u0020</string>
<string name="time_dialog_invalid_input">Input non valido, il campo deve essere un numero intero.</string>
- <string name="time_unit_seconds">secondi</string>
- <string name="time_unit_minutes">minuti</string>
- <string name="time_unit_hours">ore</string>
+ <string name="time_seconds">secondi</string>
+ <string name="time_minutes">minuti</string>
+ <string name="time_hours">ore</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 secondo</item>
+ <item quantity="other">%d secondi</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minuto</item>
+ <item quantity="other">%d minuti</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 ora</item>
+ <item quantity="other">%d ore</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORIE</string>
<string name="gpodnet_toplist_header">TOP PODCAST</string>
@@ -330,10 +386,10 @@
<string name="set_to_default_folder">Scegli la cartella predefinita</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Sospendi la riproduzione invece di abbassare il volume quando un\'altra app emette un suono</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pausa su interruzione</string>
+ <string name="pref_resumeAfterCall_title">Riprendi dopo la chiamata</string>
<!--Online feed view-->
<string name="subscribe_label">Abbonati</string>
<string name="subscribed_label">Abbonato</string>
- <string name="downloading_label">Download in corso...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Mostra i capitoli</string>
<string name="show_shownotes_label">Mostra le note dell\'episodio</string>
@@ -356,7 +412,22 @@
<!--Feed information screen-->
<string name="authentication_label">Autenticazione</string>
<string name="authentication_descr">Cambia il tuo nome utente e la tua password per questo podcast e i suoi episodi.</string>
+ <!--Progress information-->
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importazione di sottoscrizioni da applicazioni monouso in corso...</string>
<string name="search_itunes_label">Cerca su iTunes</string>
+ <string name="all_label">Tutti</string>
+ <string name="selected_all_label">Tutti gli Episodi Selezionati</string>
+ <string name="none_label">Nessuno</string>
+ <string name="deselected_all_label">Tutti gli Episodi Deselezionati</string>
+ <string name="played_label">Riprodotto</string>
+ <string name="unplayed_label">Non riprodotto</string>
+ <string name="sort_title_a_z">Titolo (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Titolo (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Data (New \u2192 Old)</string>
+ <string name="sort_date_old_new">Data (Old \u2192 New)</string>
+ <string name="sort_duration_short_long">Durata (Short \u2192 Long)</string>
+ <string name="sort_duration_long_short">Durata (Long \u2192 Short)</string>
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-iw-rIL/strings.xml b/core/src/main/res/values-iw-rIL/strings.xml
index 9e9c0e6bc..6adc16fd6 100644
--- a/core/src/main/res/values-iw-rIL/strings.xml
+++ b/core/src/main/res/values-iw-rIL/strings.xml
@@ -8,6 +8,7 @@
<string name="episodes_label">פרקים</string>
<string name="new_episodes_label">פרקים חדשים</string>
<string name="all_episodes_label">כל הפרקים</string>
+ <string name="favorite_episodes_label">מועדפים</string>
<string name="new_label">חדש</string>
<string name="waiting_list_label">רשימת המתנה</string>
<string name="settings_label">הגדרות</string>
@@ -37,6 +38,8 @@
<!--Other-->
<string name="confirm_label">אישור</string>
<string name="cancel_label">בטל</string>
+ <string name="yes">כן</string>
+ <string name="no">לא</string>
<string name="author_label">מחבר</string>
<string name="language_label">שפה</string>
<string name="podcast_settings_label">הגדרות</string>
@@ -53,12 +56,16 @@
<string name="length_prefix">אורך:\u0020</string>
<string name="size_prefix">גודל:\u0020</string>
<string name="processing_label">מעבד</string>
- <string name="loading_label">טוען...</string>
<string name="save_username_password_label">שמור שם משתמש וססמה</string>
<string name="close_label">סגור</string>
<string name="retry_label">נסה שוב</string>
<string name="auto_download_label">כלול בהורדות אוטומטיות</string>
+ <string name="auto_delete_label">מחק לאחר ההשמעה\n(גובר על ההגדרה הגלובלית)</string>
<string name="parallel_downloads_suffix">\u0020הורדות במקביל</string>
+ <string name="feed_auto_download_global">לפי הגדרה גלובלית</string>
+ <string name="feed_auto_download_always">תמיד</string>
+ <string name="feed_auto_download_never">אף פעם</string>
+ <string name="episode_cleanup_never">אף פעם</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">כתובת הזנה</string>
<string name="etxtFeedurlHint">כתובת של הזנה או אתר אינטרנט</string>
@@ -74,7 +81,6 @@
<string name="show_info_label">הצג מידע</string>
<string name="remove_feed_label">הסר פודקאסט</string>
<string name="share_link_label">שתף קישור אתר</string>
- <string name="share_source_label">שתף קישור הזנה</string>
<string name="feed_delete_confirmation_msg">אשר מחיקת הזנה זו ואת כל פרקי ההזנה שהורדת.</string>
<string name="feed_remover_msg">הסר הזנה</string>
<string name="load_complete_feed">רענן את כל ההזנה</string>
@@ -87,10 +93,12 @@
<string name="remove_label">הסר</string>
<string name="remove_episode_lable">הסר פרק</string>
<string name="mark_read_label">סמן כנקרא</string>
- <string name="mark_unread_label">סמן כלא נקרא</string>
<string name="marked_as_read_label">סומן כנקרא</string>
+ <string name="mark_unread_label">סמן כלא נקרא</string>
<string name="add_to_queue_label">הוסף לתור</string>
+ <string name="added_to_queue_label">התווסף לתור</string>
<string name="remove_from_queue_label">הסר מהתור</string>
+ <string name="add_to_favorite_label">התווסף למועדפים</string>
<string name="visit_website_label">בקר באתר</string>
<string name="support_label">תרום באמצעות Flattr</string>
<string name="enqueue_all_new">הכנס לתור הכל</string>
@@ -118,7 +126,6 @@
<string name="download_error_io_error">שגיאת קלט פלט</string>
<string name="download_error_request_error">שגיאת בקשה</string>
<string name="download_error_db_access">שגיאת גישה למסד הנתונים</string>
- <string name="downloads_left">הורדות נותרוu0020\</string>
<string name="downloads_processing">מעבד הורדות</string>
<string name="download_notification_title">מוריד פודקאסט</string>
<string name="download_report_content">%1$d הורדות הצליחו, %2$d ניכשלו</string>
@@ -184,8 +191,6 @@
<!--Variable Speed-->
<string name="download_plugin_label">הורד תוסף</string>
<string name="no_playback_plugin_title">תוסף לא מותקן</string>
- <string name="no_playback_plugin_msg">לאפשר השמעה במהירות משתנה, יש להתקין ספריית צד שלישית.\n\nלחץ על \'הורד תוסף\' להוריד תוסף חינמי מחנות Play.\n\n כל בעיה שתמצא בשימוש עם תוסף זה אינה באחריות של אנטנה-פוד ויש לדווחה לבעל התוסף.
- </string>
<string name="set_playback_speed_label">מהירויות ניגון</string>
<!--Empty list labels-->
<string name="no_items_label">אין פריטים ברשימה זו.</string>
@@ -196,15 +201,12 @@
<string name="queue_label">תור</string>
<string name="services_label">שירותים</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">השהה השמעה בניתוק האוזניות </string>
<string name="pref_unpauseOnHeadsetReconnect_sum">המשך את הניגון כשהאוזניות מחוברות מחדש</string>
<string name="pref_followQueue_sum">עבור לפריט הבא בתור כאשר הניגון מסתיים</string>
<string name="pref_auto_delete_sum">מחק פרק כהניגון מסתיים</string>
<string name="pref_auto_delete_title">מחיקה אוטומטית</string>
<string name="playback_pref">ניגון</string>
<string name="network_pref">רשת</string>
- <string name="pref_autoUpdateIntervall_title">זמן בין עידכונים</string>
- <string name="pref_autoUpdateIntervall_sum">ציין פרק זמן שבו ההזנות עוברות רענון באופן אוטומטי או לבטל ריענון</string>
<string name="pref_downloadMediaOnWifiOnly_sum">הורד קבצי מדיה רק דרך חיבור אינטרנט אלחוטי</string>
<string name="pref_followQueue_title">ניגון מתמשך</string>
<string name="pref_downloadMediaOnWifiOnly_title">הורדת מדיה דרך אינטרנט אלחוטי</string>
@@ -247,8 +249,6 @@
<string name="pref_gpodnet_setlogin_information_sum">שנה פרטי התחברות של חשבון gpodder.net.</string>
<string name="pref_playback_speed_title">מהירויות ניגון</string>
<string name="pref_playback_speed_sum">התאמת המהיריות הזמינות לניגון במהירות משתנה</string>
- <string name="pref_seek_delta_title">זמן דילוג</string>
- <string name="pref_seek_delta_sum">דלג מספר שניות זה בדילוג לאחור או קדימה</string>
<string name="pref_gpodnet_sethostname_title">הגדר שם שרת</string>
<string name="pref_gpodnet_sethostname_use_default_host">השתמש בשרת ברירת מידל</string>
<string name="pref_expandNotify_title">הרחב הודעה</string>
@@ -286,7 +286,6 @@
<string name="choose_file_from_filesystem">ממערכת הקבצים המקומית</string>
<string name="choose_file_from_external_application">השתמש באפליקציה חיצונית</string>
<string name="opml_export_label">יצוא OPML</string>
- <string name="exporting_label">מייצא...</string>
<string name="export_error_label">שגיאת יצוא</string>
<string name="opml_export_success_title">יצוא OPML הצליח.</string>
<string name="opml_export_success_sum">קובץ OPML נכתב ל:\u0020</string>
@@ -297,9 +296,6 @@
<string name="sleep_timer_label">טיימר שינה</string>
<string name="time_left_label">זמן נותר:\u0020</string>
<string name="time_dialog_invalid_input">קלט לא חוקי, זמן חייב להיות מספר שלם</string>
- <string name="time_unit_seconds">שניות</string>
- <string name="time_unit_minutes">דקות</string>
- <string name="time_unit_hours">שעות</string>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">קטגוריות</string>
<string name="gpodnet_toplist_header">פודקאסטים בכירים</string>
@@ -345,7 +341,6 @@
<!--Online feed view-->
<string name="subscribe_label">הרשם</string>
<string name="subscribed_label">נרשם</string>
- <string name="downloading_label">מוריד...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">הצג פרקים</string>
<string name="show_shownotes_label">הצג הערות פרק</string>
@@ -368,7 +363,10 @@
<!--Feed information screen-->
<string name="authentication_label">אימות</string>
<string name="authentication_descr">שנה את שם המשתמש והסיסמה שלך לפודקאסט ופרקים שלו.</string>
+ <!--Progress information-->
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">מייבא רישום מאפליקציות יעודיות...</string>
<string name="search_itunes_label">חפש בiTunes</string>
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-ja/strings.xml b/core/src/main/res/values-ja/strings.xml
index 9021b2b42..3e3b5b97f 100644
--- a/core/src/main/res/values-ja/strings.xml
+++ b/core/src/main/res/values-ja/strings.xml
@@ -8,6 +8,8 @@
<string name="episodes_label">エピソード</string>
<string name="new_episodes_label">新エピソード</string>
<string name="all_episodes_label">全てのエピソード</string>
+ <string name="all_episodes_short_label">すべて</string>
+ <string name="favorite_episodes_label">お気に入り</string>
<string name="new_label">新</string>
<string name="waiting_list_label">待ちリスト</string>
<string name="settings_label">設定</string>
@@ -20,12 +22,23 @@
<string name="playback_history_label">再生履歴</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net ログイン</string>
+ <string name="free_space_label">%1$s 空き</string>
+ <string name="episode_cache_full_title">エピソードキャッシュが一杯です</string>
+ <string name="episode_cache_full_message">エピソードキャッシュが制限に達しました。設定でキャッシュサイズを増やすことができます。</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">最近公開された</string>
<string name="episode_filter_label">新しいエピソードのみ表示</string>
<!--Main activity-->
<string name="drawer_open">メニューを開く</string>
<string name="drawer_close">メニューを閉じる</string>
+ <string name="drawer_preferences">ドロワー設定</string>
+ <string name="drawer_feed_order_unplayed_episodes">カウンターで並び替え</string>
+ <string name="drawer_feed_order_alphabetical">アルファベット順に並び替え</string>
+ <string name="drawer_feed_order_last_update">公開日で並び替え</string>
+ <string name="drawer_feed_counter_new_unplayed">新しい未再生のエピソードの数</string>
+ <string name="drawer_feed_counter_new">新しいエピソードの数</string>
+ <string name="drawer_feed_counter_unplayed">未再生のエピソードの数</string>
+ <string name="drawer_feed_counter_none">なし</string>
<!--Webview actions-->
<string name="open_in_browser_label">ブラウザーで開く</string>
<string name="copy_url_label">URLをコピー</string>
@@ -37,8 +50,11 @@
<!--Other-->
<string name="confirm_label">確認</string>
<string name="cancel_label">キャンセル</string>
+ <string name="yes">はい</string>
+ <string name="no">いいえ</string>
<string name="author_label">作者</string>
<string name="language_label">言語</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">設定</string>
<string name="cover_label">映像</string>
<string name="error_label">エラー</string>
@@ -53,12 +69,25 @@
<string name="length_prefix">長さ:\u0020</string>
<string name="size_prefix">サイズ:\u0020</string>
<string name="processing_label">処理中</string>
- <string name="loading_label">読み込み中...</string>
+ <string name="loading_label">ロード中…</string>
<string name="save_username_password_label">ユーザ名とパスワードを保存する</string>
<string name="close_label">閉じる</string>
<string name="retry_label">再試行</string>
<string name="auto_download_label">自動ダウンロードに含む</string>
+ <string name="auto_download_apply_to_items_title">前のエピソードに適用</string>
+ <string name="auto_download_apply_to_items_message">新しい <i>自動ダウンロード</i> の設定は、自動的に新しいエピソードに適用されます。\n以前に公開されたエピソードにも適用しますか?</string>
+ <string name="auto_delete_label">エピソードの自動削除\n(全般のデフォルトを上書きします)</string>
<string name="parallel_downloads_suffix">\u0020パラレル ダウンロード</string>
+ <string name="feed_auto_download_global">全般</string>
+ <string name="feed_auto_download_always">常に</string>
+ <string name="feed_auto_download_never">しない</string>
+ <string name="send_label">送信…</string>
+ <string name="episode_cleanup_never">しない</string>
+ <string name="episode_cleanup_queue_removal">キューにない時</string>
+ <string name="episode_cleanup_after_listening">完了後</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="other">完了 %d 日後</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">フィードURL</string>
<string name="etxtFeedurlHint">フィードまたはWebサイトのURL</string>
@@ -67,17 +96,33 @@
<string name="podcastdirectories_descr">名前、カテゴリや人気で、gpodder.netディレクトリ内の新しいポッドキャストを検索することができます。</string>
<string name="browse_gpoddernet_label">gpodder.netを参照</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">既読としてマーク</string>
- <string name="mark_all_read_msg">すべてのエピソードを既読にしました</string>
- <string name="mark_all_read_confirmation_msg">既読としてマークするすべてのエピソードを確認してください。</string>
- <string name="mark_all_read_feed_confirmation_msg">既読としてマークするこのフィードのすべてのエピソードを確認してください。</string>
+ <string name="mark_all_read_label">すべて再生済としてマーク</string>
+ <string name="mark_all_read_msg">すべてのエピソードを再生済にしました</string>
+ <string name="mark_all_read_confirmation_msg">再生済としてマークするすべてのエピソードを確認してください。</string>
+ <string name="mark_all_read_feed_confirmation_msg">再生済としてマークするこのフィードのすべてのエピソードを確認してください。</string>
+ <string name="mark_all_seen_label">参照済としてマーク</string>
<string name="show_info_label">情報を表示</string>
<string name="remove_feed_label">ポッドキャストを削除</string>
+ <string name="share_label">共有…</string>
<string name="share_link_label">Webサイトのリンクを共有</string>
- <string name="share_source_label">フィードのリンクを共有</string>
+ <string name="share_link_with_position_label">場所とリンクを共有</string>
+ <string name="share_feed_url_label">フィード URLを共有</string>
+ <string name="share_item_url_label">エピソード URLを共有</string>
+ <string name="share_item_url_with_position_label">場所とエピソード URL を共有</string>
<string name="feed_delete_confirmation_msg">このフィードと、このフィードのダウンロードしたすべてのエピソードを削除することを確認してください。</string>
<string name="feed_remover_msg">フィードの削除中</string>
<string name="load_complete_feed">フィードをすべて更新</string>
+ <string name="hide_episodes_title">エピソードを非表示にする</string>
+ <string name="episode_actions">操作を適用</string>
+ <string name="hide_unplayed_episodes_label">未再生</string>
+ <string name="hide_paused_episodes_label">一時停止しました</string>
+ <string name="hide_played_episodes_label">再生しました</string>
+ <string name="hide_queued_episodes_label">キューに入れました</string>
+ <string name="hide_not_queued_episodes_label">キューに入っていません</string>
+ <string name="hide_downloaded_episodes_label">ダウンロードしました</string>
+ <string name="hide_not_downloaded_episodes_label">ダウンロードしていません</string>
+ <string name="filtered_label">フィルターしました</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} 前回更新に失敗しました</string>
<!--actions on feeditems-->
<string name="download_label">ダウンロード</string>
<string name="play_label">再生</string>
@@ -86,16 +131,25 @@
<string name="stream_label">ストリーム</string>
<string name="remove_label">削除</string>
<string name="remove_episode_lable">エピソードを削除</string>
- <string name="mark_read_label">既読にする</string>
- <string name="mark_unread_label">未読にする</string>
- <string name="marked_as_read_label">既読</string>
+ <string name="mark_read_label">再生済としてマーク</string>
+ <string name="marked_as_read_label">再生済としてマークしました</string>
+ <string name="mark_unread_label">未再生としてマーク</string>
<string name="add_to_queue_label">キューに追加</string>
+ <string name="added_to_queue_label">キューに追加しました</string>
<string name="remove_from_queue_label">キューから削除</string>
+ <string name="add_to_favorite_label">お気に入りに追加</string>
+ <string name="added_to_favorites">お気に入りに追加</string>
+ <string name="remove_from_favorite_label">お気に入りから削除</string>
+ <string name="removed_from_favorites">お気に入りから削除</string>
<string name="visit_website_label">Webサイトを訪問</string>
<string name="support_label">これをFlattr</string>
<string name="enqueue_all_new">すべてキューに入れる</string>
<string name="download_all">すべてダウンロード</string>
<string name="skip_episode_label">エピソードをスキップ</string>
+ <string name="activate_auto_download">自動ダウンロードを有効にする</string>
+ <string name="deactivate_auto_download">自動ダウンロードを無効にする</string>
+ <string name="reset_position">再生位置をリセット</string>
+ <string name="removed_item">アイテムを削除しました</string>
<!--Download messages and labels-->
<string name="download_successful">完成</string>
<string name="download_failed">失敗</string>
@@ -113,12 +167,16 @@
<string name="download_error_unauthorized">認証エラー</string>
<string name="cancel_all_downloads_label">すべてのダウンロードをキャンセル</string>
<string name="download_canceled_msg">ダウンロードをキャンセルしました</string>
- <string name="download_report_title">ダウンロードが完了しました</string>
+ <string name="download_canceled_autodownload_enabled_msg">ダウンロードをキャンセルしました\nこのアイテムの <i>自動ダウンロード</i> を無効にしました</string>
+ <string name="download_report_title">ダウンロードがエラーで完了しました</string>
+ <string name="download_report_content_title">ダウンロード レポート</string>
<string name="download_error_malformed_url">不正な形式のURL</string>
<string name="download_error_io_error">IOエラー</string>
<string name="download_error_request_error">リクエストエラー</string>
<string name="download_error_db_access">データベース アクセスエラー</string>
- <string name="downloads_left">\u0020ダウンロード残</string>
+ <plurals name="downloads_left">
+ <item quantity="other">%d ダウンロード残</item>
+ </plurals>
<string name="downloads_processing">ダウンロード処理中</string>
<string name="download_notification_title">ポッドキャストデータをダウンロード中</string>
<string name="download_report_content">%1$d ダウンロード成功, %2$d 失敗</string>
@@ -129,6 +187,11 @@
<string name="download_request_error_dialog_message_prefix">ファイルのダウンロード中にエラーが発生しました:\u0020</string>
<string name="authentication_notification_title">認証が必要です</string>
<string name="authentication_notification_msg">リクエストしたリソースは、ユーザー名とパスワードが必要です</string>
+ <string name="confirm_mobile_download_dialog_title">モバイルダウンロードの確認</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">モバイルデータ接続でのダウンロードは設定で無効になっています。\n\n一時的に有効にするか、またはキューに追加するだけにしますか?\n\n<small>選択は 10 分間記憶されます。</small></string>
+ <string name="confirm_mobile_download_dialog_message">モバイルデータ接続でのダウンロードは設定で無効になっています。\n\n一時的に有効にしますか?\n\n<small>選択は 10 分間記憶されます。</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">キューに追加するだけ</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">一時的に有効にする</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">エラー!</string>
<string name="player_stopped_msg">再生するメディアがありません</string>
@@ -143,6 +206,10 @@
<string name="playbackservice_notification_title">ポッドキャストを再生中</string>
<string name="unknown_media_key">AntennaPod - 不明なメディアキー: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">キューをロック</string>
+ <string name="unlock_queue">キューのロックを解除</string>
+ <string name="queue_locked">キューをロックしました</string>
+ <string name="queue_unlocked">キューのロックを解除しました</string>
<string name="clear_queue_label">キューをクリア</string>
<string name="undo">元に戻す</string>
<string name="removed_from_queue">アイテムを削除しました</string>
@@ -184,31 +251,49 @@
<!--Variable Speed-->
<string name="download_plugin_label">プラグインをダウンロード</string>
<string name="no_playback_plugin_title">プラグイン はインストールされていません</string>
- <string name="no_playback_plugin_msg">再生速度の変更が機能するには、サードパーティのライブラリをインストールしておく必要があります。\n\n\'プラグインをダウンロード\' をタップして、Google Playから無料のプラグインをダウンロードしてください\n\nこのプラグインを使用して見つかったすべての問題は AntennaPodの責任ではなく、プラグインのオーナーに報告すべきです。</string>
+ <string name="no_playback_plugin_or_sonic_msg">再生速度の変更を機能させるには、内蔵の Sonic メディアプレーヤーを有効にすることを推奨します [Android 4.1+]。代わりに、Google Play からサードパーティのプラグイン <i>Prestissimo</i> をダウンロードすることができます。\nPrestissimo での問題は AntennaPod の責任ではなく、プラグインのオーナーに報告すべきです。</string>
<string name="set_playback_speed_label">再生速度</string>
+ <string name="enable_sonic">Sonic を有効にする</string>
<!--Empty list labels-->
<string name="no_items_label">このリストにはアイテムがありません。</string>
<string name="no_feeds_label">まだフィードを何も購読していません。</string>
+ <string name="no_chapters_label">このエピソードにチャプターはありません。</string>
<!--Preferences-->
<string name="other_pref">その他</string>
<string name="about_pref">について</string>
<string name="queue_label">キュー</string>
<string name="services_label">サービス</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">ヘッドフォンの接続が切断された時、再生を一時停止します</string>
+ <string name="pref_episode_cleanup_title">エピソード クリーンアップ</string>
+ <string name="pref_episode_cleanup_summary">キューに含まれておらず、お気に入りではないエピソードは、自動ダウンロードで新しいエピソードのためにスペースが必要な場合、除去の対象になります</string>
+ <string name="pref_pauseOnDisconnect_sum">ヘッドフォンまたはBluetoothの接続が切断された時、再生を一時停止します</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">ヘッドフォンが再接続された時に再生を再開します</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Bluetoothが再接続された時に再生を再開します</string>
+ <string name="pref_hardwareForwardButtonSkips_title">早送りボタンでスキップ</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">ハードウェアの早送りボタンを押したときに、早送りの代わりに次のエピソードにスキップします</string>
<string name="pref_followQueue_sum">再生が完了した時に次のキューのアイテムに移動します</string>
<string name="pref_auto_delete_sum">再生が完了した時にエピソードを削除します</string>
<string name="pref_auto_delete_title">自動削除</string>
+ <string name="pref_smart_mark_as_played_sum">一定の秒数未満の再生時間がまだ残っている場合でも、エピソードを再生済としてマークします</string>
+ <string name="pref_smart_mark_as_played_title">再生済としてスマートマーク</string>
+ <string name="pref_skip_keeps_episodes_sum">エピソードをスキップした時に残しておきます</string>
+ <string name="pref_skip_keeps_episodes_title">エピソードのスキップ時に残す</string>
<string name="playback_pref">再生</string>
<string name="network_pref">ネットワーク</string>
- <string name="pref_autoUpdateIntervall_title">更新間隔</string>
- <string name="pref_autoUpdateIntervall_sum">フィードが自動的に更新される間隔を指定するか、または無効にしてください</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">間隔または時間を更新</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">自動的にフィードを更新する間隔または時間を指定してください</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">\"2 時間ごと\" のような <i>間隔</i> 、\"7:00 AM\" のような特定の <i>時間</i> 、または自動更新を完全に <i>無効</i> にセットすることができます。\n\n<small>ご注意ください: 更新時間は正確ではありません。少し遅延が発生する可能性があります。</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">無効</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">間隔をセット</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">時間をセット</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">%1$s ごと</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">%1$s に</string>
<string name="pref_downloadMediaOnWifiOnly_sum">WiFi接続時のみメディアファイルをダウンロードします</string>
<string name="pref_followQueue_title">連続再生</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFiメディアダウンロード</string>
<string name="pref_pauseOnHeadsetDisconnect_title">ヘッドフォン切断</string>
<string name="pref_unpauseOnHeadsetReconnect_title">ヘッドフォン再接続</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth再接続</string>
<string name="pref_mobileUpdate_title">モバイル更新</string>
<string name="pref_mobileUpdate_sum">モバイルデータ接続時に更新を許可します</string>
<string name="refreshing_label">更新中</string>
@@ -223,6 +308,14 @@
<string name="pref_auto_flattr_sum">自動Flattrを構成</string>
<string name="user_interface_label">インターフェース</string>
<string name="pref_set_theme_title">テーマを選択</string>
+ <string name="pref_nav_drawer_title">ナビゲーションドロワーをカスタマイズ</string>
+ <string name="pref_nav_drawer_sum">ナビゲーションドロワーの外観をカスタマイズします。</string>
+ <string name="pref_nav_drawer_items_title">ナビゲーションドロワーを変更</string>
+ <string name="pref_nav_drawer_items_sum">ナビゲーションドロワーに表示するアイテムを変更します。</string>
+ <string name="pref_nav_drawer_feed_order_title">購読注文をセット</string>
+ <string name="pref_nav_drawer_feed_order_sum">購読の注文を変更します</string>
+ <string name="pref_nav_drawer_feed_counter_title">購読カウンターをセット</string>
+ <string name="pref_nav_drawer_feed_counter_sum">購読カウンターで表示される情報を変更します</string>
<string name="pref_set_theme_sum">AntennaPodの外観を変更します。</string>
<string name="pref_automatic_download_title">自動ダウンロード</string>
<string name="pref_automatic_download_sum">エピソードの自動ダウンロードを構成します。</string>
@@ -246,17 +339,31 @@
<string name="pref_gpodnet_setlogin_information_sum">gpodder.netアカウントのログイン情報を変更します。</string>
<string name="pref_playback_speed_title">再生速度</string>
<string name="pref_playback_speed_sum">可変速度音声再生に使用可能な速度をカスタマイズします</string>
- <string name="pref_seek_delta_title">シーク時間</string>
- <string name="pref_seek_delta_sum">巻き戻しまたは早送り時にこの秒数でシークします</string>
+ <string name="pref_fast_forward">早送り時間</string>
+ <string name="pref_rewind">巻き戻し時間</string>
<string name="pref_gpodnet_sethostname_title">ホスト名をセット</string>
<string name="pref_gpodnet_sethostname_use_default_host">デフォルトホストを使用</string>
<string name="pref_expandNotify_title">拡張通知</string>
<string name="pref_expandNotify_sum">常に再生ボタンを表示するように通知を展開します。</string>
<string name="pref_persistNotify_title">永続再生コントロール</string>
<string name="pref_persistNotify_sum">再生が一時停止された時に、通知およびロック画面のコントロールを保持します。</string>
+ <string name="pref_lockscreen_background_title">ロック画面の背景を設定</string>
+ <string name="pref_lockscreen_background_sum">ロック画面の背景を、現在のエピソードの画像に設定します。副作用として、これはサードパーティのアプリケーションでも画像を表示します。</string>
+ <string name="pref_showDownloadReport_title">ダウンロード レポートを表示</string>
+ <string name="pref_showDownloadReport_sum">ダウンロードが失敗した場合、失敗の詳細を表示するレポートを生成します。</string>
<string name="pref_expand_notify_unsupport_toast">Androidバージョン4.1以前では、拡張通知をサポートしていません。</string>
<string name="pref_queueAddToFront_sum">新しいエピソードをキューの先頭に追加します。</string>
<string name="pref_queueAddToFront_title">キューの先頭に入れる</string>
+ <string name="pref_smart_mark_as_played_disabled">無効</string>
+ <string name="pref_image_cache_size_title">画像キャッシュサイズ</string>
+ <string name="pref_image_cache_size_sum">画像のディスクキャッシュのサイズ。</string>
+ <string name="crash_report_title">クラッシュレポート</string>
+ <string name="crash_report_sum">メールで最新のクラッシュレポートを送信します</string>
+ <string name="send_email">メールを送信</string>
+ <string name="experimental_pref">実験的</string>
+ <string name="pref_sonic_title">Sonic メディアプレーヤー</string>
+ <string name="pref_sonic_message">Android 標準のメディアプレーヤーと Prestissimo の代わりに、内蔵のソニックメディアプレーヤーを使用します</string>
+ <string name="pref_current_value">現在の値: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">自動Flattrを有効にする</string>
<string name="auto_flattr_after_percent">%d %再生したらエピソードをFlattr </string>
@@ -271,6 +378,7 @@
<string name="found_in_title_label">タイトルで見つかりました</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPMLファイルで、あるポッドキャッチャーから別のものにポッドキャストを移動することができます。</string>
+ <string name="opml_import_option">オプション %1$d</string>
<string name="opml_import_explanation_1">ローカルのファイルシステムから指定するファイルパスを選択してください。</string>
<string name="opml_import_explanation_2">Dropbox、Google ドライブ、またはお好みのファイルマネージャなどの外部アプリケーションを使用して、OPML ファイルを開いてください。</string>
<string name="opml_import_explanation_3">Google メール、Dropbox、Google ドライブ、および多くのファイルマネージャーなどのアプリケーションで、AntennaPod <i>の</i> OPML ファイルを <i>開く</i> ことができます。</string>
@@ -282,10 +390,11 @@
<string name="opml_import_error_dir_empty">インポートディレクトリが空です。</string>
<string name="select_all_label">すべてを選択</string>
<string name="deselect_all_label">選択解除</string>
+ <string name="select_options_label">選択…</string>
<string name="choose_file_from_filesystem">ローカル ファイルシステムから</string>
<string name="choose_file_from_external_application">外部アプリケーションを使用する</string>
<string name="opml_export_label">OPMLエクスポート</string>
- <string name="exporting_label">エクスポート中...</string>
+ <string name="exporting_label">エクスポート中…</string>
<string name="export_error_label">エクスポートエラー</string>
<string name="opml_export_success_title">OPMLをエクスポートしました。</string>
<string name="opml_export_success_sum">.opml ファイルを書き込みました:\u0020</string>
@@ -296,9 +405,21 @@
<string name="sleep_timer_label">スリープタイマー</string>
<string name="time_left_label">残り時間:\u0020</string>
<string name="time_dialog_invalid_input">入力が正しくありません、時間は数字で入力してください</string>
- <string name="time_unit_seconds">秒</string>
- <string name="time_unit_minutes">分</string>
- <string name="time_unit_hours">時</string>
+ <string name="timer_about_to_expire_label"><b>タイマーが期限切れになるとき:</b></string>
+ <string name="shake_to_reset_label">振るとタイマーをリセット</string>
+ <string name="timer_vibration_label">振動</string>
+ <string name="time_seconds">秒</string>
+ <string name="time_minutes">分</string>
+ <string name="time_hours">時間</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="other">%d 秒</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="other">%d 分</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="other">%d 時間</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">カテゴリー</string>
<string name="gpodnet_toplist_header">トップ ボッドキャスト</string>
@@ -331,20 +452,27 @@
<string name="selected_folder_label">選択したフォルダー:</string>
<string name="create_folder_label">フォルダーを作成</string>
<string name="choose_data_directory">データ フォルダーを選択</string>
+ <string name="choose_data_directory_message">基本のデータフォルダーを選択してください。 AntennaPodは、適切なサブディレクトリを作成します。</string>
<string name="create_folder_msg">名前 \"%1$s\" で新しいフォルダーを作成しますか?</string>
<string name="create_folder_success">新しいフォルダーを作成しました</string>
<string name="create_folder_error_no_write_access">このフォルダーに書き込みできません</string>
<string name="create_folder_error_already_exists">フォルダーは既に存在します</string>
<string name="create_folder_error">フォルダーを作成できません</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" は存在しません</string>
+ <string name="folder_not_readable_error">\"%1$s\" は読み込みできません</string>
+ <string name="folder_not_writable_error">\"%1$s\" は書き込みできません</string>
<string name="folder_not_empty_dialog_title">フォルダーが空ではありません</string>
<string name="folder_not_empty_dialog_msg">選択したフォルダは空ではありません。メディアのダウンロードやその他のファイルはこのフォルダに直接配置されます。続けますか?</string>
<string name="set_to_default_folder">デフォルトのフォルダーを選択</string>
<string name="pref_pausePlaybackForFocusLoss_sum">他のアプリがサウンドを再生したい時に、音量を下げる代わりに再生の一時停止します</string>
<string name="pref_pausePlaybackForFocusLoss_title">割り込み時に一時停止</string>
+ <string name="pref_resumeAfterCall_sum">着信が完了した後に再生を再開します</string>
+ <string name="pref_resumeAfterCall_title">着信後に再開</string>
+ <string name="pref_restart_required">この変更を有効にするには AntennaPod を再起動する必要があります。</string>
<!--Online feed view-->
<string name="subscribe_label">購読</string>
<string name="subscribed_label">購読しました</string>
- <string name="downloading_label">ダウンロード中...</string>
+ <string name="downloading_label">ダウンロード中…</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">チャプターを表示</string>
<string name="show_shownotes_label">ショーノートを表示</string>
@@ -367,7 +495,45 @@
<!--Feed information screen-->
<string name="authentication_label">認証</string>
<string name="authentication_descr">このポッドキャストとそのエピソード用のあなたのユーザー名とパスワードを変更します。</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">データベースをアップグレードしています</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">単一目的のアプリから購読をインポート中…</string>
<string name="search_itunes_label">iTunes を検索</string>
+ <string name="select_label"><b>選択…</b></string>
+ <string name="filter">フィルター</string>
+ <string name="all_label">すべて</string>
+ <string name="selected_all_label">すべてのエピソードを選択しました</string>
+ <string name="none_label">なし</string>
+ <string name="deselected_all_label">すべてのエピソードの選択を解除しました</string>
+ <string name="played_label">再生しました</string>
+ <string name="selected_played_label">再生済のエピソードを選択しました</string>
+ <string name="unplayed_label">未再生</string>
+ <string name="selected_unplayed_label">未再生のエピソードを選択しました</string>
+ <string name="downloaded_label">ダウンロードしました</string>
+ <string name="selected_downloaded_label">ダウンロード済のエピソードを選択しました</string>
+ <string name="not_downloaded_label">ダウンロードしていません</string>
+ <string name="selected_not_downloaded_label">ダウンロードしていないエピソードを選択しました</string>
+ <string name="sort_title"><b>並び替え…</b></string>
+ <string name="sort_title_a_z">タイトル (A \u2192 Z)</string>
+ <string name="sort_title_z_a">タイトル (Z \u2192 A)</string>
+ <string name="sort_date_new_old">日付 (新 \u2192 旧)</string>
+ <string name="sort_date_old_new">日付 (旧 \u2192 新)</string>
+ <string name="sort_duration_short_long">期間 (短 \u2192 長)</string>
+ <string name="sort_duration_long_short">期間 (長 \u2192 短)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">AntennaPod は気に入りましたか?</string>
+ <string name="rating_message">AntennaPodを評価する時間をいただければ幸いです。</string>
+ <string name="rating_never_label">構わないで</string>
+ <string name="rating_later_label">後で確認する</string>
+ <string name="rating_now_label">もちろん、します!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">自動コントロール</string>
+ <string name="playback_speed">再生速度</string>
+ <string name="volume">音量</string>
+ <string name="left_short">左</string>
+ <string name="right_short">右</string>
+ <string name="audio_effects">オーディオエフェクト</string>
+ <string name="stereo_to_mono">ダウンミックス: ステレオからモノラル</string>
+ <string name="sonic_only">Sonic のみ</string>
</resources>
diff --git a/core/src/main/res/values-ko/strings.xml b/core/src/main/res/values-ko/strings.xml
index 7830be329..6802cc36d 100644
--- a/core/src/main/res/values-ko/strings.xml
+++ b/core/src/main/res/values-ko/strings.xml
@@ -8,6 +8,8 @@
<string name="episodes_label">에피소드</string>
<string name="new_episodes_label">새 에피소드</string>
<string name="all_episodes_label">모든 에피소드</string>
+ <string name="all_episodes_short_label">모두</string>
+ <string name="favorite_episodes_label">즐겨찾기</string>
<string name="new_label">신규</string>
<string name="waiting_list_label">추가 대기 목록</string>
<string name="settings_label">설정</string>
@@ -20,12 +22,23 @@
<string name="playback_history_label">재생 기록</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net 로그인</string>
+ <string name="free_space_label">%1$s 남음</string>
+ <string name="episode_cache_full_title">에피소드 캐시 꽉 참</string>
+ <string name="episode_cache_full_message">에피소드 캐시 한계값에 도달했습니다. 설정에서 캐시 크기를 늘릴 수 있습니다.</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">최근에 발표</string>
<string name="episode_filter_label">새 에피소드만 표시</string>
<!--Main activity-->
<string name="drawer_open">메뉴 열기</string>
<string name="drawer_close">메뉴 닫기</string>
+ <string name="drawer_preferences">드로어 기본 설정</string>
+ <string name="drawer_feed_order_unplayed_episodes">카운터로 정렬</string>
+ <string name="drawer_feed_order_alphabetical">사전 순서로 정렬</string>
+ <string name="drawer_feed_order_last_update">배포 날짜 순서대로 정렬</string>
+ <string name="drawer_feed_counter_new_unplayed">새로운 에피소드와 재생하지 않은 에피소드 수</string>
+ <string name="drawer_feed_counter_new">새로운 에피소드 수</string>
+ <string name="drawer_feed_counter_unplayed">재생하지 않은 에피소드 수</string>
+ <string name="drawer_feed_counter_none">없음</string>
<!--Webview actions-->
<string name="open_in_browser_label">브라우저에서 열기</string>
<string name="copy_url_label">URL 복사</string>
@@ -37,8 +50,11 @@
<!--Other-->
<string name="confirm_label">확인</string>
<string name="cancel_label">취소</string>
+ <string name="yes">예</string>
+ <string name="no">아니요</string>
<string name="author_label">저자</string>
<string name="language_label">언어</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">설정</string>
<string name="cover_label">그림</string>
<string name="error_label">오류</string>
@@ -53,12 +69,25 @@
<string name="length_prefix">길이:\u0020</string>
<string name="size_prefix">크기:\u0020</string>
<string name="processing_label">처리 중</string>
- <string name="loading_label">읽어들이는 중...</string>
+ <string name="loading_label">읽어들이는 중…</string>
<string name="save_username_password_label">사용자 이름 및 암호 저장</string>
<string name="close_label">닫기</string>
<string name="retry_label">다시 시도</string>
<string name="auto_download_label">자동 다운로드에 포함</string>
+ <string name="auto_download_apply_to_items_title">예전 에피소드에 적용</string>
+ <string name="auto_download_apply_to_items_message">새로운 \'자동 다운로드\' 설정은 새 에피소드에 자동적으로 적용됩니다. 기존에 배포된 에피소드에도 적용할까요?</string>
+ <string name="auto_delete_label">에피소드 자동 삭제\n(전체 설정보다 우선)</string>
<string name="parallel_downloads_suffix">\u0020동시 다운로드</string>
+ <string name="feed_auto_download_global">전체 설정</string>
+ <string name="feed_auto_download_always">항상</string>
+ <string name="feed_auto_download_never">안 함</string>
+ <string name="send_label">보내기…</string>
+ <string name="episode_cleanup_never">안 함</string>
+ <string name="episode_cleanup_queue_removal">대기열에 없을 때</string>
+ <string name="episode_cleanup_after_listening">재생이 끝나고 나서</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="other">재생이 끝나고 나서 %d일 뒤</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">피드 URL</string>
<string name="etxtFeedurlHint">피드의 URL 또는 홈페이지</string>
@@ -67,17 +96,33 @@
<string name="podcastdirectories_descr">gpodder.net 디렉터리에서 이름, 분류, 인기에 따라 새 팟캐스트를 검색할 수 있고, iTunes 스토어에서 검색할 수도 있습니다.</string>
<string name="browse_gpoddernet_label">gpodder.net 둘러보기</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">모두 읽은 것으로 표시</string>
- <string name="mark_all_read_msg">모든 에피소드 읽은 것으로 표시</string>
- <string name="mark_all_read_confirmation_msg">에피소드 모두를 읽은 것으로 표시하는지 확인하십시오.</string>
- <string name="mark_all_read_feed_confirmation_msg">이 피드의 에피소드 모두를 읽은 것으로 표시하는지 확인하십시오.</string>
+ <string name="mark_all_read_label">모두 재생했다고 표시</string>
+ <string name="mark_all_read_msg">모든 에피소드를 재생했다고 표시했습니다</string>
+ <string name="mark_all_read_confirmation_msg">모든 에피소드를 재생했다고 표시할지 확인하십시오.</string>
+ <string name="mark_all_read_feed_confirmation_msg">이 피드에 들어 있는 모든 에피소드를 재생했다고 표시할지 확인하십시오.</string>
+ <string name="mark_all_seen_label">모두 봤다고 표시</string>
<string name="show_info_label">정보 표시</string>
<string name="remove_feed_label">팟캐스트 제거</string>
+ <string name="share_label">공유…</string>
<string name="share_link_label">홈페이지 링크 공유</string>
- <string name="share_source_label">피드 링크 공유</string>
+ <string name="share_link_with_position_label">위치와 같이 링크 공유</string>
+ <string name="share_feed_url_label">피드 URL 공유</string>
+ <string name="share_item_url_label">에피소드 URL 공유</string>
+ <string name="share_item_url_with_position_label">위치와 같이 에피소드 URL 공유</string>
<string name="feed_delete_confirmation_msg">이 피드와 이 피드에서 다운로드한 모든 에피소드를 삭제하시려면 확인을 누르십시오.</string>
<string name="feed_remover_msg">피드 삭제하는 중</string>
<string name="load_complete_feed">전체 피드 새로고침</string>
+ <string name="hide_episodes_title">에피소드 감추기</string>
+ <string name="episode_actions">동작 적용</string>
+ <string name="hide_unplayed_episodes_label">재생 안 함</string>
+ <string name="hide_paused_episodes_label">일시 중지</string>
+ <string name="hide_played_episodes_label">재생함</string>
+ <string name="hide_queued_episodes_label">대기열 추가</string>
+ <string name="hide_not_queued_episodes_label">대기열 추가 안 함</string>
+ <string name="hide_downloaded_episodes_label">다운로드함</string>
+ <string name="hide_not_downloaded_episodes_label">다운로드 안 함</string>
+ <string name="filtered_label">필터링함</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} 최근 새로 고침 실패</string>
<!--actions on feeditems-->
<string name="download_label">다운로드</string>
<string name="play_label">재생</string>
@@ -86,16 +131,25 @@
<string name="stream_label">스트리밍</string>
<string name="remove_label">제거</string>
<string name="remove_episode_lable">에피소드 제거</string>
- <string name="mark_read_label">읽은 것으로 표시</string>
- <string name="mark_unread_label">읽지 않은 것으로 표시</string>
- <string name="marked_as_read_label">읽은 것으로 표시</string>
+ <string name="mark_read_label">재생했다고 표시</string>
+ <string name="marked_as_read_label">재생했다고 표시함</string>
+ <string name="mark_unread_label">재생하지 않음으로 표시</string>
<string name="add_to_queue_label">대기열에 추가</string>
+ <string name="added_to_queue_label">대기열에 추가함</string>
<string name="remove_from_queue_label">대기열에서 제거</string>
+ <string name="add_to_favorite_label">즐겨찾기에 추가</string>
+ <string name="added_to_favorites">즐겨찾기에 추가함</string>
+ <string name="remove_from_favorite_label">즐겨찾기에서 제거</string>
+ <string name="removed_from_favorites">즐겨찾기에서 제거함</string>
<string name="visit_website_label">홈페이지 보기</string>
<string name="support_label">Flattr하기</string>
<string name="enqueue_all_new">모두 대기열에 추가</string>
<string name="download_all">모두 다운로드</string>
<string name="skip_episode_label">에피소드 건너뛰기</string>
+ <string name="activate_auto_download">자동 다운로드 활성화</string>
+ <string name="deactivate_auto_download">자동 다운로드 해제</string>
+ <string name="reset_position">재생 위치 초기화</string>
+ <string name="removed_item">항목 제거됨</string>
<!--Download messages and labels-->
<string name="download_successful">성공</string>
<string name="download_failed">실패</string>
@@ -112,13 +166,17 @@
<string name="download_error_unknown_host">알 수 없는 호스트</string>
<string name="download_error_unauthorized">인증 오류</string>
<string name="cancel_all_downloads_label">모든 다운로드 취소</string>
- <string name="download_canceled_msg">다운로드 취소됨</string>
- <string name="download_report_title">다운로드 마침</string>
+ <string name="download_canceled_msg">다운로드 취소함</string>
+ <string name="download_canceled_autodownload_enabled_msg">다운로드 취소함\n이 항목에 <i>자동 다운로드</i>를 해제합니다</string>
+ <string name="download_report_title">다운로드 마침 (오류 있음)</string>
+ <string name="download_report_content_title">다운로드 보고서</string>
<string name="download_error_malformed_url">URL 형식 틀림</string>
<string name="download_error_io_error">입출력 오류</string>
<string name="download_error_request_error">요청 오류</string>
<string name="download_error_db_access">데이터베이스 접근 오류</string>
- <string name="downloads_left">개\u0020다운로드 남음</string>
+ <plurals name="downloads_left">
+ <item quantity="other">다운로드 %d개 남음</item>
+ </plurals>
<string name="downloads_processing">다운로드 처리 중</string>
<string name="download_notification_title">팟캐스트 데이터 다운로드 중</string>
<string name="download_report_content">다운로드 %1$d개 성공, %2$d개 실패</string>
@@ -129,6 +187,11 @@
<string name="download_request_error_dialog_message_prefix">파일을 다운로드하는 중 오류가 발생했습니다:\u0020</string>
<string name="authentication_notification_title">인증이 필요합니다</string>
<string name="authentication_notification_msg">요청한 자원은 사용자 이름과 암호가 필요합니다</string>
+ <string name="confirm_mobile_download_dialog_title">휴대전화망 다운로드 확인</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">휴대전화망 데이터 연결을 통한 다운로드는 설정에서 막혀 있습니다.\n\n임시로 다운로드를 허용할 수도 있고, 대기열에 추가만 할 수도 있습니다.\n\n<small>여기서 선택한 사항은 10분 동안 유지됩니다.</small></string>
+ <string name="confirm_mobile_download_dialog_message">휴대전화망 데이터 연결을 통한 다운로드는 설정에서 막혀 있습니다.\n\n임시로 다운로드를 열 수 있습니다\n\n<small>여기서 선택한 사항은 10분 동안 유지됩니다.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">대기열에 추가만</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">임시로 허용</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">오류!</string>
<string name="player_stopped_msg">재생 중인 미디어 없음</string>
@@ -143,6 +206,10 @@
<string name="playbackservice_notification_title">팟캐스트 재생 중</string>
<string name="unknown_media_key">안테나팟 - 알 수 없는 미디어 키: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">대기열 잠그기</string>
+ <string name="unlock_queue">대기열 잠금 해제</string>
+ <string name="queue_locked">대기열 잠겨짐</string>
+ <string name="queue_unlocked">대기열 잠금 풀림</string>
<string name="clear_queue_label">대기열 지우기</string>
<string name="undo">실행 취소</string>
<string name="removed_from_queue">항목을 제거했습니다</string>
@@ -184,31 +251,49 @@
<!--Variable Speed-->
<string name="download_plugin_label">플러그인 다운로드</string>
<string name="no_playback_plugin_title">플러그인을 설치하지 않았습니다</string>
- <string name="no_playback_plugin_msg">재생 속도 조절이 동작하려면 외부 라이브러리를 설치해야 합니다.\n\nPlay Store에서 무료 플러그인을 다운로드하려면 \'플러그인 다운로드\'를 누르십시오.\n\n이 플러그인을 사용하면서 생기는 문제는 안테나팟의 책임이 아니므로 해당 플러그인의 관리자에게 문의하십시오.</string>
+ <string name="no_playback_plugin_or_sonic_msg">여러가지 속도로 재생이 동작하려면 내부의 소닉 미디어 플레이어를 활성화해야 합니다. [Android 4.1 이상]\n\n아니면, 서드파티 플러그인 <i>Prestissimo</i>를 플레이스토어에서 다운로드할 수도 있습니다.\nPrestissimo에서 발생하는 문제는 안테나팟의 책임이 아니므로 플러그인 개발자에게 문의하십시오.</string>
<string name="set_playback_speed_label">재생 속도</string>
+ <string name="enable_sonic">소닉 플레이어 사용</string>
<!--Empty list labels-->
<string name="no_items_label">이 목록에 항목이 없습니다.</string>
<string name="no_feeds_label">아직 어떤 피드도 구독하지 않았습니다.</string>
+ <string name="no_chapters_label">에피소드에 챕터가 없습니다.</string>
<!--Preferences-->
<string name="other_pref">기타</string>
<string name="about_pref">정보</string>
<string name="queue_label">대기열</string>
<string name="services_label">서비스</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">헤드폰의 연결이 끊어졌을 때 재생을 일시 중지</string>
+ <string name="pref_episode_cleanup_title">에피소드 정리</string>
+ <string name="pref_episode_cleanup_summary">대기열에 없고 즐겨찾기에 넣지 않은 에피소드는 자동 다운로드에서 새 에피소드에 공간이 필요할 경우 제거될 수 있습니다.</string>
+ <string name="pref_pauseOnDisconnect_sum">헤드폰이나 블루투스가 연결 해제되었을 경우 일시정지</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">헤드폰 다시 연결할 때 재생 계속</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">블루투스가 다시 연결되면 재생 재개 </string>
+ <string name="pref_hardwareForwardButtonSkips_title">빨리감기 버튼을 넘김 버튼으로</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">빨리감기 하드웨어 버튼을 눌렀을 경우 빨리감기 대신 다음 에피소드로 넘깁니다</string>
<string name="pref_followQueue_sum">재생을 마쳤을 때 다음 대기열로 이동</string>
<string name="pref_auto_delete_sum">재생이 끝나면 에피소드 삭제</string>
<string name="pref_auto_delete_title">자동 삭제</string>
+ <string name="pref_smart_mark_as_played_sum">재생이 일정한 시간보다 (초 단위) 적게 남으면 에피소드를 재생한 것으로 표시</string>
+ <string name="pref_smart_mark_as_played_title">똑똑하게 재생한 것으로 표시</string>
+ <string name="pref_skip_keeps_episodes_sum">에피소드를 넘겼을 경우에도 유지</string>
+ <string name="pref_skip_keeps_episodes_title">넘긴 에피소드를 유지 보관합니다</string>
<string name="playback_pref">재생</string>
<string name="network_pref">네트워크</string>
- <string name="pref_autoUpdateIntervall_title">업데이트 주기</string>
- <string name="pref_autoUpdateIntervall_sum">피드를 새로 고칠 주기를 지정하거나 새로 고침을 하지 않음</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">업데이트 주기 또는 하루 중 시각</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">피드를 자동으로 새로 고칠 주기 또는 하루 중 특정 시각을 지정하십시오</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">\"매 2시간\"과 같이 <i>주기</i>를 지정할 수도 있고, \"오전 7:00\"와 같이 <i>하루 중 시각</i>을 지정할 수도 있고, 자동 업데이트를 모두 <i>사용 안 할</i> 수도 있습니다.\n\n<small>안내: 업데이트 시간은 정확하지 않습니다. 약간 늦게 업데이트할 수 있습니다.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">사용 안 함</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">주기 지정</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">하루 중 시각 지정</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">매 %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">%1$s에서</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Wi-Fi를 통해서만 미디어 파일 다운로드</string>
<string name="pref_followQueue_title">연속 재생</string>
<string name="pref_downloadMediaOnWifiOnly_title">Wi-Fi 미디어 다운로드</string>
<string name="pref_pauseOnHeadsetDisconnect_title">헤드폰 연결 끊김</string>
<string name="pref_unpauseOnHeadsetReconnect_title">헤드폰 재연결</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">블루투스 다시 연결</string>
<string name="pref_mobileUpdate_title">휴대전화망 업데이트</string>
<string name="pref_mobileUpdate_sum">휴대전화 데이터 연결을 통해 업데이트 허용</string>
<string name="refreshing_label">새로 고치는 중</string>
@@ -223,6 +308,14 @@
<string name="pref_auto_flattr_sum">자동 flattr 설정</string>
<string name="user_interface_label">사용자 인터페이스</string>
<string name="pref_set_theme_title">테마 선택</string>
+ <string name="pref_nav_drawer_title">네비게이션 드로어 사용자 설정</string>
+ <string name="pref_nav_drawer_sum">네비게이션 드로어의 모양을 사용자 설정합니다.</string>
+ <string name="pref_nav_drawer_items_title">네비게이션 드로어 바꾸기</string>
+ <string name="pref_nav_drawer_items_sum">네비게이션 드로어에 어떤 항목을 표시할지 바꿉니다.</string>
+ <string name="pref_nav_drawer_feed_order_title">구독 순서 설정</string>
+ <string name="pref_nav_drawer_feed_order_sum">구독 순서를 바꿉니다</string>
+ <string name="pref_nav_drawer_feed_counter_title">구독 카운터 설정</string>
+ <string name="pref_nav_drawer_feed_counter_sum">구독 카운터에 따라 표시되는 정보를 바꿉니다</string>
<string name="pref_set_theme_sum">안테나팟의 겉모양을 바꿉니다.</string>
<string name="pref_automatic_download_title">자동 다운로드</string>
<string name="pref_automatic_download_sum">에피소드 자동 다운로드를 설정합니다.</string>
@@ -246,17 +339,31 @@
<string name="pref_gpodnet_setlogin_information_sum">gpodder.net 계정의 로그인 정보를 바꿉니다.</string>
<string name="pref_playback_speed_title">재생 속도</string>
<string name="pref_playback_speed_sum">여러가지 오디오 재생 속도 직접 설정</string>
- <string name="pref_seek_delta_title">넘기기 간격</string>
- <string name="pref_seek_delta_sum">뒤나 앞으로 이동할 때 몇 초를 넘어갈지 지정합니다</string>
+ <string name="pref_fast_forward">빠르게 감기 시간</string>
+ <string name="pref_rewind">뒤로 감기 시간</string>
<string name="pref_gpodnet_sethostname_title">호스트 이름 설정</string>
<string name="pref_gpodnet_sethostname_use_default_host">기본 호스트 사용</string>
<string name="pref_expandNotify_title">알림 확장</string>
<string name="pref_expandNotify_sum">항상 알림에서 재생 버튼이 표시되도록 확장</string>
<string name="pref_persistNotify_title">재생 조작 고정</string>
<string name="pref_persistNotify_sum">재생이 일시 중지했을 때에도 알림과 잠금 화면의 조작 기능 유지</string>
+ <string name="pref_lockscreen_background_title">잠금 화면 배경 설정</string>
+ <string name="pref_lockscreen_background_sum">현재 에피소드의 이미지를 잠금 화면의 배경으로 설정합니다. 대신 이는 제3자 앱의 이미지도 표시하게 됩니다.</string>
+ <string name="pref_showDownloadReport_title">다운로드 보고서 보기</string>
+ <string name="pref_showDownloadReport_sum">다운로드가 실패하면, 실패를 자세히 표시하는 보고서를 만듭니다.</string>
<string name="pref_expand_notify_unsupport_toast">안드로이드 4.1 전 버전에서는 알림 확장을 지원하지 않습니다.</string>
<string name="pref_queueAddToFront_sum">새 에피소드를 대기열 앞에 추가합니다.</string>
<string name="pref_queueAddToFront_title">대기열 앞에 추가</string>
+ <string name="pref_smart_mark_as_played_disabled">사용 안 함</string>
+ <string name="pref_image_cache_size_title">이미지 캐시 크기</string>
+ <string name="pref_image_cache_size_sum">이미지에 사용할 디스크 캐시 크기</string>
+ <string name="crash_report_title">오류 보고</string>
+ <string name="crash_report_sum">최근의 오류 보고서를 전자메일로 보내기</string>
+ <string name="send_email">전자메일 보내기</string>
+ <string name="experimental_pref">실험적 기능</string>
+ <string name="pref_sonic_title">소닉 미디어 플레이어</string>
+ <string name="pref_sonic_message">내장 소닉 미디어 플레이어를 안드로이드 고유 미디어 플레이어와 Prestissimo 대신 사용합니다.</string>
+ <string name="pref_current_value">현재 값: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">자동 flattr 사용</string>
<string name="auto_flattr_after_percent">%d 퍼센트를 재생하면 에피소드에 flattr합니다</string>
@@ -271,6 +378,7 @@
<string name="found_in_title_label">제목에서 발견</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML 파일을 이용하면 팟캐스트 목록을 한 팟캐스트 프로그램에서 다른 팟캐스트 프로그램으로 옮길 수 있습니다.</string>
+ <string name="opml_import_option">옵션 %1$d</string>
<string name="opml_import_explanation_1">로컬 파일시스템의 특정 파일 경로를 선택하십시오.</string>
<string name="opml_import_explanation_2">OPML 파일을 여는데 Dropbox, Google Drive, 또는 파일 관리자 와 같은 외부 앱을 사용합니다.</string>
<string name="opml_import_explanation_3">Google Mail, Dropbox, Google Drive 및 대부분의 파일 관리자는 OPML 파일을 안테나팟<i>으로</i> <i>열 수</i> 있습니다.</string>
@@ -282,10 +390,11 @@
<string name="opml_import_error_dir_empty">가져오기 디렉터리가 비어 있습니다.</string>
<string name="select_all_label">모두 선택</string>
<string name="deselect_all_label">모두 선택 해제</string>
+ <string name="select_options_label">선택…</string>
<string name="choose_file_from_filesystem">로컬 파일시스템에서</string>
<string name="choose_file_from_external_application">외부 앱 사용</string>
<string name="opml_export_label">OPML 내보내기</string>
- <string name="exporting_label">내보내는 중...</string>
+ <string name="exporting_label">내보내는 중…</string>
<string name="export_error_label">내보내기 오류</string>
<string name="opml_export_success_title">OPML 내보내기가 성공했습니다.</string>
<string name="opml_export_success_sum">OPML 파일을 다음에 저장했습니다:\u0020</string>
@@ -296,9 +405,21 @@
<string name="sleep_timer_label">취침 타이머</string>
<string name="time_left_label">남은 시간:\u0020</string>
<string name="time_dialog_invalid_input">입력이 잘못되었습니다. 시간으로 숫자를 입력해야 합니다.</string>
- <string name="time_unit_seconds">초</string>
- <string name="time_unit_minutes">분</string>
- <string name="time_unit_hours">시간</string>
+ <string name="timer_about_to_expire_label"><b>타이머가 만료될 때:</b></string>
+ <string name="shake_to_reset_label">흔들어서 타이머 초기화</string>
+ <string name="timer_vibration_label">진동</string>
+ <string name="time_seconds">초</string>
+ <string name="time_minutes">분</string>
+ <string name="time_hours">시간</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="other">%d초</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="other">%d분</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="other">%d시간</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">분류</string>
<string name="gpodnet_toplist_header">상위 팟캐스트</string>
@@ -331,20 +452,27 @@
<string name="selected_folder_label">선택한 폴더:</string>
<string name="create_folder_label">폴더 만들기</string>
<string name="choose_data_directory">데이터 폴더 선택</string>
+ <string name="choose_data_directory_message">데이터 폴더를 선택하십시오. 안테나팟은 알아서 그 하위 디렉토리를 생성합니다.</string>
<string name="create_folder_msg">이름이 \"%1$s\"인 폴더를 만드시겠습니까?</string>
<string name="create_folder_success">새 폴더를 만들었습니다</string>
<string name="create_folder_error_no_write_access">이 폴더에 쓸 수 없습니다</string>
<string name="create_folder_error_already_exists">폴더가 이미 있습니다</string>
<string name="create_folder_error">폴더를 만들 수 없습니다</string>
+ <string name="folder_does_not_exist_error">\"%1$s\"이(가) 없습니다.</string>
+ <string name="folder_not_readable_error">\"%1$s\"에서 읽을 수 없습니다</string>
+ <string name="folder_not_writable_error">\"%1$s\"에 쓸 수 없습니다</string>
<string name="folder_not_empty_dialog_title">폴더가 비어 있지 않습니다</string>
<string name="folder_not_empty_dialog_msg">선택한 폴더가 비어 있지 않습니다. 다운로드한 미디어 파일 및 기타 파일이 이 폴더에 저장됩니다. 그래도 계속 하시겠습니까?</string>
<string name="set_to_default_folder">기본 폴더 선택</string>
<string name="pref_pausePlaybackForFocusLoss_sum">다른 앱이 소리를 낼 때 볼륨을 줄이지 않고 재생을 일시 중지</string>
<string name="pref_pausePlaybackForFocusLoss_title">끼어들면 일시 중지</string>
+ <string name="pref_resumeAfterCall_sum">전화 통화가 끝난 후에 재생 다시 시작</string>
+ <string name="pref_resumeAfterCall_title">통화 후에 다시 시작</string>
+ <string name="pref_restart_required">이 변경 사항을 적용하려면 안테나팟을 다시 시작해야 합니다.</string>
<!--Online feed view-->
<string name="subscribe_label">구독</string>
<string name="subscribed_label">구독함</string>
- <string name="downloading_label">다운로드하는 중...</string>
+ <string name="downloading_label">다운로드하는 중…</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">챕터 보이기</string>
<string name="show_shownotes_label">프로그램 메모 표시</string>
@@ -367,7 +495,45 @@
<!--Feed information screen-->
<string name="authentication_label">인증</string>
<string name="authentication_descr">이 팟캐스트와 에피소드에 대한 사용자 이름과 비밀번호를 바꿉니다.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">데이터베이스 업그레이드 중</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">단일 용도 앱에서 구독 정보를 가져옵니다...</string>
<string name="search_itunes_label">iTunes 검색</string>
+ <string name="select_label"><b>선택…</b></string>
+ <string name="filter">필터</string>
+ <string name="all_label">모두</string>
+ <string name="selected_all_label">모든 에피소드 선택</string>
+ <string name="none_label">없음</string>
+ <string name="deselected_all_label">모든 에피소드 선택 해제</string>
+ <string name="played_label">재생함</string>
+ <string name="selected_played_label">재생 에피소드 선택</string>
+ <string name="unplayed_label">재생 안 함</string>
+ <string name="selected_unplayed_label">재생 안 한 에피소드 선택</string>
+ <string name="downloaded_label">다운로드함</string>
+ <string name="selected_downloaded_label">다운로드한 에피소드 선택</string>
+ <string name="not_downloaded_label">다운로드 안 함</string>
+ <string name="selected_not_downloaded_label">다운로드 안 한 에피소드 선택</string>
+ <string name="sort_title"><b>정렬…</b></string>
+ <string name="sort_title_a_z">제목 (A \u2192 Z)</string>
+ <string name="sort_title_z_a">제목 (Z \u2192 A)</string>
+ <string name="sort_date_new_old">시각 (최근 \u2192 과거)</string>
+ <string name="sort_date_old_new">시각 (과거 \u2192 최근)</string>
+ <string name="sort_duration_short_long">길이 (짧은 \u2192 긴)</string>
+ <string name="sort_duration_long_short">길이 (긴 \u2192 짧은)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">안테나팟이 좋으신가요?</string>
+ <string name="rating_message">안테나팟을 평가하는데 시간을 잠깐 내주시면 감사하겠습니다.</string>
+ <string name="rating_never_label">안 할래요</string>
+ <string name="rating_later_label">나중에 알림</string>
+ <string name="rating_now_label">해봅시다!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">오디오 조정</string>
+ <string name="playback_speed">재생 속도</string>
+ <string name="volume">볼륨</string>
+ <string name="left_short">좌</string>
+ <string name="right_short">우</string>
+ <string name="audio_effects">오디오 효과</string>
+ <string name="stereo_to_mono">다운믹스: 스테레오에서 모노로</string>
+ <string name="sonic_only">소닉 전용</string>
</resources>
diff --git a/core/src/main/res/values-nb/strings.xml b/core/src/main/res/values-nb/strings.xml
new file mode 100644
index 000000000..e3aa1ca5d
--- /dev/null
+++ b/core/src/main/res/values-nb/strings.xml
@@ -0,0 +1,510 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="app_name">AntennaPod</string>
+ <string name="feeds_label">Strømmer</string>
+ <string name="add_feed_label">Legg til podcast</string>
+ <string name="podcasts_label">PODCASTER</string>
+ <string name="episodes_label">Episoder</string>
+ <string name="new_episodes_label">Nye episoder</string>
+ <string name="all_episodes_label">Alle episoder</string>
+ <string name="all_episodes_short_label">Alle</string>
+ <string name="favorite_episodes_label">Favoritter</string>
+ <string name="new_label">Nye</string>
+ <string name="waiting_list_label">Venteliste</string>
+ <string name="settings_label">Innstillinger</string>
+ <string name="add_new_feed_label">Legg til podcast</string>
+ <string name="downloads_label">Nedlastninger</string>
+ <string name="downloads_running_label">Kjører</string>
+ <string name="downloads_completed_label">Fullført</string>
+ <string name="downloads_log_label">Logg</string>
+ <string name="cancel_download_label">Avbryt\nLast ned</string>
+ <string name="playback_history_label">Avspillingshistorikk</string>
+ <string name="gpodnet_main_label">gpodder.net</string>
+ <string name="gpodnet_auth_label">gpodder.net-innlogging</string>
+ <string name="free_space_label">%1$s ledig</string>
+ <!--New episodes fragment-->
+ <string name="recently_published_episodes_label">Nylig publisert</string>
+ <string name="episode_filter_label">Vis kun nye episoder</string>
+ <!--Main activity-->
+ <string name="drawer_open">Åpne menyen</string>
+ <string name="drawer_close">Lukk menyen</string>
+ <string name="drawer_preferences">Skuff-innstillinger</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sorter på teller</string>
+ <string name="drawer_feed_order_alphabetical">Sorter alfabetisk</string>
+ <string name="drawer_feed_order_last_update">Sorter på utgivelsesdato</string>
+ <string name="drawer_feed_counter_new_unplayed">Antall nye og uavspilte episoder</string>
+ <string name="drawer_feed_counter_new">Antall nye episoder</string>
+ <string name="drawer_feed_counter_unplayed">Antall uavspilte episoder</string>
+ <string name="drawer_feed_counter_none">Ingen</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">Åpne i nettleser</string>
+ <string name="copy_url_label">Kopier URL</string>
+ <string name="share_url_label">Del URL</string>
+ <string name="copied_url_msg">URL er kopiert til utklippstavlen</string>
+ <string name="go_to_position_label">Gå til denne posisjonen</string>
+ <!--Playback history-->
+ <string name="clear_history_label">Tøm historikk</string>
+ <!--Other-->
+ <string name="confirm_label">Bekreft</string>
+ <string name="cancel_label">Avbryt</string>
+ <string name="yes">Ja</string>
+ <string name="no">Nei</string>
+ <string name="author_label">Opphavsperson</string>
+ <string name="language_label">Språk</string>
+ <string name="url_label">URL</string>
+ <string name="podcast_settings_label">Innstillinger</string>
+ <string name="cover_label">Bilde</string>
+ <string name="error_label">Feil</string>
+ <string name="error_msg_prefix">Det oppsto en feil:</string>
+ <string name="refresh_label">Oppdater</string>
+ <string name="external_storage_error_msg">Finner ikke ekstern lagring. Sjekk at det eksterne lagringsmediet er montert.</string>
+ <string name="chapters_label">Kapittel</string>
+ <string name="shownotes_label">Shownotater</string>
+ <string name="description_label">Beskrivelse</string>
+ <string name="most_recent_prefix">Nyeste episode:\u0020</string>
+ <string name="episodes_suffix">\u0020episoder</string>
+ <string name="length_prefix">Lengde:\u0020</string>
+ <string name="size_prefix">Størrelse:\u0020</string>
+ <string name="processing_label">Behandler</string>
+ <string name="save_username_password_label">Lagre brukernavn og passord</string>
+ <string name="close_label">Lukk</string>
+ <string name="retry_label">Prøv igjen</string>
+ <string name="auto_download_label">Inkluder i automatiske nedlastninger</string>
+ <string name="auto_download_apply_to_items_title">Angi for tidligere episoder</string>
+ <string name="auto_download_apply_to_items_message">Den nye <i>Automatisk nedlasting</i>-innstillingen vil automatisk aktiveres for nye episoder.\nØnsker du å aktivere den for tidligere utgitte episoder også?</string>
+ <string name="auto_delete_label">Automatisk sletting av episode\n(overstyr den globale standarden)</string>
+ <string name="parallel_downloads_suffix">\u0020samtidige nedlastinger</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Alltid</string>
+ <string name="feed_auto_download_never">Aldri</string>
+ <string name="episode_cleanup_never">AldriAldri</string>
+ <string name="episode_cleanup_queue_removal">Når ikke i kø</string>
+ <string name="episode_cleanup_after_listening">Etter den er ferdig</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dag etter fullført avspilling</item>
+ <item quantity="other">%d dager etter fullført avspilling</item>
+ </plurals>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">Strømmens URL</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
+ <string name="txtvfeedurl_label">Legg til en podcast via URL</string>
+ <string name="podcastdirectories_label">Finn podcast i katalog</string>
+ <string name="podcastdirectories_descr">Du kan søke etter nye podcaster på navn, kategori eller popularitet i gpodder.nets katalog eller på iTunes.</string>
+ <string name="browse_gpoddernet_label">Bla gjennom gpodder.net</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Marker alle som avspilt</string>
+ <string name="mark_all_read_msg">Marker alle episoder som avspilt</string>
+ <string name="mark_all_read_confirmation_msg">Vennligst bekreft at du ønsker å markere alle episoder som avspilt.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Vennligst bekreft at du ønsker å markere alle episoder i denne strømmen som avspilt.</string>
+ <string name="mark_all_seen_label">Marker alle som sett</string>
+ <string name="show_info_label">Vis informasjon</string>
+ <string name="remove_feed_label">Fjern podcast</string>
+ <string name="share_link_label">Del lenke</string>
+ <string name="share_link_with_position_label">Del lenke med plassering</string>
+ <string name="share_feed_url_label">Del strømmens URL</string>
+ <string name="share_item_url_label">Del episodens URL</string>
+ <string name="share_item_url_with_position_label">Del episodens URL med posisjon</string>
+ <string name="feed_delete_confirmation_msg">Vil du virkelig slette denne strømmen og alle episodene av denne strømmen du har lastet ned?</string>
+ <string name="feed_remover_msg">Fjerner strøm</string>
+ <string name="load_complete_feed">Oppdater hele strømmen</string>
+ <string name="hide_episodes_title">Skjul episoder</string>
+ <string name="episode_actions">Lagre handlinger</string>
+ <string name="hide_unplayed_episodes_label">Ikke avspilt</string>
+ <string name="hide_paused_episodes_label">Pauset</string>
+ <string name="hide_played_episodes_label">Avspilt</string>
+ <string name="hide_queued_episodes_label">I kø</string>
+ <string name="hide_not_queued_episodes_label">Ikke i kø</string>
+ <string name="hide_downloaded_episodes_label">Nedlastet</string>
+ <string name="hide_not_downloaded_episodes_label">Ikke nedlastet</string>
+ <string name="filtered_label">Filtrert</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Siste oppdatering mislyktes</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Last ned</string>
+ <string name="play_label">Spill</string>
+ <string name="pause_label">Pause</string>
+ <string name="stop_label">Stopp</string>
+ <string name="stream_label">Stream</string>
+ <string name="remove_label">Fjern</string>
+ <string name="remove_episode_lable">Fjern episode</string>
+ <string name="mark_read_label">Marker som avspilt</string>
+ <string name="marked_as_read_label">Marker som avspilt</string>
+ <string name="mark_unread_label">Marker som ikke avspilt</string>
+ <string name="add_to_queue_label">Legg til queue</string>
+ <string name="added_to_queue_label">Lagt til i kø</string>
+ <string name="remove_from_queue_label">Fjern fra queue</string>
+ <string name="add_to_favorite_label">Legg til i favoritter</string>
+ <string name="remove_from_favorite_label">Fjern fra favoritter</string>
+ <string name="visit_website_label">Besøk nettside</string>
+ <string name="support_label">Flattr\'e dette</string>
+ <string name="enqueue_all_new">Legg alle til queue</string>
+ <string name="download_all">Last ned alle</string>
+ <string name="skip_episode_label">Skip episode</string>
+ <string name="activate_auto_download">Aktiver automatisk nedlasting</string>
+ <string name="deactivate_auto_download">Deaktiver automatisk nedlasting</string>
+ <string name="reset_position">Tilbakestill avspillingsposisjon</string>
+ <string name="removed_item">Element fjernet</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">nedlastning lyktes</string>
+ <string name="download_failed">mislyktes</string>
+ <string name="download_pending">Nedlastning venter</string>
+ <string name="download_running">Nedlasting pågår</string>
+ <string name="download_error_device_not_found">Lagringsenhet ikke funnet</string>
+ <string name="download_error_insufficient_space">Ikke nok plass</string>
+ <string name="download_error_file_error">Fil-feil</string>
+ <string name="download_error_http_data_error">HTTP-datafeil</string>
+ <string name="download_error_error_unknown">Ukjent feil</string>
+ <string name="download_error_parser_exception">Parser-unntak</string>
+ <string name="download_error_unsupported_type">Strøm-typen er ikke støttet</string>
+ <string name="download_error_connection_error">Tilkoblingsfeil</string>
+ <string name="download_error_unknown_host">Ukjent vert</string>
+ <string name="download_error_unauthorized">Autentiseringsfeil</string>
+ <string name="cancel_all_downloads_label">Avbryt alle nedlastninger</string>
+ <string name="download_canceled_msg">Nedlasting avbrutt</string>
+ <string name="download_canceled_autodownload_enabled_msg">Nedlasting avbrutt\n<i>Automatisk nedlasting</i> for dette elementet er deaktivert</string>
+ <string name="download_report_title">Nedlasting fullført med feilmeldinger</string>
+ <string name="download_report_content_title">Nedlastingsrapport</string>
+ <string name="download_error_malformed_url">Misformet URL</string>
+ <string name="download_error_io_error">IO feil</string>
+ <string name="download_error_request_error">Forespørselfeil</string>
+ <string name="download_error_db_access">Tilgangsfeil for database</string>
+ <string name="downloads_processing">Behandler nedlastninger</string>
+ <string name="download_notification_title">Laster ned data til podcast</string>
+ <string name="download_report_content">%1$d nedlastninger lyktes, %2$d mislyktes</string>
+ <string name="download_log_title_unknown">Ukjent tittel</string>
+ <string name="download_type_feed">Strøm</string>
+ <string name="download_type_media">Mediafil</string>
+ <string name="download_type_image">Bilde</string>
+ <string name="download_request_error_dialog_message_prefix">En feil oppsto under nedlastningen av filen:\u0020</string>
+ <string name="authentication_notification_title">Autentisering påkreves</string>
+ <string name="authentication_notification_msg">Ressursen du forespør krever brukernavn og passord</string>
+ <string name="confirm_mobile_download_dialog_title">Bekreft nedlasting over mobildata</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Nedlasting over mobildata er deaktivert i innstillingene.\n\nDu kan velge å legge episoden til i køen eller tillate midlertidig nedlasting over mobildata.\n\n<small>Valget ditt vil gjelde i 10 minutter.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Nedlasting over mobildata er deaktivert i innstillingene.\n\nVil du tillate nedlasting over mobildata midlertidig?\n\n<small>Valget ditt vil gjelde i 10 minutter.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Legg til i kø</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Tillat midlertidig</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Error!</string>
+ <string name="player_stopped_msg">Ingen media spillende for øyeblikket.</string>
+ <string name="player_preparing_msg">Forbereder</string>
+ <string name="player_ready_msg">Klar</string>
+ <string name="player_seeking_msg">Oppsøker</string>
+ <string name="playback_error_server_died">Serveren døde</string>
+ <string name="playback_error_unknown">Ukjent feil</string>
+ <string name="no_media_playing_label">Ingen media spillende for øyeblikket.</string>
+ <string name="position_default_label">00:00:00</string>
+ <string name="player_buffering_msg">Bufrer</string>
+ <string name="playbackservice_notification_title">Spiller podcast</string>
+ <string name="unknown_media_key">AntennaPod - Ukjent medienøkkel: %1$d</string>
+ <!--Queue operations-->
+ <string name="lock_queue">Lås køen</string>
+ <string name="unlock_queue">Lås opp køen</string>
+ <string name="clear_queue_label">Slett køen</string>
+ <string name="undo">Angre</string>
+ <string name="removed_from_queue">Objekt er fjernet</string>
+ <string name="move_to_top_label">Gå til toppen</string>
+ <string name="move_to_bottom_label">Gå til bunnen</string>
+ <string name="sort">Sortér</string>
+ <string name="alpha">Alfabetisk</string>
+ <string name="date">På dato</string>
+ <string name="duration">På varighet</string>
+ <string name="ascending">Økende</string>
+ <string name="descending">Synkende</string>
+ <string name="clear_queue_confirmation_msg">Vennligst bekreft at du ønsker å slette ALLE elementer i køen</string>
+ <!--Flattr-->
+ <string name="flattr_auth_label">Flattr innlogging</string>
+ <string name="flattr_auth_explanation">Trykk knappen nedenfor for å starte autentiseringsprosessen. Du vil videresendes til flattr sin innloggsinsskjerm i din nettleser og spurt om å gi AntennaPod tillatelse til å flattr\'e ting. Etter at du har gitt tillatelse blir du returnert hit automatisk.</string>
+ <string name="authenticate_label">Autentiser</string>
+ <string name="return_home_label">Returner hjem</string>
+ <string name="flattr_auth_success">Autentisering fullført! Nå kan du flattr tingene i denne appen.</string>
+ <string name="no_flattr_token_title">Flattr-token ikke funnet</string>
+ <string name="no_flattr_token_notification_msg">Det virker som at Flattr-kontoen din ikke er sammenkoblet med AntennaPod. Trykk her for å autentisere.</string>
+ <string name="no_flattr_token_msg">Det virker ikke som din flattr konto er koblet til AntennaPod. Du kan enten koble kontoen din til AntennaPod for å flattr\'e ting i appen eller du kan besøke nettsiden til tingen for å flattr\'e det der.</string>
+ <string name="authenticate_now_label">Autentiser</string>
+ <string name="action_forbidden_title">Handling forbudt</string>
+ <string name="action_forbidden_msg">AntennaPod har ikke tilgang til denne handlingen. Grunnen kan være at tilgangstokenet til kontoen din er blitt inndratt. Du kan enten re-autentisere eller besøke tjenestens nettsted.</string>
+ <string name="access_revoked_title">Tilgang opphevet</string>
+ <string name="access_revoked_info">Du har fjernet AntennaPods tilgang til kontoen din. For å fullføre prossessen må du fjerne denne appen fra listen over tillatte apper på flattr-nettsiden.</string>
+ <!--Flattr-->
+ <string name="flattr_click_success">Flattr\'erte en ting!</string>
+ <string name="flattr_click_success_count">Flattret %d ting!</string>
+ <string name="flattr_click_success_queue">Flattret: %s.</string>
+ <string name="flattr_click_failure_count">Klarte ikke flattre %d ting!</string>
+ <string name="flattr_click_failure">Ikke flattret: %s.</string>
+ <string name="flattr_click_enqueued">Tingen vil bli flattret senere</string>
+ <string name="flattring_thing">Flattrer %s</string>
+ <string name="flattring_label">AntennaPod flattrer</string>
+ <string name="flattrd_label">AntennaPod har flattret</string>
+ <string name="flattrd_failed_label">AntennaPods flattring feilet</string>
+ <string name="flattr_retrieving_status">Henter flattrede ting</string>
+ <!--Variable Speed-->
+ <string name="download_plugin_label">Last ned programtillegg</string>
+ <string name="no_playback_plugin_title">Programtillegg er ikke installert</string>
+ <string name="set_playback_speed_label">Avspillingshastigheter</string>
+ <string name="enable_sonic">Skru på Sonic</string>
+ <!--Empty list labels-->
+ <string name="no_items_label">Det er ingen objekter på denne listen.</string>
+ <string name="no_feeds_label">Du abonnerer ikke på noen strømmer enda.</string>
+ <!--Preferences-->
+ <string name="other_pref">Annet</string>
+ <string name="about_pref">Om</string>
+ <string name="queue_label">Queue</string>
+ <string name="services_label">Tjenester</string>
+ <string name="flattr_label">Flattr</string>
+ <string name="pref_episode_cleanup_title">Episodeopprydding</string>
+ <string name="pref_pauseOnDisconnect_sum">Sett playback på pause når hodetelefoner eller bluetooth er frakoblet</string>
+ <string name="pref_unpauseOnHeadsetReconnect_sum">Gjenoppta avspilling når hodetelefoner gjeninnkoples</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Fortsett avspilling når bluetooth er tilkoblet igjen</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Forover-knapp hopper</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Ved pressing av hardware forover-knapp hopp til neste episode istedenfor forover-spoling</string>
+ <string name="pref_followQueue_sum">Hopp til neste element i køen når avspillingen er ferdig</string>
+ <string name="pref_auto_delete_sum">Slett episode når avspillingen er ferdig</string>
+ <string name="pref_auto_delete_title">Automatisk sletting</string>
+ <string name="pref_smart_mark_as_played_sum">Marker episoder som avspilt selv om det er X antall sekunder igjen av avspillingen</string>
+ <string name="pref_smart_mark_as_played_title">Smart markering av avspilt</string>
+ <string name="pref_skip_keeps_episodes_sum">Behold episoder når de hoppes over</string>
+ <string name="pref_skip_keeps_episodes_title">Behold episoder som er hoppet over</string>
+ <string name="playback_pref">Avspilling</string>
+ <string name="network_pref">Nettverk</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Oppdateringsintervall eler tidspunkt</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Spesifiser en intervall eller et spesifikt tidspunkt når strømmer skal oppdateres automatisk</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Du kan sette en <i>intervall</i> som «hver andre time», et <i>spesifikt tidspunkt</i> som «07:00» eller <i>skru av</i> automatiske oppdateringer helt.\n\n<small>Merk: Oppdateringstider er ikke eksakte; du kan oppleve små forsinkelser.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Skru av</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Sett intervall</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Angi klokkeslett</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Last ned mediafiler eksklusivt med WiFi</string>
+ <string name="pref_followQueue_title">Kontinuerlig avspilling</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">WiFi media nedlastning</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Frakobling av hodetelefoner</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">Gjeninnkopling av hodetelefoner</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Blutetooth tilkoblet igjen</string>
+ <string name="pref_mobileUpdate_title">Mobiloppdateringer</string>
+ <string name="pref_mobileUpdate_sum">Tillat oppdateringer over kobling via mobildata</string>
+ <string name="refreshing_label">Oppdaterer</string>
+ <string name="flattr_settings_label">Flattr innstillinger</string>
+ <string name="pref_flattr_auth_title">Flattr logg in</string>
+ <string name="pref_flattr_auth_sum">Logg på din flattr konto for å flattr ting direkte fra appen.</string>
+ <string name="pref_flattr_this_app_title">Flattr denne appen</string>
+ <string name="pref_flattr_this_app_sum">Støtt utviklingen av AntennaPod ved å flattr\'e det. Tusen takk!</string>
+ <string name="pref_revokeAccess_title">Opphev tilgang</string>
+ <string name="pref_revokeAccess_sum">Opphev tilgangstillatelsen til din flattr konto for denne appen.</string>
+ <string name="pref_auto_flattr_title">Automatisk Flattr</string>
+ <string name="pref_auto_flattr_sum">Konfigurer automatisk flattring</string>
+ <string name="user_interface_label">Brukergrensesnitt</string>
+ <string name="pref_set_theme_title">Velg tema</string>
+ <string name="pref_nav_drawer_title">Skreddersy navigasjonsskuff</string>
+ <string name="pref_nav_drawer_sum">Velg utseendet til navigasjonsskuffen.</string>
+ <string name="pref_nav_drawer_items_title">Velg elementer i navigasjonsskuffen</string>
+ <string name="pref_nav_drawer_items_sum">Endre hvilke elementer som vises i navigeringsfanen.</string>
+ <string name="pref_nav_drawer_feed_order_title">Velg rekkefølge på abbonement</string>
+ <string name="pref_nav_drawer_feed_order_sum">Endre rekkefølgen på abbonementene dine</string>
+ <string name="pref_nav_drawer_feed_counter_title">Velg abbonementsteller</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Endre informasjonen vist av abonnementstelleren</string>
+ <string name="pref_set_theme_sum">Endre utseendet til AntennaPod</string>
+ <string name="pref_automatic_download_title">Automatisk nedlasting</string>
+ <string name="pref_automatic_download_sum">Konfigurer automatisk nedlasting av episoder.</string>
+ <string name="pref_autodl_wifi_filter_title">Skru på Wi-Fi-filter</string>
+ <string name="pref_autodl_wifi_filter_sum">Tillat automatisk nedlasting kun for valgte Wi-Fi-nettverk.</string>
+ <string name="pref_automatic_download_on_battery_title">Last ned når enheten ikke lader</string>
+ <string name="pref_automatic_download_on_battery_sum">Tillat automatisk nedlasting når enheten ikke står til lading</string>
+ <string name="pref_parallel_downloads_title">Parallelle nedlastinger</string>
+ <string name="pref_episode_cache_title">Mellomlager for episoder</string>
+ <string name="pref_theme_title_light">Lyst</string>
+ <string name="pref_theme_title_dark">Mørkt</string>
+ <string name="pref_episode_cache_unlimited">Ulimitert</string>
+ <string name="pref_update_interval_hours_plural">Timer</string>
+ <string name="pref_update_interval_hours_singular">Time</string>
+ <string name="pref_update_interval_hours_manual">Manuelt</string>
+ <string name="pref_gpodnet_authenticate_title">Logg inn</string>
+ <string name="pref_gpodnet_authenticate_sum">Logg inn med din gpodder.net konto for å synkronisere dine abonnementer.</string>
+ <string name="pref_gpodnet_logout_title">Logg ut</string>
+ <string name="pref_gpodnet_logout_toast">Utloggelse lyktes</string>
+ <string name="pref_gpodnet_setlogin_information_title">Endre innloggingsinformasjon</string>
+ <string name="pref_gpodnet_setlogin_information_sum">Endre innlogginsinformasjonen til din gpodder.net konto</string>
+ <string name="pref_playback_speed_title">Avspillingshastigheter</string>
+ <string name="pref_playback_speed_sum">Egendefiner hastighetene tilgjengelig for variabel avspillingshastighet</string>
+ <string name="pref_fast_forward">Spoling fremover</string>
+ <string name="pref_rewind">Spoling bakover</string>
+ <string name="pref_gpodnet_sethostname_title">Sett vertsnavn</string>
+ <string name="pref_gpodnet_sethostname_use_default_host">Bruk standard vert</string>
+ <string name="pref_expandNotify_title">Utvid varsel</string>
+ <string name="pref_expandNotify_sum">Utvider alltid varselet for å inkludere avspillingsknapper.</string>
+ <string name="pref_persistNotify_title">Vedvarende avspillingskontroller</string>
+ <string name="pref_persistNotify_sum">Behold varsel- og låseskjermkontroller når avspilling er satt på pause.</string>
+ <string name="pref_lockscreen_background_title">Angi som bakgrunn på låseskjermen</string>
+ <string name="pref_lockscreen_background_sum">Angir låseskjermbakgrunnsbildet til å være den nåværende episodens bilde. Som en sideeffekt vil dette også vise bildet i tredjepartsapper.</string>
+ <string name="pref_showDownloadReport_title">Vis nedlastingsrapport</string>
+ <string name="pref_showDownloadReport_sum">Generer en rapport som viser detaljer dersom nedlastinger feiler.</string>
+ <string name="pref_expand_notify_unsupport_toast">Android-versjoner tidligere enn 4.1 støtter ikke utvidede varsler.</string>
+ <string name="pref_queueAddToFront_sum">Legg til nye episoder i begynnelsen av køen.</string>
+ <string name="pref_queueAddToFront_title">Legg til foran i køen</string>
+ <string name="pref_smart_mark_as_played_disabled">Deaktivert</string>
+ <string name="pref_image_cache_size_title">Størrelse for bildemellomlager</string>
+ <string name="pref_image_cache_size_sum">Størrelsen på mellomlageret for bilder.</string>
+ <string name="crash_report_title">Kræsj-rapport</string>
+ <string name="crash_report_sum">Send den siste kræsj-rapporten via e-post</string>
+ <string name="send_email">Send e-post</string>
+ <string name="experimental_pref">Eksperimentell</string>
+ <string name="pref_sonic_title">Sonic medieavspiller</string>
+ <!--Auto-Flattr dialog-->
+ <string name="auto_flattr_enable">Aktiver automatisk flattring</string>
+ <string name="auto_flattr_after_percent">Flattre episode så snart %d prosent er avspilt</string>
+ <string name="auto_flattr_ater_beginning">Flattre episode når avspillingen starter</string>
+ <string name="auto_flattr_ater_end">Flattre episode når avspillingen tar slutt</string>
+ <!--Search-->
+ <string name="search_hint">Søk etter strømmer eller episoder</string>
+ <string name="found_in_shownotes_label">Funnet i shownotater</string>
+ <string name="found_in_chapters_label">Funnet i kapitler</string>
+ <string name="search_status_no_results">Ingen resultater ble funnet</string>
+ <string name="search_label">Søk</string>
+ <string name="found_in_title_label">Funnet i tittel</string>
+ <!--OPML import and export-->
+ <string name="opml_import_txtv_button_lable">OPML-filer lar deg flytte podcastene dine fra en podcatcher til en annen.</string>
+ <string name="opml_import_option">Valg %1$d</string>
+ <string name="opml_import_explanation_1">Velg en spesifikk filbane fra det lokale filsystemet.</string>
+ <string name="opml_import_explanation_2">Bruk en ektern applikasjon som Dropbox, Google Drive eller favoritt-filbehandleren din for å åpne en OPML-fil.</string>
+ <string name="opml_import_explanation_3">Mange applikasjoner som Gmail, Dropbox, Google Drive og de fleste filbehandlere kan <i>åpne</i> OPML-filer <i>med</i> AntennaPod.</string>
+ <string name="start_import_label">Start importering</string>
+ <string name="opml_import_label">OPML-import</string>
+ <string name="opml_directory_error">ERROR!</string>
+ <string name="reading_opml_label">Leser OPML-fil</string>
+ <string name="opml_reader_error">En feil oppsto under lesingen av opml dokumentet:</string>
+ <string name="opml_import_error_dir_empty">Importkatalogen er tom.</string>
+ <string name="select_all_label">Velg alle</string>
+ <string name="deselect_all_label">Opphev alle markeringene</string>
+ <string name="choose_file_from_filesystem">Fra lokalt filsystem</string>
+ <string name="choose_file_from_external_application">Bruk ekstern applikasjon</string>
+ <string name="opml_export_label">OPML-eksportering</string>
+ <string name="export_error_label">Eksporteringserror</string>
+ <string name="opml_export_success_title">OPML-import vellykket.</string>
+ <string name="opml_export_success_sum">.opml-filen ble skrevet til:\u0020</string>
+ <!--Sleep timer-->
+ <string name="set_sleeptimer_label">Sett opp sovetimer</string>
+ <string name="disable_sleeptimer_label">Deaktiver sovetimer</string>
+ <string name="enter_time_here_label">Legg til tid</string>
+ <string name="sleep_timer_label">Sovetimer</string>
+ <string name="time_left_label">Tid igjen:\u0020</string>
+ <string name="time_dialog_invalid_input">Ugyldig innspill, tid må være et heltall</string>
+ <string name="timer_about_to_expire_label"><b>Når nedtellingen er i ferd med å utløpe:</b></string>
+ <string name="shake_to_reset_label">Rist for å tilbakestille nedtellingen</string>
+ <string name="timer_vibration_label">Vibrer</string>
+ <string name="time_seconds">sekunder</string>
+ <string name="time_minutes">minutter</string>
+ <string name="time_hours">timer</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 sekund</item>
+ <item quantity="other">%d sekunder</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minutt</item>
+ <item quantity="other">%d minutter</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 time</item>
+ <item quantity="other">%d timer</item>
+ </plurals>
+ <!--gpodder.net-->
+ <string name="gpodnet_taglist_header">KATEGORIER</string>
+ <string name="gpodnet_toplist_header">TOPP-PODCASTER</string>
+ <string name="gpodnet_suggestions_header">FORSLAG</string>
+ <string name="gpodnet_search_hint">Søk på gpodder.net</string>
+ <string name="gpodnetauth_login_title">Logg inn</string>
+ <string name="gpodnetauth_login_descr">Velkommen til gpodder.net innlogginsprosess. Først begynner vi med å skrive inn innlogginsinformasjon.</string>
+ <string name="gpodnetauth_login_butLabel">Logg inn</string>
+ <string name="gpodnetauth_login_register">Dersom du ikke har en konto enda kan du opprette en her:\nhttps://gpodder.net/register/</string>
+ <string name="username_label">Brukernavn</string>
+ <string name="password_label">Passord</string>
+ <string name="gpodnetauth_device_title">Enhetsvalg</string>
+ <string name="gpodnetauth_device_descr">Lag en ny enhet til å bruke for din gpodder.net konto eller velg en som allerede eksisterer.</string>
+ <string name="gpodnetauth_device_deviceID">EnhetsID:\u0020</string>
+ <string name="gpodnetauth_device_caption">Tekst</string>
+ <string name="gpodnetauth_device_butCreateNewDevice">Lag en ny enhet</string>
+ <string name="gpodnetauth_device_chooseExistingDevice">Velg eksisterende enhet:</string>
+ <string name="gpodnetauth_device_errorEmpty">Device ID kan ikke være tom</string>
+ <string name="gpodnetauth_device_errorAlreadyUsed">EnhetsID er allerede i bruk</string>
+ <string name="gpodnetauth_device_butChoose">Velg</string>
+ <string name="gpodnetauth_finish_title">Innlogging lyktes!</string>
+ <string name="gpodnetauth_finish_descr">Gratulerer! Din gpodder.net konto er nå linket opp med din enhet. AntennaPod vil nå automatisk synkronisere abonnementer på din enhet med din gpodder.net konto.</string>
+ <string name="gpodnetauth_finish_butsyncnow">Start synkronisering nå.</string>
+ <string name="gpodnetauth_finish_butgomainscreen">Gå til hovedskjermen</string>
+ <string name="gpodnetsync_auth_error_title">gpodder.net-autentiseringsfeil</string>
+ <string name="gpodnetsync_auth_error_descr">Feil brukernavn eller passord.</string>
+ <string name="gpodnetsync_error_title">gpodder.net synkroniseringserror</string>
+ <string name="gpodnetsync_error_descr">En feil oppsto under synkronisering av:\u0020</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Valgt mappe</string>
+ <string name="create_folder_label">Lag mappe</string>
+ <string name="choose_data_directory">Velg datamappe</string>
+ <string name="choose_data_directory_message">Vennligst velg basisen for datamappen din. AntennaPod vil lage de nødvendige submappene dine.</string>
+ <string name="create_folder_msg">Lag en ny mappe med navn \"%1$s\"?</string>
+ <string name="create_folder_success">Lagde en ny mappe</string>
+ <string name="create_folder_error_no_write_access">Kan ikke skrive til denne mappen</string>
+ <string name="create_folder_error_already_exists">Mappe eksisterer allerede</string>
+ <string name="create_folder_error">Kunne ikke lage mappe</string>
+ <string name="folder_does_not_exist_error">%1$s eksisterer ikke</string>
+ <string name="folder_not_readable_error">%1$s kan ikke leses</string>
+ <string name="folder_not_writable_error">%1$s kan ikke skrives til</string>
+ <string name="folder_not_empty_dialog_title">Mappen er ikke tom</string>
+ <string name="folder_not_empty_dialog_msg">Mappen du har valgt er ikke tom. Nedlastet media og andre filer vil bli plassert direkte i denne mappen? Vil du fortsette?</string>
+ <string name="set_to_default_folder">Velg standardmappe</string>
+ <string name="pref_pausePlaybackForFocusLoss_sum">Sett pause på playback istedenfor å skru ned volumet når en annen app har lyst å spille av lyder</string>
+ <string name="pref_pausePlaybackForFocusLoss_title">Pause for avbrytelser</string>
+ <string name="pref_resumeAfterCall_sum">Gjenoppta avspilling etter at telefonsamtaler avsluttes</string>
+ <string name="pref_resumeAfterCall_title">Gjenoppta etter samtale</string>
+ <string name="pref_restart_required">AntennaPod må startes om for at denne endringen skal lagres.</string>
+ <!--Online feed view-->
+ <string name="subscribe_label">Abonner</string>
+ <string name="subscribed_label">Abonnert</string>
+ <!--Content descriptions for image buttons-->
+ <string name="show_chapters_label">Vis kapitler</string>
+ <string name="show_shownotes_label">Vis notater</string>
+ <string name="show_cover_label">Vis bilde</string>
+ <string name="rewind_label">Spol tilbake</string>
+ <string name="fast_forward_label">Spol fremover</string>
+ <string name="media_type_audio_label">Lyd</string>
+ <string name="media_type_video_label">Video</string>
+ <string name="navigate_upwards_label">Naviger oppover</string>
+ <string name="butAction_label">Flere handlinger</string>
+ <string name="status_playing_label">Episode spilles nå</string>
+ <string name="status_downloading_label">Episode lastes ned nå</string>
+ <string name="status_downloaded_label">Episode har blitt lastet ned</string>
+ <string name="status_unread_label">Objekt er nytt</string>
+ <string name="in_queue_label">Episoden er i queue</string>
+ <string name="new_episodes_count_label">Antall nye episoder</string>
+ <string name="in_progress_episodes_count_label">Antall episoder du har begynt å høre på</string>
+ <string name="drag_handle_content_description">Dra for å endre posisjonen til dette objektet</string>
+ <string name="load_next_page_label">Last inn neste side</string>
+ <!--Feed information screen-->
+ <string name="authentication_label">Autentisering</string>
+ <string name="authentication_descr">Endre brukernavnet og passordet for denne podcasten og dens episoder.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Oppgraderer databasen</string>
+ <!--AntennaPodSP-->
+ <string name="sp_apps_importing_feeds_msg">Importerer abbonementer fra enkeltstående applikasjoner ...</string>
+ <string name="search_itunes_label">Søk på iTunes</string>
+ <string name="all_label">Alle</string>
+ <string name="selected_all_label">Valgte alle episoder</string>
+ <string name="none_label">Ingen</string>
+ <string name="deselected_all_label">Fjernet valg av alle episoder</string>
+ <string name="played_label">Avspilt</string>
+ <string name="selected_played_label">Valgte avspilte episoder</string>
+ <string name="unplayed_label">Uavspilt</string>
+ <string name="selected_unplayed_label">Valgte uavspilte episoder</string>
+ <string name="downloaded_label">Nedlastede</string>
+ <string name="selected_downloaded_label">Valgte nedlastede episoder</string>
+ <string name="not_downloaded_label">Ikke nedlastet</string>
+ <string name="selected_not_downloaded_label">Valgte ikke-nedlastede episoder</string>
+ <string name="sort_title_a_z">Tittel (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Tittel (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Dato (Ny \u2192 Gammel)</string>
+ <string name="sort_date_old_new">Dato (Gammel \u2192 Ny)</string>
+ <string name="sort_duration_short_long">Lengde (Kort \u2192 Lang)</string>
+ <string name="sort_duration_long_short">Lengde (Lang \u2192 Kort)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Liker du AntennaPod?</string>
+ <string name="rating_message">Vi ville satt pris på om du tok deg tid til å vurdere AntennaPod.</string>
+ <string name="rating_never_label">La meg være i fred</string>
+ <string name="rating_later_label">Minn meg på dette senere</string>
+ <string name="rating_now_label">Naturligvis, kom igjen!</string>
+ <!--Audio controls-->
+</resources>
diff --git a/core/src/main/res/values-nl/strings.xml b/core/src/main/res/values-nl/strings.xml
index 0f447d54a..5cbc40d78 100644
--- a/core/src/main/res/values-nl/strings.xml
+++ b/core/src/main/res/values-nl/strings.xml
@@ -3,31 +3,58 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Feeds</string>
+ <string name="add_feed_label">Podcast toevoegen</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">AFLEVERINGEN</string>
+ <string name="episodes_label">Afleveringen</string>
+ <string name="new_episodes_label">Nieuwe afleveringen</string>
+ <string name="all_episodes_label">Alle afleveringen</string>
+ <string name="all_episodes_short_label">Alle</string>
+ <string name="favorite_episodes_label">Favorieten</string>
<string name="new_label">Nieuw</string>
<string name="waiting_list_label">Wachtlijst</string>
<string name="settings_label">Instellingen</string>
<string name="add_new_feed_label">Podcast toevoegen</string>
<string name="downloads_label">Downloads</string>
+ <string name="downloads_running_label">Bezig</string>
+ <string name="downloads_completed_label">Voltooid</string>
+ <string name="downloads_log_label">Geschiedenis</string>
<string name="cancel_download_label">Annuleer download</string>
<string name="playback_history_label">Afspeelgeschiedenis</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net login</string>
+ <string name="free_space_label">%1$s beschikbaar</string>
+ <string name="episode_cache_full_title">Afleveringen cache is vol</string>
+ <string name="episode_cache_full_message">Het maximum aantal gecachte afleveringen is bereikt. U kunt het maximum verhogen in de instellingen.</string>
<!--New episodes fragment-->
+ <string name="recently_published_episodes_label">Recent gepubliceerd</string>
+ <string name="episode_filter_label">Alleen nieuwe afleveringen weergeven</string>
<!--Main activity-->
+ <string name="drawer_open">Menu openen</string>
+ <string name="drawer_close">Menu sluiten</string>
+ <string name="drawer_preferences">Menu voorkeuren</string>
+ <string name="drawer_feed_order_unplayed_episodes">Op aantal afleveringen</string>
+ <string name="drawer_feed_order_alphabetical">Op alfabetische volgorde</string>
+ <string name="drawer_feed_order_last_update">Op datum van feed-update</string>
+ <string name="drawer_feed_counter_new_unplayed">Aantal nieuwe en onbeluisterde afleveringen</string>
+ <string name="drawer_feed_counter_new">Aantal nieuwe afleveringen</string>
+ <string name="drawer_feed_counter_unplayed">Aantal onbeluisterde afleveringen</string>
+ <string name="drawer_feed_counter_none">Geen (alfabetisch sorteren)</string>
<!--Webview actions-->
<string name="open_in_browser_label">In de browser openen</string>
- <string name="copy_url_label">URL kopieren</string>
+ <string name="copy_url_label">URL kopiëren</string>
<string name="share_url_label">URL delen</string>
<string name="copied_url_msg">URL naar klembord gekopieerd.</string>
+ <string name="go_to_position_label">Ga naar specifiek tijdstip</string>
<!--Playback history-->
<string name="clear_history_label">Geschiedenis wissen</string>
<!--Other-->
<string name="confirm_label">Bevestig</string>
- <string name="cancel_label">Annuleer</string>
+ <string name="cancel_label">Annuleren</string>
+ <string name="yes">Ja</string>
+ <string name="no">Nee</string>
<string name="author_label">Auteur</string>
<string name="language_label">Taal</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Instellingen</string>
<string name="cover_label">Beeld</string>
<string name="error_label">Fout</string>
@@ -42,37 +69,91 @@
<string name="length_prefix">Lengte:\u0020</string>
<string name="size_prefix">Grootte:\u0020</string>
<string name="processing_label">Aan het verwerken</string>
- <string name="loading_label">Laden...</string>
+ <string name="loading_label">Laden…</string>
<string name="save_username_password_label">Gebruikersnaam en wachtwoord opslaan</string>
<string name="close_label">Sluiten</string>
<string name="retry_label">Opnieuw proberen</string>
- <string name="auto_download_label">Voor het automatisch downloaden beschouwen</string>
+ <string name="auto_download_label">Meenemen bij automatisch downloaden</string>
+ <string name="auto_download_apply_to_items_title">Toepassen op bestaande afleveringen</string>
+ <string name="auto_download_apply_to_items_message">De instelling voor <i>Automatisch downloaden</i> wordt automatisch toegepast op toekomstige afleveringen.\nWilt u het ook toepassen op eerder gepubliceerde afleveringen?</string>
+ <string name="auto_delete_label">Automatisch aflevering verwijderen</string>
+ <string name="parallel_downloads_suffix">\u0020parallele downloads</string>
+ <string name="feed_auto_download_global">Standaardinstelling</string>
+ <string name="feed_auto_download_always">Altijd</string>
+ <string name="feed_auto_download_never">Nooit</string>
+ <string name="send_label">Versturen…</string>
+ <string name="episode_cleanup_never">Nooit</string>
+ <string name="episode_cleanup_queue_removal">Wanneer niet in de wachtrij</string>
+ <string name="episode_cleanup_after_listening">Wanneer aflevering volledig is afgespeeld</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dag nadat de aflevering als afgespeeld is gemarkeerd</item>
+ <item quantity="other">%d dagen nadat de aflevering als afgespeeld is gemarkeerd</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Feed URL</string>
- <string name="txtvfeedurl_label">Podcast toevoegen bij URL</string>
+ <string name="etxtFeedurlHint">www.voorbeeld.nl/feed</string>
+ <string name="txtvfeedurl_label">Podcast toevoegen via URL</string>
+ <string name="podcastdirectories_label">Podcast vinden in de gids</string>
+ <string name="podcastdirectories_descr">U kunt nieuwe podcasts zoeken op naam, categorie of populariteit in de gpodder.net database, of de iTunes winkel doorzoeken.</string>
+ <string name="browse_gpoddernet_label">gpodder.net doorbladeren</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Alles als gelezen markeren</string>
+ <string name="mark_all_read_label">Alles als beluisterd markeren</string>
+ <string name="mark_all_read_msg">Alle afleveringen als afgespeeld markeren</string>
+ <string name="mark_all_read_confirmation_msg">Bevestig aub dat u alle afleveringen als afgespeeld wilt markeren.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Bevestig aub dat u alle afleveringen van deze feed als afgespeeld wilt markeren.</string>
+ <string name="mark_all_seen_label">\'Nieuw\' label van alle afleveringen verwijderen.</string>
<string name="show_info_label">Toon informatie</string>
- <string name="share_link_label">Website link delen</string>
- <string name="share_source_label">Feed link delen</string>
+ <string name="remove_feed_label">Podcast verwijderen</string>
+ <string name="share_label">Delen…</string>
+ <string name="share_link_label">Link van de aflevering delen</string>
+ <string name="share_link_with_position_label">Link van de aflevering met tijdstip delen</string>
+ <string name="share_feed_url_label">URL van de feed delen</string>
+ <string name="share_item_url_label">URL van het mediabestand delen</string>
+ <string name="share_item_url_with_position_label">URL van mediabestand met tijdstip delen</string>
<string name="feed_delete_confirmation_msg">Bevestig dat u deze feed en ALLE afleveringen van deze feed die u hebt gedownload wilt verwijderen.</string>
<string name="feed_remover_msg">Feed verwijderen</string>
+ <string name="load_complete_feed">Hele feed vernieuwen</string>
+ <string name="hide_episodes_title">Afleveringen verbergen</string>
+ <string name="episode_actions">Afleveringen beheren</string>
+ <string name="hide_unplayed_episodes_label">Niet afgespeeld</string>
+ <string name="hide_paused_episodes_label">Gepauzeerd</string>
+ <string name="hide_played_episodes_label">Afgespeeld</string>
+ <string name="hide_queued_episodes_label">In de wachtrij</string>
+ <string name="hide_not_queued_episodes_label">Niet in de wachtrij</string>
+ <string name="hide_downloaded_episodes_label">Gedownload</string>
+ <string name="hide_not_downloaded_episodes_label">Niet gedownload</string>
+ <string name="filtered_label">Gefilterd</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Laatste vernieuwing mislukt</string>
<!--actions on feeditems-->
<string name="download_label">Download</string>
<string name="play_label">Spelen</string>
<string name="pause_label">Pauze</string>
+ <string name="stop_label">Stop</string>
<string name="stream_label">Stream</string>
<string name="remove_label">Verwijderen</string>
- <string name="mark_read_label">Als gelezen markeren</string>
- <string name="mark_unread_label">Als ongelezen markeren</string>
+ <string name="remove_episode_lable">Aflevering(en) verwijderen</string>
+ <string name="mark_read_label">Als afgespeeld markeren</string>
+ <string name="marked_as_read_label">Als afgespeeld gemarkeerd</string>
+ <string name="mark_unread_label">Als niet afgespeeld markeren</string>
<string name="add_to_queue_label">Voeg toe aan wachtrij</string>
+ <string name="added_to_queue_label">Aan wachtrij toegevoegd</string>
<string name="remove_from_queue_label">Verwijder van wachtrij</string>
+ <string name="add_to_favorite_label">Toevoegen aan favorieten</string>
+ <string name="added_to_favorites">Aan favorieten toegevoegd</string>
+ <string name="remove_from_favorite_label">Verwijderen uit favorieten</string>
+ <string name="removed_from_favorites">Verwijderd uit favorieten</string>
<string name="visit_website_label">Website bezoeken</string>
<string name="support_label">Flattr dit</string>
- <string name="enqueue_all_new">Alle in wachtrij plaatsen</string>
+ <string name="enqueue_all_new">Alle aan wachtrij toevoegen</string>
<string name="download_all">Alles downloaden</string>
<string name="skip_episode_label">Aflevering overslaan</string>
+ <string name="activate_auto_download">Automatisch downloaden activeren</string>
+ <string name="deactivate_auto_download">Automatisch downloaden deactiveren</string>
+ <string name="reset_position">Afspeelpositie resetten</string>
+ <string name="removed_item">Aflevering verwijderd</string>
<!--Download messages and labels-->
+ <string name="download_successful">succesvol</string>
+ <string name="download_failed">mislukt</string>
<string name="download_pending">Download in afwachting</string>
<string name="download_running">Aan het downloaden</string>
<string name="download_error_device_not_found">Opslagmedium niet gevonden</string>
@@ -87,12 +168,18 @@
<string name="download_error_unauthorized">Authenticatie fout</string>
<string name="cancel_all_downloads_label">Alle downloads annuleren</string>
<string name="download_canceled_msg">Download geannuleerd</string>
- <string name="download_report_title">Downloads afgerond</string>
+ <string name="download_canceled_autodownload_enabled_msg">Downloaden geannuleerd\n<i>Automatisch downloaden</i> uitgezet voor deze aflevering</string>
+ <string name="download_report_title">Downloads afgerond met fouten</string>
+ <string name="download_report_content_title">Downloadrapport</string>
<string name="download_error_malformed_url">Misvormde URL</string>
<string name="download_error_io_error">IO fout</string>
<string name="download_error_request_error">Fout in de aanvraag</string>
<string name="download_error_db_access">Databasetoegangsfout</string>
- <string name="downloads_left">Nog \u0020 downloads</string>
+ <plurals name="downloads_left">
+ <item quantity="one">Nog %d download</item>
+ <item quantity="other">Nog %d downloads</item>
+ </plurals>
+ <string name="downloads_processing">Downloads verwerken</string>
<string name="download_notification_title">Podcast gegevens aan het downloaden</string>
<string name="download_report_content">%1$d downloads geslaagd, %2$d mislukt</string>
<string name="download_log_title_unknown">Onbekende titel</string>
@@ -102,6 +189,11 @@
<string name="download_request_error_dialog_message_prefix">Er is een fout opgetreden bij het ​​downloaden van bestand:\u0020</string>
<string name="authentication_notification_title">Authenticatie vereist</string>
<string name="authentication_notification_msg">De opgevraagde bron vereist een gebruikersnaam en een wachtwoord</string>
+ <string name="confirm_mobile_download_dialog_title">Bevestig downloaden over mobiel netwerk</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Het downloaden via mobiele verbindingen is uitgezet in de instellingen.\n\nU kunt er voor kiezen om slechts de aflevering aan de wachtrij toe te voegen, of om downloaden via mobiele netwerken tijdelijk toe te staan.\n<small>Uw keuze is 10 minuten geldig.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Het downloaden via mobiele verbindingen is uitgezet in de instellingen.\n\nWilt u dit tijdelijk toestaan?\n<small>Uw keuze is 10 minuten geldig.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Toevoegen aan wachtrij</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Tijdelijk toestaan</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Fout!</string>
<string name="player_stopped_msg">Geen media aan het afspelen</string>
@@ -114,12 +206,24 @@
<string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Buffering</string>
<string name="playbackservice_notification_title">Podcast aan het afspelen</string>
+ <string name="unknown_media_key">AntennaPod - Mediaknop onbekend: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Wachtrij vastzetten</string>
+ <string name="unlock_queue">Wachtrij ontgrendelen</string>
+ <string name="queue_locked">Wachtrij vergrendeld</string>
+ <string name="queue_unlocked">Wachtrij ontgrendeld</string>
<string name="clear_queue_label">Wachtrij leeg maken</string>
<string name="undo">Ongedaan maken</string>
<string name="removed_from_queue">Item verwijderd</string>
<string name="move_to_top_label">Naar boven verplaatsen</string>
<string name="move_to_bottom_label">Naar beneden verplaatsen</string>
+ <string name="sort">Sorteren</string>
+ <string name="alpha">Alfabetisch</string>
+ <string name="date">Datum</string>
+ <string name="duration">Lengte</string>
+ <string name="ascending">Oplopend</string>
+ <string name="descending">Aflopend</string>
+ <string name="clear_queue_confirmation_msg">Bevestig aub dat u alle afleveringen uit de wachtrij wilt verwijderen</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr inloggen</string>
<string name="flattr_auth_explanation">Druk op onderstaande knop om het verificatieproces te starten. U wordt doorgestuurd naar de Flattr inlogscherm in uw browser en wordt gevraagd om toestemming aan AntennaPod te geven om dingen te Flattr\'en. Nadat u toestemming hebt gegeven, keert u automatisch terug naar dit scherm.</string>
@@ -127,6 +231,7 @@
<string name="return_home_label">Terug naar de startscherm</string>
<string name="flattr_auth_success">Authenticatie is geslaagd! U kunt nu dingen vanuit de app Flattr\'en.</string>
<string name="no_flattr_token_title">Geen Flattr token gevonden</string>
+ <string name="no_flattr_token_notification_msg">Het lijkt er op dat uw Flattr account niet is verbonden met AntennaPod. Klik hier om te authenticeren.</string>
<string name="no_flattr_token_msg">Uw Flattr account lijkt niet aangesloten te zijn op AntennaPod. U kunt uw account aan AntennaPod sluiten om dingen vanuit de app te Flattr\'en, of u kunt op de website van het ding terecht om het daar te Flattr\'en.</string>
<string name="authenticate_now_label">Authenticeren</string>
<string name="action_forbidden_title">Actie verboden</string>
@@ -147,27 +252,50 @@
<string name="flattr_retrieving_status">Geflattr\'de dingen aan het ontvangen</string>
<!--Variable Speed-->
<string name="download_plugin_label">Plugin downloaden</string>
- <string name="no_playback_plugin_title">Plugin niet geinstalleerd</string>
+ <string name="no_playback_plugin_title">Plugin niet geïnstalleerd</string>
+ <string name="no_playback_plugin_or_sonic_msg">Om variabele afspeelsnelheid te kunnen gebruiken, raden wij u aan om de ingebouwde Sonic mediaspeler te activeren [Android 4.1+]\n\nEventueel kunt u de externe plugin <i>Prestissimo</i> downloaden van de Play Store.\nProblemen bij het gebruik van deze plugin zijn niet de verantwoordelijkheid van AntennaPod en moeten gemeld worden bij de ontwikkelaar van de plugin.</string>
<string name="set_playback_speed_label">Afspeelsnelheden</string>
+ <string name="enable_sonic">Sonic instellen</string>
<!--Empty list labels-->
<string name="no_items_label">Er zijn geen items in deze lijst.</string>
<string name="no_feeds_label">U bent nog tot geen enkele feed geabonneerd.</string>
+ <string name="no_chapters_label">Deze aflevering heeft geen hoofdstukken.</string>
<!--Preferences-->
<string name="other_pref">Overig</string>
<string name="about_pref">Over AntennaPod</string>
<string name="queue_label">Wachtrij</string>
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Afspelen pauzeren wanneer de hoofdtelefoon wordt losgekoppeld</string>
- <string name="pref_followQueue_sum">Volgende wachtrij item afspelen als de episode voltooid is</string>
+ <string name="pref_episode_cleanup_title">Automatisch opschonen</string>
+ <string name="pref_episode_cleanup_summary">Afleveringen die niet in de wachtrij staan en die niet als favoriet zijn gemarkeerd, mogen verwijderd worden als Automatisch Downloaden ruimte nodig heeft voor nieuwe afleveringen.</string>
+ <string name="pref_pauseOnDisconnect_sum">Afspelen pauzeren wanneer de koptelefoon wordt losgekoppeld of de bluetooth verbinding wordt verbroken</string>
+ <string name="pref_unpauseOnHeadsetReconnect_sum">Afspelen hervatten wanneer de koptelefoon opnieuw wordt aangesloten</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Afspelen hervatten wanneer de bluetooth verbinding hervat wordt</string>
+ <string name="pref_hardwareForwardButtonSkips_title">\'Volgende\' knop voor overslaan</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Aflevering overslaan ipv vooruitspoelen wanneer op een fysieke \'volgende\' knop wordt gedrukt</string>
+ <string name="pref_followQueue_sum">Volgende item in de wachtrij afspelen als de aflevering voltooid is</string>
+ <string name="pref_auto_delete_sum">Afleveringen verwijderen als ze zijn afgespeeld</string>
+ <string name="pref_auto_delete_title">Automatisch verwijderen</string>
+ <string name="pref_smart_mark_as_played_sum">Als afgespeeld markeren wanneer minder dan een bepaald aantal seconden van de afspeeltijd over is</string>
+ <string name="pref_smart_mark_as_played_title">Slimme afgespeeld markering</string>
+ <string name="pref_skip_keeps_episodes_sum">Afleveringen bewaren en in de wachtrij houden als u op \'overslaan\' klikt</string>
+ <string name="pref_skip_keeps_episodes_title">Overgeslagen afleveringen bewaren</string>
<string name="playback_pref">Afspelen</string>
<string name="network_pref">Netwerk</string>
- <string name="pref_autoUpdateIntervall_title">Update interval</string>
- <string name="pref_autoUpdateIntervall_sum">Voer een tijdsinterval in waarin de feeds automatisch worden vernieuwd, of schakel het uit</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Feed update interval of tijdstip</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Specificeer een interval of tijdstip waarop feeds automatisch vernieuwd moeten worden</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">U kunt een <i>interval</i> zoals \'elke twee uur\' of een specifiek <i>tijdstip</i> zoals \'07:00 uur\' instellen, of het automatisch verversen van feeds uitzetten.\n\n<small>Nota bene: tijdstippen voor updates zijn niet precies. Er kan een kleine vertraging plaatsvinden.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Uitschakelen</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Interval instellen</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Tijdstip instellen</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">elke %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">op %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Download mediabestanden alleen via WiFi</string>
<string name="pref_followQueue_title">Continu afspelen</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi download van media</string>
- <string name="pref_pauseOnHeadsetDisconnect_title">Loskoppeling van de hoofdtelefoon</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Pauzeren bij loskoppeling</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">Aansluiten koptelefoon</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Verbinden met bluetooth</string>
<string name="pref_mobileUpdate_title">Mobiele updates</string>
<string name="pref_mobileUpdate_sum">Updates toestaan ​​via de mobiele dataverbinding</string>
<string name="refreshing_label">Aan het verversen</string>
@@ -179,18 +307,30 @@
<string name="pref_revokeAccess_title">Toegang intrekken</string>
<string name="pref_revokeAccess_sum">Trek de toegang van deze app in tot je Flattr account.</string>
<string name="pref_auto_flattr_title">Automatische Flattr</string>
+ <string name="pref_auto_flattr_sum">Automatisch flattr\'en instellen</string>
<string name="user_interface_label">User Interface</string>
- <string name="pref_set_theme_title">Kies theme</string>
+ <string name="pref_set_theme_title">Kies kleurschema</string>
+ <string name="pref_nav_drawer_title">Menu aanpassen</string>
+ <string name="pref_nav_drawer_sum">Het uiterlijk en andere instellingen van het menu aanpassen.</string>
+ <string name="pref_nav_drawer_items_title">Menu-items instellen</string>
+ <string name="pref_nav_drawer_items_sum">Aanpassen welke items in het menu worden getoond</string>
+ <string name="pref_nav_drawer_feed_order_title">Feed volgorde instellen</string>
+ <string name="pref_nav_drawer_feed_order_sum">De volgorde van uw feeds instellen</string>
+ <string name="pref_nav_drawer_feed_counter_title">Teller instellen</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Pas aan welke aantallen in het menu worden getoond</string>
<string name="pref_set_theme_sum">Verander het uiterlijk van AntennaPod.</string>
<string name="pref_automatic_download_title">Automatisch downloaden</string>
<string name="pref_automatic_download_sum">Configureer het automatisch downloaden van afleveringen.</string>
<string name="pref_autodl_wifi_filter_title">Wi-Fi filter inschakelen</string>
<string name="pref_autodl_wifi_filter_sum">Automatisch downloaden alleen toestaan voor geselecteerde Wi-Fi-netwerken.</string>
+ <string name="pref_automatic_download_on_battery_title">Downloaden zonder opladen</string>
+ <string name="pref_automatic_download_on_battery_sum">Downloaden toestaan als het apparaat niet wordt opgeladen</string>
+ <string name="pref_parallel_downloads_title">Gelijktijdige downloads</string>
<string name="pref_episode_cache_title">Afleveringen cache</string>
<string name="pref_theme_title_light">Licht</string>
<string name="pref_theme_title_dark">Donker</string>
<string name="pref_episode_cache_unlimited">Onbeperkt</string>
- <string name="pref_update_interval_hours_plural">uren</string>
+ <string name="pref_update_interval_hours_plural">uur</string>
<string name="pref_update_interval_hours_singular">uur</string>
<string name="pref_update_interval_hours_manual">Handmatig</string>
<string name="pref_gpodnet_authenticate_title">Log in</string>
@@ -201,9 +341,36 @@
<string name="pref_gpodnet_setlogin_information_sum">Wijzig de aanmeldingsgegevens van je gpodder.net account.</string>
<string name="pref_playback_speed_title">Afspeelsnelheden</string>
<string name="pref_playback_speed_sum">Pas de beschikbare snelheden aan voor de variabele audio afspeelsnelheid</string>
+ <string name="pref_fast_forward">Snelheid van vooruitspoelen</string>
+ <string name="pref_rewind">Snelheid terugspoelen</string>
<string name="pref_gpodnet_sethostname_title">Definieer hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Gebruik standaard host</string>
+ <string name="pref_expandNotify_title">Bedienen via melding</string>
+ <string name="pref_expandNotify_sum">Altijd de bedieningsknoppen tonen bij de melding in het notificatiecentrum.</string>
+ <string name="pref_persistNotify_title">Behoud bedieningsknoppen</string>
+ <string name="pref_persistNotify_sum">Ook bedieningsknoppen in het notificatiecentrum en op het vergrendelingsscherm behouden als het afspelen gepauzeerd wordt.</string>
+ <string name="pref_lockscreen_background_title">Achtergorndafbeelding vergrendelingsscherm</string>
+ <string name="pref_lockscreen_background_sum">Laat de afbeelding van de huidige aflevering zien. Ten gevolge van deze functie is de aflevering ook beschikbaar voor andere apps.</string>
+ <string name="pref_showDownloadReport_title">Laat downloadrapport zien</string>
+ <string name="pref_showDownloadReport_sum">Genereer een rapport met fout-details als downloads mislukken</string>
+ <string name="pref_expand_notify_unsupport_toast">Android versies lager dan 4.1 ondersteunen geen knoppen in het notificatiecentrum.</string>
+ <string name="pref_queueAddToFront_sum">Nieuwe afleveringen aan het begin van de wachtrij toevoegen.</string>
+ <string name="pref_queueAddToFront_title">Bovenaan wachtrij toevoegen</string>
+ <string name="pref_smart_mark_as_played_disabled">Uitgeschakeld</string>
+ <string name="pref_image_cache_size_title">Grootte cachegeheugen</string>
+ <string name="pref_image_cache_size_sum">De groote van het cachegeheugen voor afbeeldingen aanpassen.</string>
+ <string name="crash_report_title">Crashreport</string>
+ <string name="crash_report_sum">Verstuur laatste crashreport via email</string>
+ <string name="send_email">Verstuur email</string>
+ <string name="experimental_pref">Experimentele functie</string>
+ <string name="pref_sonic_title">Sonic mediaspeler</string>
+ <string name="pref_sonic_message">Gebruik AntennaPod\'s ingebouwde Sonic mediaspeler als een alternatief voor Prestissimo en de mediaspeler van Android.</string>
+ <string name="pref_current_value">Huidige instelling: %1$s</string>
<!--Auto-Flattr dialog-->
+ <string name="auto_flattr_enable">Automatisch flattr\'en aanzetten</string>
+ <string name="auto_flattr_after_percent">Flattr een aflevering zodra %d procent is afgespeeld</string>
+ <string name="auto_flattr_ater_beginning">Flattr de aflevering bij afspelen</string>
+ <string name="auto_flattr_ater_end">Flattr aflevering als afspelen is gestopt</string>
<!--Search-->
<string name="search_hint">Feeds of afleveringen zoeken</string>
<string name="found_in_shownotes_label">Gevonden in de shownotes</string>
@@ -213,17 +380,25 @@
<string name="found_in_title_label">Gevonden in de titel</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Met OPML-bestanden kan je podcasts van de ene naar de andere podcatcher verplaatsen.</string>
+ <string name="opml_import_option">Optie %1$d</string>
+ <string name="opml_import_explanation_1">Kies een bestand van een specifieke locatie via bestandsbeheer.</string>
+ <string name="opml_import_explanation_2">Kies een OPML bestand via een andere app, zoals Dropbox en Google Drive, of via uw bestandsbeheerder.</string>
+ <string name="opml_import_explanation_3">Ga naar een OPML bestand in apps zoals GMail, Dropbox en Google Drive om het met AntennaPod te openen.</string>
<string name="start_import_label">Start importeren</string>
- <string name="opml_import_label">OPML import</string>
+ <string name="opml_import_label">OPML importeren</string>
<string name="opml_directory_error">FOUT!</string>
<string name="reading_opml_label">OPML-bestand aan het lezen</string>
<string name="opml_reader_error">Er is een fout opgetreden bij het lezen van het OPML-bestand:</string>
<string name="opml_import_error_dir_empty">De import map is leeg.</string>
<string name="select_all_label">Selecteer alles</string>
<string name="deselect_all_label">Deselecteer alles</string>
+ <string name="select_options_label">Selecteren…</string>
+ <string name="choose_file_from_filesystem">Via bestandsbeheer</string>
+ <string name="choose_file_from_external_application">Via externe app</string>
<string name="opml_export_label">OPML export</string>
- <string name="exporting_label">Aan het exporteren...</string>
+ <string name="exporting_label">Exporteren…</string>
<string name="export_error_label">Export fout</string>
+ <string name="opml_export_success_title">OPML bestand succesvol geëxporteerd.</string>
<string name="opml_export_success_sum"> Het OPML-bestand is in \u0020 geplaatst</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Slaap timer instellen</string>
@@ -232,6 +407,24 @@
<string name="sleep_timer_label">Slaap timer</string>
<string name="time_left_label">Resterende tijd:\u0020</string>
<string name="time_dialog_invalid_input">Ongeldige invoer, de tijd moet een geheel getal zijn</string>
+ <string name="timer_about_to_expire_label"><b>Als de timer bijna afloopt:</b></string>
+ <string name="shake_to_reset_label">Schudden om opnieuw in te stellen</string>
+ <string name="timer_vibration_label">Trillen</string>
+ <string name="time_seconds">seconden</string>
+ <string name="time_minutes">minuten</string>
+ <string name="time_hours">uur</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 seconde</item>
+ <item quantity="other">%d seconden</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minuut</item>
+ <item quantity="other">%d minuten</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 uur</item>
+ <item quantity="other">%d uur</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORIEËN</string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
@@ -240,12 +433,13 @@
<string name="gpodnetauth_login_title">Log in</string>
<string name="gpodnetauth_login_descr">Welkom op de gpodder.net login proces. Eerst, typ je login gegevens:</string>
<string name="gpodnetauth_login_butLabel">Log in</string>
+ <string name="gpodnetauth_login_register">Als u nog geen account hebt, kunt u er hier een aanmaken:\nhttps://gpodder.net/register/</string>
<string name="username_label">Gebruikersnaam</string>
<string name="password_label">Wachtwoord</string>
<string name="gpodnetauth_device_title">Apparaatselectie</string>
<string name="gpodnetauth_device_descr">Maak een nieuw apparaat aan om voor je gpodder.net account te gebruiken of kies een bestaande:</string>
<string name="gpodnetauth_device_deviceID">Device ID:\u0020</string>
- <string name="gpodnetauth_device_caption">Titel</string>
+ <string name="gpodnetauth_device_caption">Apparaatnaam</string>
<string name="gpodnetauth_device_butCreateNewDevice">Maak een nieuw apparaat aan</string>
<string name="gpodnetauth_device_chooseExistingDevice">Kies een bestaand apparaat:</string>
<string name="gpodnetauth_device_errorEmpty">Apparaat ID mag niet leeg zijn</string>
@@ -263,20 +457,27 @@
<string name="selected_folder_label">Geselecteerde map:</string>
<string name="create_folder_label">Map aanmaken</string>
<string name="choose_data_directory">Kies data map</string>
+ <string name="choose_data_directory_message">Kies de hoofdmap voor uw data. AntennaPod zal de benodigde submappen creeëren.</string>
<string name="create_folder_msg">Maak een nieuwe map aan met de naam \"%1$s\"?</string>
<string name="create_folder_success">Nieuwe map aangemaakt</string>
<string name="create_folder_error_no_write_access">Kan in deze map niet schrijven</string>
<string name="create_folder_error_already_exists">Map bestaat al</string>
<string name="create_folder_error">Kon map niet aanmaken</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" bestaat niet</string>
+ <string name="folder_not_readable_error">\"%1$s\" kan niet gelezen worden</string>
+ <string name="folder_not_writable_error">in \"%1$s\" kan geen data geschreven worden</string>
<string name="folder_not_empty_dialog_title">Map is niet leeg</string>
<string name="folder_not_empty_dialog_msg">De map die je hebt gekozen is niet leeg. Media downloads en andere bestanden zullen rechtstreeks in deze map geplaatst worden. Toch doorgaan?</string>
<string name="set_to_default_folder">Kies default map</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Het afspelen onderbreken in plaats van het volume te verlagen wanneer er een andere app geluiden af wilt spelen</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pauze voor onderbrekingen</string>
+ <string name="pref_resumeAfterCall_sum">Afspelen hervatten na beëindigen telefoongesprek</string>
+ <string name="pref_resumeAfterCall_title">Hervatten na gesprek</string>
+ <string name="pref_restart_required">AntennaPod moet opnieuw worden opgestart om deze wijziging door te voeren.</string>
<!--Online feed view-->
<string name="subscribe_label">Abonneren</string>
<string name="subscribed_label">Geabonneerd</string>
- <string name="downloading_label">Aan het downloaden</string>
+ <string name="downloading_label">Downloaden…</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Hoofdstukken tonen</string>
<string name="show_shownotes_label">Shownotes tonen</string>
@@ -294,7 +495,50 @@
<string name="in_queue_label">Aflevering is in de queue</string>
<string name="new_episodes_count_label">Aantal nieuwe afleveringen</string>
<string name="in_progress_episodes_count_label">Aantal afleveringen dat begonnen te luisteren zijn</string>
+ <string name="drag_handle_content_description">Item verslepen om het te verplaatsen</string>
+ <string name="load_next_page_label">Volgende pagina laden</string>
<!--Feed information screen-->
+ <string name="authentication_label">Authenticatie</string>
+ <string name="authentication_descr">Gebruikersnaam en wachtwoord aanpassen voor deze podcast</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Database upgraden</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Abonnementen aan het importeren vanuit single-purpose apps...</string>
+ <string name="search_itunes_label">Zoeken in iTunes</string>
+ <string name="select_label"><b>Selecteren…</b></string>
+ <string name="filter">Filter</string>
+ <string name="all_label">Alle</string>
+ <string name="selected_all_label">Alle afleveringen selecteren</string>
+ <string name="none_label">Geen</string>
+ <string name="deselected_all_label">Alle afleveringen deselecteren</string>
+ <string name="played_label">Afgespeeld</string>
+ <string name="selected_played_label">Alle afgespeelde afleveringen selecteren</string>
+ <string name="unplayed_label">Niet afgespeeld</string>
+ <string name="selected_unplayed_label">Niet afgespeelde afleveringen geselecteerd</string>
+ <string name="downloaded_label">Gedownload</string>
+ <string name="selected_downloaded_label">Gedownloadde afleveringen geselecteerd</string>
+ <string name="not_downloaded_label">Niet gedownload</string>
+ <string name="selected_not_downloaded_label">Niet gedownloadde afleveringen geselecteerd</string>
+ <string name="sort_title"><b>Sorteren op…</b></string>
+ <string name="sort_title_a_z">Titel (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Titel (A \u2192 A)</string>
+ <string name="sort_date_new_old">Datum (nieuw \u2192 oud)</string>
+ <string name="sort_date_old_new">Datum (oud \u2192 nieuw)</string>
+ <string name="sort_duration_short_long">Lengte (kort \u2192 lang)</string>
+ <string name="sort_duration_long_short">Lengte (lang \u2192 kort)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Wat vind u van AntennaPod?</string>
+ <string name="rating_message">We zouden het op prijs stellen als u even de tijd kunt nemen om AntennaPod te beoordelen.</string>
+ <string name="rating_never_label">Nee, bedankt.</string>
+ <string name="rating_later_label">Herinner me later</string>
+ <string name="rating_now_label">Ja, doen we!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Audio-instellingen</string>
+ <string name="playback_speed">Afspeelsnelheid</string>
+ <string name="volume">Volume</string>
+ <string name="left_short">L</string>
+ <string name="right_short">R</string>
+ <string name="audio_effects">Terugmixen</string>
+ <string name="stereo_to_mono">Stereo terugmixen tot monogeluid op beide kanalen</string>
+ <string name="sonic_only">Alleen Sonic</string>
</resources>
diff --git a/core/src/main/res/values-pl-rPL/strings.xml b/core/src/main/res/values-pl-rPL/strings.xml
index ba1a0bb91..81d9ebec4 100644
--- a/core/src/main/res/values-pl-rPL/strings.xml
+++ b/core/src/main/res/values-pl-rPL/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Kanały</string>
<string name="add_feed_label">Dodaj podcast</string>
<string name="podcasts_label">PODCASTY</string>
- <string name="episodes_label">ODCINKI</string>
+ <string name="episodes_label">Odcinki</string>
<string name="new_episodes_label">Nowe odcinki</string>
<string name="all_episodes_label">Wszystkie odcinki</string>
+ <string name="all_episodes_short_label">Wszystkie</string>
+ <string name="favorite_episodes_label">Ulubione</string>
<string name="new_label">Nowy</string>
<string name="waiting_list_label">Lista oczekujących</string>
<string name="settings_label">Ustawienia</string>
@@ -26,6 +28,14 @@
<!--Main activity-->
<string name="drawer_open">Otwórz menu</string>
<string name="drawer_close">Zamknij menu</string>
+ <string name="drawer_preferences">Ustawienia panelu</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sortuj wg liczby</string>
+ <string name="drawer_feed_order_alphabetical">Sortuj alfabetycznie</string>
+ <string name="drawer_feed_order_last_update">Sortuj wg daty publikacji</string>
+ <string name="drawer_feed_counter_new_unplayed">Liczba nowych i nieodtworzonych odcinków</string>
+ <string name="drawer_feed_counter_new">Liczba nowych odcinków</string>
+ <string name="drawer_feed_counter_unplayed">Liczba nieodtworzonych odcinków</string>
+ <string name="drawer_feed_counter_none">Brak</string>
<!--Webview actions-->
<string name="open_in_browser_label">Otwórz w przeglądarce</string>
<string name="copy_url_label">Kopiuj adres</string>
@@ -37,6 +47,8 @@
<!--Other-->
<string name="confirm_label">Potwierdź </string>
<string name="cancel_label">Anuluj</string>
+ <string name="yes">Tak</string>
+ <string name="no">Nie</string>
<string name="author_label">Autor</string>
<string name="language_label">Język</string>
<string name="podcast_settings_label">Ustawienia</string>
@@ -53,11 +65,25 @@
<string name="length_prefix">Długość:\u0020</string>
<string name="size_prefix">Rozmiar:\u0020</string>
<string name="processing_label">Przetwarzanie</string>
- <string name="loading_label">Ładowanie...</string>
<string name="save_username_password_label">Zapisz nazwę użytkownika i hasło</string>
<string name="close_label">Zamknij</string>
<string name="retry_label">Spróbuj ponownie</string>
<string name="auto_download_label">Dołącz do automatycznego pobierania</string>
+ <string name="auto_download_apply_to_items_title">Zastosuj do poprzednich odcinków</string>
+ <string name="auto_download_apply_to_items_message">Nowe ustawienie <i>automatycznego pobierania</i> zostanie zastosowane do nowych odcinków.\n Czy chcesz zastosować je także do odcinków opublikowanych wcześniej?</string>
+ <string name="auto_delete_label">Automatyczne usuwanie odcinków\n(nadpisz ustawienia globalne)</string>
+ <string name="parallel_downloads_suffix">\u0020równoległych pobierań</string>
+ <string name="feed_auto_download_global">Globalnie</string>
+ <string name="feed_auto_download_always">Zawsze</string>
+ <string name="feed_auto_download_never">Nigdy</string>
+ <string name="episode_cleanup_never">Nigdy</string>
+ <string name="episode_cleanup_queue_removal">Kiedy nie są w kolejce</string>
+ <string name="episode_cleanup_after_listening">Po odtworzeniu</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dzień po odtworzeniu</item>
+ <item quantity="few">%d dni po odtworzeniu</item>
+ <item quantity="other">%d dni po odtworzeniu</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Adres kanału</string>
<string name="etxtFeedurlHint">Adres URL kanału lub strony internetowej</string>
@@ -66,15 +92,27 @@
<string name="podcastdirectories_descr">Możesz wyszukiwać nowe podcasty ze względu na nazwę, kategorię lub popularność na gpodder.net</string>
<string name="browse_gpoddernet_label">Przeglądaj gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Oznacz wszystkie jako przeczytane</string>
- <string name="mark_all_read_msg">Wszystkie odcinki zaznaczone jako przeczytane</string>
+ <string name="mark_all_read_label">Oznacz wszystkie jako odtworzone</string>
+ <string name="mark_all_read_msg">Wszystkie odcinki zaznaczono jako odtworzone</string>
+ <string name="mark_all_seen_label">Oznacz wszystkie jako widziane</string>
<string name="show_info_label">Pokaż informacje</string>
<string name="remove_feed_label">Usuń podcast</string>
<string name="share_link_label">Udostępnij stronę</string>
- <string name="share_source_label">Udostępnij kanał</string>
+ <string name="share_link_with_position_label">Udostępnij link z aktualną pozycją</string>
+ <string name="share_feed_url_label">Udostępnij adres kanału</string>
+ <string name="share_item_url_label">Udostępnij adres URL odcinka</string>
+ <string name="share_item_url_with_position_label">Udostępnij adres URL odcinka z aktualną pozycją</string>
<string name="feed_delete_confirmation_msg">Potwierdź chęć usunięcia tego kanału wraz ze WSZYSTKIMI odcinkami, które zostały pobrane.</string>
<string name="feed_remover_msg">Usuwanie kanału</string>
<string name="load_complete_feed">Odśwież cały kanał</string>
+ <string name="hide_episodes_title">Ukryj odcinki</string>
+ <string name="hide_unplayed_episodes_label">Nieodtworzone</string>
+ <string name="hide_paused_episodes_label">Zatrzymane</string>
+ <string name="hide_played_episodes_label">Odtworzone</string>
+ <string name="hide_queued_episodes_label">W kolejce</string>
+ <string name="hide_not_queued_episodes_label">Nie w kolejce</string>
+ <string name="hide_downloaded_episodes_label">Pobrane</string>
+ <string name="hide_not_downloaded_episodes_label">Nie pobrane</string>
<!--actions on feeditems-->
<string name="download_label">Pobierz</string>
<string name="play_label">Odtwórz</string>
@@ -83,15 +121,22 @@
<string name="stream_label">Strumień</string>
<string name="remove_label">Usuń</string>
<string name="remove_episode_lable">Usuń odcinek</string>
- <string name="mark_read_label">Oznacz jako przeczytane</string>
- <string name="mark_unread_label">Oznacz jako nieprzeczytane</string>
+ <string name="mark_read_label">Oznacz jako odtworzone</string>
+ <string name="marked_as_read_label">Oznaczone jako odtworzone</string>
+ <string name="mark_unread_label">Oznacz jako nieodtworzone</string>
<string name="add_to_queue_label">Dodaj do kolejki</string>
+ <string name="added_to_queue_label">Dodano do Kolejki</string>
<string name="remove_from_queue_label">Usuń z kolejki</string>
+ <string name="add_to_favorite_label">Dodaj do Ulubionych</string>
+ <string name="remove_from_favorite_label">Usuń z Ulubionych</string>
<string name="visit_website_label">Odwiedź stronę</string>
<string name="support_label">Wspomóż na Flattr</string>
<string name="enqueue_all_new">Dodaj wszystko do kolejki</string>
<string name="download_all">Pobierz wszystkie</string>
<string name="skip_episode_label">Pomiń odcinek</string>
+ <string name="activate_auto_download">Włącz automatyczne pobieranie</string>
+ <string name="deactivate_auto_download">Wyłącz automatyczne pobieranie</string>
+ <string name="removed_item">Pozycja usunięta</string>
<!--Download messages and labels-->
<string name="download_successful">Operacja zakończona sukcesem</string>
<string name="download_failed">Operacja nie powiodła się</string>
@@ -110,11 +155,11 @@
<string name="cancel_all_downloads_label">Anuluj wszystkie pobierania</string>
<string name="download_canceled_msg">Pobieranie anulowane</string>
<string name="download_report_title">Pobieranie ukończone</string>
+ <string name="download_report_content_title">Raport pobierania</string>
<string name="download_error_malformed_url">Niepoprawny adres</string>
<string name="download_error_io_error">Błąd wejścia/wyjścia</string>
<string name="download_error_request_error">Błąd żądania</string>
<string name="download_error_db_access">Błąd dostępu do bazy danych</string>
- <string name="downloads_left">:\u0020pobrań pozostało</string>
<string name="downloads_processing">Przetwarzanie pobranych</string>
<string name="download_notification_title">Pobieranie danych podcastu</string>
<string name="download_report_content">%1$d pobierania poprawne, %2$d nieudane</string>
@@ -125,6 +170,11 @@
<string name="download_request_error_dialog_message_prefix">Wystąpił błąd przy próbie pobierania:\u0020</string>
<string name="authentication_notification_title">Wymagana autoryzacja</string>
<string name="authentication_notification_msg">Żądany zasób wymaga podania nazwy użytkownika oraz hasła</string>
+ <string name="confirm_mobile_download_dialog_title">Potwierdź pobieranie przez sieć komórkową</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Pobieranie przez sieć komórkową jest wyłączone w ustawieniach.\n\nMożesz dodać odcinek do kolejki lub tymczasowo zezwolić na pobieranie.\n\n<small>Twój wybór zostanie zapamiętany na 10 minut.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Pobieranie przez sieć komórkową jest wyłączone w ustawieniach.\n\nCzy chcesz tymczasowo zezwolić na pobieranie?\n\n<small>Twój wybór zostanie zapamiętany na 10 minut.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Dodaj do kolejki</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Zezwól tymczasowo</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Błąd!</string>
<string name="player_stopped_msg">Żadne media nie odtwarzane </string>
@@ -139,6 +189,8 @@
<string name="playbackservice_notification_title">Odtwarzenie podcastu </string>
<string name="unknown_media_key">AntennaPod - Nieznany klawisz: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Zablokuj Kolejkę</string>
+ <string name="unlock_queue">Odblokuj Kolejkę</string>
<string name="clear_queue_label">Wyczyść kolejkę</string>
<string name="undo">Cofnij</string>
<string name="removed_from_queue">Element usunięty</string>
@@ -150,6 +202,7 @@
<string name="duration">Według długości</string>
<string name="ascending">Rosnąco</string>
<string name="descending">Malejąco</string>
+ <string name="clear_queue_confirmation_msg">Potwierdź usunięcie WSZYSTKICH pozycji z kolejki</string>
<!--Flattr-->
<string name="flattr_auth_label">Logowanie do Flattr</string>
<string name="flattr_auth_explanation">Naciśnij przycisk poniżej by zacząć proces autoryzacji. Zostaniesz przekierowany na stronę logowania do flattr w przeglądarce i zostaniesz poproszony o przyznanie zezwolenia AntennaPod-owi na flattr-owanie. Po daniu zezwolenia powrócisz do tej strony automatycznie.</string>
@@ -179,7 +232,6 @@
<!--Variable Speed-->
<string name="download_plugin_label">Pobierz wtyczkę</string>
<string name="no_playback_plugin_title">Wtyczka nie zainstalowana</string>
- <string name="no_playback_plugin_msg">Aby odtwarzać ze zmienną prędkością niezbędna jest instalacja biblioteki innej firmy.\n\nDotknij \'Pobierz wtyczkę\' aby pobrać darmową wtyczkę z Play Store\n\nAntennaPod nie odpowiada za problemy wynikłe z używania tej wtyczki, które powinny zostać zgłoszone bezpośrednio autorowi wtyczki.</string>
<string name="set_playback_speed_label">Prędkość odtwarzania</string>
<!--Empty list labels-->
<string name="no_items_label">Brak elementów na tej liście.</string>
@@ -190,20 +242,27 @@
<string name="queue_label">Kolejka</string>
<string name="services_label">Usługi</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Wstrzymaj odtwarzanie kiedy słuchawki zostaną odłączone</string>
+ <string name="pref_episode_cleanup_title">Usuwanie odcinków</string>
+ <string name="pref_pauseOnDisconnect_sum">Wstrzymaj odtwarzanie po rozłączeniu słuchawek lub Bluetooth</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Wznów odtwarzanie kiedy słuchawki zostaną podłączone ponownie</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Wznów odtwarzanie po przywróceniu połączenia Bluetooth</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Przycisk \'Do przodu\' pomija odcinek</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Przyciśnięcie fizycznego przycisku \'Do przodu\' przeskakuje do następnego odcinka zamiast przewijania</string>
<string name="pref_followQueue_sum">Przeskocz do następnego elementu kolejki po zakończeniu odtwarzania</string>
<string name="pref_auto_delete_sum">Usuń odcinek kiedy jego odtwarzanie zostanie zakończone</string>
<string name="pref_auto_delete_title">Automatyczne usuwanie</string>
+ <string name="pref_smart_mark_as_played_sum">Oznacz odcinek jako odtworzony, jeśli do końca pozostało mniej niż określona ilość czasu</string>
+ <string name="pref_smart_mark_as_played_title">Inteligentnie oznacz jako odtworzone</string>
+ <string name="pref_skip_keeps_episodes_sum">Zachowuje pominięte odcinki w kolejce</string>
+ <string name="pref_skip_keeps_episodes_title">Zachowaj pominięte odcinki</string>
<string name="playback_pref">Odtwarzanie</string>
<string name="network_pref">Sieć</string>
- <string name="pref_autoUpdateIntervall_title">Częstość aktualizacji</string>
- <string name="pref_autoUpdateIntervall_sum">Określ częstotliwość automatycznego odświeżania lub je wyłącz</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Pobieraj pliki tylko przez WiFi</string>
<string name="pref_followQueue_title">Odtwarzanie ciągłe</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi media pobrane</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Słuchawki odłączone</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Słuchawki podłączone ponownie</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth podłączony ponownie</string>
<string name="pref_mobileUpdate_title">Aktualizacje mobilne</string>
<string name="pref_mobileUpdate_sum">Zezwól na aktualizacje poprzez sieć komórkową</string>
<string name="refreshing_label">Odświeżanie</string>
@@ -218,11 +277,22 @@
<string name="pref_auto_flattr_sum">Skonfiguruj automatyczne flattr-owanie</string>
<string name="user_interface_label">Interfejs użytkownika</string>
<string name="pref_set_theme_title">Wybierz motyw</string>
+ <string name="pref_nav_drawer_title">Dopasuj panel nawigacyjny</string>
+ <string name="pref_nav_drawer_sum">Dopasuj wygląd panelu nawigacyjnego.</string>
+ <string name="pref_nav_drawer_items_title">Wybierz pozycje panelu nawigacyjnego</string>
+ <string name="pref_nav_drawer_items_sum">Zmienia pozycje widoczne w panelu nawigacyjnym.</string>
+ <string name="pref_nav_drawer_feed_order_title">Ustaw kolejność subskrypcji</string>
+ <string name="pref_nav_drawer_feed_order_sum">Zmień kolejność subskrybowanych kanałów</string>
+ <string name="pref_nav_drawer_feed_counter_title">Ustaw licznik subskrypcji</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Zmień informację wyświetlaną przez licznik subskrypcji</string>
<string name="pref_set_theme_sum">Zmień wygląd AntennaPod.</string>
<string name="pref_automatic_download_title">Automatyczne pobieranie</string>
<string name="pref_automatic_download_sum">Skonfiguruj automatyczne pobieranie odcinków.</string>
<string name="pref_autodl_wifi_filter_title">Włącz filtr Wi-Fi</string>
<string name="pref_autodl_wifi_filter_sum">Zezwól na automatyczne pobieranie tylko dla określonych sieci Wi-Fi.</string>
+ <string name="pref_automatic_download_on_battery_title">Pobieraj, gdy nie ładuje</string>
+ <string name="pref_automatic_download_on_battery_sum">Zezwól na automatyczne pobieranie, gdy bateria nie jest ładowana.</string>
+ <string name="pref_parallel_downloads_title">Liczba równoległych pobierań</string>
<string name="pref_episode_cache_title">Pamięć podręczna odcinków</string>
<string name="pref_theme_title_light">Jasny</string>
<string name="pref_theme_title_dark">Ciemny</string>
@@ -238,15 +308,26 @@
<string name="pref_gpodnet_setlogin_information_sum">Zmień dane logowania konta gpodder.net.</string>
<string name="pref_playback_speed_title">Prędkość odtwarzania</string>
<string name="pref_playback_speed_sum">Dostosuj prędkości dostępne dla odtwarzania audio o zmiennej prędkości</string>
- <string name="pref_seek_delta_title">Seek time</string>
- <string name="pref_seek_delta_sum">Przeskocz o tyle sekund przewijając</string>
<string name="pref_gpodnet_sethostname_title">Ustaw nazwę hosta</string>
<string name="pref_gpodnet_sethostname_use_default_host">Użyj domyślnego hosta</string>
<string name="pref_expandNotify_title">Rozwiń Powiadomienia</string>
<string name="pref_expandNotify_sum">Zawsze rozwijaj powiadomienie żeby pokazać przyciski odtwarzacza.</string>
<string name="pref_persistNotify_title">Stałe przyciski odtwarzacza</string>
<string name="pref_persistNotify_sum">Utrzymuj powiadomienie i przyciski odtwarzania na ekranie blokady gdy odtwarzanie jest wstrzymane.</string>
+ <string name="pref_lockscreen_background_title">Ustaw tło ekranu blokady</string>
+ <string name="pref_showDownloadReport_title">Pokaż raport z pobierania</string>
+ <string name="pref_showDownloadReport_sum">Jeżeli pobieranie się nie powiedzie, pokaż raport ze szczegółami błędu.</string>
<string name="pref_expand_notify_unsupport_toast">Android starszy niż 4.1 nie wspiera rozszerzonych powiadomień.</string>
+ <string name="pref_queueAddToFront_sum">Dodaj nowe odcinki na początku kolejki.</string>
+ <string name="pref_queueAddToFront_title">Dodaj na początku</string>
+ <string name="pref_smart_mark_as_played_disabled">Wyłączone</string>
+ <string name="pref_image_cache_size_title">Pamięć podręczna obrazów</string>
+ <string name="pref_image_cache_size_sum">Rozmiar przestrzeni na dysku dla pamięci podręcznej obrazów</string>
+ <string name="crash_report_title">Raport o błędach</string>
+ <string name="crash_report_sum">Wyślij ostatni raport o błędach przez e-mail</string>
+ <string name="send_email">Wyślij e-mail</string>
+ <string name="experimental_pref">Eksperymentalne</string>
+ <string name="pref_sonic_title">Odtwarzacz mediów Sonic</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Włącz automatyczne wspieranie na flattr.</string>
<string name="auto_flattr_after_percent">Z-flattr-uj odcinki odegrane %d procentach.</string>
@@ -261,6 +342,7 @@
<string name="found_in_title_label">Znaleziono w tytułach</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Pliki OPML pozwalają na przenoszenie podcastów między aplikacjami.</string>
+ <string name="opml_import_explanation_2">Użyj zewnętrznej aplikacji takiej jak Dropbox, Google Drive lub ulubionego menedżera plików, aby otworzyć plik OPML.</string>
<string name="start_import_label">Rozpocznij import</string>
<string name="opml_import_label">Import OPML</string>
<string name="opml_directory_error">BŁĄD!</string>
@@ -269,8 +351,8 @@
<string name="opml_import_error_dir_empty">Katalog importowania jest pusty.</string>
<string name="select_all_label">Zaznacz wszystko</string>
<string name="deselect_all_label">Odznacz wszystko</string>
+ <string name="choose_file_from_external_application">Użyj zewnętrznej aplikacji</string>
<string name="opml_export_label">Eksport OPML</string>
- <string name="exporting_label">Eksportowanie...</string>
<string name="export_error_label">Błąd eksportu</string>
<string name="opml_export_success_title">Eksport OPML udany.</string>
<string name="opml_export_success_sum">Plik .opml został zapisany do:\u0020</string>
@@ -281,9 +363,27 @@
<string name="sleep_timer_label">Wyłącznik czasowy</string>
<string name="time_left_label">Pozostały czas:\u0020</string>
<string name="time_dialog_invalid_input">Błąd wpisu, czas musi być liczbą całkowitą</string>
- <string name="time_unit_seconds">sekundy</string>
- <string name="time_unit_minutes">minuty</string>
- <string name="time_unit_hours">godziny</string>
+ <string name="timer_about_to_expire_label"><b>Kiedy odliczanie dobiega końca:</b></string>
+ <string name="shake_to_reset_label">Potrząśnij, aby zresetować odliczanie</string>
+ <string name="timer_vibration_label">Wibruj</string>
+ <string name="time_seconds">sekund</string>
+ <string name="time_minutes">minut</string>
+ <string name="time_hours">godzin</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 sekunda</item>
+ <item quantity="few">%d sekundy</item>
+ <item quantity="other">%d sekund</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minuta</item>
+ <item quantity="few">%d minuty</item>
+ <item quantity="other">%d minut</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 godzina</item>
+ <item quantity="few">%d godziny</item>
+ <item quantity="other">%d godzin</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORIE</string>
<string name="gpodnet_toplist_header">TOP PODCASTY</string>
@@ -317,20 +417,26 @@ https://gpodder.net/register/</string>
<string name="selected_folder_label">Wybrany folder:</string>
<string name="create_folder_label">Utwórz folder</string>
<string name="choose_data_directory">Wybierz folder danych</string>
+ <string name="choose_data_directory_message">Wybierz główny folder dla danych. AntennaPod utworzy odpowiednie podkatalogi.</string>
<string name="create_folder_msg">Utworzyć nowy folder o nazwie \"%1$s\"?</string>
<string name="create_folder_success">Utworzono nowy folder</string>
<string name="create_folder_error_no_write_access">Nie można zapisać do tego folderu</string>
<string name="create_folder_error_already_exists">Folder już istnieje</string>
<string name="create_folder_error">Nie można utworzyć folderu</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" nie istnieje</string>
+ <string name="folder_not_readable_error">\"%1$s\" nie może być odczytany</string>
+ <string name="folder_not_writable_error">\"%1$s\" nie może być zapisany</string>
<string name="folder_not_empty_dialog_title">Folder nie jest pusty</string>
<string name="folder_not_empty_dialog_msg">Wybrany folder nie jest pusty. Pobierane media i inne pliki będą umieszczane bezpośrednio w folderze, czy kontynuować?</string>
<string name="set_to_default_folder">Wybierz domyślny folder</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Wstrzymaj odtwarzanie zamiast wyciszenia jeśli inna aplikacja chce odtworzyć dźwięk.</string>
<string name="pref_pausePlaybackForFocusLoss_title">Wstrzymaj przy przerwaniu</string>
+ <string name="pref_resumeAfterCall_sum">Wznów odtwarzanie po zakończeniu połączenia telefonicznego</string>
+ <string name="pref_resumeAfterCall_title">Wznów po połączeniu</string>
+ <string name="pref_restart_required">AntennaPod musi zostać uruchomiony ponownie, by zmiany odniosły skutek.</string>
<!--Online feed view-->
<string name="subscribe_label">Subskrybuj</string>
<string name="subscribed_label">Subskrybowane</string>
- <string name="downloading_label">Pobieranie...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Pokaż rozdziały</string>
<string name="show_shownotes_label">Pokaż opis odcinka</string>
@@ -353,6 +459,34 @@ https://gpodder.net/register/</string>
<!--Feed information screen-->
<string name="authentication_label">Autoryzacja</string>
<string name="authentication_descr">Zmień swoją nazwę użytkownika oraz hasło dla tego podcastu i jego odcinków</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Aktualizacja bazy danych</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importowanie subskrybcji z jednozadaniowych aplikacji</string>
+ <string name="search_itunes_label">Szukaj w iTunes</string>
+ <string name="all_label">Wszystkie</string>
+ <string name="selected_all_label">Zaznaczono wszystkie odcinki</string>
+ <string name="none_label">Brak</string>
+ <string name="deselected_all_label">Odznaczono wszystkie odcinki</string>
+ <string name="played_label">Odtworzone</string>
+ <string name="selected_played_label">Zaznaczono odtworzone odcinki</string>
+ <string name="unplayed_label">Nieodtworzone</string>
+ <string name="selected_unplayed_label">Zaznaczono nieodtworzone odcinki</string>
+ <string name="downloaded_label">Pobrane</string>
+ <string name="selected_downloaded_label">Zaznaczono pobrane odcinki</string>
+ <string name="not_downloaded_label">Nie pobrane</string>
+ <string name="selected_not_downloaded_label">Zaznaczono niepobrane odcinki</string>
+ <string name="sort_title_a_z">Tytuł (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Tytuł (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Data (Nowe \u2192 Stare)</string>
+ <string name="sort_date_old_new">Data (Stare \u2192 Nowe)</string>
+ <string name="sort_duration_short_long">Długość (Krótkie \u2192 Długie)</string>
+ <string name="sort_duration_long_short">Długość (Długie \u2192 Krótkie)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Podoba Ci się AntennaPod?</string>
+ <string name="rating_message">Bylibyśmy wdzięczni, jeśli poświęciłbyś chwilę aby ocenić AntennaPod.</string>
+ <string name="rating_never_label">Daj mi spokój</string>
+ <string name="rating_later_label">Przypomnij później</string>
+ <string name="rating_now_label">Pewnie, zróbmy to!</string>
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-pt-rBR/strings.xml b/core/src/main/res/values-pt-rBR/strings.xml
index c3523acfb..95b279022 100644
--- a/core/src/main/res/values-pt-rBR/strings.xml
+++ b/core/src/main/res/values-pt-rBR/strings.xml
@@ -3,8 +3,10 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Feeds</string>
+ <string name="add_feed_label">Adicionar Podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISÓDIOS</string>
+ <string name="new_episodes_label">Novos Episódios</string>
+ <string name="all_episodes_label">Todos Episódios</string>
<string name="new_label">Novo</string>
<string name="waiting_list_label">Lista de espera</string>
<string name="settings_label">Configurações</string>
@@ -15,19 +17,28 @@
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net login</string>
<!--New episodes fragment-->
+ <string name="episode_filter_label">Mostrar apenas novos episódios</string>
<!--Main activity-->
+ <string name="drawer_open">Abrir menu</string>
+ <string name="drawer_close">Fechar menu</string>
+ <string name="drawer_feed_counter_new">Numero de novos episódios</string>
+ <string name="drawer_feed_counter_none">Nenhum</string>
<!--Webview actions-->
<string name="open_in_browser_label">Abrir no navegador</string>
<string name="copy_url_label">Copiar URL</string>
<string name="share_url_label">Compartilhar URL</string>
<string name="copied_url_msg">URL copiada para área de transferência.</string>
+ <string name="go_to_position_label">Ir para esta posição</string>
<!--Playback history-->
<string name="clear_history_label">Apagar histórico</string>
<!--Other-->
<string name="confirm_label">Confirmar</string>
<string name="cancel_label">Cancelar</string>
+ <string name="yes">Sim</string>
+ <string name="no">Não</string>
<string name="author_label">Autor</string>
<string name="language_label">Idioma</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Configurações</string>
<string name="cover_label">Capa</string>
<string name="error_label">Erro</string>
@@ -42,37 +53,56 @@
<string name="length_prefix">Duração:\u0020</string>
<string name="size_prefix">Tamanho:\u0020</string>
<string name="processing_label">Processando</string>
- <string name="loading_label">Carregando...</string>
<string name="save_username_password_label">Salvar nome do usuário e senha</string>
<string name="close_label">Fechar</string>
<string name="retry_label">Tentar novamente</string>
<string name="auto_download_label">Incluir em downloads automáticos</string>
+ <string name="feed_auto_download_always">Sempre</string>
+ <string name="feed_auto_download_never">Nunca</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL do Feed</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">Adicionar podcast por URL</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Marcar todos como lido</string>
+ <string name="mark_all_seen_label">Marcar todos como lido</string>
<string name="show_info_label">Mostrar informação</string>
+ <string name="remove_feed_label">Remover Podcast</string>
<string name="share_link_label">Compartilhar link do site</string>
- <string name="share_source_label">Compartilhar link do feed</string>
+ <string name="share_link_with_position_label">Compartilhar link com posição</string>
+ <string name="share_item_url_label">Compartilhar url do episódio</string>
+ <string name="share_item_url_with_position_label">Compartilhar url do episódio com posição</string>
<string name="feed_delete_confirmation_msg">Por favor confirme que você deseja apagar este feed e TODOS os episódios que você fez download deste feed.</string>
<string name="feed_remover_msg">Removendo feed</string>
+ <string name="hide_episodes_title">Ocultar Episódios</string>
+ <string name="episode_actions">Aplicar ações</string>
+ <string name="hide_paused_episodes_label">Pausado</string>
+ <string name="hide_downloaded_episodes_label">Baixado</string>
+ <string name="hide_not_downloaded_episodes_label">Não baixado</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Última Atualização falhou</string>
<!--actions on feeditems-->
<string name="download_label">Download</string>
<string name="play_label">Reproduzir</string>
<string name="pause_label">Pausar</string>
+ <string name="stop_label">Parar</string>
<string name="stream_label">Stream</string>
<string name="remove_label">Remover</string>
+ <string name="remove_episode_lable">Remover Episódio</string>
<string name="mark_read_label">Marcar como lido</string>
+ <string name="marked_as_read_label">Marcado como lido</string>
<string name="mark_unread_label">Marcar como não lido</string>
<string name="add_to_queue_label">Adicionar à fila</string>
+ <string name="added_to_queue_label">Adicionado à fila</string>
<string name="remove_from_queue_label">Remover da fila</string>
<string name="visit_website_label">Visitar Website</string>
<string name="support_label">Adicionar ao Flattr</string>
<string name="enqueue_all_new">Enfileirar todos</string>
<string name="download_all">Baixar todos</string>
<string name="skip_episode_label">Pular episódio</string>
+ <string name="activate_auto_download">Ativar download automático </string>
+ <string name="deactivate_auto_download">Desativar download automático</string>
<!--Download messages and labels-->
+ <string name="download_successful">com sucesso</string>
<string name="download_pending">Download pendente</string>
<string name="download_running">Download em execução</string>
<string name="download_error_device_not_found">Dispositivo de armazenamento não encontrado</string>
@@ -84,6 +114,7 @@
<string name="download_error_unsupported_type">Tipo de feed não suportado</string>
<string name="download_error_connection_error">Erro de conexão</string>
<string name="download_error_unknown_host">Host desconhecido</string>
+ <string name="download_error_unauthorized">Erro de autenticação</string>
<string name="cancel_all_downloads_label">Cancelar todos os downloads</string>
<string name="download_canceled_msg">Download cancelado</string>
<string name="download_report_title">Downloads finalizados</string>
@@ -91,7 +122,7 @@
<string name="download_error_io_error">Erro de IO</string>
<string name="download_error_request_error">Erro de requisição</string>
<string name="download_error_db_access">Erro no acesso ao Banco de dados</string>
- <string name="downloads_left">\u0020Downloads restantes</string>
+ <string name="downloads_processing">Processando downloads</string>
<string name="download_notification_title">Baixando dados do podcast</string>
<string name="download_report_content">%1$d downloads com sucesso, %2$d falharam</string>
<string name="download_log_title_unknown">Título desconhecido</string>
@@ -99,6 +130,7 @@
<string name="download_type_media">Arquivo de mídia</string>
<string name="download_type_image">Imagem</string>
<string name="download_request_error_dialog_message_prefix">Ocorreu um erro durante download do arquivo:\u0020</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Permitir temporariamente</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Erro!</string>
<string name="player_stopped_msg">Nenhuma mídia tocando</string>
@@ -117,6 +149,12 @@
<string name="removed_from_queue">Item removido</string>
<string name="move_to_top_label">Mover para o topo</string>
<string name="move_to_bottom_label">Mover para o fim</string>
+ <string name="sort">Ordenar</string>
+ <string name="alpha">Alfabeticamente</string>
+ <string name="date">Data</string>
+ <string name="duration">Duração</string>
+ <string name="ascending">Crescente</string>
+ <string name="descending">Decrescente</string>
<!--Flattr-->
<string name="flattr_auth_label">Logar no Flattr</string>
<string name="flattr_auth_explanation">Pressione o botão abaixo para iniciar o processo de autenticação. Você será direcionado para a tela de login do Flattr, que pedirá autorização para que o AntennaPod utilize o Flattr. Após conceder a permissão, você retornará a esta tela automaticamente.</string>
@@ -144,12 +182,9 @@
<string name="queue_label">Fila</string>
<string name="services_label">Serviços</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Interromper a reprodução quando o fone de ouvido for desconectado</string>
<string name="pref_followQueue_sum">Pular para próximo item da fila quando a reprodução terminar</string>
<string name="playback_pref">Reprodução</string>
<string name="network_pref">Rede</string>
- <string name="pref_autoUpdateIntervall_title">Intervalo de atualização</string>
- <string name="pref_autoUpdateIntervall_sum">Especifica o intervalo com que os feeds serão atualizados automaticamente ou desabilita esta funcionalidade</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Fazer download dos arquivos apenas via rede WiFi</string>
<string name="pref_followQueue_title">Reprodução contínua</string>
<string name="pref_downloadMediaOnWifiOnly_title">Download de mídia via WiFi</string>
@@ -207,7 +242,6 @@
<string name="select_all_label">Selecionar todos</string>
<string name="deselect_all_label">Remover seleção</string>
<string name="opml_export_label">Exportar OPML</string>
- <string name="exporting_label">Exportando...</string>
<string name="export_error_label">Erro na exportação</string>
<string name="opml_export_success_sum">O arquivo .opml foi gravado em:\u0020</string>
<!--Sleep timer-->
@@ -217,6 +251,9 @@
<string name="sleep_timer_label">Desligamento automático</string>
<string name="time_left_label">Tempo restante:\u0020</string>
<string name="time_dialog_invalid_input">Entrada inválida, a duração precisa ser um número inteiro</string>
+ <string name="time_seconds">segundos</string>
+ <string name="time_minutes">minutos</string>
+ <string name="time_hours">horas</string>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORIAS</string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
@@ -225,6 +262,7 @@
<string name="gpodnetauth_login_title">Login</string>
<string name="gpodnetauth_login_descr">Bem-vindo ao processo de login gpodder.net. Primeiramente, digite suas informações:</string>
<string name="gpodnetauth_login_butLabel">Login</string>
+ <string name="gpodnetauth_login_register">Se você ainda não possui uma conta, você pode criar uma aqui:\nhttps://gpodder.net/register/</string>
<string name="username_label">Nome do usuário</string>
<string name="password_label">Senha</string>
<string name="gpodnetauth_device_title">Seleção de dispositivo</string>
@@ -261,7 +299,6 @@
<!--Online feed view-->
<string name="subscribe_label">Assinar</string>
<string name="subscribed_label">Assinado</string>
- <string name="downloading_label">Baixando...</string>
<!--Content descriptions for image buttons-->
<string name="show_cover_label">Mostrar imagem</string>
<string name="butAction_label">Mais ações</string>
@@ -271,5 +308,8 @@
<string name="in_queue_label">Episódio está na fila</string>
<string name="new_episodes_count_label">Numero de novos episódios</string>
<!--Feed information screen-->
+ <!--Progress information-->
<!--AntennaPodSP-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-pt/strings.xml b/core/src/main/res/values-pt/strings.xml
index d9e201e21..f3fbc0623 100644
--- a/core/src/main/res/values-pt/strings.xml
+++ b/core/src/main/res/values-pt/strings.xml
@@ -8,43 +8,59 @@
<string name="episodes_label">Episódios</string>
<string name="new_episodes_label">Novos episódios</string>
<string name="all_episodes_label">Todos os episódios</string>
- <string name="new_label">Novo</string>
+ <string name="all_episodes_short_label">Todos</string>
+ <string name="favorite_episodes_label">Favoritos</string>
+ <string name="new_label">Novos</string>
<string name="waiting_list_label">Lista de espera</string>
<string name="settings_label">Definições</string>
<string name="add_new_feed_label">Adicionar podcast</string>
- <string name="downloads_label">Transferências</string>
+ <string name="downloads_label">Descargas</string>
<string name="downloads_running_label">Em curso</string>
- <string name="downloads_completed_label">Terminadas</string>
+ <string name="downloads_completed_label">Terminada</string>
<string name="downloads_log_label">Registo</string>
- <string name="cancel_download_label">Cancelar transferência</string>
+ <string name="cancel_download_label">Cancelar\ndescarga</string>
<string name="playback_history_label">Histórico de reprodução</string>
<string name="gpodnet_main_label">gpodder.net</string>
- <string name="gpodnet_auth_label">Acesso gpodder.net</string>
+ <string name="gpodnet_auth_label">Dados gpodder.net</string>
+ <string name="free_space_label">%1$s disponível</string>
+ <string name="episode_cache_full_title">Cache de episódios cheia</string>
+ <string name="episode_cache_full_message">Atingido o limite máximo de itens em cache. Pode aumentar o tamanho de cache nas definições.</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Publicados recentemente</string>
<string name="episode_filter_label">Mostrar apenas novos episódios</string>
<!--Main activity-->
<string name="drawer_open">Abrir menu</string>
<string name="drawer_close">Fechar menu</string>
+ <string name="drawer_preferences">Preferências do menu</string>
+ <string name="drawer_feed_order_unplayed_episodes">Ordenar por contador</string>
+ <string name="drawer_feed_order_alphabetical">Ordenar alfabeticamente</string>
+ <string name="drawer_feed_order_last_update">Ordenar por data de publicação</string>
+ <string name="drawer_feed_counter_new_unplayed">Número de episódios novos ou por reproduzir</string>
+ <string name="drawer_feed_counter_new">Número de novos episódios</string>
+ <string name="drawer_feed_counter_unplayed">Número de episódios por reproduzir</string>
+ <string name="drawer_feed_counter_none">Nenhum</string>
<!--Webview actions-->
<string name="open_in_browser_label">Abrir no navegador</string>
<string name="copy_url_label">Copiar URL</string>
<string name="share_url_label">Partilhar URL</string>
- <string name="copied_url_msg">URL copiado para a área de transferência.</string>
+ <string name="copied_url_msg">URL copiado para a área de transferência</string>
<string name="go_to_position_label">Ir para esta posição</string>
<!--Playback history-->
<string name="clear_history_label">Limpar histórico</string>
<!--Other-->
- <string name="confirm_label">Confirmar</string>
+ <string name="confirm_label">Confirmação</string>
<string name="cancel_label">Cancelar</string>
+ <string name="yes">Sim</string>
+ <string name="no">Não</string>
<string name="author_label">Autor</string>
<string name="language_label">Idioma</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Definições</string>
<string name="cover_label">Imagem</string>
<string name="error_label">Erro</string>
<string name="error_msg_prefix">Ocorreu um erro:</string>
<string name="refresh_label">Atualizar</string>
- <string name="external_storage_error_msg">Não existe um cartão SD. Certifique-se que inseriu o cartão corretamente.</string>
+ <string name="external_storage_error_msg">Cartão SD não disponível. Certifique-se de que inseriu o cartão corretamente para que a aplicação funcione corretamente.</string>
<string name="chapters_label">Capítulos</string>
<string name="shownotes_label">Notas</string>
<string name="description_label">Descrição</string>
@@ -54,53 +70,92 @@
<string name="size_prefix">Tamanho:\u0020</string>
<string name="processing_label">A processar...</string>
<string name="loading_label">A carregar...</string>
- <string name="save_username_password_label">Gravar utilizador e senha</string>
+ <string name="save_username_password_label">Guardar utilizador e palavra-passe</string>
<string name="close_label">Fechar</string>
<string name="retry_label">Tentar novamente</string>
- <string name="auto_download_label">Incluir nas transferências automáticas</string>
- <string name="parallel_downloads_suffix">\u0020transferências simultâneas</string>
+ <string name="auto_download_label">Incluir nas descargas automáticas</string>
+ <string name="auto_download_apply_to_items_title">Aplicar aos episódios anteriores</string>
+ <string name="auto_download_apply_to_items_message">A definição <i>Descarga automática</i> será aplicada a todos os novos episódios.\nGostaria de também a aplicar aos episódios anteriores?</string>
+ <string name="auto_delete_label">Apagar episódio automaticamente\n(altera a definição global)</string>
+ <string name="parallel_downloads_suffix">\u0020descargas simultâneas</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Sempre</string>
+ <string name="feed_auto_download_never">Nunca</string>
+ <string name="send_label">Enviar...</string>
+ <string name="episode_cleanup_never">Nunca</string>
+ <string name="episode_cleanup_queue_removal">Se não estiver na fila</string>
+ <string name="episode_cleanup_after_listening">Ao terminar</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dia depois de terminar</item>
+ <item quantity="other">%d dias depois de terminar</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL da fonte</string>
- <string name="etxtFeedurlHint">URL da fonte ou sítio web</string>
+ <string name="etxtFeedurlHint">URL da fonte ou do sítio web</string>
<string name="txtvfeedurl_label">Adicionar podcast via URL</string>
<string name="podcastdirectories_label">Localizar podcasts no diretório</string>
- <string name="podcastdirectories_descr">Pode procurar os novos podcasts no gPodder.net por nome, categoria ou popularidade e também na loja iTunes.</string>
+ <string name="podcastdirectories_descr">Pode procurar por novos podcasts no gPodder.net e também na loja iTunes. Pode procurar por nome, categoria e popularidade.</string>
<string name="browse_gpoddernet_label">Procurar no gPodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Marcar tudo como lido</string>
- <string name="mark_all_read_msg">Marcar todos os episódios como lidos</string>
- <string name="mark_all_read_confirmation_msg">Por favor confirme que deseja marcar todos os episódios como lidos.</string>
- <string name="mark_all_read_feed_confirmation_msg">Por favor confirme que deseja marcar todos os episódios desta fonte como lidos.</string>
+ <string name="mark_all_read_label">Marcar tudo como reproduzido</string>
+ <string name="mark_all_read_msg">Marcar todos os episódios como reproduzidos</string>
+ <string name="mark_all_read_confirmation_msg">Por favor confirme que deseja marcar todos os episódios como reproduzidos</string>
+ <string name="mark_all_read_feed_confirmation_msg">Por favor confirme que deseja marcar todos os episódios desta fonte como reproduzidos</string>
+ <string name="mark_all_seen_label">Marcar tudo como visto</string>
<string name="show_info_label">Mostrar informações</string>
<string name="remove_feed_label">Remover podcast</string>
- <string name="share_link_label">Partilhar ligação do sítio web</string>
- <string name="share_source_label">Partilhar ligação da fonte</string>
- <string name="feed_delete_confirmation_msg">Confirme a eliminação desta fonte e de todos os episódios a ela petencentes.</string>
+ <string name="share_label">Partilhar...</string>
+ <string name="share_link_label">Partilhar ligação</string>
+ <string name="share_link_with_position_label">Partilhar ligação com posição</string>
+ <string name="share_feed_url_label">Partilhar URL da fonte</string>
+ <string name="share_item_url_label">Partilhar URL do episódio</string>
+ <string name="share_item_url_with_position_label">Partilhar URL do episódio com posição</string>
+ <string name="feed_delete_confirmation_msg">Confirma a remoção desta fonte e de todos os episódios descarregados?</string>
<string name="feed_remover_msg">Remover fonte</string>
<string name="load_complete_feed">Atualizar todas as páginas da fonte</string>
+ <string name="hide_episodes_title">Ocultar episódios</string>
+ <string name="episode_actions">Aplicar ações</string>
+ <string name="hide_unplayed_episodes_label">Não reproduzidos</string>
+ <string name="hide_paused_episodes_label">Em pausa</string>
+ <string name="hide_played_episodes_label">Reproduzidos</string>
+ <string name="hide_queued_episodes_label">Na fila</string>
+ <string name="hide_not_queued_episodes_label">Não na fila</string>
+ <string name="hide_downloaded_episodes_label">Descarregados</string>
+ <string name="hide_not_downloaded_episodes_label">Não descarregados</string>
+ <string name="filtered_label">Filtrados</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Última atualização falhada</string>
<!--actions on feeditems-->
- <string name="download_label">Transferir</string>
+ <string name="download_label">Descarregar</string>
<string name="play_label">Reproduzir</string>
<string name="pause_label">Pausa</string>
<string name="stop_label">Parar</string>
<string name="stream_label">Emitir</string>
<string name="remove_label">Remover</string>
<string name="remove_episode_lable">Remover episódio</string>
- <string name="mark_read_label">Marcar como lido</string>
- <string name="mark_unread_label">Marcar como novo</string>
- <string name="marked_as_read_label">Marcar como lido</string>
+ <string name="mark_read_label">Marcar como reproduzido</string>
+ <string name="marked_as_read_label">Marcado como reproduzido</string>
+ <string name="mark_unread_label">Marcar como não reproduzido</string>
<string name="add_to_queue_label">Adicionar à fila</string>
+ <string name="added_to_queue_label">Adicionado à fila</string>
<string name="remove_from_queue_label">Remover da fila</string>
+ <string name="add_to_favorite_label">Adicionar aos favoritos</string>
+ <string name="added_to_favorites">Adicionado aos favoritos</string>
+ <string name="remove_from_favorite_label">Remover dos favoritos</string>
+ <string name="removed_from_favorites">Removido dos favoritos</string>
<string name="visit_website_label">Aceder ao sítio web</string>
<string name="support_label">Flattr</string>
<string name="enqueue_all_new">Colocar tudo na fila</string>
- <string name="download_all">Transferir tudo</string>
+ <string name="download_all">Descarregar tudo</string>
<string name="skip_episode_label">Ignorar episódio</string>
+ <string name="activate_auto_download">Ativar descarga automática</string>
+ <string name="deactivate_auto_download">Desativar descarga automática</string>
+ <string name="reset_position">Repor posição de reprodução</string>
+ <string name="removed_item">Item removido</string>
<!--Download messages and labels-->
<string name="download_successful">sucesso</string>
<string name="download_failed">falha</string>
- <string name="download_pending">Transferência pendente</string>
- <string name="download_running">Transferência atual</string>
+ <string name="download_pending">Descarga pendente</string>
+ <string name="download_running">Descarga atual</string>
<string name="download_error_device_not_found">Cartão SD não encontrado</string>
<string name="download_error_insufficient_space">Espaço insuficiente</string>
<string name="download_error_file_error">Erro no ficheiro</string>
@@ -111,30 +166,40 @@
<string name="download_error_connection_error">Erro de ligação</string>
<string name="download_error_unknown_host">Servidor desconhecido</string>
<string name="download_error_unauthorized">Erro de autenticação</string>
- <string name="cancel_all_downloads_label">Cancelar transferências</string>
- <string name="download_canceled_msg">Transferência cancelada</string>
- <string name="download_report_title">Transferências terminadas</string>
+ <string name="cancel_all_downloads_label">Cancelar descargas</string>
+ <string name="download_canceled_msg">Descarga cancelada</string>
+ <string name="download_canceled_autodownload_enabled_msg">Descarga cancelada\n<i>Descarga automática</i> desativada para este item</string>
+ <string name="download_report_title">Descargas terminadas com erros</string>
+ <string name="download_report_content_title">Relatório de descargas</string>
<string name="download_error_malformed_url">URL inválido</string>
<string name="download_error_io_error">Erro I/O</string>
<string name="download_error_request_error">Erro de pedido</string>
<string name="download_error_db_access">Erro de acesso à base de dados</string>
- <string name="downloads_left">\u0020Transferências em falta</string>
- <string name="downloads_processing">Processamento de transferências</string>
- <string name="download_notification_title">A transferir dados...</string>
- <string name="download_report_content">%1$d transferências efetuadas, %2$d falhadas</string>
+ <plurals name="downloads_left">
+ <item quantity="one">%d descarga em curso</item>
+ <item quantity="other">%d descargas em curso</item>
+ </plurals>
+ <string name="downloads_processing">Processamento de descargas</string>
+ <string name="download_notification_title">A descarregar dados do podcast</string>
+ <string name="download_report_content">%1$d descargas efetuadas, %2$d falhadas</string>
<string name="download_log_title_unknown">Título desconhecido</string>
<string name="download_type_feed">Fonte</string>
<string name="download_type_media">Ficheiro multimédia</string>
<string name="download_type_image">Imagem</string>
- <string name="download_request_error_dialog_message_prefix">Ocorreu um erro ao transferir o ficheiro:\u0020</string>
+ <string name="download_request_error_dialog_message_prefix">Ocorreu um erro ao tentar descarregar o ficheiro:\u0020</string>
<string name="authentication_notification_title">Requer autenticação</string>
- <string name="authentication_notification_msg">O recurso solicitado requer um utilizador e uma senha</string>
+ <string name="authentication_notification_msg">O recurso solicitado requer um utilizador e uma palavra-passe</string>
+ <string name="confirm_mobile_download_dialog_title">Confirmação de descarga</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">A descarga através de dados móveis está desativada nas definições.\n\nAtivar temporariamente ou apenas adicionar à fila?\n\n<small>A sua decisão será memorizada durante 10 minutos.</small></string>
+ <string name="confirm_mobile_download_dialog_message">A descarga através de dados móveis está desativada nas definições.\n\nAtivar temporariamente?\n\n<small>A sua decisão será memorizada durante 10 minutos.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Apenas adicionados à fila</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Ativar temporariamente</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Erro!</string>
<string name="player_stopped_msg">Nada em reprodução</string>
- <string name="player_preparing_msg">A preparar</string>
+ <string name="player_preparing_msg">A preparar...</string>
<string name="player_ready_msg">Pronto</string>
- <string name="player_seeking_msg">A procurar</string>
+ <string name="player_seeking_msg">A procurar...</string>
<string name="playback_error_server_died">Erro de servidor</string>
<string name="playback_error_unknown">Erro desconhecido</string>
<string name="no_media_playing_label">Nada em reprodução</string>
@@ -143,6 +208,10 @@
<string name="playbackservice_notification_title">Reproduzir podcast</string>
<string name="unknown_media_key">Tecla multimédia desconhecida: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Bloquear fila</string>
+ <string name="unlock_queue">Desbloquear fila</string>
+ <string name="queue_locked">Fila bloqueada</string>
+ <string name="queue_unlocked">Fila desbloqueada</string>
<string name="clear_queue_label">Limpar fila</string>
<string name="undo">Anular</string>
<string name="removed_from_queue">Item removido</string>
@@ -154,7 +223,7 @@
<string name="duration">Duração</string>
<string name="ascending">Crescente</string>
<string name="descending">Decrescente</string>
- <string name="clear_queue_confirmation_msg">Por favor confirme que deseja limpar todos os episódios da fila de reprodução.</string>
+ <string name="clear_queue_confirmation_msg">Tem a certeza de que deseja remover todos os episódios da lista de reprodução?</string>
<!--Flattr-->
<string name="flattr_auth_label">Sessão Flattr</string>
<string name="flattr_auth_explanation">Prima o botão abaixo para iniciar a autenticação. O seu navegador web abrirá o ecrã da sessão flattr e ser-lhe-á solicitada a permissão para o AntennaPod efetuar as alterações. Após ser dada a permissão, voltará novamente a este ecrã.</string>
@@ -165,7 +234,7 @@
<string name="no_flattr_token_notification_msg">Parece que a sua conta flattr não está integrada ao AntennaPod. Clique aqui para autenticar.</string>
<string name="no_flattr_token_msg">Parece que a sua conta flattr não está vinculada ao AntennaPod. Pode vincular a sua conta ao AntennaPod ou aceder ao sítio web para fazer o flattr.</string>
<string name="authenticate_now_label">Autenticar</string>
- <string name="action_forbidden_title">Ação negada</string>
+ <string name="action_forbidden_title">Ação proibida</string>
<string name="action_forbidden_msg">O AntennaPod não possui as permissões para esta ação. É possível que o token de acesso ao flattr via AntennaPod tenha sido revogado. Pode efetuar nova autenticação ou aceder ao sítio web do item.</string>
<string name="access_revoked_title">Acesso revogado</string>
<string name="access_revoked_info">Você revogou o token de acesso do AntennaPod à sua conta. Para concluir o processo, tem que remover esta aplicação da lista de aplicações presentes nas definições de conta no sítio web do flattr.</string>
@@ -182,55 +251,81 @@
<string name="flattrd_failed_label">O AntennaPod não fez o flattr</string>
<string name="flattr_retrieving_status">A obter itens com flattr</string>
<!--Variable Speed-->
- <string name="download_plugin_label">Transferir extra</string>
+ <string name="download_plugin_label">Descarregar extra</string>
<string name="no_playback_plugin_title">Extra não instalado</string>
- <string name="no_playback_plugin_msg">Para que a velocidade de reprodução variável funcione, tem que instalar um biblioteca de terceiros.\n\nClique em Transferir extra para a transferir no Google Play.\n\nQuaisquer problemas que ocorram na utilização do extra devem ser reportados diretamente ao seu programador.</string>
+ <string name="no_playback_plugin_or_sonic_msg">Para que a velocidade variável de reprodução funcione, recomendamos que ative o Sonic Media Player incorporado [Android 4.1+].\n\nEm alternativa, pode transferir o extra <i>Prestissimo</i>, disponível na Google Play.\nQuaisquer problemas que ocorram com o Prestissimo não são da responsabilidade dos programadores do AntennaPod e devem ser reportados ao dono do extra.</string>
<string name="set_playback_speed_label">Velocidades de reprodução</string>
+ <string name="enable_sonic">Ativar Sonic</string>
<!--Empty list labels-->
- <string name="no_items_label">Não existem itens na lista.</string>
- <string name="no_feeds_label">Ainda não possui quaisquer fontes.</string>
+ <string name="no_items_label">Não existem itens nesta lista</string>
+ <string name="no_feeds_label">Ainda não possui quaisquer fontes</string>
+ <string name="no_chapters_label">Este episódio não tem capítulos.</string>
<!--Preferences-->
<string name="other_pref">Outras</string>
<string name="about_pref">Sobre</string>
<string name="queue_label">Fila</string>
<string name="services_label">Serviços</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Parar reprodução ao remover os auscultadores</string>
+ <string name="pref_episode_cleanup_title">Limpeza de episódios</string>
+ <string name="pref_episode_cleanup_summary">Os episódios que não estejam na fila e não sejam favoritos podem ser elegíveis para serem removidos se a Descarga automática necessitar de espaço para novos episódios.</string>
+ <string name="pref_pauseOnDisconnect_sum">Pausa na reprodução ao desligar os auscultadores ou o bluetooth</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Continuar reprodução ao ligar os auscultadores</string>
- <string name="pref_followQueue_sum">Ir para a faixa seguinte ao terminar a reprodução</string>
- <string name="pref_auto_delete_sum">Eliminar episódio ao terminar a reprodução</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Continuar reprodução ao estabelecer a ligação bluetooth</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Botão para avançar</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Ao premir o botão Avançar, ir para o episódio seguinte em vez de avançar a reprodução</string>
+ <string name="pref_followQueue_sum">Ir para a episódio seguinte ao terminar a reprodução</string>
+ <string name="pref_auto_delete_sum">Apagar episódio ao terminar a reprodução</string>
<string name="pref_auto_delete_title">Eliminação automática</string>
+ <string name="pref_smart_mark_as_played_sum">Marcar episódio como reproduzido mesmo que restem alguns segundos de reprodução</string>
+ <string name="pref_smart_mark_as_played_title">Marcar como reproduzido (inteligente)</string>
+ <string name="pref_skip_keeps_episodes_sum">Manter episódios mesmo que tenham sido ignorados</string>
+ <string name="pref_skip_keeps_episodes_title">Manter episódios ignorados</string>
<string name="playback_pref">Reprodução</string>
<string name="network_pref">Rede</string>
- <string name="pref_autoUpdateIntervall_title">Intervalo entre atualizações</string>
- <string name="pref_autoUpdateIntervall_sum">Indique o intervalo de tempo entre as atualizações de fontes ou desative a opção</string>
- <string name="pref_downloadMediaOnWifiOnly_sum">Apenas transferir pelas redes sem fios</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Intervalo de atualização ou hora do dia</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Define um intervalo de atualização ou hora para atualizar automaticamente a fonte</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Pode definir um <i>intervalo</i>, ex.: \"a cada 2 horas\", definir uma <i>hora do dia</i>, ex.: \"7:00 AM\" ou <i>desativar</i> as atualizações automáticas.\n\n<small>Tenha em conta que a hora de atualização não é precisa. Pode existir um pouco de atraso.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Desativar</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Definir intervalo</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Definir hora do dia</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">a cada %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">às %1$s</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Apenas descarregar através de redes sem fios</string>
<string name="pref_followQueue_title">Reprodução contínua</string>
- <string name="pref_downloadMediaOnWifiOnly_title">Transferência Wi-Fi</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">Descarregar por Wi-Fi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Auscultadores removidos</string>
- <string name="pref_unpauseOnHeadsetReconnect_title">Auscultadores ligados</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">Auscultadores inseridos</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Ligação bluetooth</string>
<string name="pref_mobileUpdate_title">Atualizações móveis</string>
- <string name="pref_mobileUpdate_sum">Permitir atualizações através da rede de dados</string>
+ <string name="pref_mobileUpdate_sum">Permitir atualizações através da rede de dados móveis</string>
<string name="refreshing_label">A atualizar</string>
<string name="flattr_settings_label">Definições flattr</string>
<string name="pref_flattr_auth_title">Sessão flattr</string>
- <string name="pref_flattr_auth_sum">Inicie sessão na sua conta flattr para fazer o flattr no AntennaPod.</string>
+ <string name="pref_flattr_auth_sum">Inicie sessão na sua conta flattr para fazer o flattr no AntennaPod</string>
<string name="pref_flattr_this_app_title">Flattr desta aplicação</string>
<string name="pref_flattr_this_app_sum">Ajude no desenvolvimento do AntennaPod através do Flattr. Obrigado!</string>
<string name="pref_revokeAccess_title">Revogar acesso</string>
- <string name="pref_revokeAccess_sum">Revogar permissões de acesso da aplicação à sua conta flattr.</string>
+ <string name="pref_revokeAccess_sum">Revogar permissões de acesso da aplicação à sua conta flattr</string>
<string name="pref_auto_flattr_title">Flattr automático</string>
<string name="pref_auto_flattr_sum">Configurar flattr automático</string>
<string name="user_interface_label">Interface</string>
<string name="pref_set_theme_title">Tema</string>
- <string name="pref_set_theme_sum">Mudar o aspeto do AntennaPod.</string>
- <string name="pref_automatic_download_title">Transferência automática</string>
- <string name="pref_automatic_download_sum">Configure a transferência automática dos episódios.</string>
+ <string name="pref_nav_drawer_title">Menu de navegação</string>
+ <string name="pref_nav_drawer_sum">Personalizar a aparência do menu de navegação</string>
+ <string name="pref_nav_drawer_items_title">Alterar elementos do menu</string>
+ <string name="pref_nav_drawer_items_sum">Alterar os itens que aparecem no menu de navegação</string>
+ <string name="pref_nav_drawer_feed_order_title">Definir ordem de subscrição</string>
+ <string name="pref_nav_drawer_feed_order_sum">Alterar a ordem das suas subscrições</string>
+ <string name="pref_nav_drawer_feed_counter_title">Definir contador de subscrições</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Alterar a informação mostrada no contador de subscrições</string>
+ <string name="pref_set_theme_sum">Alterar a aparência do AntennaPod</string>
+ <string name="pref_automatic_download_title">Descarga automática</string>
+ <string name="pref_automatic_download_sum">Configure a descarga automática dos episódios</string>
<string name="pref_autodl_wifi_filter_title">Ativar filtro Wi-Fi</string>
- <string name="pref_autodl_wifi_filter_sum">Apenas permitir transferências automáticas através de redes sem fios.</string>
- <string name="pref_automatic_download_on_battery_title">Transferência se não estiver a carregar</string>
- <string name="pref_automatic_download_on_battery_sum">Permitir transferência automática se a bateria não estiver a ser carregada</string>
- <string name="pref_parallel_downloads_title">Transferências simultâneas</string>
+ <string name="pref_autodl_wifi_filter_sum">Apenas permitir descargas automáticas através de redes sem fios</string>
+ <string name="pref_automatic_download_on_battery_title">Descarregar se não estiver a carregar</string>
+ <string name="pref_automatic_download_on_battery_sum">Permitir descarga automática se a bateria não estiver a ser carregada</string>
+ <string name="pref_parallel_downloads_title">Descargas simultâneas</string>
<string name="pref_episode_cache_title">Cache de episódios</string>
<string name="pref_theme_title_light">Claro</string>
<string name="pref_theme_title_dark">Escuro</string>
@@ -239,24 +334,38 @@
<string name="pref_update_interval_hours_singular">hora</string>
<string name="pref_update_interval_hours_manual">Manual</string>
<string name="pref_gpodnet_authenticate_title">Acesso</string>
- <string name="pref_gpodnet_authenticate_sum">Aceda à sua conta gpodder.net para poder sincronizar as subscrições.</string>
+ <string name="pref_gpodnet_authenticate_sum">Aceda à sua conta gpodder.net para poder sincronizar as subscrições</string>
<string name="pref_gpodnet_logout_title">Sair</string>
<string name="pref_gpodnet_logout_toast">Sessão terminada</string>
- <string name="pref_gpodnet_setlogin_information_title">Mudar informação de acesso</string>
- <string name="pref_gpodnet_setlogin_information_sum">Mudar informação de acesso à sua conta gpodder.net.</string>
+ <string name="pref_gpodnet_setlogin_information_title">Alterar informação de acesso</string>
+ <string name="pref_gpodnet_setlogin_information_sum">Alterar a informação de acesso à sua conta gpodder.net</string>
<string name="pref_playback_speed_title">Velocidades de reprodução</string>
- <string name="pref_playback_speed_sum">Personalize as velocidades de reprodução disponíveis.</string>
- <string name="pref_seek_delta_title">Intervalo de procura</string>
- <string name="pref_seek_delta_sum">Ao recuar ou avançar, procurar este valor de segundos</string>
+ <string name="pref_playback_speed_sum">Personalizar as velocidades de reprodução disponíveis</string>
+ <string name="pref_fast_forward">Tempo a avançar</string>
+ <string name="pref_rewind">Tempo a recuar</string>
<string name="pref_gpodnet_sethostname_title">Definir nome de servidor</string>
- <string name="pref_gpodnet_sethostname_use_default_host">Utilizar pré-definição</string>
+ <string name="pref_gpodnet_sethostname_use_default_host">Utilizar predefinições</string>
<string name="pref_expandNotify_title">Expansão de notificação</string>
- <string name="pref_expandNotify_sum">Expandir sempre a notificação para mostrar os botões de reprodução.</string>
- <string name="pref_persistNotify_title">Controlos de reprodução persistentes</string>
- <string name="pref_persistNotify_sum">Manter controlos de notificação e ecrã de bloqueio ao colocar a reprodução em pausa.</string>
- <string name="pref_expand_notify_unsupport_toast">As versões Android anteriores à 4.1 não possuem suporte à expansão de notificações.</string>
- <string name="pref_queueAddToFront_sum">Colocar novos episódios no inicio da fila.</string>
- <string name="pref_queueAddToFront_title">Novos episódios no inicio</string>
+ <string name="pref_expandNotify_sum">Expandir sempre a notificação para mostrar os botões de reprodução</string>
+ <string name="pref_persistNotify_title">Controlos de reprodução</string>
+ <string name="pref_persistNotify_sum">Manter controlos de notificação e ecrã de bloqueio ao colocar a reprodução em pausa</string>
+ <string name="pref_lockscreen_background_title">Definir fundo do ecrã de bloqueio</string>
+ <string name="pref_lockscreen_background_sum">Define a imagem do episódio como fundo do ecrã de bloqueio. Efeito colateral: também será mostrada em outras aplicações</string>
+ <string name="pref_showDownloadReport_title">Mostrar relatório de erros</string>
+ <string name="pref_showDownloadReport_sum">Se a descarga falhar, gera um relatório que mostra os detalhes do erro</string>
+ <string name="pref_expand_notify_unsupport_toast">As versões Android anteriores à 4.1 não possuem suporte à expansão de notificações</string>
+ <string name="pref_queueAddToFront_sum">Colocar novos episódios no início da fila</string>
+ <string name="pref_queueAddToFront_title">Novos episódios no início</string>
+ <string name="pref_smart_mark_as_played_disabled">Desativada</string>
+ <string name="pref_image_cache_size_title">Cache de imagens</string>
+ <string name="pref_image_cache_size_sum">O tamanho da cache de imagens</string>
+ <string name="crash_report_title">Relatório de erro</string>
+ <string name="crash_report_sum">Enviar o relatório de erros por e-mail</string>
+ <string name="send_email">Enviar e-mail</string>
+ <string name="experimental_pref">Experimental</string>
+ <string name="pref_sonic_title">Reprodutor Sonic</string>
+ <string name="pref_sonic_message">Utilizar o Sonic Media Player como substituto do reprodutor nativo do Android e do Prestissimo</string>
+ <string name="pref_current_value">Valor atual: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Ativar flattr automático</string>
<string name="auto_flattr_after_percent">Flattr de episódios ao atingir %d porcento de reprodução</string>
@@ -270,35 +379,52 @@
<string name="search_label">Procura</string>
<string name="found_in_title_label">Encontrado no título</string>
<!--OPML import and export-->
- <string name="opml_import_txtv_button_lable">Os ficheiros OPML permitem-lhe mover os podcasts entre aplicações.</string>
- <string name="opml_import_explanation_1">Escolha um caminho especifico no sistema local de ficheiros.</string>
- <string name="opml_import_explanation_2">Utilize aplicações externas como o Dropbox, Google Drive ou o seu gestor de ficheiros preferido para abrir o ficheiro OPML.</string>
- <string name="opml_import_explanation_3">As aplicações como o Google Mail, Dropbox, Google Drive ou gestores de ficheiros podem <i>abrir</i> os ficheiros OPML <i>através</i> do AntennaPod.</string>
+ <string name="opml_import_txtv_button_lable">Os ficheiros OPML permitem-lhe mover os podcasts entre aplicações</string>
+ <string name="opml_import_option">Opção %1$d</string>
+ <string name="opml_import_explanation_1">Escolha um caminho especifico no sistema local de ficheiros</string>
+ <string name="opml_import_explanation_2">Utilize aplicações externas como o Dropbox, Google Drive ou o seu gestor de ficheiros preferido para abrir o ficheiro OPML</string>
+ <string name="opml_import_explanation_3">As aplicações como o Google Mail, Dropbox, Google Drive ou gestores de ficheiros podem <i>abrir</i> os ficheiros OPML <i>através</i> do AntennaPod</string>
<string name="start_import_label">Iniciar importação</string>
<string name="opml_import_label">Importação OPML</string>
<string name="opml_directory_error">Erro!</string>
<string name="reading_opml_label">A ler ficheiro OPML</string>
<string name="opml_reader_error">Ocorreu um erro ao ler o ficheiro OPML:</string>
- <string name="opml_import_error_dir_empty">O diretório de importação está vazio.</string>
+ <string name="opml_import_error_dir_empty">O diretório de importação está vazio</string>
<string name="select_all_label">Marcar tudo</string>
<string name="deselect_all_label">Desmarcar tudo</string>
+ <string name="select_options_label">Selecionar...</string>
<string name="choose_file_from_filesystem">Do sistema local de ficheiros</string>
<string name="choose_file_from_external_application">Utilizar aplicação externa</string>
<string name="opml_export_label">Exportação OPML</string>
- <string name="exporting_label">Exportação...</string>
+ <string name="exporting_label">A exportar...</string>
<string name="export_error_label">Erro de exportação</string>
- <string name="opml_export_success_title">Exportação efetuada.</string>
- <string name="opml_export_success_sum">O ficheiro .opml foi gravado em:\u0020</string>
+ <string name="opml_export_success_title">Exportação efetuada</string>
+ <string name="opml_export_success_sum">O ficheiro .opml foi guardado em:\u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Definir temporizador</string>
<string name="disable_sleeptimer_label">Desativar temporizador</string>
- <string name="enter_time_here_label">Introduza o tempo</string>
+ <string name="enter_time_here_label">Tempo</string>
<string name="sleep_timer_label">Temporizador</string>
<string name="time_left_label">Tempo restante:\u0020</string>
- <string name="time_dialog_invalid_input">Valor inválido. Tem que ser um inteiro.</string>
- <string name="time_unit_seconds">segundos</string>
- <string name="time_unit_minutes">minutos</string>
- <string name="time_unit_hours">horas</string>
+ <string name="time_dialog_invalid_input">Tem que introduzir um número inteiro</string>
+ <string name="timer_about_to_expire_label"><b>Se o temporizador estiver a expirar:</b></string>
+ <string name="shake_to_reset_label">Agitar para repor</string>
+ <string name="timer_vibration_label">Vibrar</string>
+ <string name="time_seconds">segundos</string>
+ <string name="time_minutes">minutos</string>
+ <string name="time_hours">horas</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 segundo</item>
+ <item quantity="other">%d segundos</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minuto</item>
+ <item quantity="other">%d minutos</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 hora</item>
+ <item quantity="other">%d horas</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">Categorias</string>
<string name="gpodnet_toplist_header">Melhores</string>
@@ -309,11 +435,11 @@
<string name="gpodnetauth_login_butLabel">Acesso</string>
<string name="gpodnetauth_login_register">Se ainda não possui uma conta, pode criar uma em:\nhttps://gpodder.net/register/</string>
<string name="username_label">Utilizador</string>
- <string name="password_label">Senha</string>
+ <string name="password_label">Palavra-passe</string>
<string name="gpodnetauth_device_title">Seleção de dispositivo</string>
<string name="gpodnetauth_device_descr">Criar um novo dispositivo ou escolher um existente para aceder à sua conta gpodder.net</string>
<string name="gpodnetauth_device_deviceID">ID do dispositivo:\u0020</string>
- <string name="gpodnetauth_device_caption">Legenda</string>
+ <string name="gpodnetauth_device_caption">Nome</string>
<string name="gpodnetauth_device_butCreateNewDevice">Criar novo dispositivo</string>
<string name="gpodnetauth_device_chooseExistingDevice">Escolher dispositivo:</string>
<string name="gpodnetauth_device_errorEmpty">ID do dispositivo não pode estar vazia</string>
@@ -323,51 +449,96 @@
<string name="gpodnetauth_finish_descr">Parabéns! A sua conta gpodder.net está vinculada ao seu dispositivo. Agora, já pode sincronizar as subscrições no dispositivo com a sua conta gpodder.net.</string>
<string name="gpodnetauth_finish_butsyncnow">Sincronizar agora</string>
<string name="gpodnetauth_finish_butgomainscreen">Ir para o ecrã principal</string>
- <string name="gpodnetsync_auth_error_title">Erro de autenticação gpodder.net</string>
- <string name="gpodnetsync_auth_error_descr">Utilizador ou senha inválido</string>
+ <string name="gpodnetsync_auth_error_title">Erro de autenticação no gpodder.net</string>
+ <string name="gpodnetsync_auth_error_descr">Utilizador ou palavra-passe inválida</string>
<string name="gpodnetsync_error_title">Erro de sincronização gpodder.net</string>
<string name="gpodnetsync_error_descr">Ocorreu um erro ao sincronizar:\u0020</string>
<!--Directory chooser-->
<string name="selected_folder_label">Diretório escolhido:</string>
- <string name="create_folder_label">Criar diretório</string>
- <string name="choose_data_directory">Escolha o diretório</string>
- <string name="create_folder_msg">Criar um diretório com o nome \"%1$s\"?</string>
- <string name="create_folder_success">Novo diretório criado</string>
- <string name="create_folder_error_no_write_access">Não é possível gravar neste diretório</string>
- <string name="create_folder_error_already_exists">O diretório já existe</string>
- <string name="create_folder_error">Não é possível criar o diretório</string>
- <string name="folder_not_empty_dialog_title">Diretório não vazio</string>
- <string name="folder_not_empty_dialog_msg">O diretório escolhido não está vazio. As transferências serão colocadas neste diretório. Continuar?</string>
+ <string name="create_folder_label">Criar pasta</string>
+ <string name="choose_data_directory">Escolha a pasta de dados</string>
+ <string name="choose_data_directory_message">Escolha a base da pasta de dados. O AntennaPod irá criar as subpastas apropriadas.</string>
+ <string name="create_folder_msg">Criar uma pasta com o nome \"%1$s\"?</string>
+ <string name="create_folder_success">Nova pasta criada</string>
+ <string name="create_folder_error_no_write_access">Não é possível guardar nesta pasta</string>
+ <string name="create_folder_error_already_exists">A pasta já existe</string>
+ <string name="create_folder_error">Não é possível criar a pasta</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" não existe</string>
+ <string name="folder_not_readable_error">\"%1$s\" não permite leitura</string>
+ <string name="folder_not_writable_error">\"%1$s\" não permite escrita</string>
+ <string name="folder_not_empty_dialog_title">A pasta não está vazia</string>
+ <string name="folder_not_empty_dialog_msg">A pasta escolhida não está vazia. Os dados multimédia e os outros ficheiros serão colocados nesta pasta. Continuar?</string>
<string name="set_to_default_folder">Escolha a pasta pré-definida</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pausa na reprodução em vez de baixar o volume se outra aplicação quiser reproduzir sons</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pausa nas interrupções</string>
+ <string name="pref_resumeAfterCall_sum">Continuar reprodução ao terminar a chamada</string>
+ <string name="pref_resumeAfterCall_title">Continuar após a chamada</string>
+ <string name="pref_restart_required">Tem que reiniciar o AntennaPod para aplicar as alterações</string>
<!--Online feed view-->
<string name="subscribe_label">Subscrever</string>
<string name="subscribed_label">Subscrito</string>
- <string name="downloading_label">Transferência...</string>
+ <string name="downloading_label">A descarregar...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Mostrar capítulos</string>
<string name="show_shownotes_label">Mostrar notas</string>
<string name="show_cover_label">Mostrar imagem</string>
- <string name="rewind_label">Recuar</string>
+ <string name="rewind_label">Recuo rápido</string>
<string name="fast_forward_label">Avanço rápido</string>
<string name="media_type_audio_label">Áudio</string>
<string name="media_type_video_label">Vídeo</string>
<string name="navigate_upwards_label">Navegar para cima</string>
<string name="butAction_label">Mais ações</string>
<string name="status_playing_label">Episódio em reprodução</string>
- <string name="status_downloading_label">Episódio a ser transferido</string>
- <string name="status_downloaded_label">Episódio transferido</string>
+ <string name="status_downloading_label">Episódio a ser descarregado</string>
+ <string name="status_downloaded_label">Episódio descarregado</string>
<string name="status_unread_label">Novo item</string>
<string name="in_queue_label">Episódio está na fila</string>
<string name="new_episodes_count_label">Número de novos episódios</string>
<string name="in_progress_episodes_count_label">Número de episódios que já foi iniciada a reprodução</string>
- <string name="drag_handle_content_description">Arraste para mudar a posição deste item</string>
+ <string name="drag_handle_content_description">Arraste para alterar a posição deste item</string>
<string name="load_next_page_label">Carregar próxima página</string>
<!--Feed information screen-->
<string name="authentication_label">Autenticação</string>
- <string name="authentication_descr">Altere o seu nome de utilizador e senha para este podcast e seus episódios.</string>
+ <string name="authentication_descr">Altere o seu nome de utilizador e palavra-passe para este podcast e seus episódios</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Atualizando base de dados</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importar subscrições de aplicações single-purpose...</string>
<string name="search_itunes_label">Procurar no iTunes</string>
+ <string name="select_label"><b>Selecionar...</b></string>
+ <string name="filter">Filtro</string>
+ <string name="all_label">Todos</string>
+ <string name="selected_all_label">Marcar todos os episódios</string>
+ <string name="none_label">Nenhum</string>
+ <string name="deselected_all_label">Desmarcar todos os episódios</string>
+ <string name="played_label">Reproduzidos</string>
+ <string name="selected_played_label">Selecionar episódios reproduzidos</string>
+ <string name="unplayed_label">Não reproduzidos</string>
+ <string name="selected_unplayed_label">Selecionar episódios não reproduzidos</string>
+ <string name="downloaded_label">Descarregados</string>
+ <string name="selected_downloaded_label">Selecionar episódios descarregados</string>
+ <string name="not_downloaded_label">Não descarregados</string>
+ <string name="selected_not_downloaded_label">Selecionar episódios não descarregados</string>
+ <string name="sort_title"><b>Ordenar por…</b></string>
+ <string name="sort_title_a_z">Título (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Título (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Data (Recente \u2192 Antiga)</string>
+ <string name="sort_date_old_new">Data (Antiga \u2192 Recente)</string>
+ <string name="sort_duration_short_long">Duração (Curta \u2192 Longa)</string>
+ <string name="sort_duration_long_short">Duração (Longa \u2192 Curta)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Gosta do AntennaPod?</string>
+ <string name="rating_message">Gostaríamos que dispensasse algum tempo para avaliar o AntennaPod.</string>
+ <string name="rating_never_label">Não avaliar</string>
+ <string name="rating_later_label">Lembrar mais tarde</string>
+ <string name="rating_now_label">Claro, vamos a isso!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Controlos de áudio</string>
+ <string name="playback_speed">Velocidade de reprodução</string>
+ <string name="volume">Volume</string>
+ <string name="left_short">E</string>
+ <string name="right_short">D</string>
+ <string name="audio_effects">Efeitos áudio</string>
+ <string name="stereo_to_mono">Mistura: estéreo para mono</string>
+ <string name="sonic_only">Apenas Sonic</string>
</resources>
diff --git a/core/src/main/res/values-ro-rRO/strings.xml b/core/src/main/res/values-ro-rRO/strings.xml
index 390f50767..a112db8dd 100644
--- a/core/src/main/res/values-ro-rRO/strings.xml
+++ b/core/src/main/res/values-ro-rRO/strings.xml
@@ -4,7 +4,6 @@
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Feeduri</string>
<string name="podcasts_label">PODCASTURI</string>
- <string name="episodes_label">EPISOADE</string>
<string name="new_label">Nou</string>
<string name="waiting_list_label">Listă de așteptare</string>
<string name="settings_label">Setări</string>
@@ -40,7 +39,6 @@
<string name="length_prefix">Durată:\u0020</string>
<string name="size_prefix">Dimensiune:\u0020</string>
<string name="processing_label">Procesează</string>
- <string name="loading_label">Încărcare...</string>
<string name="save_username_password_label">Salvează numele de utilizator și parola</string>
<string name="close_label">închide</string>
<string name="retry_label">Reîncearcă</string>
@@ -50,7 +48,6 @@
<string name="mark_all_read_label">Marchează toate ca citite</string>
<string name="show_info_label">Arată informații</string>
<string name="share_link_label">Împarte adresă website</string>
- <string name="share_source_label">Împarte adresă feed</string>
<string name="feed_delete_confirmation_msg">Confirmați ștergerea feedului și a TUTUROR episoadelor pe care le-ați descărcat.</string>
<!--actions on feeditems-->
<string name="download_label">Descarcă</string>
@@ -85,7 +82,6 @@
<string name="download_error_malformed_url">URL malformat</string>
<string name="download_error_io_error">Eroare IO</string>
<string name="download_error_request_error">Eroare cerere</string>
- <string name="downloads_left">\u0020descărcări rămase</string>
<string name="download_notification_title">Descarcă date podcast</string>
<string name="download_report_content">%1$d descărcari cu succes, %2$d eșuate</string>
<string name="download_log_title_unknown">Titlu necunoscut</string>
@@ -136,12 +132,9 @@
<string name="queue_label">Coadă</string>
<string name="services_label">Servicii</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pune pauză când căștile sunt deconectate</string>
<string name="pref_followQueue_sum">Sari la următorul element din coadă cand se termină ascultarea</string>
<string name="playback_pref">Ascultare</string>
<string name="network_pref">Rețea</string>
- <string name="pref_autoUpdateIntervall_title">Interval actualizare</string>
- <string name="pref_autoUpdateIntervall_sum">Specifică un interval în care feedurile sunt actualizate automat sau oprește funcția</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Descarcă fișiere media doar pe WiFi</string>
<string name="pref_followQueue_title">Ascultare continuă</string>
<string name="pref_downloadMediaOnWifiOnly_title">Descărcare media pe WiFi</string>
@@ -191,7 +184,6 @@
<string name="select_all_label">Selectează toate</string>
<string name="deselect_all_label">Deselectează toate</string>
<string name="opml_export_label">Exportă OPML</string>
- <string name="exporting_label">Exportă...</string>
<string name="export_error_label">Eroare exportare</string>
<string name="opml_export_success_sum">Fișierul .opml a fost scris în:\u0020</string>
<!--Sleep timer-->
@@ -234,8 +226,10 @@
<!--Online feed view-->
<string name="subscribe_label">Abonează-te</string>
<string name="subscribed_label">Abonat</string>
- <string name="downloading_label">Se descarcă...</string>
<!--Content descriptions for image buttons-->
<!--Feed information screen-->
+ <!--Progress information-->
<!--AntennaPodSP-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-ru/strings.xml b/core/src/main/res/values-ru/strings.xml
index 187a92861..46a9d5feb 100644
--- a/core/src/main/res/values-ru/strings.xml
+++ b/core/src/main/res/values-ru/strings.xml
@@ -8,6 +8,8 @@
<string name="episodes_label">Выпуски</string>
<string name="new_episodes_label">Новые выпуски</string>
<string name="all_episodes_label">Все выпуски</string>
+ <string name="all_episodes_short_label">Все</string>
+ <string name="favorite_episodes_label">Избранное</string>
<string name="new_label">Новые</string>
<string name="waiting_list_label">В ожидании</string>
<string name="settings_label">Настройки</string>
@@ -17,7 +19,7 @@
<string name="downloads_completed_label">Завершено</string>
<string name="downloads_log_label">Журнал</string>
<string name="cancel_download_label">Отменить загрузку</string>
- <string name="playback_history_label">История воспроизведения</string>
+ <string name="playback_history_label">Журнал</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">Войти на gpodder.net</string>
<!--New episodes fragment-->
@@ -26,6 +28,14 @@
<!--Main activity-->
<string name="drawer_open">Открыть меню</string>
<string name="drawer_close">Закрыть меню</string>
+ <string name="drawer_preferences">Настройка бокового меню</string>
+ <string name="drawer_feed_order_unplayed_episodes">Сортировать по количеству</string>
+ <string name="drawer_feed_order_alphabetical">Сортировать по алфавиту</string>
+ <string name="drawer_feed_order_last_update">Сортировать по дате</string>
+ <string name="drawer_feed_counter_new_unplayed">Количество новых и непрослушанных выпусков</string>
+ <string name="drawer_feed_counter_new">Количество новых выпусков</string>
+ <string name="drawer_feed_counter_unplayed">Количество непрослушанных выпусков</string>
+ <string name="drawer_feed_counter_none">Ничего</string>
<!--Webview actions-->
<string name="open_in_browser_label">Открыть в браузере</string>
<string name="copy_url_label">Скопировать ссылку</string>
@@ -37,8 +47,11 @@
<!--Other-->
<string name="confirm_label">Подтвердить</string>
<string name="cancel_label">Отмена</string>
+ <string name="yes">Да</string>
+ <string name="no">Нет</string>
<string name="author_label">Автор</string>
<string name="language_label">Язык</string>
+ <string name="url_label">Адрес</string>
<string name="podcast_settings_label">Настройки</string>
<string name="cover_label">Обложка</string>
<string name="error_label">Ошибка</string>
@@ -53,12 +66,20 @@
<string name="length_prefix">Продолжительность:\u0020</string>
<string name="size_prefix">Размер:\u0020</string>
<string name="processing_label">Обработка</string>
- <string name="loading_label">Загрузка...</string>
<string name="save_username_password_label">Сохранить имя пользователя и пароль</string>
<string name="close_label">Закрыть</string>
<string name="retry_label">Повторить</string>
<string name="auto_download_label">Добавить в автозагрузки</string>
+ <string name="auto_download_apply_to_items_title">Применить к предыдущим выпускам</string>
+ <string name="auto_download_apply_to_items_message">Новые настройки <i>Автозагрузки</i> будут автоматически применены к новым выпускам. \nХотите ли вы применить их к ранее опубликованным выпускам?</string>
+ <string name="auto_delete_label">Автоматическое удаление выпусков\n(игнорирует общие настройки)</string>
<string name="parallel_downloads_suffix">\u0020одновременных загрузок</string>
+ <string name="feed_auto_download_global">Общие</string>
+ <string name="feed_auto_download_always">Всегда</string>
+ <string name="feed_auto_download_never">Никогда</string>
+ <string name="episode_cleanup_never">Никогда</string>
+ <string name="episode_cleanup_queue_removal">Когда не в очереди</string>
+ <string name="episode_cleanup_after_listening">После прослушивания</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL канала</string>
<string name="etxtFeedurlHint">www.example.com/feed</string>
@@ -71,13 +92,27 @@
<string name="mark_all_read_msg">Отметить все выпуски как прослушанные</string>
<string name="mark_all_read_confirmation_msg">Подтвердите, что хотите пометить все эпизоды как прослушанные.</string>
<string name="mark_all_read_feed_confirmation_msg">Подтвердите, что хотите пометить все эпизоды в этом канале как прослушанные.</string>
+ <string name="mark_all_seen_label">Отметить все как про</string>
<string name="show_info_label">Показать информацию</string>
<string name="remove_feed_label">Удалить подкаст</string>
- <string name="share_link_label">Поделиться ссылкой на сайт</string>
- <string name="share_source_label">Ссылка на канал</string>
+ <string name="share_link_label">Поделиться ссылкой</string>
+ <string name="share_link_with_position_label">Поделиться ссылкой с отметкой времени</string>
+ <string name="share_feed_url_label">Поделиться ссылкой на канал</string>
+ <string name="share_item_url_label">Поделиться ссылкой на выпуск</string>
+ <string name="share_item_url_with_position_label">Поделиться ссылкой с отметкой времени</string>
<string name="feed_delete_confirmation_msg">Подтвердите удаление канала и всех выпусков, загруженных с этого канала.</string>
<string name="feed_remover_msg">Удаление канала</string>
<string name="load_complete_feed">Обновить весь канал</string>
+ <string name="hide_episodes_title">Скрыть выпуск</string>
+ <string name="episode_actions">Применить действия</string>
+ <string name="hide_unplayed_episodes_label">Непрослушанное</string>
+ <string name="hide_paused_episodes_label">Приостановленное</string>
+ <string name="hide_played_episodes_label">Прослушанное</string>
+ <string name="hide_queued_episodes_label">В очереди</string>
+ <string name="hide_not_queued_episodes_label">Не в очереди</string>
+ <string name="hide_downloaded_episodes_label">Загружено</string>
+ <string name="hide_not_downloaded_episodes_label">Не загружено</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Последнее обновление не удалось</string>
<!--actions on feeditems-->
<string name="download_label">Загрузить</string>
<string name="play_label">Воспроизвести</string>
@@ -85,17 +120,24 @@
<string name="stop_label">Остановить</string>
<string name="stream_label">Воспроизвести из сети</string>
<string name="remove_label">Удалить</string>
- <string name="remove_episode_lable">Удалить</string>
- <string name="mark_read_label">Отметить как прочитанное</string>
- <string name="mark_unread_label">Отметить как непрочитанное</string>
+ <string name="remove_episode_lable">Удалить выпуск</string>
+ <string name="mark_read_label">Отметить как прослушанное</string>
<string name="marked_as_read_label">Помечено как прослушанное</string>
+ <string name="mark_unread_label">Отметить как непрослушанное</string>
<string name="add_to_queue_label">Добавить в очередь</string>
+ <string name="added_to_queue_label">Добавлено в очередь</string>
<string name="remove_from_queue_label">Удалить из очереди</string>
+ <string name="add_to_favorite_label">Добавить в избранное</string>
+ <string name="remove_from_favorite_label">Удалить из избранного</string>
<string name="visit_website_label">Посетить сайт</string>
<string name="support_label">Поддержать через Flattr</string>
<string name="enqueue_all_new">Добавить всё в очередь</string>
<string name="download_all">Загрузить всё</string>
<string name="skip_episode_label">Пропустить выпуск</string>
+ <string name="activate_auto_download">Включить автоматическую загрузку</string>
+ <string name="deactivate_auto_download">Выключить автоматическую загрузку</string>
+ <string name="reset_position">Сбросить время воспроизведения</string>
+ <string name="removed_item">Удалено</string>
<!--Download messages and labels-->
<string name="download_successful">успешно</string>
<string name="download_failed">не удалось</string>
@@ -113,12 +155,13 @@
<string name="download_error_unauthorized">Ошибка авторизации</string>
<string name="cancel_all_downloads_label">Отменить все загрузки</string>
<string name="download_canceled_msg">Загрузка отменена</string>
+ <string name="download_canceled_autodownload_enabled_msg">Загрузка отменена\nОтключена <i>Автоматическая загрузка</i> для этого эпизода</string>
<string name="download_report_title">Загрузки завершены</string>
+ <string name="download_report_content_title">Отчет о загрузках</string>
<string name="download_error_malformed_url">Неправильный адрес</string>
<string name="download_error_io_error">Ошибка ввода-вывода</string>
<string name="download_error_request_error">Ошибка запроса</string>
<string name="download_error_db_access">Ошибка доступа к базе данных</string>
- <string name="downloads_left">Осталось\u0020загрузок</string>
<string name="downloads_processing">Производится загрузка</string>
<string name="download_notification_title">Получение данных подкаста</string>
<string name="download_report_content">%1$d загрузок завершено, %2$d не удалось</string>
@@ -129,6 +172,11 @@
<string name="download_request_error_dialog_message_prefix">Ошибка при загрузки файла:\u0020</string>
<string name="authentication_notification_title">Необходима авторизация</string>
<string name="authentication_notification_msg">Для доступа к ресурсу необходимо ввести имя пользователя и пароль</string>
+ <string name="confirm_mobile_download_dialog_title">Подтвердите загрузку через мобильное соединение</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Загрузка через мобильное соединение отключена в настройках.\n\nВы можете добавить выпуск в очередь или временно разрешить загрузку.\n\n<small>Настройка сохранится на 10 минут.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Загрузка через мобильное соединение отключена в настройках.\n\nВы желаете временно разрешить загрузку?\n\n<small>Настройка сохранится на 10 минут.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Добавить в очередь</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Разрешить временно</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Ошибка</string>
<string name="player_stopped_msg">Ничего не воспроизводится</string>
@@ -141,7 +189,10 @@
<string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Буферизация</string>
<string name="playbackservice_notification_title">Воспроизведение подкаста</string>
+ <string name="unknown_media_key">AntennaPod - неизвестный ключ носителя: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Заблокировать очередь</string>
+ <string name="unlock_queue">Разблокировать очередь</string>
<string name="clear_queue_label">Очистить очередь</string>
<string name="undo">Отмена</string>
<string name="removed_from_queue">Удалено</string>
@@ -161,6 +212,7 @@
<string name="return_home_label">Вернуться к началу</string>
<string name="flattr_auth_success">Успешная авторизация. Теперь можно использовать Flattr прямо из приложения.</string>
<string name="no_flattr_token_title">Токен Flattr не найден</string>
+ <string name="no_flattr_token_notification_msg">Ваша учетная запись Flattr не подключена к AntennaPod. Нажмите здесь, чтобы войти.</string>
<string name="no_flattr_token_msg">Кажется, ваш аккаунт Flattr не подключен к AntennaPod. Можно подключить аккаунт к AntennaPod или посетить сайт канала, чтобы пожертвовать через Flattr прямо на сайте.</string>
<string name="authenticate_now_label">Авторизоваться</string>
<string name="action_forbidden_title">Действие запрещено</string>
@@ -183,6 +235,7 @@
<string name="download_plugin_label">Загрузить плагин</string>
<string name="no_playback_plugin_title">Плагин не установлен</string>
<string name="set_playback_speed_label">Скорость воспроизведения</string>
+ <string name="enable_sonic">Включить Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Список пуст</string>
<string name="no_feeds_label">Вы еще не подписаны ни на один канал</string>
@@ -192,20 +245,31 @@
<string name="queue_label">Очередь</string>
<string name="services_label">Сервисы</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Приостановить воспроизведение, когда наушники отсоединены</string>
+ <string name="pref_episode_cleanup_title">Удаление выпусков</string>
+ <string name="pref_pauseOnDisconnect_sum">Приостановить воспроизведение, когда наушники или bluetooth отключены</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Продолжать воспроизведение после подключения наушников</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Продолжать воспроизведение после подключения наушников или восстановления bluetooth-соединения</string>
<string name="pref_followQueue_sum">После завершения воспроизведения перейти к следующему в очереди</string>
<string name="pref_auto_delete_sum">Удалять эпизод после завершения воспроизведения</string>
<string name="pref_auto_delete_title">Автоматическое удаление</string>
+ <string name="pref_smart_mark_as_played_sum">Отмечать выпуск как прослушанный, даже если до завершения остается некоторое время</string>
+ <string name="pref_smart_mark_as_played_title">Предварительная отметка \"прослушано\"</string>
+ <string name="pref_skip_keeps_episodes_sum">Сохранять выпуски, помеченные как пропущенные</string>
+ <string name="pref_skip_keeps_episodes_title">Сохранять пропущенные выпуски</string>
<string name="playback_pref">Воспроизведение</string>
<string name="network_pref">Сеть</string>
- <string name="pref_autoUpdateIntervall_title">Интервал обновлений</string>
- <string name="pref_autoUpdateIntervall_sum">Укажите интервал через который каналы обновляются автоматически, или отключите его</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Время для обновлений</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Выбрать часы или время суток для автоматического обновления каналов</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Вы можете задать <i>интервал</i>, например \"каждые 2 часа\", выбрать <i>время</i>, например \"7:00\" или <i>отключить</i> автоматическое обновление. \n\n<small>Обратите внимание: время для обновлений приблизительное. Вы можете заметить небольшие задержки.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Отключить</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Задать интервал</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Задать время</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Загружать файлы только через Wi-Fi</string>
<string name="pref_followQueue_title">Непрерывное воспроизведение</string>
<string name="pref_downloadMediaOnWifiOnly_title">Загрузка по Wi-Fi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Наушники отсоединены</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Наушники отсоединены</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth-соединение восстановлено</string>
<string name="pref_mobileUpdate_title">Мобильные обновления</string>
<string name="pref_mobileUpdate_sum">Позволить обновления через мобильное интернет-подключение</string>
<string name="refreshing_label">Обновление</string>
@@ -217,8 +281,17 @@
<string name="pref_revokeAccess_title">Отозвать доступ</string>
<string name="pref_revokeAccess_sum">Отменить доступ этого приложения к вашему аккаунту Flattr.</string>
<string name="pref_auto_flattr_title">Автоматически поддерживать через Flattr</string>
+ <string name="pref_auto_flattr_sum">Настройка автоматической поддержки через Flattr</string>
<string name="user_interface_label">Интерфейс</string>
<string name="pref_set_theme_title">Выбор темы</string>
+ <string name="pref_nav_drawer_title">Настроить боковую панель</string>
+ <string name="pref_nav_drawer_sum">Настроить вид боковой панели</string>
+ <string name="pref_nav_drawer_items_title">Выбрать пункты боковой панели</string>
+ <string name="pref_nav_drawer_items_sum">Изменение отображения пунктов в меню боковой панели</string>
+ <string name="pref_nav_drawer_feed_order_title">Порядок подписок</string>
+ <string name="pref_nav_drawer_feed_order_sum">Выбрать порядок отображения подписок</string>
+ <string name="pref_nav_drawer_feed_counter_title">Счетчик подписок</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Выбрать какую информацию показывать в счетчике подписок</string>
<string name="pref_set_theme_sum">Изменить тему оформления AntennaPod</string>
<string name="pref_automatic_download_title">Автоматическая загрузка</string>
<string name="pref_automatic_download_sum">Настроить автоматическую загрузку выпусков.</string>
@@ -242,16 +315,30 @@
<string name="pref_gpodnet_setlogin_information_sum">Изменить информацию авторизации для аккаунта gpodder.net</string>
<string name="pref_playback_speed_title">Скорость воспроизведения</string>
<string name="pref_playback_speed_sum">Настроить скорости воспроизведения</string>
- <string name="pref_seek_delta_title">Перемотка</string>
- <string name="pref_seek_delta_sum">Пропускать секунд при перемотке назад или вперед</string>
+ <string name="pref_fast_forward">Интервал быстрой перемотки вперед</string>
+ <string name="pref_rewind">Интервал быстрой перемотки назад</string>
<string name="pref_gpodnet_sethostname_title">Задать имя узла</string>
<string name="pref_gpodnet_sethostname_use_default_host">Использовать узел по умолчанию</string>
<string name="pref_expandNotify_title">Расширенное уведомление</string>
<string name="pref_expandNotify_sum">Всегда показывать в уведомлении кнопки управления вопспроизведением</string>
<string name="pref_persistNotify_title">Постоянный контрооль воспроизведения</string>
<string name="pref_persistNotify_sum">Сохранять уведомление и кнопки воспроизведения на экране блокировки во время паузы.</string>
+ <string name="pref_lockscreen_background_title">Выбрать фон экрана блокировки</string>
+ <string name="pref_lockscreen_background_sum">Изменяет фон экрана блокировки на обложку выпуска. Кроме того показывает обложку в сторонних приложениях.</string>
+ <string name="pref_showDownloadReport_title">Показывать отчет о загрузках</string>
+ <string name="pref_showDownloadReport_sum">Если загрузка не удается, показывать отчет с подробностями об ошибке.</string>
<string name="pref_expand_notify_unsupport_toast">Версии Android ниже 4.1 не поддерживают расширенные уведомления.</string>
+ <string name="pref_queueAddToFront_sum">Добавлять новые выпуски в начало очереди.</string>
+ <string name="pref_queueAddToFront_title">В начало очереди.</string>
+ <string name="pref_smart_mark_as_played_disabled">Отключено</string>
+ <string name="pref_image_cache_size_title">Размер кэша для изображений</string>
+ <string name="pref_image_cache_size_sum">Размер дискового кэша для изображений</string>
+ <string name="crash_report_title">Отчет о сбое</string>
+ <string name="send_email">Отправить Email</string>
+ <string name="experimental_pref">Экспериментальные настройки</string>
+ <string name="pref_sonic_title">Проигрыватель Sonic</string>
<!--Auto-Flattr dialog-->
+ <string name="auto_flattr_enable">Включить автоматическую поддержку через Flattr</string>
<string name="auto_flattr_after_percent">Поддерживать через Flattr эпизоды, прослушанные на %d процентов</string>
<string name="auto_flattr_ater_beginning">Поддерживать эпизод через Flattr в начале воспроизведения</string>
<string name="auto_flattr_ater_end">Поддерживать эпизод через Flattr в конце воспроизведения</string>
@@ -264,7 +351,10 @@
<string name="found_in_title_label">Найдено в заголовке</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML файлы позволяют перемещать ваши подкасты из одного менеджера подкастов в другой.</string>
+ <string name="opml_import_option">Настройка %1$d</string>
<string name="opml_import_explanation_1">Укажите путь к файлу на устройстве</string>
+ <string name="opml_import_explanation_2">Откройте OPML-файл с помощью внешних приложений: Dropbox, Google Drive или любого файлового менеджера.</string>
+ <string name="opml_import_explanation_3">Множество приложений умеют <i>открывать</i> OPML-файлы <i>в</i> AntennaPod, например: Google Mail, Dropbox, Google Drive и большинство файловых менеджеров.</string>
<string name="start_import_label">Начать импорт</string>
<string name="opml_import_label">Импорт OPML</string>
<string name="opml_directory_error">Ошибка</string>
@@ -273,9 +363,11 @@
<string name="opml_import_error_dir_empty">Каталог для импорта пуст.</string>
<string name="select_all_label">Отметить все</string>
<string name="deselect_all_label">Снять все отметки</string>
+ <string name="choose_file_from_filesystem">Из файловой системы</string>
+ <string name="choose_file_from_external_application">С помощью внешнего приложения</string>
<string name="opml_export_label">Экспорт в OPML</string>
- <string name="exporting_label">Экспортируется...</string>
<string name="export_error_label">Ошибка экспорта</string>
+ <string name="opml_export_success_title">OPML успешно экспортирован.</string>
<string name="opml_export_success_sum">Файл OPML был записан в:\u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Установить таймер сна</string>
@@ -284,9 +376,30 @@
<string name="sleep_timer_label">Таймер сна</string>
<string name="time_left_label">Осталось времени:\u0020</string>
<string name="time_dialog_invalid_input">Неправильный ввод, время должно быть в виде числа</string>
- <string name="time_unit_seconds">с</string>
- <string name="time_unit_minutes">м</string>
- <string name="time_unit_hours">ч</string>
+ <string name="timer_about_to_expire_label"><b>Когда истекает таймер:</b></string>
+ <string name="shake_to_reset_label">Потрясти для сброса таймера</string>
+ <string name="timer_vibration_label">Вибрация</string>
+ <string name="time_seconds">с</string>
+ <string name="time_minutes">м</string>
+ <string name="time_hours">ч</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 секунда</item>
+ <item quantity="few">%d сек.</item>
+ <item quantity="many">%d секунд</item>
+ <item quantity="other">%d секунд</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 минута</item>
+ <item quantity="few">%d мин.</item>
+ <item quantity="many">%d минут</item>
+ <item quantity="other">%d минут</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 час</item>
+ <item quantity="few">%d ч.</item>
+ <item quantity="many">%d часов</item>
+ <item quantity="other">%d часов</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">Категории</string>
<string name="gpodnet_toplist_header">Лучшее</string>
@@ -324,15 +437,20 @@
<string name="create_folder_error_no_write_access">Запись в эту папку невозможна</string>
<string name="create_folder_error_already_exists">Папка уже существует</string>
<string name="create_folder_error">Невозможно создать папку</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" не существует</string>
+ <string name="folder_not_readable_error">\"%1$s\" недоступен для чтения</string>
+ <string name="folder_not_writable_error">\"%1$s\" недоступен для записи</string>
<string name="folder_not_empty_dialog_title">Папка не пуста</string>
<string name="folder_not_empty_dialog_msg">Выбранная папка не пуста. Загрузки и прочие файлы будут сохранены в эту папку. Продолжить?</string>
<string name="set_to_default_folder">Выберите папку по умолчанию</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Пауза вместо уменьшения громкости, когда другое приложение проигрывает звуки</string>
<string name="pref_pausePlaybackForFocusLoss_title">Пауза при прерывании</string>
+ <string name="pref_resumeAfterCall_sum">Продолжение воспроизведения после завершения звонка</string>
+ <string name="pref_resumeAfterCall_title">Включать после звонка</string>
+ <string name="pref_restart_required">AntennaPod нужно перезапустить для применения настроек.</string>
<!--Online feed view-->
<string name="subscribe_label">Подписаться</string>
<string name="subscribed_label">Подписка оформлена</string>
- <string name="downloading_label">Загрузка...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Показать главы</string>
<string name="show_shownotes_label">Показать примечания к выпуску</string>
@@ -354,7 +472,35 @@
<string name="load_next_page_label">Загрузить следующую страницу</string>
<!--Feed information screen-->
<string name="authentication_label">Авторизация</string>
+ <string name="authentication_descr">Изменить имя пользователя и пароль для этого подкаста и его выпусков.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Обновление базы данных</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Импорт подписок из одноцелевых приложений…</string>
<string name="search_itunes_label">Поиск в iTunes</string>
+ <string name="all_label">Все</string>
+ <string name="selected_all_label">Отмечены все выпуски</string>
+ <string name="none_label">Нет</string>
+ <string name="deselected_all_label">Отметки сняты со всех выпусков</string>
+ <string name="played_label">Прослушанное</string>
+ <string name="selected_played_label">Выбраны прослушанные выпуски</string>
+ <string name="unplayed_label">Непрослушанное</string>
+ <string name="selected_unplayed_label">Выбраны непрослушанные выпуски</string>
+ <string name="downloaded_label">Загружено</string>
+ <string name="selected_downloaded_label">Выбраны загруженные выпуски</string>
+ <string name="not_downloaded_label">Не загружено</string>
+ <string name="selected_not_downloaded_label">Выбраны незагруженные выпуски</string>
+ <string name="sort_title_a_z">Заголовку (А \u2192 Я)</string>
+ <string name="sort_title_z_a">Заголовку (Я \u2192 А)</string>
+ <string name="sort_date_new_old">Дате (Новое \u2192 Старое)</string>
+ <string name="sort_date_old_new">Дате (Старое \u2192 Новое)</string>
+ <string name="sort_duration_short_long">Продолжительности (Короткие \u2192 Длинные)</string>
+ <string name="sort_duration_long_short">Продолжительности (Длинные \u2192 Короткие)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Нравится AntennaPod?</string>
+ <string name="rating_message">Мы будем благодарны, если вы оцените AntennaPod.</string>
+ <string name="rating_never_label">Отстань от меня</string>
+ <string name="rating_later_label">Напомни позже</string>
+ <string name="rating_now_label">Конечно, давай!</string>
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-sv-rSE/strings.xml b/core/src/main/res/values-sv-rSE/strings.xml
index e70ca5d3f..b91b030d6 100644
--- a/core/src/main/res/values-sv-rSE/strings.xml
+++ b/core/src/main/res/values-sv-rSE/strings.xml
@@ -3,42 +3,58 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Flöden</string>
- <string name="add_feed_label">Lägg till podcast</string>
+ <string name="add_feed_label">Lägg till Podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">AVSNITT</string>
- <string name="new_episodes_label">Nya episoder</string>
- <string name="all_episodes_label">Alla episoder</string>
- <string name="new_label">Ny</string>
+ <string name="episodes_label">Episoder</string>
+ <string name="new_episodes_label">Nya Episoder</string>
+ <string name="all_episodes_label">Alla Episoder</string>
+ <string name="all_episodes_short_label">Alla</string>
+ <string name="favorite_episodes_label">Favoriter</string>
+ <string name="new_label">Nya</string>
<string name="waiting_list_label">Väntelista</string>
<string name="settings_label">Inställningar</string>
- <string name="add_new_feed_label">Lägg till podcast</string>
+ <string name="add_new_feed_label">Lägg till Podcast</string>
<string name="downloads_label">Nedladdningar</string>
- <string name="downloads_running_label">Körs</string>
+ <string name="downloads_running_label">Pågående</string>
<string name="downloads_completed_label">Färdiga</string>
<string name="downloads_log_label">Logg</string>
- <string name="cancel_download_label">Avbryt nedladdning</string>
+ <string name="cancel_download_label">Avbryt\nNedladdning</string>
<string name="playback_history_label">Uppspelningshistorik</string>
<string name="gpodnet_main_label">gpodder.net</string>
- <string name="gpodnet_auth_label">gpodder.net login</string>
+ <string name="gpodnet_auth_label">Inloggning till gpodder.net</string>
+ <string name="free_space_label">%1$s kvar</string>
+ <string name="episode_cache_full_title">Episodcachen är full</string>
+ <string name="episode_cache_full_message">Episodcachens gräns har nåtts. Du kan öka cachens storlek i inställningarna.</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Nyligen publicerade</string>
- <string name="episode_filter_label">Visa bara nya episoder</string>
+ <string name="episode_filter_label">Visa bara nya Episoder</string>
<!--Main activity-->
<string name="drawer_open">Öppna meny</string>
<string name="drawer_close">Stäng meny</string>
+ <string name="drawer_preferences">Menyinställningar</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sortera efter räknare</string>
+ <string name="drawer_feed_order_alphabetical">Sortera alfabetiskt</string>
+ <string name="drawer_feed_order_last_update">Sortera efter publiceringsdatum</string>
+ <string name="drawer_feed_counter_new_unplayed">Antal nya och ospelade episoder</string>
+ <string name="drawer_feed_counter_new">Antal nya episoder</string>
+ <string name="drawer_feed_counter_unplayed">Antal ospelade episoder</string>
+ <string name="drawer_feed_counter_none">Inga</string>
<!--Webview actions-->
- <string name="open_in_browser_label">Öppna i webbläsare</string>
+ <string name="open_in_browser_label">Öppna i Webbläsare</string>
<string name="copy_url_label">Kopiera URL</string>
<string name="share_url_label">Dela URL</string>
- <string name="copied_url_msg">Kopierade URL till clipboard.</string>
- <string name="go_to_position_label">Gå hit</string>
+ <string name="copied_url_msg">Kopierade URL:en till Urklipp</string>
+ <string name="go_to_position_label">Gå till denna Position</string>
<!--Playback history-->
- <string name="clear_history_label">Rensa historik</string>
+ <string name="clear_history_label">Rensa Historiken</string>
<!--Other-->
<string name="confirm_label">Bekräfta</string>
<string name="cancel_label">Avbryt</string>
+ <string name="yes">Ja</string>
+ <string name="no">Nej</string>
<string name="author_label">Skapare</string>
<string name="language_label">Språk</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Inställningar</string>
<string name="cover_label">Bild</string>
<string name="error_label">Fel</string>
@@ -48,36 +64,66 @@
<string name="chapters_label">Kapitel</string>
<string name="shownotes_label">Shownotes</string>
<string name="description_label">Beskrivning</string>
- <string name="most_recent_prefix">Senaste avsnittet:\u0020</string>
+ <string name="most_recent_prefix">Senaste episoden:\u0020</string>
<string name="episodes_suffix">\u0020episoder</string>
<string name="length_prefix">Längd:\u0020</string>
<string name="size_prefix">Storlek:\u0020</string>
<string name="processing_label">Bearbetar</string>
- <string name="loading_label">Laddar...</string>
+ <string name="loading_label">Laddar…</string>
<string name="save_username_password_label">Spara användarnamn och lösenord</string>
<string name="close_label">Stäng</string>
<string name="retry_label">Försök igen</string>
<string name="auto_download_label">Inkludera i automatiska nedladdningar</string>
+ <string name="auto_download_apply_to_items_title">Applicera på Föregående Episoder</string>
+ <string name="auto_download_apply_to_items_message">Den nya inställningen <i>Automatisk Nedladdning</i> kommer automatiskt att appliceras på nya episoder.\nVill du även applicera det på tidigare publicerade episoder?</string>
+ <string name="auto_delete_label">Ta automatiskt bort episod\n(åsidosätter global standardinställning)</string>
<string name="parallel_downloads_suffix">\u0020parallella nedladdningar</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Alltid</string>
+ <string name="feed_auto_download_never">Aldrig</string>
+ <string name="send_label">Skicka…</string>
+ <string name="episode_cleanup_never">Aldrig</string>
+ <string name="episode_cleanup_queue_removal">Om inte köad</string>
+ <string name="episode_cleanup_after_listening">Efter färdigspelad</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dag efter färdigspelad</item>
+ <item quantity="other">%d dagar efter färdigspelad</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Flödets URL</string>
<string name="etxtFeedurlHint">URL till flöde eller webbsida</string>
<string name="txtvfeedurl_label">Lägg till podcast via URL</string>
- <string name="podcastdirectories_label">Hitta podcast i mapp</string>
- <string name="podcastdirectories_descr">Du kan söka efter podcasts baserat på namn, kategori eller populäritet med tjänsten gpodder.net</string>
+ <string name="podcastdirectories_label">Hitta Podcast i Biblioteket</string>
+ <string name="podcastdirectories_descr">Du kan söka efter podcasts baserat på namn, kategori eller populäritet med tjänsten gpodder.net eller på iTunes Store.</string>
<string name="browse_gpoddernet_label">Bläddra på gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Markera alla som lästa</string>
- <string name="mark_all_read_msg">Markera alla episoder som lästa</string>
- <string name="mark_all_read_confirmation_msg">Bekräfta att du vill markera alla avsnitt som lästa.</string>
- <string name="mark_all_read_feed_confirmation_msg">Bekräfta att du vill markera alla avsnitt i detta flöde som lästa.</string>
+ <string name="mark_all_read_label">Markera alla som spelade</string>
+ <string name="mark_all_read_msg">Markera alla Episoder som spelade</string>
+ <string name="mark_all_read_confirmation_msg">Bekräfta att du verkligen vill markera alla episoder som spelade.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Bekräfta att du verkligen vill markera alla episoder i detta flöde som spelade.</string>
+ <string name="mark_all_seen_label">Markera alla som sedda</string>
<string name="show_info_label">Visa information</string>
- <string name="remove_feed_label">Ta bort podcast</string>
- <string name="share_link_label">Dela hemsidans länk</string>
- <string name="share_source_label">Dela flödeslänk</string>
+ <string name="remove_feed_label">Ta bort Podcast</string>
+ <string name="share_label">Dela…</string>
+ <string name="share_link_label">Dela Länk</string>
+ <string name="share_link_with_position_label">Dela Länk med Position</string>
+ <string name="share_feed_url_label">Dela Flödets URL</string>
+ <string name="share_item_url_label">Dela Episodens URL</string>
+ <string name="share_item_url_with_position_label">Dela Episodens URL med Position</string>
<string name="feed_delete_confirmation_msg">Bekräfta att du vill ta bort denna feed och ALLA avsnitt av denna feed som du har hämtat.</string>
- <string name="feed_remover_msg">Tar bort flöde</string>
- <string name="load_complete_feed">Uppdatera hela flödet</string>
+ <string name="feed_remover_msg">Tar bort Flöde</string>
+ <string name="load_complete_feed">Uppdatera hela Flödet</string>
+ <string name="hide_episodes_title">Dölj Episoder</string>
+ <string name="episode_actions">Applicera åtgärder</string>
+ <string name="hide_unplayed_episodes_label">Ospelade</string>
+ <string name="hide_paused_episodes_label">Pausade</string>
+ <string name="hide_played_episodes_label">Spelad</string>
+ <string name="hide_queued_episodes_label">Köade</string>
+ <string name="hide_not_queued_episodes_label">Inte köade</string>
+ <string name="hide_downloaded_episodes_label">Nedladdade</string>
+ <string name="hide_not_downloaded_episodes_label">Ej nedladdade</string>
+ <string name="filtered_label">Filtrerad</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Senaste uppdateringen misslyckades</string>
<!--actions on feeditems-->
<string name="download_label">Ladda ned</string>
<string name="play_label">Spela</string>
@@ -85,50 +131,69 @@
<string name="stop_label">Stopp</string>
<string name="stream_label">Strömma</string>
<string name="remove_label">Ta bort</string>
- <string name="remove_episode_lable">Ta bort episod</string>
- <string name="mark_read_label">Markera som läst</string>
- <string name="mark_unread_label">Markera som oläst</string>
- <string name="marked_as_read_label">Markerad som läst</string>
+ <string name="remove_episode_lable">Ta bort Episod</string>
+ <string name="mark_read_label">Markera som spelad</string>
+ <string name="marked_as_read_label">Markera som spelad</string>
+ <string name="mark_unread_label">Markera som ospelad</string>
<string name="add_to_queue_label">Lägg till i kön</string>
+ <string name="added_to_queue_label">Lägg till i Kö</string>
<string name="remove_from_queue_label">Ta bort från Kön</string>
+ <string name="add_to_favorite_label">Lägg till i Favoriter</string>
+ <string name="added_to_favorites">Tillagd i Favoriter</string>
+ <string name="remove_from_favorite_label">Ta bort från Favoriter</string>
+ <string name="removed_from_favorites">Borrtagen ur Favoriter</string>
<string name="visit_website_label">Besök websidan</string>
<string name="support_label">Flattra det här</string>
<string name="enqueue_all_new">Lägg till alla i kön</string>
<string name="download_all">Ladda ner alla</string>
- <string name="skip_episode_label">Hoppa över episod</string>
+ <string name="skip_episode_label">Hoppa över episoden</string>
+ <string name="activate_auto_download">Aktivera Automatisk Nedladdning</string>
+ <string name="deactivate_auto_download">Avaktivera Automatisk Nedladdning</string>
+ <string name="reset_position">Nollställ Uppspelningspositionen</string>
+ <string name="removed_item">Borttagen</string>
<!--Download messages and labels-->
<string name="download_successful">lyckades</string>
<string name="download_failed">misslyckades</string>
<string name="download_pending">Avvaktar nedladdning</string>
<string name="download_running">Nedladdning pågår</string>
- <string name="download_error_device_not_found">Lagringsenhet hittades inte</string>
- <string name="download_error_insufficient_space">Otillräckligt utrymme</string>
+ <string name="download_error_device_not_found">Hittade ingen lagringsenhet</string>
+ <string name="download_error_insufficient_space">Otillräckligt Utrymme</string>
<string name="download_error_file_error">Filfel</string>
<string name="download_error_http_data_error">HTTP data fel</string>
<string name="download_error_error_unknown">Okänt fel</string>
<string name="download_error_parser_exception">Tolkningsfel</string>
- <string name="download_error_unsupported_type">Flödestyp utan stöd</string>
+ <string name="download_error_unsupported_type">Flödestypen stöds inte</string>
<string name="download_error_connection_error">Anslutningsfel</string>
- <string name="download_error_unknown_host">Okänd värd</string>
- <string name="download_error_unauthorized">Autentiseringsproblem</string>
+ <string name="download_error_unknown_host">Okänd Värd</string>
+ <string name="download_error_unauthorized">Autentiseringsfel</string>
<string name="cancel_all_downloads_label">Avbryt alla nedladdningar</string>
<string name="download_canceled_msg">Nedladdning avbruten</string>
- <string name="download_report_title">Nedladdningar färdiga</string>
+ <string name="download_canceled_autodownload_enabled_msg">Nedladdning avbruten\nStängde av <i>Automatisk nedladdning</i> för denna sak</string>
+ <string name="download_report_title">Nedladdningar avslutades med fel</string>
+ <string name="download_report_content_title">Nedladdningsrapport</string>
<string name="download_error_malformed_url">Felaktig webbadress</string>
<string name="download_error_io_error">IO fel</string>
- <string name="download_error_request_error">Request fel</string>
- <string name="download_error_db_access">Ingen tillgång till databasen</string>
- <string name="downloads_left">\u0020Nedladdningar kvar</string>
+ <string name="download_error_request_error">Förfrågningsfel</string>
+ <string name="download_error_db_access">Åtkomstfel till databasen</string>
+ <plurals name="downloads_left">
+ <item quantity="one">%d nedladdning kvar</item>
+ <item quantity="other">%d nedladdningar kvar</item>
+ </plurals>
<string name="downloads_processing">Bearbetar nedladdningar</string>
<string name="download_notification_title">Laddar ner podcastdata</string>
<string name="download_report_content">%1$d nedladdningar lyckades, %2$d misslyckades</string>
- <string name="download_log_title_unknown">Okänd titel</string>
+ <string name="download_log_title_unknown">Okänd Titel</string>
<string name="download_type_feed">Flöde</string>
<string name="download_type_media">Mediafil</string>
<string name="download_type_image">Bild</string>
<string name="download_request_error_dialog_message_prefix">Ett fel uppstod vid försöket att ladda ner filen:\u0020</string>
<string name="authentication_notification_title">Autentisering krävs</string>
<string name="authentication_notification_msg">Resursen du begärde kräver ett användarnamn och ett lösenord</string>
+ <string name="confirm_mobile_download_dialog_title">Bekräfta mobil nedladdning</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Nedladdning över mobil dataanslutning är avaktiverat i inställningarna.\n\nDu kan välja att antingen bara lägga till episoden i kön eller att tillfälligt tillåta nedladdning.\n\n<small>Ditt val gäller i 10 minuter.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Nedladdning över mobil dataanslutning är avaktiverat i inställningarna.\n\nVill du tillfälligt tillåta nedladdning?\n\n<small>Ditt val gäller i 10 minuter.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Köa</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Tillåt tillfälligt</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Fel! </string>
<string name="player_stopped_msg">Inget media spelar</string>
@@ -143,7 +208,11 @@
<string name="playbackservice_notification_title">Spelar podcast</string>
<string name="unknown_media_key">AntannaPod - Okänd mediaknapp: %1$d</string>
<!--Queue operations-->
- <string name="clear_queue_label">Rensa kön</string>
+ <string name="lock_queue">Lås Kön</string>
+ <string name="unlock_queue">Lås upp Kön</string>
+ <string name="queue_locked">Kön låst</string>
+ <string name="queue_unlocked">Kön upplåst</string>
+ <string name="clear_queue_label">Rensa Kön</string>
<string name="undo">Ångra</string>
<string name="removed_from_queue">Föremålet avlägsnades</string>
<string name="move_to_top_label">Flytta längst upp</string>
@@ -154,7 +223,7 @@
<string name="duration">Längd</string>
<string name="ascending">Stigande</string>
<string name="descending">Fallande</string>
- <string name="clear_queue_confirmation_msg">Bekräfta att du vill rensa kön från ALLA avsnitt.</string>
+ <string name="clear_queue_confirmation_msg">Bekräfta att du vill rensa kön från ALLA episoder.</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr inloggning</string>
<string name="flattr_auth_explanation">Tryck på knappen nedan för att starta autentiseringen. Du kommer att vidarebefordras till Flattrs inloggningsskärm i din webbläsare och uppmanas att ge AntennaPod tillstånd att Flattra saker. Efter att du har gett tillstånd, kommer du automatiskt tillbaka till den här skärmen.</string>
@@ -184,54 +253,80 @@
<!--Variable Speed-->
<string name="download_plugin_label">Ladda ner tillägg</string>
<string name="no_playback_plugin_title">Tillägg ej installerat</string>
- <string name="no_playback_plugin_msg">För att variabel uppspelningshastighet skall fungera måste ett tredjepartstillägg installeras.\n\nTryck på \'Ladda ner tillägg\' för att ladda ner ett gratis tillägg från Play Store.\n\nAntennaPod ansvarar inte för problem med detta tillägg och de bör rapporteras till tilläggets skapare.</string>
+ <string name="no_playback_plugin_or_sonic_msg">För att variabel uppspelningshastighet ska fungera rekommenderas att aktivera den inbyggda Sonic mediaspelaren [Android 4.1+].\n\nAlternativt kan du ladda ner tredjepartstillägget <i>Prestissimo</i> från Google Play.\nProblem med Prestissimo är inte AntennaPods ansvar och bör rapporteras till tilläggets ägare.</string>
<string name="set_playback_speed_label">Uppspelningshastigheter</string>
+ <string name="enable_sonic">Aktivera Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Det finns inget i denna lista.</string>
<string name="no_feeds_label">Du har inte prenumererat på något flöde ännu.</string>
+ <string name="no_chapters_label">Denna episod har inga kapitel.</string>
<!--Preferences-->
<string name="other_pref">Annat</string>
<string name="about_pref">Om</string>
<string name="queue_label">Kö</string>
<string name="services_label">Tjänster</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pausa uppspelningen när hörlurarna bortkopplas</string>
+ <string name="pref_episode_cleanup_title">Episodupprensning</string>
+ <string name="pref_episode_cleanup_summary">Episoder som inte är i kön och inte är favoriter kan tas bort om Automatisk Nedladdning behöver utrymme för nya episoder</string>
+ <string name="pref_pauseOnDisconnect_sum">Pausa uppspelningen när hörlurar eller bluetooth kopplas ifrån.</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Fortsätt uppspelningen när hörlurarna återansluts</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Fortsätt uppspelningen när bluetooth återansluts</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Framåt-knappen hoppar</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">Hoppa till nästa episod istället för att snabbspola vid tryck på hårdvaruknappen</string>
<string name="pref_followQueue_sum">Hoppa till nästa i kön när uppspelningen är klar</string>
<string name="pref_auto_delete_sum">Ta bort episoden när uppspelningen är klar</string>
- <string name="pref_auto_delete_title">Automatisk borttagning</string>
+ <string name="pref_auto_delete_title">Automatisk Borttagning</string>
+ <string name="pref_smart_mark_as_played_sum">Markera episoder som spelade även om mindre än ett visst antal sekunder är kvar</string>
+ <string name="pref_smart_mark_as_played_title">Smart markering av uppspelat innehåll</string>
+ <string name="pref_skip_keeps_episodes_sum">Ta inte bort episoder när de hoppas över</string>
+ <string name="pref_skip_keeps_episodes_title">Behåll Överhoppade Episoder</string>
<string name="playback_pref">Uppspelning</string>
<string name="network_pref">Nätverk </string>
- <string name="pref_autoUpdateIntervall_title">Uppdateringsintervall</string>
- <string name="pref_autoUpdateIntervall_sum">Ange ett intervall för att automatiskt uppdatera flödet eller avaktivera det</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Uppdateringsintervall eller Tid på Dagen</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Ange ett intervall eller specifik tid på dagen för att uppdatera flödena automatisk.</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Du kan välja ett <i>intervall</i> som \"var 2 timmar\", en specifik <i>tid på dagen</i> som \"07:00\" eller <i>avaktivera</i> automatiska uppdateringar helt.\n\n<small>Notera: Uppdateringstiderna är inte exakta. Korta fördröjningar kan uppstå.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Avaktivera</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Sätt intervall</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Sätt Tid på Dagen</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">var %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">vid %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Hämta mediefiler endast över WiFi</string>
- <string name="pref_followQueue_title">Kontinuerlig uppspelning</string>
+ <string name="pref_followQueue_title">Kontinuerlig Uppspelning</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi nedladdning</string>
- <string name="pref_pauseOnHeadsetDisconnect_title">Hörlurar bortkopplade</string>
- <string name="pref_unpauseOnHeadsetReconnect_title">Hörlurar återanslutna</string>
- <string name="pref_mobileUpdate_title">Mobila uppdateringar</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Hörlurar Bortkopplas</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">Hörlurar Återanslutna</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Blutetooth Återansluts</string>
+ <string name="pref_mobileUpdate_title">Mobila Uppdateringar</string>
<string name="pref_mobileUpdate_sum">Tillåt uppdateringar via mobil dataanslutning</string>
<string name="refreshing_label">Uppdaterar</string>
<string name="flattr_settings_label">Flattr inställningar</string>
<string name="pref_flattr_auth_title">Flattr inloggning</string>
<string name="pref_flattr_auth_sum">För att Flattra saker direkt från appen, logga in på ditt Flattr-konto.</string>
- <string name="pref_flattr_this_app_title">Flattra den här appen</string>
+ <string name="pref_flattr_this_app_title">Flattra denna App</string>
<string name="pref_flattr_this_app_sum">Stöd utvecklingen av AntennaPod genom att flattra den. Tack!</string>
<string name="pref_revokeAccess_title">Återkalla åtkomst</string>
<string name="pref_revokeAccess_sum">Återkalla behörigheten till ditt Flattr-konto för denna app.</string>
<string name="pref_auto_flattr_title">Automatisk Flattring</string>
<string name="pref_auto_flattr_sum">Konfigurerar automatisk Flattring</string>
<string name="user_interface_label">Användargränssnitt</string>
- <string name="pref_set_theme_title">Välj tema</string>
+ <string name="pref_set_theme_title">Välj Tema</string>
+ <string name="pref_nav_drawer_title">Anpassa Navigeringsmenyn</string>
+ <string name="pref_nav_drawer_sum">Anpassa utseendet på navigeringmenyn.</string>
+ <string name="pref_nav_drawer_items_title">Välj Objekt i Navigeringsmenyn</string>
+ <string name="pref_nav_drawer_items_sum">Ändra vilka saker som visas på navigationsmenyn.</string>
+ <string name="pref_nav_drawer_feed_order_title">Välj Prenumerationsordning</string>
+ <string name="pref_nav_drawer_feed_order_sum">Ändra ordningen på dina prenumerationer</string>
+ <string name="pref_nav_drawer_feed_counter_title">Val För Prenumerationsräknaren</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Ändra informationen som visas av prenumerationsräknaren</string>
<string name="pref_set_theme_sum">Ändra utseendet på AntennaPod.</string>
- <string name="pref_automatic_download_title">Automatisk nedladdning</string>
+ <string name="pref_automatic_download_title">Automatisk Nedladdning</string>
<string name="pref_automatic_download_sum">Konfigurera automatisk nedladdning av episoder.</string>
<string name="pref_autodl_wifi_filter_title">Aktivera WiFi filtrering</string>
<string name="pref_autodl_wifi_filter_sum">Tillåt automatisk nedladdning endast för utvalda WiFi-nätverk.</string>
<string name="pref_automatic_download_on_battery_title">Nedladdning vid batteridrift</string>
<string name="pref_automatic_download_on_battery_sum">Tillåt automatisk nedladdning när batteriet inte laddas</string>
- <string name="pref_parallel_downloads_title">Parallella nedladdningar</string>
- <string name="pref_episode_cache_title">Avsnittscache</string>
+ <string name="pref_parallel_downloads_title">Parallella Nedladdningar</string>
+ <string name="pref_episode_cache_title">Episodcache</string>
<string name="pref_theme_title_light">Ljust</string>
<string name="pref_theme_title_dark">Mörkt</string>
<string name="pref_episode_cache_unlimited">Obegränsat</string>
@@ -246,24 +341,38 @@
<string name="pref_gpodnet_setlogin_information_sum">Ändra inloggningsinformationen för ditt gpodder.net konto.</string>
<string name="pref_playback_speed_title">Uppspelningshastigheter</string>
<string name="pref_playback_speed_sum">Anpassa de tillgängliga hastigheterna för variabel uppspelningshastighet.</string>
- <string name="pref_seek_delta_title">Söklängd</string>
- <string name="pref_seek_delta_sum">Sök så här många sekunder vid snabbspolning bakåt eller framåt</string>
+ <string name="pref_fast_forward">Spola framåt</string>
+ <string name="pref_rewind">Spola bakåt</string>
<string name="pref_gpodnet_sethostname_title">Sätt värdnamn</string>
<string name="pref_gpodnet_sethostname_use_default_host">Använd standardvärden</string>
- <string name="pref_expandNotify_title">Expandera notifieringar</string>
- <string name="pref_expandNotify_sum">Expandera alltid notifieringen för att visa uppspelningskontrollerna.</string>
- <string name="pref_persistNotify_title">Bestående uppspelningskontroller</string>
- <string name="pref_persistNotify_sum">Behåll notifiering och kontroller på låsskärmen när uppspelningen pausas.</string>
- <string name="pref_expand_notify_unsupport_toast">Androidversioner före 4.1 har inte stöd för expanderade notifieringar.</string>
- <string name="pref_queueAddToFront_sum">Lägg till avsnitt först i kön.</string>
- <string name="pref_queueAddToFront_title">Köa först.</string>
+ <string name="pref_expandNotify_title">Expandera aviseringen</string>
+ <string name="pref_expandNotify_sum">Expandera alltid aviseringen för att visa uppspelningskontrollerna.</string>
+ <string name="pref_persistNotify_title">Bestående Uppspelningskontroller</string>
+ <string name="pref_persistNotify_sum">Behåll avisering och kontroller på låsskärmen när uppspelningen pausas.</string>
+ <string name="pref_lockscreen_background_title">Sätt Låsskärmens Bakgrund</string>
+ <string name="pref_lockscreen_background_sum">Sätt låsskärmens bakgrund till den spelade episodens bild. En bieffekt är att även tredjepartsappar kan visa bilden.</string>
+ <string name="pref_showDownloadReport_title">Visa Nedladdningsrapport</string>
+ <string name="pref_showDownloadReport_sum">Visa en rapport med detaljer om felet när nedladdningar misslyckas.</string>
+ <string name="pref_expand_notify_unsupport_toast">Androidversioner före 4.1 har inte stöd för expanderade aviseringar.</string>
+ <string name="pref_queueAddToFront_sum">Lägg till episoder först i kön.</string>
+ <string name="pref_queueAddToFront_title">Köa Först</string>
+ <string name="pref_smart_mark_as_played_disabled">Avaktiverad</string>
+ <string name="pref_image_cache_size_title">Bildcachestorlek</string>
+ <string name="pref_image_cache_size_sum">Storleken på bildcachen på disken.</string>
+ <string name="crash_report_title">Krashrapport</string>
+ <string name="crash_report_sum">Sänd den senaste krashrapporten via e-post</string>
+ <string name="send_email">Sänd e-post</string>
+ <string name="experimental_pref">Experimentellt</string>
+ <string name="pref_sonic_title">Sonic mediaspelare</string>
+ <string name="pref_sonic_message">Använd den inbyggda Sonic mediaspelare som ersättning för Androids egna mediaspelare och Prestissimo</string>
+ <string name="pref_current_value">Nuvarande värde: %1$s</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Aktivera automatisk Flattring</string>
<string name="auto_flattr_after_percent">Flattra episoden så snart %d procent har spelats</string>
<string name="auto_flattr_ater_beginning">Flattra episoden när den startas</string>
<string name="auto_flattr_ater_end">Flattra episoden när den spelats klart</string>
<!--Search-->
- <string name="search_hint">Sök efter flöden eller avsnitt</string>
+ <string name="search_hint">Sök efter flöden eller episoder</string>
<string name="found_in_shownotes_label">Hittad i shownotes</string>
<string name="found_in_chapters_label">Hittad i kapitel</string>
<string name="search_status_no_results">Inga resultat hittades</string>
@@ -271,23 +380,25 @@
<string name="found_in_title_label">Hittad i titeln</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML-filer låter dig flytta dina podcasts från en podcatcher till en annan.</string>
+ <string name="opml_import_option">Val %1$d</string>
<string name="opml_import_explanation_1">Välj en specifik sökväg från det lokala filsystemet.</string>
<string name="opml_import_explanation_2">Använd en extern applikation som Dropbox, Google Drive eller ditt favoritval av filhanterare för att öppna en OPML fil.</string>
<string name="opml_import_explanation_3">Flera applikationer som Google Mail, Dropbox, Google Drive och de flesta filhanterare kan <i>öppna</i> OPML filer <i>med</i> AntennaPod.</string>
<string name="start_import_label">Påbörja importering</string>
- <string name="opml_import_label">Importera OPML-fil</string>
+ <string name="opml_import_label">OPML Importering</string>
<string name="opml_directory_error">FEL! </string>
<string name="reading_opml_label">Läser OPML-fil</string>
<string name="opml_reader_error">Ett fel har skett vid iläsning av opml dokumentet:</string>
<string name="opml_import_error_dir_empty">Katalogen är tom.</string>
<string name="select_all_label">Välj alla</string>
<string name="deselect_all_label">Avmarkera alla</string>
+ <string name="select_options_label">Välj…</string>
<string name="choose_file_from_filesystem">Från lokalt filsystem</string>
<string name="choose_file_from_external_application">Använd extern applikation</string>
<string name="opml_export_label">OPML export</string>
- <string name="exporting_label">Exporterar...</string>
+ <string name="exporting_label">Exporterar…</string>
<string name="export_error_label">Exporteringsfel</string>
- <string name="opml_export_success_title">OPML export lyckades</string>
+ <string name="opml_export_success_title">OPML Exportering lyckades.</string>
<string name="opml_export_success_sum">.opml filen skrevs till:\u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Ställ in sömntimer</string>
@@ -296,9 +407,24 @@
<string name="sleep_timer_label">Sömntimer</string>
<string name="time_left_label">Återstående tid:\u0020</string>
<string name="time_dialog_invalid_input">Ogiltigt tal, tiden måste vara ett heltal</string>
- <string name="time_unit_seconds">sekunder</string>
- <string name="time_unit_minutes">minuter</string>
- <string name="time_unit_hours">timmar</string>
+ <string name="timer_about_to_expire_label"><b>När timern är nära noll:</b></string>
+ <string name="shake_to_reset_label">Skaka för att återställa</string>
+ <string name="timer_vibration_label">Vibrera</string>
+ <string name="time_seconds">sekunder</string>
+ <string name="time_minutes">minuter</string>
+ <string name="time_hours">timmar</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 sekund</item>
+ <item quantity="other">%d sekunder</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minut</item>
+ <item quantity="other">%d minuter</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 timme</item>
+ <item quantity="other">%d timmar</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORIER</string>
<string name="gpodnet_toplist_header">BÄSTA PODCASTS</string>
@@ -330,21 +456,28 @@
<!--Directory chooser-->
<string name="selected_folder_label">Vald mapp:</string>
<string name="create_folder_label">Skapa mapp</string>
- <string name="choose_data_directory">Välj mapp</string>
+ <string name="choose_data_directory">Välj Datakatalog</string>
+ <string name="choose_data_directory_message">Välj rotkatalogen för din data. AntennaPod skapar de underkataloger som behövs.</string>
<string name="create_folder_msg">Skapa ny mapp med namnet \"%1$s\"?</string>
<string name="create_folder_success">Skapade ny mapp</string>
<string name="create_folder_error_no_write_access">Kan inte skriva till den här mappen</string>
<string name="create_folder_error_already_exists">Mappen finns redan</string>
<string name="create_folder_error">Kunde inte skapa mapp</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" existerar inte</string>
+ <string name="folder_not_readable_error">\"%1$s\" är inte läsbar</string>
+ <string name="folder_not_writable_error">\"%1$s\" är inte skrivbar</string>
<string name="folder_not_empty_dialog_title">Mappen är inte tom</string>
<string name="folder_not_empty_dialog_msg">Den mapp du har valt är inte tom. Filer kommer att placeras direkt i denna mapp. Fortsätt ändå?</string>
<string name="set_to_default_folder">Välj standardmapp</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pausa uppspelning istället för att sänka volymen när en annan app vill spela ljud</string>
- <string name="pref_pausePlaybackForFocusLoss_title">Pausa för avbrott</string>
+ <string name="pref_pausePlaybackForFocusLoss_title">Pausa vid Avbrott</string>
+ <string name="pref_resumeAfterCall_sum">Återuppta uppspelning när ett telefonsamtal avslutas</string>
+ <string name="pref_resumeAfterCall_title">Fortsätt efter Samtal</string>
+ <string name="pref_restart_required">AntennaPod behöver startas om för att denna inställning ska gälla.</string>
<!--Online feed view-->
<string name="subscribe_label">Prenumerera</string>
<string name="subscribed_label">Prenumererar</string>
- <string name="downloading_label">Laddar ner...</string>
+ <string name="downloading_label">Laddar ner…</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Visa kapitel</string>
<string name="show_shownotes_label">Visa shownotes</string>
@@ -367,7 +500,45 @@
<!--Feed information screen-->
<string name="authentication_label">Autentisering</string>
<string name="authentication_descr">Byt ditt användarnamn och lösenord för den här podcasten och dess episoder.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Uppgraderar databasen</string>
<!--AntennaPodSP-->
- <string name="sp_apps_importing_feeds_msg">Importerar prenumerationer från appar gjorda för ett enda syfte...</string>
+ <string name="sp_apps_importing_feeds_msg">Importerar prenumerationer från appar gjorda för ett enda syfte…</string>
<string name="search_itunes_label">Leta i iTunes</string>
+ <string name="select_label"><b>Välj…</b></string>
+ <string name="filter">Filtrera</string>
+ <string name="all_label">Alla</string>
+ <string name="selected_all_label">Välj alla Episoder</string>
+ <string name="none_label">Inga</string>
+ <string name="deselected_all_label">Avmarkera alla Episoder</string>
+ <string name="played_label">Spelade</string>
+ <string name="selected_played_label">Valde spelade Episoder</string>
+ <string name="unplayed_label">Ospelade</string>
+ <string name="selected_unplayed_label">Valde ospelade Episoder</string>
+ <string name="downloaded_label">Nedladdade</string>
+ <string name="selected_downloaded_label">Valde nedladdade Episoder</string>
+ <string name="not_downloaded_label">Ej nedladdade</string>
+ <string name="selected_not_downloaded_label">Valde ej nedladdade Episoder</string>
+ <string name="sort_title"><b>Sortera efter…</b></string>
+ <string name="sort_title_a_z">Titel (A \u2192 Ö)</string>
+ <string name="sort_title_z_a">Titel (Ö \u2192 A)</string>
+ <string name="sort_date_new_old">Datum (Ny \u2192 Gammal)</string>
+ <string name="sort_date_old_new">Datum (Gammal \u2192 Ny)</string>
+ <string name="sort_duration_short_long">Längd (Kort \u2192 Lång)</string>
+ <string name="sort_duration_long_short">Längd (Lång \u2192 Kort)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">Gillar du AntennaPod?</string>
+ <string name="rating_message">Vi skulle uppskatta om du tog dig tid att betygsätta AntennaPod.</string>
+ <string name="rating_never_label">Lämna mig ifred</string>
+ <string name="rating_later_label">Påminn mig senare</string>
+ <string name="rating_now_label">Okej, gör det nu!</string>
+ <!--Audio controls-->
+ <string name="audio_controls">Ljudkontroller</string>
+ <string name="playback_speed">Uppspelningshastighet</string>
+ <string name="volume">Volym</string>
+ <string name="left_short">V</string>
+ <string name="right_short">H</string>
+ <string name="audio_effects">Ljudeffekter</string>
+ <string name="stereo_to_mono">Nedmixning: Stereo till mono</string>
+ <string name="sonic_only">Bara Sonic</string>
</resources>
diff --git a/core/src/main/res/values-tr/strings.xml b/core/src/main/res/values-tr/strings.xml
index e83c9b48e..6dca19d57 100644
--- a/core/src/main/res/values-tr/strings.xml
+++ b/core/src/main/res/values-tr/strings.xml
@@ -5,9 +5,11 @@
<string name="feeds_label">Beslemeler</string>
<string name="add_feed_label">Cep yayını ekle</string>
<string name="podcasts_label">CEP YAYINLARI</string>
- <string name="episodes_label">BÖLÜMLER</string>
+ <string name="episodes_label">Bölümler</string>
<string name="new_episodes_label">Yeni bölümler</string>
<string name="all_episodes_label">Tüm bölümler</string>
+ <string name="all_episodes_short_label">Tümü</string>
+ <string name="favorite_episodes_label">Favoriler</string>
<string name="new_label">Yeni</string>
<string name="waiting_list_label">Bekleme listesi</string>
<string name="settings_label">Ayarlar</string>
@@ -26,6 +28,14 @@
<!--Main activity-->
<string name="drawer_open">Münüyü aç</string>
<string name="drawer_close">Menüyü kapat</string>
+ <string name="drawer_preferences">Çekmece Seçenekleri</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sayaca göre sırala</string>
+ <string name="drawer_feed_order_alphabetical">Alfabetik olarak sırala</string>
+ <string name="drawer_feed_order_last_update">Yayınlanma tarihine göre sırala</string>
+ <string name="drawer_feed_counter_new_unplayed">Yeni ve çalınmamış bölümlerin sayısı</string>
+ <string name="drawer_feed_counter_new">Yeni bölümlerin sayısı</string>
+ <string name="drawer_feed_counter_unplayed">Çalınmamış bölümlerin sayısı</string>
+ <string name="drawer_feed_counter_none">Hiçbiri</string>
<!--Webview actions-->
<string name="open_in_browser_label">Tarayıcıda aç</string>
<string name="copy_url_label">URL\'yi kopyala</string>
@@ -37,8 +47,11 @@
<!--Other-->
<string name="confirm_label">Onayla</string>
<string name="cancel_label">İptal</string>
+ <string name="yes">Evet</string>
+ <string name="no">Hayır</string>
<string name="author_label">Yayıncı</string>
<string name="language_label">Dil</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Ayarlar</string>
<string name="cover_label">Resim</string>
<string name="error_label">Hata</string>
@@ -53,12 +66,23 @@
<string name="length_prefix">Uzunluk:\u0020</string>
<string name="size_prefix">Boyut:\u0020</string>
<string name="processing_label">İşleniyor</string>
- <string name="loading_label">Yükleniyor...</string>
<string name="save_username_password_label">Kullanıcı adı ve şifreyi kaydet</string>
<string name="close_label">Kapat</string>
<string name="retry_label">Yeniden dene</string>
<string name="auto_download_label">Otomatik indirmelere dahil et</string>
+ <string name="auto_download_apply_to_items_title">Önceki Bölümlere Uygula</string>
+ <string name="auto_delete_label">Bölümü Otomatik Olarak Sil\n(genel varsayılanı yoksayar)</string>
<string name="parallel_downloads_suffix">\u0020paralel indirmeler</string>
+ <string name="feed_auto_download_global">Genel</string>
+ <string name="feed_auto_download_always">Her zaman</string>
+ <string name="feed_auto_download_never">Hiçbir zaman</string>
+ <string name="episode_cleanup_never">Hiçbir zaman</string>
+ <string name="episode_cleanup_queue_removal">Sırada değilse</string>
+ <string name="episode_cleanup_after_listening">Bittikten sonra</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">Bittikten 1 gün sonra</item>
+ <item quantity="other">Bittikten %d gün sonra</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Besleme Adresi</string>
<string name="etxtFeedurlHint">www.example.com/feed</string>
@@ -67,17 +91,32 @@
<string name="podcastdirectories_descr">gdpodder.net dizininde yeni cep yayınlarını isme, kategoriye veya popülerliğe göre arayabilirsiniz veya iTunes mağazasında arama yapabilirsiniz.</string>
<string name="browse_gpoddernet_label">gpodder.net\'e gözat</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Hepsini okundu olarak işaretle</string>
- <string name="mark_all_read_msg">Tüm bölümler okundu olarak işaretlendi</string>
- <string name="mark_all_read_confirmation_msg">Lütfen tüm bölümleri okundu olarak işaretlemek istediğinizi onaylayın.</string>
- <string name="mark_all_read_feed_confirmation_msg">Lütfen bu besleme içindeki tüm bölümleri okundu olarak işaretlemek istediğinizi onaylayın.</string>
+ <string name="mark_all_read_label">Hepsini oynatıldı olarak işaretle</string>
+ <string name="mark_all_read_msg">Tüm bölümleri oynatıldı olarak işaretle</string>
+ <string name="mark_all_read_confirmation_msg">Lütfen tüm bölümleri oynatıldı olarak işaretlemek istediğinizi onaylayın.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Lütfen bu besleme içindeki tüm bölümleri oynatıldı olarak işaretlemek istediğinizi onaylayın.</string>
+ <string name="mark_all_seen_label">Hepsini görüldü olarak işaretle</string>
<string name="show_info_label">Bilgiyi göster</string>
<string name="remove_feed_label">Cep yayını kaldır</string>
- <string name="share_link_label">Web sayfası bağlantısı paylaş</string>
- <string name="share_source_label">Besleme bağlantısını paylaş</string>
+ <string name="share_link_label">Link\'i paylaş</string>
+ <string name="share_link_with_position_label">Link\'i Konumla birlikte paylaş</string>
+ <string name="share_feed_url_label">Besleme Adresini Paylaş</string>
+ <string name="share_item_url_label">Bölüm Adresini Paylaş</string>
+ <string name="share_item_url_with_position_label">Bölüm Adresini Konumla birlikte paylaş</string>
<string name="feed_delete_confirmation_msg">Lütfen bu beslemeyi ve bu beslemeye ait indirilmiş BÜTÜN bölümleri silme isteğinizi onaylayın.</string>
<string name="feed_remover_msg">Besleme kaldırılıyor</string>
<string name="load_complete_feed">Tüm beslemeyi yenile</string>
+ <string name="hide_episodes_title">Bölümleri gizle</string>
+ <string name="episode_actions">Eylemleri uygula</string>
+ <string name="hide_unplayed_episodes_label">Oynatılmadı</string>
+ <string name="hide_paused_episodes_label">Duraklatıldı</string>
+ <string name="hide_played_episodes_label">Oynatıldı</string>
+ <string name="hide_queued_episodes_label">Kuyrukta</string>
+ <string name="hide_not_queued_episodes_label">Kuyrukta değil</string>
+ <string name="hide_downloaded_episodes_label">İndirildi</string>
+ <string name="hide_not_downloaded_episodes_label">İndirilmedi</string>
+ <string name="filtered_label">Filtrelendi</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Son yenileme başarısız oldu</string>
<!--actions on feeditems-->
<string name="download_label">İndir</string>
<string name="play_label">Oynat</string>
@@ -86,16 +125,23 @@
<string name="stream_label">Akış</string>
<string name="remove_label">Kaldır</string>
<string name="remove_episode_lable">Bölümü kaldır</string>
- <string name="mark_read_label">Okundu olarak işaretle</string>
- <string name="mark_unread_label">Okunmadı olarak işaretle</string>
- <string name="marked_as_read_label">Okundu olarak işaretlendi</string>
+ <string name="mark_read_label">Oynatıldı olarak işaretle</string>
+ <string name="marked_as_read_label">Oynatıldı olarak işaretlendi</string>
+ <string name="mark_unread_label">Oynatılmadı olarak işaretle</string>
<string name="add_to_queue_label">Kuyruğa Ekle</string>
+ <string name="added_to_queue_label">Kuyruğa Eklendi</string>
<string name="remove_from_queue_label">Kuyruktan Kaldır</string>
+ <string name="add_to_favorite_label">Favorilere Ekle</string>
+ <string name="remove_from_favorite_label">Favorilerden Kaldır</string>
<string name="visit_website_label">Siteyi Ziyaret Et</string>
<string name="support_label">Flattr ile destekle</string>
<string name="enqueue_all_new">Hepsini kuyruğa ekle</string>
<string name="download_all">Hepsini indir</string>
<string name="skip_episode_label">Bölümü atla</string>
+ <string name="activate_auto_download">Otomatik indirmeyi etkinleştir</string>
+ <string name="deactivate_auto_download">Otomatik indirmeyi devre dışı bırak</string>
+ <string name="reset_position">Çalme konumunu sıfırla</string>
+ <string name="removed_item">Öge kaldırıldı</string>
<!--Download messages and labels-->
<string name="download_successful">başarılı</string>
<string name="download_failed">başarısız</string>
@@ -113,12 +159,13 @@
<string name="download_error_unauthorized">Yetkilendirme hatası</string>
<string name="cancel_all_downloads_label">Bütün indirmeleri iptal et</string>
<string name="download_canceled_msg">İndirme iptal edildi</string>
- <string name="download_report_title">İndirme tamamlandı</string>
+ <string name="download_canceled_autodownload_enabled_msg">İndirme iptal edildi\nBu öğe için <i>Otomatik İndirme</i> devre dışı</string>
+ <string name="download_report_title">İndirme hata(lar) ile tamamlandı</string>
+ <string name="download_report_content_title">İndirme raporu</string>
<string name="download_error_malformed_url">Bozuk URL</string>
<string name="download_error_io_error">G/Ç Hatası</string>
<string name="download_error_request_error">İstek hatası</string>
<string name="download_error_db_access">Veritabanı erişim hatası</string>
- <string name="downloads_left">\u0020Kalan indirme</string>
<string name="downloads_processing">İndirmeler işleniyor</string>
<string name="download_notification_title">Cep yayını verileri indiriliyor</string>
<string name="download_report_content">%1$d indirme başarılı, %2$d başarısız</string>
@@ -129,6 +176,11 @@
<string name="download_request_error_dialog_message_prefix">Dosyayı indirmeye çalışırken bir hata oluştu:\u0020</string>
<string name="authentication_notification_title">Yetkilendirme gerekiyor</string>
<string name="authentication_notification_msg">İstediğiniz kaynak kullanıcı adı ve şifre istiyor</string>
+ <string name="confirm_mobile_download_dialog_title">Mobil İndirmeyi Onaylayın</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Mobil veri ile indirme ayarlarda devre dışıdır.\n\nGeçici olarak açılsın mı yoksa sadece kuyruğa mı eklensin?\n\n<small>Bu tercihiniz 10 dakika boyunca hatırlanacak.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Mobil veri ile indirme ayarlarda devre dışıdır.\n\nGeçici olarak açılsın mı?\n\n<small>Bu tercihiniz 10 dakika boyunca hatırlanacak.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Sadece Kuyruğa ekle</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Geçici olarak aç</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Hata!</string>
<string name="player_stopped_msg">Çalınan medya yok</string>
@@ -143,6 +195,8 @@
<string name="playbackservice_notification_title">Cep yayını çalınıyor</string>
<string name="unknown_media_key">AntennaPod - Bilinmeyen medya anahtarı: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Kuyruğu kilitle</string>
+ <string name="unlock_queue">Kuyruğun kilidini aç</string>
<string name="clear_queue_label">Kuyruğu temizle</string>
<string name="undo">Geri al</string>
<string name="removed_from_queue">Öge kaldırıldı</string>
@@ -184,8 +238,8 @@
<!--Variable Speed-->
<string name="download_plugin_label">Eklentiyi İndir</string>
<string name="no_playback_plugin_title">Eklenti Yüklenmedi</string>
- <string name="no_playback_plugin_msg">Değişken hızlı yürütmenin çalışabilmesi için üçüncü parti bir kütüphane yüklemelisiniz.\n\nEklentiyi Play Store\'dan indirmek için \'Eklentiyi İndir\' butonuna tıklayın\n\nBu eklentiden doğacak hatalar AntennaPod\'un sorumluluğunda değildir ve bu durum eklenti sahibine bildirilmelidir.</string>
<string name="set_playback_speed_label">Çalma hızları</string>
+ <string name="enable_sonic">Sonic\'i Etkinleştir</string>
<!--Empty list labels-->
<string name="no_items_label">Bu listede hiç öge yok.</string>
<string name="no_feeds_label">Henüz hiçbir beslemeye abone değilsiniz.</string>
@@ -195,15 +249,22 @@
<string name="queue_label">Kuyruk</string>
<string name="services_label">Servisler</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Kulaklıklar çıkarıldığında çalmayı duraklat</string>
+ <string name="pref_episode_cleanup_title">Bölüm Temizliği</string>
+ <string name="pref_pauseOnDisconnect_sum">Kulaklıklar çıkarıldığında veya bluetooth bağlantısı kesildiğinde çalmayı duraklat</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Kulaklıklar yeniden bağlandığında çalmaya devam et</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Bluetooth yeniden bağlandığında çalmaya devam et</string>
<string name="pref_followQueue_sum">Çalma tamamlandığında kuyruktaki diğer öğeye geç</string>
<string name="pref_auto_delete_sum">Çalma bittiğinde bölümü sil</string>
<string name="pref_auto_delete_title">Otomatik Silme</string>
+ <string name="pref_smart_mark_as_played_sum">Bitmesine birkaç saniye kalmış olsa da bölümleri oynatıldı olarak işaretle</string>
<string name="playback_pref">Çalma</string>
<string name="network_pref">Ağ</string>
- <string name="pref_autoUpdateIntervall_title">Güncelleme aralığı</string>
- <string name="pref_autoUpdateIntervall_sum">Beslemeleri yenilemek için bir aralık belirtin veya devre dışı bırakın.</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Güncelleme Aralığı veya Belirli Zamanı</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Beslemelerin otomatik olarak güncellenmesi için bir zaman aralığı veya belirli bir saat ayarlayın</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Otomatik güncelleştirmelerin yapılması için \"her 2 saatte bir\" gibi <i>aralık</i>, \"07:00\" gibi \"belirli saat\" ayarlayabilirsiniz veya <i>devre dışı</i> bırakabilirsiniz.\n\n<small>Not: Güncelleme zamanları kesin değildir, kısa gecikmeler yaşanabilir.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Devre dışı</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Aralık ayarla</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Belirli saat ayarla</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Medya dosyalarını sadece kablosuz bağlantı üzerinden indir</string>
<string name="pref_followQueue_title">Devamlı çalma</string>
<string name="pref_downloadMediaOnWifiOnly_title">Kablosuz medya indirmesi</string>
@@ -223,6 +284,10 @@
<string name="pref_auto_flattr_sum">Otomatik Flattr\'lamayı Yapılandır</string>
<string name="user_interface_label">Kullanıcı Arayüzü</string>
<string name="pref_set_theme_title">Temayı seç</string>
+ <string name="pref_nav_drawer_title">Uygulama Çekmecesini Özelleştir</string>
+ <string name="pref_nav_drawer_sum">Uygulama çekmecesinin görüntüsünü özelleştir.</string>
+ <string name="pref_nav_drawer_items_title">Uygulama Çekmecesi öğelerini ayarla</string>
+ <string name="pref_nav_drawer_items_sum">Hangi öğelerin uygulama çekmecesinde görüneceğini değiştir.</string>
<string name="pref_set_theme_sum">AntennaPod\'un görüntüsünü değiştir.</string>
<string name="pref_automatic_download_title">Otomatk indirme</string>
<string name="pref_automatic_download_sum">Bölümlerin otomatik indirilmesini yapılandır.</string>
@@ -246,17 +311,27 @@
<string name="pref_gpodnet_setlogin_information_sum">gpodder.net hesabınız için giriş bilgisini değiştirin.</string>
<string name="pref_playback_speed_title">Çalma hızları</string>
<string name="pref_playback_speed_sum">Değişken hızlı ses yürütmesi için kullanılabilir hızları özelleştirin</string>
- <string name="pref_seek_delta_title">Arama zamanı</string>
- <string name="pref_seek_delta_sum">Geri veya ileri sararken bu kadar saniye atla</string>
+ <string name="pref_fast_forward">İleri sarma süresi</string>
+ <string name="pref_rewind">Geri sarma süresi</string>
<string name="pref_gpodnet_sethostname_title">Sunucu ismini ayarla</string>
<string name="pref_gpodnet_sethostname_use_default_host">Varsayılan sunucuyu kullan</string>
<string name="pref_expandNotify_title">Bildirimi Genişlet</string>
<string name="pref_expandNotify_sum">Çalma tuşlarını göstermek için bildirimi her zaman genişlet.</string>
<string name="pref_persistNotify_title">Kalıcı oynatma kontrolleri</string>
<string name="pref_persistNotify_sum">Çalma duraklatıldığında bildirim ve ekran kilidi ayarlarını sakla.</string>
+ <string name="pref_showDownloadReport_title">İndirme Raporunu Göster</string>
+ <string name="pref_showDownloadReport_sum">Eğer indirme başarısız olursa, hatanın ayrıntılarını gösteren bir rapor oluştur.</string>
<string name="pref_expand_notify_unsupport_toast">Android 4.1 öncesi sürümler genişletilmiş bildirimleri desteklememektedir.</string>
<string name="pref_queueAddToFront_sum">Yeni bölümleri kuyruğun önüne ekle.</string>
<string name="pref_queueAddToFront_title">Kuyruğun önüne ekle.</string>
+ <string name="pref_smart_mark_as_played_disabled">Devre dışı</string>
+ <string name="pref_image_cache_size_title">Görüntü Önbelleği Boyutu</string>
+ <string name="pref_image_cache_size_sum">Görüntüler için diskte tutulacak önbelleğin boyutu.</string>
+ <string name="crash_report_title">Çökme Raporu</string>
+ <string name="crash_report_sum">En son çökme raporunu e-posta ile gönder</string>
+ <string name="send_email">E-posta gönder</string>
+ <string name="experimental_pref">Deneysel</string>
+ <string name="pref_sonic_title">Sonic ortam yürütücüsü</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Otomatik Flattr\'lamayı etkinleştir</string>
<string name="auto_flattr_after_percent">Bölümün yüzde %d kısmı oynatıldığında Flattr\'la</string>
@@ -271,6 +346,7 @@
<string name="found_in_title_label">Başlıkta bulundu</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML dosyaları cep yayınlarını bir cihazdan diğerine aktarmanıza yarar.</string>
+ <string name="opml_import_option">Seçenek %1$d</string>
<string name="opml_import_explanation_1">Yerel dosya sisteminden belirli bir yol seçin.</string>
<string name="opml_import_explanation_2">OPML dosyasını açmak için harici uygulamalardan Dropbox, Google Drive veya kendi favori dosya yöneticinizi kullanın.</string>
<string name="opml_import_explanation_3">Google Mail, Dropbox, Google Drive gibi birçok uygulama ve çoğu dosya yöneticisi OPML dosyalarını AntennaPod <i>ile</i> <i>açabilir.</i></string>
@@ -285,7 +361,6 @@
<string name="choose_file_from_filesystem">Yerel dosya sisteminden</string>
<string name="choose_file_from_external_application">Harici uygulama kullan</string>
<string name="opml_export_label">OPML dışa aktar</string>
- <string name="exporting_label">Dışa aktarılıyor...</string>
<string name="export_error_label">Dışa aktarma hatası</string>
<string name="opml_export_success_title">Opml dışa aktarma başarılı.</string>
<string name="opml_export_success_sum">.opml dosyasy yazıldı: \u0020</string>
@@ -296,9 +371,24 @@
<string name="sleep_timer_label">Zamanlayıcı</string>
<string name="time_left_label">Kalan süre:\u0020</string>
<string name="time_dialog_invalid_input">Geçersiz giriş, zaman bir tam sayı olmalıdır</string>
- <string name="time_unit_seconds">saniye</string>
- <string name="time_unit_minutes">dakika</string>
- <string name="time_unit_hours">saat</string>
+ <string name="timer_about_to_expire_label"><b>Zamanlayıcı süresi dolduğunda:</b></string>
+ <string name="shake_to_reset_label">Zamanyacıyı sıfırlamak için salla</string>
+ <string name="timer_vibration_label">Titret</string>
+ <string name="time_seconds">saniye</string>
+ <string name="time_minutes">dakika</string>
+ <string name="time_hours">saat</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 saniye</item>
+ <item quantity="other">%d saniye</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 dakika</item>
+ <item quantity="other">%d dakika</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 saat</item>
+ <item quantity="other">%d saat</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORİLER</string>
<string name="gpodnet_toplist_header">POPÜLER CEP YAYINLARI</string>
@@ -331,20 +421,26 @@
<string name="selected_folder_label">Seçilen dizin:</string>
<string name="create_folder_label">Dizin yarat</string>
<string name="choose_data_directory">Veri dizinini seç</string>
+ <string name="choose_data_directory_message">Lütfen verileriniz için ana klasörü seçin. AntennaPod gerekli alt-klasörleri oluşturacaktır.</string>
<string name="create_folder_msg">\"%1$s\" isminde yeni dizin oluştur?</string>
<string name="create_folder_success">Yeni dizin yaratıldı</string>
<string name="create_folder_error_no_write_access">Bu dizine yazılamadı</string>
<string name="create_folder_error_already_exists">Dizin zaten var</string>
<string name="create_folder_error">Dizin yaratılamadı</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" adlı klasör yok</string>
+ <string name="folder_not_readable_error">\"%1$s\" klasörü okunabilir değil</string>
+ <string name="folder_not_writable_error">\"%1$s\" klasörü yazılabilir değil</string>
<string name="folder_not_empty_dialog_title">Dizin boş değil</string>
<string name="folder_not_empty_dialog_msg">Seçtiğiniz dizin dolu. Medya indirmeleri ve diğer dosyalar doğrudan bu dizine yerleştirilecek. Devam edilsin mi?</string>
<string name="set_to_default_folder">Vaysayılan dizini seç</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Başka bir uygulama ses çalmak istediğinde sesi kısmak yerine yürütmeyi duraklat</string>
<string name="pref_pausePlaybackForFocusLoss_title">Kesintiler için duraklat</string>
+ <string name="pref_resumeAfterCall_sum">Bir telefon konuşması tamamlandıktan sonra çalmaya kaldığı yerden devam et</string>
+ <string name="pref_resumeAfterCall_title">Konuşmadan sonra devam et</string>
+ <string name="pref_restart_required">Bu değişikliğin geçerli olması için AntennaPod yeniden başlatılmalıdır.</string>
<!--Online feed view-->
<string name="subscribe_label">Üye ol</string>
<string name="subscribed_label">Üye olundu</string>
- <string name="downloading_label">İndiriliyor...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Kısımları göster</string>
<string name="show_shownotes_label">Notları göster</string>
@@ -367,7 +463,34 @@
<!--Feed information screen-->
<string name="authentication_label">Yetkilendirme</string>
<string name="authentication_descr">Bu cep yayını ve içerdiği bölümler için kullanıcı adı şifreyi değiştir.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Veritabanı yükseltiliyor</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Üyelikler tek-amaçlı uygulamalardan içe aktarılıyor...</string>
<string name="search_itunes_label">iTunes\'da Arama</string>
+ <string name="all_label">Tümü</string>
+ <string name="selected_all_label">Tüm Bölümler Seçildi</string>
+ <string name="none_label">Hiçbiri</string>
+ <string name="deselected_all_label">Tüm Bölümlerin Seçimi Kaldırıldı</string>
+ <string name="played_label">Çalınan</string>
+ <string name="selected_played_label">Çalınan Bölümler Seçildi</string>
+ <string name="unplayed_label">Çalınmayan</string>
+ <string name="selected_unplayed_label">Çalınmayan Bölümler Seçildi</string>
+ <string name="downloaded_label">İndirilen</string>
+ <string name="selected_downloaded_label">İndirilen Bölümler Seçildi</string>
+ <string name="not_downloaded_label">İndirilmeyen</string>
+ <string name="selected_not_downloaded_label">İndirilmeyen Bölümler Seçildi</string>
+ <string name="sort_title_a_z">Başlık (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Başlık (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Tarih (Yeni \u2192 Eski)</string>
+ <string name="sort_date_old_new">Tarih (Eski \u2192 Yeni)</string>
+ <string name="sort_duration_short_long">Süre (Kısa \u2192 Uzun)</string>
+ <string name="sort_duration_long_short">Süre (Uzun \u2192 Kısa)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">AntennaPod\'u beğendiniz mi?</string>
+ <string name="rating_message">Eğer AntennaPod\'a oy vermek için biraz zamanını ayırırsanız memnun oluruz.</string>
+ <string name="rating_never_label">Bir daha gösterme</string>
+ <string name="rating_later_label">Daha sonra hatırlat</string>
+ <string name="rating_now_label">Evet, şimdi yapalım!</string>
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-uk-rUA/strings.xml b/core/src/main/res/values-uk-rUA/strings.xml
index 20374232a..abc5fcec1 100644
--- a/core/src/main/res/values-uk-rUA/strings.xml
+++ b/core/src/main/res/values-uk-rUA/strings.xml
@@ -8,6 +8,8 @@
<string name="episodes_label">Епізоди</string>
<string name="new_episodes_label">Нові епізоди</string>
<string name="all_episodes_label">Всі епізоди</string>
+ <string name="all_episodes_short_label">Всі</string>
+ <string name="favorite_episodes_label">Улюблені</string>
<string name="new_label">Нові</string>
<string name="waiting_list_label">Черга</string>
<string name="settings_label">Налаштування</string>
@@ -26,6 +28,14 @@
<!--Main activity-->
<string name="drawer_open">Показати меню</string>
<string name="drawer_close">Сховати меню</string>
+ <string name="drawer_preferences">Настройки меню навігації</string>
+ <string name="drawer_feed_order_unplayed_episodes">Сортувати за лічильником</string>
+ <string name="drawer_feed_order_alphabetical">Сортування за абеткою</string>
+ <string name="drawer_feed_order_last_update">Сортувати за датою публікації</string>
+ <string name="drawer_feed_counter_new_unplayed">Кількість нових та непрослуханих епізодів</string>
+ <string name="drawer_feed_counter_new">Кількість нових епізодів</string>
+ <string name="drawer_feed_counter_unplayed">Кількість непрослуханих епізодів</string>
+ <string name="drawer_feed_counter_none">Жодних</string>
<!--Webview actions-->
<string name="open_in_browser_label">Відкрити в браузері</string>
<string name="copy_url_label">Копіювати URL</string>
@@ -37,14 +47,17 @@
<!--Other-->
<string name="confirm_label"> Підтвердити</string>
<string name="cancel_label">Скасувати</string>
+ <string name="yes">Так</string>
+ <string name="no">Ні</string>
<string name="author_label">Автор</string>
<string name="language_label">Мова</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Налаштування</string>
<string name="cover_label">Зображення</string>
<string name="error_label">Помилка</string>
<string name="error_msg_prefix">Трапилась помілка:</string>
<string name="refresh_label">Оновити</string>
- <string name="external_storage_error_msg">Немає доступної флешки. Зовнішній носій потрібен для коректної роботи додатку</string>
+ <string name="external_storage_error_msg">Немає доступної карти пам\'яті. Зовнішній носій потрібен для коректної роботи додатку</string>
<string name="chapters_label">Глави</string>
<string name="shownotes_label">Нотатки до епізода</string>
<string name="description_label">Опис</string>
@@ -53,12 +66,25 @@
<string name="length_prefix">Довжина:\u0020</string>
<string name="size_prefix">Розмір:\u0020</string>
<string name="processing_label">Обробка</string>
- <string name="loading_label">Завантаження категорій ...</string>
<string name="save_username_password_label">Зберегти ім\'я користувача та пароль</string>
<string name="close_label">Закрити</string>
<string name="retry_label">Повторити знову</string>
<string name="auto_download_label">Включити до автозавантаження</string>
- <string name="parallel_downloads_suffix">\u0020паралельні завантаження</string>
+ <string name="auto_download_apply_to_items_title">Застосувати до попередніх епізодів</string>
+ <string name="auto_download_apply_to_items_message">Нове налаштування <i>Автозагрузка</i> буде автоматично застосоване до нових епізодів.\nБажаєте також застосувати його до тих епізодів що було опубліковано раніше?</string>
+ <string name="auto_delete_label">Автоматичне видалення епізода\n(перевизначити глобальне налаштування за замовчуванням)</string>
+ <string name="parallel_downloads_suffix">\u0020паралельних завантажень</string>
+ <string name="feed_auto_download_global">Для всіх</string>
+ <string name="feed_auto_download_always">Завжди</string>
+ <string name="feed_auto_download_never">Ніколи</string>
+ <string name="episode_cleanup_never">Ніколи</string>
+ <string name="episode_cleanup_queue_removal">Якщо не в черзі</string>
+ <string name="episode_cleanup_after_listening">Після закінчення</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 день після закінчення</item>
+ <item quantity="few">%d дні після закінчення</item>
+ <item quantity="other">%d днів після закінчення</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Посилання на канал</string>
<string name="etxtFeedurlHint">URL канала або сайта</string>
@@ -67,17 +93,32 @@
<string name="podcastdirectories_descr">В каталозі gpodder.net можливий пошук за назвою, категорією або популярністю.</string>
<string name="browse_gpoddernet_label">Переглянути gpodder.net</string>
<!--Actions on feeds-->
- <string name="mark_all_read_label">Позначити всі як переглянуті</string>
- <string name="mark_all_read_msg">Позначено всі епізоди як переглянуті</string>
- <string name="mark_all_read_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте позначити всі епізоди як прочитані.</string>
- <string name="mark_all_read_feed_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте позначити всі епізоди цього канала як прочитані.</string>
+ <string name="mark_all_read_label">Позначити всі як грані</string>
+ <string name="mark_all_read_msg">Позначено всі епізоди як грані</string>
+ <string name="mark_all_read_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте позначити всі епізоди як грані.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте позначити всі епізоди цього канала як грані.</string>
+ <string name="mark_all_seen_label">Позначити всі як переглянуті</string>
<string name="show_info_label">Інформація</string>
<string name="remove_feed_label">Видалити подкаст</string>
<string name="share_link_label">Поділитися URL сайту</string>
- <string name="share_source_label">Поділитися URL каналу</string>
- <string name="feed_delete_confirmation_msg">Ви впенені що хочете видаліти канал та всі завантажені епізоди</string>
+ <string name="share_link_with_position_label">Поділитись посиланням на позицію</string>
+ <string name="share_feed_url_label">Поділитись посиланням на канал</string>
+ <string name="share_item_url_label">Поділитись посиланням на епізод</string>
+ <string name="share_item_url_with_position_label">Поділитись посиланням на епізод з позицією</string>
+ <string name="feed_delete_confirmation_msg">Ви впенені що хочете видаліти канал та всі завантажені епізоди?</string>
<string name="feed_remover_msg">Удаляю канал</string>
<string name="load_complete_feed">Оновити канал цілком</string>
+ <string name="hide_episodes_title">Приховати епізоди</string>
+ <string name="episode_actions">Застосувати дії</string>
+ <string name="hide_unplayed_episodes_label">Неграні</string>
+ <string name="hide_paused_episodes_label">На паузі</string>
+ <string name="hide_played_episodes_label">Грані</string>
+ <string name="hide_queued_episodes_label">В черзі</string>
+ <string name="hide_not_queued_episodes_label">Не в черзі</string>
+ <string name="hide_downloaded_episodes_label">Завантажені</string>
+ <string name="hide_not_downloaded_episodes_label">Не завантажені</string>
+ <string name="filtered_label">Фільтровані</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Останнє оновлення було невдалим</string>
<!--actions on feeditems-->
<string name="download_label">Завантажити</string>
<string name="play_label">Грати</string>
@@ -86,16 +127,23 @@
<string name="stream_label">Прослухати без завантаження</string>
<string name="remove_label">Видалити</string>
<string name="remove_episode_lable">Видалити епізод</string>
- <string name="mark_read_label">Позначити як переглянутий</string>
- <string name="mark_unread_label">Позначити як не переглянутий</string>
- <string name="marked_as_read_label">Позначено як прочитане</string>
+ <string name="mark_read_label">Позначити як граний</string>
+ <string name="marked_as_read_label">Позначено як граний</string>
+ <string name="mark_unread_label">Позначити як не граний</string>
<string name="add_to_queue_label">Додати до черги</string>
+ <string name="added_to_queue_label">Додано до черги</string>
<string name="remove_from_queue_label">Видалити з черги</string>
+ <string name="add_to_favorite_label">Додати до улюблених</string>
+ <string name="remove_from_favorite_label">Видалити з улюблених</string>
<string name="visit_website_label">Відкрити сайт</string>
<string name="support_label">Підтримати за допомогою Flattr</string>
<string name="enqueue_all_new">Додати до черги</string>
<string name="download_all">Завантажити все</string>
<string name="skip_episode_label">Пропустити епізод</string>
+ <string name="activate_auto_download">Включити автозавантаження</string>
+ <string name="deactivate_auto_download">Виключити автозавантаження</string>
+ <string name="reset_position">Вернути початкову позицію відтворення</string>
+ <string name="removed_item">Видалено</string>
<!--Download messages and labels-->
<string name="download_successful">успішно</string>
<string name="download_failed">з помилками</string>
@@ -112,13 +160,14 @@
<string name="download_error_unknown_host">Невідомий host</string>
<string name="download_error_unauthorized">Помилка автентифікації</string>
<string name="cancel_all_downloads_label">Скасувати всі завантаження</string>
- <string name="download_canceled_msg">Відмінено завантаження</string>
- <string name="download_report_title">Завантажили</string>
+ <string name="download_canceled_msg">Завантаження скасоване</string>
+ <string name="download_canceled_autodownload_enabled_msg">Завантаження скасоване\n<i>Автозавантаження</i> для цього елемента вимкнуто</string>
+ <string name="download_report_title">Завантаження завершені з помилками</string>
+ <string name="download_report_content_title">Звіт про завантаження</string>
<string name="download_error_malformed_url">Невірний URL</string>
<string name="download_error_io_error">Помилка IO</string>
<string name="download_error_request_error">Помилка запиту</string>
<string name="download_error_db_access">Помилка бази даних</string>
- <string name="downloads_left">\0020 залишилось завантажити</string>
<string name="downloads_processing">Обробка завантаженого</string>
<string name="download_notification_title">Завантаження даних подкасту</string>
<string name="download_report_content">Завантажилось %1$d успішно, %2$d з помилками</string>
@@ -129,6 +178,11 @@
<string name="download_request_error_dialog_message_prefix">Помилка при завантажені файлу:\u0020</string>
<string name="authentication_notification_title">Потрібна автентифікація</string>
<string name="authentication_notification_msg">Для доступа до цього ресурса потрібні ім\'я та пароль </string>
+ <string name="confirm_mobile_download_dialog_title">Підтвердження мобільних завантажень</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Завантаження через мобільний зв\'язок вимкнено в настройках.\n\nУвімкнути тимчасово або тільки додати до черги?\n\n<small>Ваш вибір буде запам\'ятовано на 10 хвилин.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Завантаження через мобільний зв\'язок вимкнено в настройках.\n\nУвімкнути тимчасово?\n\n<small>Ваш вибір буде запам\'ятовано на 10 хвилин.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Лише додати до черги</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Увімкнути тимчасово</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Помилка!</string>
<string name="player_stopped_msg"> Нічого грати</string>
@@ -137,12 +191,14 @@
<string name="player_seeking_msg">Шукаю</string>
<string name="playback_error_server_died">Сервер помер</string>
<string name="playback_error_unknown">Невідома помилка</string>
- <string name="no_media_playing_label">Німає що грати</string>
+ <string name="no_media_playing_label">Немає що грати</string>
<string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Буферізую</string>
<string name="playbackservice_notification_title">Грає подкаст</string>
<string name="unknown_media_key">AntennaPod - Невідомий медіа ключ: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">Заблокувати чергу</string>
+ <string name="unlock_queue">Розблокувати чергу</string>
<string name="clear_queue_label">Очистити чергу</string>
<string name="undo">Скасувати</string>
<string name="removed_from_queue">Видалено</string>
@@ -157,10 +213,10 @@
<string name="clear_queue_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте вилучити всі епізоди з черги.</string>
<!--Flattr-->
<string name="flattr_auth_label">Увійти до Flattr</string>
- <string name="flattr_auth_explanation">Нажміть цю кнопку для початку авторізації. Буде відкрито flattr в браузері, буде запит на дозвіл доступу Antennapod до flattr. Після надання доступу ви повернетесь до цього екрану автоматично</string>
+ <string name="flattr_auth_explanation">Нажміть цю кнопку для початку авторізації. Буде відкрито flattr в браузері, буде запит на дозвіл доступу AntennaPod до flattr. Після надання доступу ви повернетесь до цього екрану автоматично</string>
<string name="authenticate_label">Ввісти ім\'я та пароль</string>
<string name="return_home_label">Повернення до початку</string>
- <string name="flattr_auth_success">Вийшло авторізуватись. Тепер ви можете flattr things за допомогою додатку</string>
+ <string name="flattr_auth_success">Вдала авторизація. Тепер ви можете flattr things за допомогою додатку</string>
<string name="no_flattr_token_title">Немає flattr token</string>
<string name="no_flattr_token_notification_msg">Ваш обліковий запис flattr, здається, не підключений до AntennaPod. Натисніть тут для підключення.</string>
<string name="no_flattr_token_msg">Здається ваш обліковий запис flattr не під\'єднано до AntennaPod. Ви можете або під\'єднати її або відкривати web сторінку в браузері</string>
@@ -182,10 +238,10 @@
<string name="flattrd_failed_label">AntennaPod flattr failed</string>
<string name="flattr_retrieving_status">Retrieving flattr\'ed things</string>
<!--Variable Speed-->
- <string name="download_plugin_label">Завантажити Plugin</string>
- <string name="no_playback_plugin_title">Plugin не встановлено</string>
- <string name="no_playback_plugin_msg">Для керування швидкістю програвання потрібно встановити додаток.\n\nНатисніть \"Завантажити додаток\" для завантаження безкоштовного додатку з Play Store\n\nЯкщо при використанні plugin виникнуть будь які проблеми - це відповідальність автора додатка, а не автора AntennaPod.</string>
+ <string name="download_plugin_label">Завантажити додаток</string>
+ <string name="no_playback_plugin_title">Додаток не встановлено</string>
<string name="set_playback_speed_label">Швидкість програвання</string>
+ <string name="enable_sonic">Включити Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Нічого в цьому списку</string>
<string name="no_feeds_label">Немає підписаних каналів </string>
@@ -195,20 +251,31 @@
<string name="queue_label">Черга</string>
<string name="services_label">Сервіси</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Зупинятись коли навушники витягнуті</string>
+ <string name="pref_episode_cleanup_title">Чищення епізодів</string>
+ <string name="pref_pauseOnDisconnect_sum">Зупинятись коли навушники або блютуз від’єднано</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Поновити відтворення коли навушники повторно під’єднано</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Поновити відтворення коли блютуз повторно під’єднано</string>
<string name="pref_followQueue_sum">Перейти до наступного епізода в черзі коли поточний закінчено</string>
<string name="pref_auto_delete_sum">Видалити епізод після повного відтворення</string>
<string name="pref_auto_delete_title">Автовидалення</string>
+ <string name="pref_smart_mark_as_played_sum">Позначити епізоди як грані навіть якщо залишилось менш ніж зазначене число секунд до кінця відтворення</string>
+ <string name="pref_smart_mark_as_played_title">Розумне позначення граних епізодів</string>
+ <string name="pref_skip_keeps_episodes_sum">Зберігати епізоди що пропущені при програванні </string>
+ <string name="pref_skip_keeps_episodes_title">Зберігати пропущені при програванні епізоди </string>
<string name="playback_pref">Відтворення</string>
<string name="network_pref">Мережа</string>
- <string name="pref_autoUpdateIntervall_title">Частота оновлень</string>
- <string name="pref_autoUpdateIntervall_sum">Визначити інтервал часу для автооновлювання або відключити автооновлення</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Частота оновлень або оновлення в зазначений час</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Визначити інтервал часу або визначити час щодня для автооновлення</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Можливо встановити <i>інтервал</i> як то \"кожні 2 години\", встановити <i>час щодня</i> як то \"7:00\" або <i>відключити</i> автоматичне оновлення взагалі.\n\n<small>Зверніть увагу: Час оновлення не є точним. Можливі короткі затримки.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Вимкнути</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Інтервал</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Встановити час щодня</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Завантажувати тільки через Wifi</string>
<string name="pref_followQueue_title">Грати безперервно</string>
<string name="pref_downloadMediaOnWifiOnly_title">Завантаження через Wifi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Навушники витягнуті</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Повторне під’єднання навушників</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Повторне під’єднання блютуз</string>
<string name="pref_mobileUpdate_title">Мобільне оновлення</string>
<string name="pref_mobileUpdate_sum">Дозволити оновлення через оператора зв\'язку</string>
<string name="refreshing_label">Оновлення</string>
@@ -223,13 +290,21 @@
<string name="pref_auto_flattr_sum">Налаштування автоматичного заохочення авторів через сервіс flattr</string>
<string name="user_interface_label">Вигляд</string>
<string name="pref_set_theme_title">Обрати тему</string>
+ <string name="pref_nav_drawer_title">Налаштувати меню навігації</string>
+ <string name="pref_nav_drawer_sum">Налаштувати вигляд меню навігації</string>
+ <string name="pref_nav_drawer_items_title">Налаштувати зміст меню навігації</string>
+ <string name="pref_nav_drawer_items_sum">Вибрати елементи які мають бути у меню навігації</string>
+ <string name="pref_nav_drawer_feed_order_title">Встановити порядок підписок</string>
+ <string name="pref_nav_drawer_feed_order_sum">Змінити порядок ваших підписок</string>
+ <string name="pref_nav_drawer_feed_counter_title">Налаштувати лічильник підписок</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Змінити інформацію яку відображає лічильник підписок</string>
<string name="pref_set_theme_sum">Змінити вигляд AntennaPod</string>
- <string name="pref_automatic_download_title">Автоматичне завантаження</string>
- <string name="pref_automatic_download_sum">Налаштування автоматичного завантаження епізодів</string>
+ <string name="pref_automatic_download_title">Автозавантаження</string>
+ <string name="pref_automatic_download_sum">Налаштування автозавантаження епізодів</string>
<string name="pref_autodl_wifi_filter_title">Увімкнути фільтр Wi-Fi</string>
- <string name="pref_autodl_wifi_filter_sum">Дозволити автоматичне завантаження тільки в цих Wi-Fi мережах</string>
+ <string name="pref_autodl_wifi_filter_sum">Дозволити автозавантаження тільки в цих Wi-Fi мережах</string>
<string name="pref_automatic_download_on_battery_title">Завантаження без зарядного пристрою</string>
- <string name="pref_automatic_download_on_battery_sum">Дозволити завантаження коли зарядний пристрій не підключено</string>
+ <string name="pref_automatic_download_on_battery_sum">Дозволити автозавантаження коли зарядний пристрій не підключено</string>
<string name="pref_parallel_downloads_title">Паралельні завантаження</string>
<string name="pref_episode_cache_title">Кеш епізодів</string>
<string name="pref_theme_title_light">Світла</string>
@@ -246,17 +321,26 @@
<string name="pref_gpodnet_setlogin_information_sum">Змінити інформацію щодо облікового запису gpodder.net</string>
<string name="pref_playback_speed_title">Швидкість програвання</string>
<string name="pref_playback_speed_sum">Налаштування швідкості доступно для змінної швидкості програвання</string>
- <string name="pref_seek_delta_title">Час перемотки</string>
- <string name="pref_seek_delta_sum">Перейти на таку кількість секунд при перемотуванні назад або вперед</string>
+ <string name="pref_fast_forward">Час перемотки вперед</string>
+ <string name="pref_rewind">Час перемотки назад</string>
<string name="pref_gpodnet_sethostname_title">Встановити ім\'я хоста</string>
<string name="pref_gpodnet_sethostname_use_default_host">Використати хост по замовчанню</string>
<string name="pref_expandNotify_title">Розгорнути повідомлення</string>
<string name="pref_expandNotify_sum">Завжди розгортати повідомлення, щоб показати кнопки керування.</string>
<string name="pref_persistNotify_title">Завжди показувати елементи керування відтворенням</string>
- <string name="pref_persistNotify_sum">Показувати повідомлення та елементи керування на lockscreen в режимі паузи.</string>
+ <string name="pref_persistNotify_sum">Показувати повідомлення та елементи керування на екрані блокування в режимі паузи.</string>
+ <string name="pref_lockscreen_background_title">Змінювати фон екрана блокування</string>
+ <string name="pref_lockscreen_background_sum">Встановити картинку поточного епізода як фон екрана блокування. Побічний ефект - це зображення буде також видно в інших додатках.</string>
+ <string name="pref_showDownloadReport_title">Показати звіт про завантаження</string>
+ <string name="pref_showDownloadReport_sum">У разі помилки при завантаженні створити детальний звіт про помилку.</string>
<string name="pref_expand_notify_unsupport_toast">Android до версії 4.1 не підтримує розширені повідомлення.</string>
<string name="pref_queueAddToFront_sum">Додавати нові епізоди до початку черги.</string>
<string name="pref_queueAddToFront_title">Додавати в початок черги.</string>
+ <string name="pref_smart_mark_as_played_disabled">Вимкнено</string>
+ <string name="pref_image_cache_size_title">Розмір кеша зображень</string>
+ <string name="pref_image_cache_size_sum">Розмір дискового кеша для зображень.</string>
+ <string name="experimental_pref">Експериментальні</string>
+ <string name="pref_sonic_title">Програвач Sonic</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Включити автоматичне заохочення авторів через сервіс flattr</string>
<string name="auto_flattr_after_percent">Заохотити автора через Flattr щойно %d відсотків епізода було відтворено</string>
@@ -271,6 +355,7 @@
<string name="found_in_title_label">Знайдено у назві</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML файли дозволяют вам перенести подкасти з однієї программи до іншої</string>
+ <string name="opml_import_option">Варіант %1$d</string>
<string name="opml_import_explanation_1">Виберіть локальну папку.</string>
<string name="opml_import_explanation_2">Вибрати OPML файл за допомогою таких додатків як Dropbox, Google Drive або файловий менеджер.</string>
<string name="opml_import_explanation_3">Багато додатків таких як Google Mail, Dropbox, Google Drive та більшість файлових менеджерів здатні <i>відкрити</i> OPML файли <i>для</i> AntennaPod.</string>
@@ -285,7 +370,6 @@
<string name="choose_file_from_filesystem">З локальної файлової системи</string>
<string name="choose_file_from_external_application">За допомогою додатка</string>
<string name="opml_export_label">OPML экспорт</string>
- <string name="exporting_label">Експорт ...</string>
<string name="export_error_label">Помилка експорту</string>
<string name="opml_export_success_title">OPML експорт успішний</string>
<string name="opml_export_success_sum">OPML файл записаний в:\u0020</string>
@@ -296,9 +380,27 @@
<string name="sleep_timer_label">Таймер сну</string>
<string name="time_left_label">Залишилось:\u0020</string>
<string name="time_dialog_invalid_input">Помилка вводу, час повинен бути цілим</string>
- <string name="time_unit_seconds">секунд</string>
- <string name="time_unit_minutes">хвилин</string>
- <string name="time_unit_hours">годин</string>
+ <string name="timer_about_to_expire_label"><b>Коли таймер добігає кінця:</b></string>
+ <string name="shake_to_reset_label">Струснути для перезапуска таймера</string>
+ <string name="timer_vibration_label">Вібрувати</string>
+ <string name="time_seconds">секунд</string>
+ <string name="time_minutes">хвилин</string>
+ <string name="time_hours">годин</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 секунда</item>
+ <item quantity="few">%d секунди</item>
+ <item quantity="other">%d секунд</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 хвилина</item>
+ <item quantity="few">%d хвилини</item>
+ <item quantity="other">%d хвилин</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 година</item>
+ <item quantity="few">%d години</item>
+ <item quantity="other">%d годин</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">КАТЕГОРІЇ</string>
<string name="gpodnet_toplist_header">ТОП ПОДКАСТІВ</string>
@@ -340,11 +442,13 @@
<string name="folder_not_empty_dialog_msg">В папці щось є. Всі завантаження зберігаються в цю папку. Все рівно продовжувати?</string>
<string name="set_to_default_folder">Обрати папку по замовчанню</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Призупиняти програвання замість зниження гучності коли інша програма хоче програти звук</string>
- <string name="pref_pausePlaybackForFocusLoss_title">Пауза для перевивання</string>
+ <string name="pref_pausePlaybackForFocusLoss_title">Пауза в разі переривання</string>
+ <string name="pref_resumeAfterCall_sum">Відновити відтворення після закінчення дзвінка</string>
+ <string name="pref_resumeAfterCall_title">Відновити після дзвінка</string>
+ <string name="pref_restart_required">Для застосування змін потрібно перезапустити AntennaPod</string>
<!--Online feed view-->
<string name="subscribe_label">Підписатися</string>
<string name="subscribed_label">Підписано</string>
- <string name="downloading_label">Завантаження...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Показати глави</string>
<string name="show_shownotes_label">Показати нотатки</string>
@@ -367,7 +471,29 @@
<!--Feed information screen-->
<string name="authentication_label">Автентикація</string>
<string name="authentication_descr">Змінити ваші логін та пароль для подкаста та епізодів</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Оновлення бази даних</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Імпорт подкастів з інших програм...</string>
<string name="search_itunes_label">Пошук в iTunes</string>
+ <string name="all_label">Всі</string>
+ <string name="selected_all_label">Обрано всі епізоди</string>
+ <string name="none_label">Жодного</string>
+ <string name="deselected_all_label">Жодного епізода обрано</string>
+ <string name="played_label">Переглянуті</string>
+ <string name="selected_played_label">Обрано переглянуті епізоди</string>
+ <string name="unplayed_label">Непереглянуті</string>
+ <string name="selected_unplayed_label">Обрано непереглянуті епізоди</string>
+ <string name="downloaded_label">Завантажені</string>
+ <string name="selected_downloaded_label">Обрано завантажені епізоди</string>
+ <string name="not_downloaded_label">Незавантажені</string>
+ <string name="selected_not_downloaded_label">Обрано незавантажені епізоди</string>
+ <string name="sort_title_a_z">Назва (А \u2192 Я)</string>
+ <string name="sort_title_z_a">Назва (Я \u2192 А)</string>
+ <string name="sort_date_new_old">Дата (Нові \u2192 Старі)</string>
+ <string name="sort_date_old_new">Дата (Старі \u2192 Нові)</string>
+ <string name="sort_duration_short_long">Тривалість (Короткі \u2192 Довгі)</string>
+ <string name="sort_duration_long_short">Тривалість (Довгі \u2192 Короткі)</string>
+ <!--Rating dialog-->
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values-v16/styles.xml b/core/src/main/res/values-v16/styles.xml
index e7c56b5f5..a92790152 100644
--- a/core/src/main/res/values-v16/styles.xml
+++ b/core/src/main/res/values-v16/styles.xml
@@ -9,7 +9,7 @@
<style name="AntennaPod.Dialog.Title" parent="@android:style/TextAppearance.Medium">
<item name="android:textSize">@dimen/text_size_medium</item>
- <item name="android:textColor">@color/bright_blue</item>
+ <item name="android:textColor">@color/holo_blue_light</item>
<item name="android:maxLines">2</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">sans-serif-light</item>
diff --git a/core/src/main/res/values-v21/styles.xml b/core/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..42bd358c7
--- /dev/null
+++ b/core/src/main/res/values-v21/styles.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <style name="Widget.AntennaPod.Button" parent="Widget.AppCompat.Button">
+ <item name="textAllCaps">true</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/core/src/main/res/values-zh-rCN/strings.xml b/core/src/main/res/values-zh-rCN/strings.xml
index 0a2b9355e..78017b383 100644
--- a/core/src/main/res/values-zh-rCN/strings.xml
+++ b/core/src/main/res/values-zh-rCN/strings.xml
@@ -3,11 +3,13 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">订阅</string>
- <string name="add_feed_label">添加博客</string>
+ <string name="add_feed_label">添加播客</string>
<string name="podcasts_label">播客</string>
<string name="episodes_label">曲目</string>
<string name="new_episodes_label">新曲目</string>
<string name="all_episodes_label">所有曲目</string>
+ <string name="all_episodes_short_label">全部</string>
+ <string name="favorite_episodes_label">收藏</string>
<string name="new_label">最新</string>
<string name="waiting_list_label">等待列表</string>
<string name="settings_label">设置</string>
@@ -26,18 +28,30 @@
<!--Main activity-->
<string name="drawer_open">打开菜单</string>
<string name="drawer_close">关闭菜单</string>
+ <string name="drawer_preferences">侧边栏设置</string>
+ <string name="drawer_feed_order_unplayed_episodes">按数量排序</string>
+ <string name="drawer_feed_order_alphabetical">按名称排序</string>
+ <string name="drawer_feed_order_last_update">按出版日期排序</string>
+ <string name="drawer_feed_counter_new_unplayed">新曲目和播放曲目数</string>
+ <string name="drawer_feed_counter_new">新曲目数</string>
+ <string name="drawer_feed_counter_unplayed">未播曲目数</string>
+ <string name="drawer_feed_counter_none">无</string>
<!--Webview actions-->
<string name="open_in_browser_label">在浏览器打开</string>
<string name="copy_url_label">复制 URL</string>
<string name="share_url_label">分享 URL</string>
<string name="copied_url_msg">复制 URL 到剪贴板.</string>
+ <string name="go_to_position_label">去往该位置</string>
<!--Playback history-->
<string name="clear_history_label">清空历史</string>
<!--Other-->
<string name="confirm_label">确定</string>
<string name="cancel_label">取消</string>
+ <string name="yes">是</string>
+ <string name="no">否</string>
<string name="author_label">作者</string>
<string name="language_label">语言</string>
+ <string name="url_label">链接地址</string>
<string name="podcast_settings_label">设置</string>
<string name="cover_label">图片</string>
<string name="error_label">错误</string>
@@ -52,41 +66,81 @@
<string name="length_prefix">长度:\u0020</string>
<string name="size_prefix">大小:\u0020</string>
<string name="processing_label">处理中</string>
- <string name="loading_label">加载中...</string>
<string name="save_username_password_label">保存用户名密码</string>
<string name="close_label">关闭</string>
<string name="retry_label">重试</string>
<string name="auto_download_label">包含到自动下载</string>
+ <string name="auto_download_apply_to_items_title"> 应用到之前的曲目中</string>
+ <string name="auto_delete_label">自动删除曲目(覆盖全局选项)</string>
+ <string name="parallel_downloads_suffix">\u0020 并行下载</string>
+ <string name="feed_auto_download_global">全局</string>
+ <string name="feed_auto_download_always"> 总是</string>
+ <string name="feed_auto_download_never">从不</string>
+ <string name="episode_cleanup_never">从不</string>
+ <string name="episode_cleanup_queue_removal">当不在队列中</string>
+ <string name="episode_cleanup_after_listening">结束后</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="other">结束后 %d 天</item>
+ </plurals>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">订阅 URL</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">添加播客 URL</string>
+ <string name="podcastdirectories_label">从目录中寻找播客</string>
<string name="podcastdirectories_descr">您可以在 gpodder.net 通过名称、类别或热门来搜索新播客</string>
<string name="browse_gpoddernet_label">浏览 gpodder.net</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">全部标识已读</string>
- <string name="mark_all_read_msg">将所有曲目标记为已读</string>
+ <string name="mark_all_read_msg">将所有曲目标记为已播放</string>
+ <string name="mark_all_read_confirmation_msg">请确认您要将所有曲目标为已播放</string>
+ <string name="mark_all_read_feed_confirmation_msg">请确认您要将该订阅下的所有曲目标为已播放</string>
+ <string name="mark_all_seen_label">所有可见</string>
<string name="show_info_label">查看信息</string>
<string name="remove_feed_label">删除播客</string>
<string name="share_link_label">分享网站链接</string>
- <string name="share_source_label">分享订阅链接</string>
+ <string name="share_link_with_position_label">分享网站链接与位置</string>
+ <string name="share_feed_url_label">分享订阅地址</string>
+ <string name="share_item_url_label">分享曲目下载地址</string>
+ <string name="share_item_url_with_position_label">分享曲目下载地址与位置</string>
<string name="feed_delete_confirmation_msg">确认要删除这些订阅吗? 该订阅所有已经下载的曲目将一并删除. </string>
<string name="feed_remover_msg">删除订阅</string>
+ <string name="load_complete_feed">刷新全部订阅</string>
+ <string name="hide_episodes_title">隐藏曲目</string>
+ <string name="episode_actions">启用</string>
+ <string name="hide_unplayed_episodes_label">未播放</string>
+ <string name="hide_paused_episodes_label">已暂停</string>
+ <string name="hide_played_episodes_label">已播放</string>
+ <string name="hide_queued_episodes_label">已在播放列表中</string>
+ <string name="hide_not_queued_episodes_label">不在播放列表中</string>
+ <string name="hide_downloaded_episodes_label">已下载</string>
+ <string name="hide_not_downloaded_episodes_label">未下载</string>
+ <string name="filtered_label">已过滤的</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} 上次刷新失败</string>
<!--actions on feeditems-->
<string name="download_label">下载</string>
<string name="play_label">播放</string>
<string name="pause_label">暂停</string>
+ <string name="stop_label">停止</string>
<string name="stream_label">流媒体</string>
<string name="remove_label">删除</string>
<string name="remove_episode_lable">移除曲目</string>
- <string name="mark_read_label">标记已读</string>
- <string name="mark_unread_label">标记未读</string>
+ <string name="mark_read_label">标记已播放</string>
+ <string name="marked_as_read_label">已标记为已播放</string>
+ <string name="mark_unread_label">标记未播放</string>
<string name="add_to_queue_label">添加到播放列表</string>
+ <string name="added_to_queue_label">已添加到播放列表</string>
<string name="remove_from_queue_label">从播放列表中删除</string>
+ <string name="add_to_favorite_label">加入收藏</string>
+ <string name="remove_from_favorite_label">从收藏删除</string>
<string name="visit_website_label">访问网站</string>
<string name="support_label">Flattr 他</string>
<string name="enqueue_all_new">全部添加到播放列表</string>
<string name="download_all">全部下载</string>
<string name="skip_episode_label">跳过曲目</string>
+ <string name="activate_auto_download">开启自动下载</string>
+ <string name="deactivate_auto_download">关闭自动下载</string>
+ <string name="reset_position">重置播放进度</string>
+ <string name="removed_item">已删除项</string>
<!--Download messages and labels-->
<string name="download_successful">成功</string>
<string name="download_failed">失败</string>
@@ -104,12 +158,13 @@
<string name="download_error_unauthorized">认证错误</string>
<string name="cancel_all_downloads_label">取消所有下载</string>
<string name="download_canceled_msg">已取消下载</string>
+ <string name="download_canceled_autodownload_enabled_msg">已取消下载\n对该曲目禁用<i>自动下载</i></string>
<string name="download_report_title">下载完成</string>
+ <string name="download_report_content_title">下载报告</string>
<string name="download_error_malformed_url">畸形 URL</string>
<string name="download_error_io_error">IO 错误</string>
<string name="download_error_request_error">请求出错</string>
<string name="download_error_db_access">数据库访问错误</string>
- <string name="downloads_left">\u0020 下载剩余</string>
<string name="downloads_processing">正在处理下载</string>
<string name="download_notification_title">下载播客数据</string>
<string name="download_report_content">%1$d 下载成功, %2$d 失败</string>
@@ -120,6 +175,7 @@
<string name="download_request_error_dialog_message_prefix">尝试下载文件:\u0020 时出错</string>
<string name="authentication_notification_title">需要认证</string>
<string name="authentication_notification_msg">您所请求的资源需要用户名和密码</string>
+ <string name="confirm_mobile_download_dialog_title">确认手机下载</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">错误!</string>
<string name="player_stopped_msg">没有可播放媒体</string>
@@ -132,12 +188,22 @@
<string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">缓冲中</string>
<string name="playbackservice_notification_title">播客播放中</string>
+ <string name="unknown_media_key">AntennaPod - 未知媒体密钥: %1$d</string>
<!--Queue operations-->
+ <string name="lock_queue">锁定播放列表</string>
+ <string name="unlock_queue">解锁播放列表</string>
<string name="clear_queue_label">清空播放列表</string>
<string name="undo">撤消</string>
<string name="removed_from_queue">已删除项</string>
<string name="move_to_top_label">移到顶端</string>
<string name="move_to_bottom_label">移到下部</string>
+ <string name="sort">排序</string>
+ <string name="alpha">按字母</string>
+ <string name="date">按日期</string>
+ <string name="duration">按时长</string>
+ <string name="ascending">升序</string>
+ <string name="descending">降序</string>
+ <string name="clear_queue_confirmation_msg">请确认您要清除队列中的全部曲目</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr 登录</string>
<string name="flattr_auth_explanation">按下面的按钮开始身份验证过程. 将在浏览器中打开 Flattr 登录界面并要求给予 AntennaPod 访问 Flattr 的权限. 权限许可后, 将自动回到这个界面.</string>
@@ -145,13 +211,25 @@
<string name="return_home_label">返回主页</string>
<string name="flattr_auth_success">验证成功! 现在可以使用应用内 Flattr 相关功能了.</string>
<string name="no_flattr_token_title">没有找到 Flattr 验证令牌信息</string>
- <string name="no_flattr_token_msg">您的 flattr 账户似乎并没有连接到 AntennaPod. You can either connect your account to AntennaPod to flattr things within the app or you can visit the website of the thing to flattr it there.</string>
+ <string name="no_flattr_token_notification_msg">您的 flattr 账户似乎并没有连接到 AntennaPod,点这里验证。</string>
+ <string name="no_flattr_token_msg">您的 flattr 账户似乎并没有连接到 AntennaPod. 您可以关联您的账户到AntennaPod以便在应用内flattr条目,或者您可以自己访问条目的网站完成flattr</string>
<string name="authenticate_now_label">验证</string>
<string name="action_forbidden_title">被禁止</string>
<string name="action_forbidden_msg">AntennaPod 没有权限执行本动作. 原因可能是: AntennaPod 对您账户的访问令牌被撤销. 你可以重新\"验证\"或访问该网站来授权.</string>
<string name="access_revoked_title">撤销访问</string>
<string name="access_revoked_info">您已经成功撤销 AntennaPod 对账户令牌的访问. 为了完成这个过程, 您必须到 Flattr 网站 \"账户设置-&gt;已批准应用\" 列表内删除本应用.</string>
<!--Flattr-->
+ <string name="flattr_click_success">成功Flattr一个条目</string>
+ <string name="flattr_click_success_count">成功Flattr%d个条目</string>
+ <string name="flattr_click_success_queue">Flattr%s成功.</string>
+ <string name="flattr_click_failure_count">Flattr失败%d个条目</string>
+ <string name="flattr_click_failure">%sFlattr失败.</string>
+ <string name="flattr_click_enqueued">稍后将Flattr该条目</string>
+ <string name="flattring_thing">正在Flattr%s</string>
+ <string name="flattring_label">AntennaPod正在Flattr</string>
+ <string name="flattrd_label">AntennaPod已经Flattr</string>
+ <string name="flattrd_failed_label">AntennaPod Flattr失败</string>
+ <string name="flattr_retrieving_status">查询Flattr结果状态</string>
<!--Variable Speed-->
<string name="download_plugin_label">插件下载</string>
<string name="no_playback_plugin_title">插件没有安装</string>
@@ -165,15 +243,19 @@
<string name="queue_label">播放列表</string>
<string name="services_label">服务</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">耳机断开时暂停播放 </string>
<string name="pref_unpauseOnHeadsetReconnect_sum">当耳机重新连接时恢复播放</string>
<string name="pref_followQueue_sum">播放完成跳转到播放列表下一项</string>
- <string name="pref_auto_delete_sum">当播放完成后删除单集</string>
+ <string name="pref_auto_delete_sum">当播放完成后删除曲目</string>
<string name="pref_auto_delete_title">自动删除</string>
+ <string name="pref_smart_mark_as_played_sum">当曲目的剩余未播放时间小于一个确定值时将其表为已播放</string>
+ <string name="pref_smart_mark_as_played_title">智能标记为已播放</string>
<string name="playback_pref">播放</string>
<string name="network_pref">网络</string>
- <string name="pref_autoUpdateIntervall_title">更新周期</string>
- <string name="pref_autoUpdateIntervall_sum">设置订阅自动刷新周期</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">定时更新</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">可以设置更新 <i>间隔</i>,比如“2 小时”;或着每天特定的 <i>时间</i> ,比如\"上午 7:00\";或者 <i>禁用</i> 自动更新。\n\n<small>请注意:更新时间不是严格的,可能会有少许的延时。</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">禁用</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">设置间隔</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">设置时间</string>
<string name="pref_downloadMediaOnWifiOnly_sum">仅在 WIFI 情况下载媒体文件</string>
<string name="pref_followQueue_title">连续播放</string>
<string name="pref_downloadMediaOnWifiOnly_title">仅在 WIFI 情况下载</string>
@@ -190,13 +272,26 @@
</string>
<string name="pref_revokeAccess_title">撤销访问</string>
<string name="pref_revokeAccess_sum">撤销访问本应用对您 Flattr 账户的访问权限.</string>
+ <string name="pref_auto_flattr_title">自动Flattr</string>
+ <string name="pref_auto_flattr_sum">设置自动 flattring</string>
<string name="user_interface_label">界面</string>
<string name="pref_set_theme_title">主题选择</string>
+ <string name="pref_nav_drawer_title">侧边栏设置</string>
+ <string name="pref_nav_drawer_sum">侧边栏外观设置</string>
+ <string name="pref_nav_drawer_items_title">设置侧边栏的选项</string>
+ <string name="pref_nav_drawer_items_sum">改变侧边栏中的选项</string>
+ <string name="pref_nav_drawer_feed_order_title">设置订阅的排序方式</string>
+ <string name="pref_nav_drawer_feed_order_sum">改变你的订阅的排序方式</string>
+ <string name="pref_nav_drawer_feed_counter_title">设定订阅数</string>
+ <string name="pref_nav_drawer_feed_counter_sum">改变订阅数所代表的信息</string>
<string name="pref_set_theme_sum">改变 AntennaPod 外观</string>
<string name="pref_automatic_download_title">自动下载</string>
<string name="pref_automatic_download_sum">配置自动下载的曲目</string>
<string name="pref_autodl_wifi_filter_title">打开 Wi-Fi 过滤器</string>
<string name="pref_autodl_wifi_filter_sum">只允许在 Wi-Fi 网络下自动下载</string>
+ <string name="pref_automatic_download_on_battery_title">未充电时下载</string>
+ <string name="pref_automatic_download_on_battery_sum">未充电时允许自动下载</string>
+ <string name="pref_parallel_downloads_title">并行下载</string>
<string name="pref_episode_cache_title">曲目缓存</string>
<string name="pref_theme_title_light">浅色</string>
<string name="pref_theme_title_dark">暗色</string>
@@ -212,16 +307,30 @@
<string name="pref_gpodnet_setlogin_information_sum">改变 gpodder.net 账户登录信息.</string>
<string name="pref_playback_speed_title">播放速度</string>
<string name="pref_playback_speed_sum">自定义音频播放速度</string>
- <string name="pref_seek_delta_title">定位时间</string>
- <string name="pref_seek_delta_sum">当倒退或快速回放时以这些秒为单位</string>
+ <string name="pref_rewind">时间倒回</string>
<string name="pref_gpodnet_sethostname_title">设置主机名</string>
<string name="pref_gpodnet_sethostname_use_default_host">使用默认主机</string>
<string name="pref_expandNotify_title">扩展通知</string>
<string name="pref_expandNotify_sum">总是扩展通知以显示播放按钮</string>
<string name="pref_persistNotify_title">保持播放控制</string>
<string name="pref_persistNotify_sum">在暂停时保持通知和锁屏界面的控制。</string>
- <string name="pref_expand_notify_unsupport_toast">Android 版本 4.1 之前不支持扩展通知</string>
+ <string name="pref_lockscreen_background_title">设置锁屏背景</string>
+ <string name="pref_showDownloadReport_title">显示下载报告</string>
+ <string name="pref_showDownloadReport_sum">如果下载失败,生成一份显示详细失败信息的报告。</string>
+ <string name="pref_expand_notify_unsupport_toast">Android 4.1 之前不支持扩展通知。</string>
+ <string name="pref_queueAddToFront_sum">添加新曲目到队列之前。</string>
+ <string name="pref_queueAddToFront_title">新曲目从播放列表的前排插入</string>
+ <string name="pref_smart_mark_as_played_disabled">已禁用</string>
+ <string name="pref_image_cache_size_title">图像缓存大小</string>
+ <string name="pref_image_cache_size_sum">用于缓存图像的存储空间大小</string>
+ <string name="crash_report_title">崩溃报告</string>
+ <string name="crash_report_sum">通过 E-mail 发送最后崩溃报告</string>
+ <string name="send_email">发送 E-mail</string>
<!--Auto-Flattr dialog-->
+ <string name="auto_flattr_enable">启用自动 flattring</string>
+ <string name="auto_flattr_after_percent">当播放到百分之%d时Flattr改曲目</string>
+ <string name="auto_flattr_ater_beginning">当播放开始时Flattr改曲目</string>
+ <string name="auto_flattr_ater_end">当播放结束时Flattr改曲目</string>
<!--Search-->
<string name="search_hint">搜索订阅或者曲目</string>
<string name="found_in_shownotes_label">笔记中查找</string>
@@ -231,6 +340,10 @@
<string name="found_in_title_label">标题中查找</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML 文件可以方便的从别的播客转移数据过来。</string>
+ <string name="opml_import_option">设置 %1$d</string>
+ <string name="opml_import_explanation_1">从本地文件系统选择一个特定文件地址。</string>
+ <string name="opml_import_explanation_2">使用外部应用程序,例如 Dropbox,Google Drive 或您喜爱的文件管理器打开 OPML 文件。</string>
+ <string name="opml_import_explanation_3"><i>与</i> AntennaPod 一样,许多应用例如Google Mail,Dropbox,Google Drive和大多数文件管理器均可 <i>打开</i> OPML文件。</string>
<string name="start_import_label">开始导入</string>
<string name="opml_import_label">OPML 导入</string>
<string name="opml_directory_error">错误!</string>
@@ -239,8 +352,9 @@
<string name="opml_import_error_dir_empty">导入目录为空.</string>
<string name="select_all_label">全选</string>
<string name="deselect_all_label">取消所有选择</string>
+ <string name="choose_file_from_filesystem">来自本地文件系统</string>
+ <string name="choose_file_from_external_application">使用外部应用</string>
<string name="opml_export_label">OPML 导出</string>
- <string name="exporting_label">导出中...</string>
<string name="export_error_label">导出出错</string>
<string name="opml_export_success_title">OPML 导出成功.</string>
<string name="opml_export_success_sum">.opml 文件已保存到:\u0020</string>
@@ -251,9 +365,20 @@
<string name="sleep_timer_label">休眠计时器</string>
<string name="time_left_label">计时剩余:\u0020</string>
<string name="time_dialog_invalid_input">无效的输入, 时间是一个整数</string>
- <string name="time_unit_seconds">秒</string>
- <string name="time_unit_minutes">分钟</string>
- <string name="time_unit_hours">小时</string>
+ <string name="timer_about_to_expire_label"><b>当计时器过期时:</b></string>
+ <string name="timer_vibration_label">振动</string>
+ <string name="time_seconds">秒</string>
+ <string name="time_minutes">分</string>
+ <string name="time_hours">小时</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="other">%d 秒</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="other">%d 分</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="other">%d 小时</item>
+ </plurals>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">目录</string>
<string name="gpodnet_toplist_header">头条播客</string>
@@ -262,6 +387,7 @@
<string name="gpodnetauth_login_title">登录</string>
<string name="gpodnetauth_login_descr">欢迎进入 gpodder.net 登录流程. 首先, 输入请你的登录信息:</string>
<string name="gpodnetauth_login_butLabel">登录</string>
+ <string name="gpodnetauth_login_register">如果您目前没有账户,可以从这里创建:\nhttps://gpodder.net/register/</string>
<string name="username_label">用户名</string>
<string name="password_label">密码</string>
<string name="gpodnetauth_device_title">设备选择</string>
@@ -290,15 +416,20 @@
<string name="create_folder_error_no_write_access">本文件夹不能写入</string>
<string name="create_folder_error_already_exists">文件夹已存在</string>
<string name="create_folder_error">不能创建文件夹</string>
+ <string name="folder_does_not_exist_error">\"%1$s\" 不存在</string>
+ <string name="folder_not_readable_error">\"%1$s\" 不可读</string>
+ <string name="folder_not_writable_error">\"%1$s\" 不可写</string>
<string name="folder_not_empty_dialog_title">文件夹不能为空</string>
<string name="folder_not_empty_dialog_msg">您所选择的文件夹不能为空. 媒体下载和其他文件将被直接放在本文件夹. 确认继续吗?</string>
<string name="set_to_default_folder">选择默认文件夹</string>
<string name="pref_pausePlaybackForFocusLoss_sum">当另一个应用程序要播放声音时暂停播放, 而不是降低音量</string>
<string name="pref_pausePlaybackForFocusLoss_title">中断暂停</string>
+ <string name="pref_resumeAfterCall_sum">通话结束后恢复播放</string>
+ <string name="pref_resumeAfterCall_title">通话后恢复</string>
+ <string name="pref_restart_required">需要重启 AntennaPod 使改变生效。</string>
<!--Online feed view-->
<string name="subscribe_label">订阅</string>
<string name="subscribed_label">已订阅</string>
- <string name="downloading_label">下载中...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">显示章节</string>
<string name="show_shownotes_label">显示笔记</string>
@@ -317,9 +448,37 @@
<string name="new_episodes_count_label">新曲目数</string>
<string name="in_progress_episodes_count_label">已收听曲目数</string>
<string name="drag_handle_content_description">拖动以变更本项目的位置</string>
+ <string name="load_next_page_label">载入下一页</string>
<!--Feed information screen-->
<string name="authentication_label">验证</string>
<string name="authentication_descr">给本播客及曲目变更用户名及密码</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">升级数据库</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">正在从选定的应用中导入订阅...</string>
+ <string name="search_itunes_label">搜索 iTunes</string>
+ <string name="all_label">全部</string>
+ <string name="selected_all_label">全选</string>
+ <string name="none_label">无</string>
+ <string name="deselected_all_label">取消全选</string>
+ <string name="played_label">已播放</string>
+ <string name="selected_played_label">选择已播放的曲目</string>
+ <string name="unplayed_label">未播放</string>
+ <string name="selected_unplayed_label">选择未播放曲目</string>
+ <string name="downloaded_label">已下载</string>
+ <string name="selected_downloaded_label">选择已下载的曲目</string>
+ <string name="not_downloaded_label">未下载</string>
+ <string name="selected_not_downloaded_label">选择未下载的曲目</string>
+ <string name="sort_title_a_z">标题 (A \u2192 Z)</string>
+ <string name="sort_title_z_a">标题 (Z \u2192 A)</string>
+ <string name="sort_date_new_old">日期 (新 \u2192 旧)</string>
+ <string name="sort_date_old_new">日期 (旧 \u2192 新)</string>
+ <string name="sort_duration_short_long">时长 (短 \u2192 长)</string>
+ <string name="sort_duration_long_short">时长 (长 \u2192 短)</string>
+ <!--Rating dialog-->
+ <string name="rating_title">喜欢 AntennaPod?</string>
+ <string name="rating_never_label">请勿打扰</string>
+ <string name="rating_later_label">稍后提醒</string>
+ <string name="rating_now_label">好的, 就这样!</string>
+ <!--Audio controls-->
</resources>
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 28e390879..39a60edbf 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -1,12 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <string-array name="spnAutoDeleteItems">
+ <item>@string/feed_auto_download_global</item>
+ <item>@string/feed_auto_download_always</item>
+ <item>@string/feed_auto_download_never</item>
+ </string-array>
+
<string-array name="smart_mark_as_played_values">
<item>0</item>
<item>15</item>
<item>30</item>
- <item>45</item>
<item>60</item>
+ <item>120</item>
+ <item>300</item>
</string-array>
@@ -20,56 +27,64 @@
<item>60</item>
</integer-array>
- <string-array name="update_intervall_options">
- <item>Manual</item>
- <item>1 hour</item>
- <item>2 hours</item>
- <item>4 hours</item>
- <item>8 hours</item>
- <item>12 hours</item>
- <item>24 hours</item>
- </string-array>
-
<string-array name="update_intervall_values">
- <item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>12</item>
<item>24</item>
+ <item>72</item>
</string-array>
+
<string-array name="episode_cache_size_entries">
- <item>@string/pref_episode_cache_unlimited</item>
- <item>1</item>
- <item>2</item>
<item>5</item>
<item>10</item>
- <item>20</item>
- <item>40</item>
- <item>60</item>
- <item>80</item>
+ <item>25</item>
+ <item>50</item>
<item>100</item>
+ <item>@string/pref_episode_cache_unlimited</item>
</string-array>
+
<string-array name="episode_cache_size_values">
- <item>-1</item>
- <item>1</item>
- <item>2</item>
<item>5</item>
<item>10</item>
- <item>20</item>
- <item>40</item>
- <item>60</item>
- <item>80</item>
+ <item>25</item>
+ <item>50</item>
<item>100</item>
+ <item>-1</item>
+ </string-array>
+
+ <string-array name="episode_cleanup_entries">
+ <item>@string/episode_cleanup_queue_removal</item>
+ <item>0</item>
+ <item>1</item>
+ <item>3</item>
+ <item>5</item>
+ <item>7</item>
+ <item>@string/episode_cleanup_never</item>
</string-array>
+
+ <string-array name="episode_cleanup_values">
+ <item>-1</item>
+ <item>0</item>
+ <item>1</item>
+ <item>3</item>
+ <item>5</item>
+ <item>7</item>
+ <item>-2</item>
+ </string-array>
+
<string-array name="playback_speed_values">
- <item>0.5</item>
- <item>0.6</item>
- <item>0.7</item>
- <item>0.8</item>
- <item>0.9</item>
- <item>1.0</item>
+ <item>0.50</item>
+ <item>0.60</item>
+ <item>0.70</item>
+ <item>0.75</item>
+ <item>0.80</item>
+ <item>0.85</item>
+ <item>0.90</item>
+ <item>0.95</item>
+ <item>1.00</item>
<item>1.05</item>
<item>1.10</item>
<item>1.15</item>
@@ -130,15 +145,38 @@
<string-array name="nav_drawer_titles">
<item>@string/queue_label</item>
- <item>@string/new_episodes_label</item>
- <item>@string/all_episodes_label</item>
+ <item>@string/episodes_label</item>
<item>@string/downloads_label</item>
<item>@string/playback_history_label</item>
<item>@string/my_subscriptions</item>
<item>@string/add_feed_label</item>
</string-array>
- <string-array name="episode_hide_options">
+ <string-array name="nav_drawer_feed_order_options">
+ <item>@string/drawer_feed_order_unplayed_episodes</item>
+ <item>@string/drawer_feed_order_alphabetical</item>
+ <item>@string/drawer_feed_order_last_update</item>
+ </string-array>
+ <string-array name="nav_drawer_feed_order_values">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ </string-array>
+
+ <string-array name="nav_drawer_feed_counter_options">
+ <item>@string/drawer_feed_counter_new_unplayed</item>
+ <item>@string/drawer_feed_counter_new</item>
+ <item>@string/drawer_feed_counter_unplayed</item>
+ <item>@string/drawer_feed_counter_none</item>
+ </string-array>
+ <string-array name="nav_drawer_feed_counter_values">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ </string-array>
+
+ <string-array name="episode_filter_options">
<item>@string/hide_unplayed_episodes_label</item>
<item>@string/hide_paused_episodes_label</item>
<item>@string/hide_played_episodes_label</item>
@@ -148,7 +186,7 @@
<item>@string/hide_not_downloaded_episodes_label</item>
</string-array>
- <string-array name="episode_hide_values">
+ <string-array name="episode_filter_values">
<item>unplayed</item>
<item>paused</item>
<item>played</item>
@@ -158,4 +196,20 @@
<item>not_downloaded</item>
</string-array>
+ <string-array name="image_cache_size_options">
+ <item>20 MiB</item>
+ <item>50 MiB</item>
+ <item>100 MiB</item>
+ <item>250 MiB</item>
+ <item>500 MiB</item>
+ </string-array>
+
+ <string-array name="image_cache_size_values">
+ <item>20</item>
+ <item>50</item>
+ <item>100</item>
+ <item>250</item>
+ <item>500</item>
+ </string-array>
+
</resources>
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index 3923189ca..48f8ae12b 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <attr name="action_bar_icon_color" format="reference"/>
<attr name="action_about" format="reference"/>
<attr name="action_search" format="reference"/>
<attr name="action_stream" format="reference"/>
@@ -12,7 +13,6 @@
<attr name="content_discard" format="reference"/>
<attr name="content_new" format="reference"/>
<attr name="feed" format="reference"/>
- <attr name="device_access_time" format="reference"/>
<attr name="location_web_site" format="reference"/>
<attr name="navigation_accept" format="reference"/>
<attr name="navigation_cancel" format="reference"/>
@@ -37,14 +37,24 @@
<attr name="av_pause_big" format="reference"/>
<attr name="av_ff_big" format="reference"/>
<attr name="av_rew_big" format="reference"/>
+ <attr name="av_skip_big" format="reference"/>
<attr name="ic_settings" format="reference"/>
<attr name="ic_lock_open" format="reference"/>
<attr name="ic_lock_closed" format="reference"/>
<attr name="ic_filter" format="reference"/>
+ <attr name="progressBarTheme" format="reference"/>
+ <attr name="ic_fav" format="reference"/>
+ <attr name="ic_unfav" format="reference"/>
+ <attr name="ic_sleep" format="reference"/>
+ <attr name="ic_sleep_off" format="reference"/>
+ <attr name="ic_check_box" format="reference"/>
+ <attr name="ic_check_box_outline" format="reference"/>
+ <attr name="ic_indeterminate_check_box" format="reference"/>
+ <attr name="ic_sort" format="reference"/>
<!-- Used in itemdescription -->
<attr name="non_transparent_background" format="reference"/>
<attr name="overlay_background" format="color"/>
<attr name="nav_drawer_background" format="color"/>
-</resources> \ No newline at end of file
+</resources>
diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml
index e558a5c4e..ff061be4c 100644
--- a/core/src/main/res/values/colors.xml
+++ b/core/src/main/res/values/colors.xml
@@ -3,8 +3,11 @@
<color name="white">#FFFFFF</color>
<color name="gray">#808080</color>
+ <color name="grey600">#757575</color>
+ <color name="light_gray">#bfbfbf</color>
<color name="black">#000000</color>
- <color name="bright_blue">#33B5E5</color>
+ <color name="holo_blue_light">#33B5E5</color>
+ <color name="holo_blue_dark">#0099CC</color>
<color name="ics_gray">#858585</color>
<color name="actionbar_gray">#DDDDDD</color>
<color name="download_success_green">#669900</color>
@@ -24,7 +27,8 @@
<!-- Theme colors -->
<color name="primary_light">#FFFFFF</color>
- <color name="color_accent">#009EC8</color>
+ <color name="highlight_light">#DDDDDD</color>
+ <color name="highlight_dark">#414141</color>
-</resources> \ No newline at end of file
+</resources>
diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml
index 2bd40f34a..4f549efd7 100644
--- a/core/src/main/res/values/dimens.xml
+++ b/core/src/main/res/values/dimens.xml
@@ -3,7 +3,7 @@
<dimen name="widget_margin">8dp</dimen>
<dimen name="thumbnail_length">70dp</dimen>
- <dimen name="external_player_height">70dp</dimen>
+ <dimen name="external_player_height">56dp</dimen>
<dimen name="enc_icons_size">20dp</dimen>
<dimen name="text_size_micro">12sp</dimen>
<dimen name="text_size_small">14sp</dimen>
@@ -34,6 +34,6 @@
<dimen name="listitem_icon_leftpadding">16dp</dimen>
<dimen name="listitem_icon_rightpadding">16dp</dimen>
- <dimen name="audioplayer_playercontrols_length">64dp</dimen>
+ <dimen name="audioplayer_playercontrols_length">48dp</dimen>
-</resources> \ No newline at end of file
+</resources>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 8cedc43b3..37ccbb80b 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -1,53 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<resources
xmlns:tools="http://schemas.android.com/tools"
- tools:ignore="MissingTranslation"
- >
+ tools:ignore="MissingTranslation">
<!-- Activitiy and fragment titles -->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Feeds</string>
- <string name="add_feed_label">Add podcast</string>
+ <string name="add_feed_label">Add Podcast</string>
<string name="podcasts_label">PODCASTS</string>
- <string name="episodes_label">EPISODES</string>
- <string name="new_episodes_label">New episodes</string>
- <string name="all_episodes_label">All episodes</string>
+ <string name="episodes_label">Episodes</string>
+ <string name="new_episodes_label">New Episodes</string>
+ <string name="all_episodes_label">All Episodes</string>
+ <string name="all_episodes_short_label">All</string>
+ <string name="favorite_episodes_label">Favorites</string>
<string name="new_label">New</string>
- <string name="waiting_list_label">Waiting list</string>
+ <string name="waiting_list_label">Waiting List</string>
<string name="settings_label">Settings</string>
- <string name="add_new_feed_label">Add podcast</string>
+ <string name="add_new_feed_label">Add Podcast</string>
<string name="downloads_label">Downloads</string>
<string name="downloads_running_label">Running</string>
<string name="downloads_completed_label">Completed</string>
<string name="downloads_log_label">Log</string>
- <string name="cancel_download_label">Cancel Download</string>
- <string name="playback_history_label">Playback history</string>
<string name="my_subscriptions">Subscriptions</string>
+ <string name="cancel_download_label">Cancel\nDownload</string>
+ <string name="playback_history_label">Playback History</string>
<string name="gpodnet_main_label">gpodder.net</string>
- <string name="gpodnet_auth_label">gpodder.net login</string>
+ <string name="gpodnet_auth_label">gpodder.net Login</string>
+ <string name="free_space_label">%1$s free</string>
+ <string name="episode_cache_full_title">Episode cache full</string>
+ <string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string>
<!-- New episodes fragment -->
<string name="recently_published_episodes_label">Recently published</string>
- <string name="episode_filter_label">Show only new episodes</string>
+ <string name="episode_filter_label">Show only new Episodes</string>
<!-- Main activity -->
<string name="drawer_open">Open menu</string>
<string name="drawer_close">Close menu</string>
<string name="drawer_preferences">Drawer Preferences</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sort by counter</string>
+ <string name="drawer_feed_order_alphabetical">Sort alphabetically</string>
+ <string name="drawer_feed_order_last_update">Sort by publication date</string>
+ <string name="drawer_feed_counter_new_unplayed">Number of new and unplayed episodes</string>
+ <string name="drawer_feed_counter_new">Number of new episodes</string>
+ <string name="drawer_feed_counter_unplayed">Number of unplayed episodes</string>
+ <string name="drawer_feed_counter_none">None</string>
<!-- Webview actions -->
- <string name="open_in_browser_label">Open in browser</string>
+ <string name="open_in_browser_label">Open in Browser</string>
<string name="copy_url_label">Copy URL</string>
<string name="share_url_label">Share URL</string>
- <string name="copied_url_msg">Copied URL to clipboard.</string>
- <string name="go_to_position_label">Go to this position</string>
+ <string name="copied_url_msg">Copied URL to Clipboard</string>
+ <string name="go_to_position_label">Go to this Position</string>
<!-- Playback history -->
- <string name="clear_history_label">Clear history</string>
+ <string name="clear_history_label">Clear History</string>
<!-- Other -->
<string name="confirm_label">Confirm</string>
<string name="cancel_label">Cancel</string>
+ <string name="yes">Yes</string>
+ <string name="no">No</string>
<string name="author_label">Author</string>
<string name="language_label">Language</string>
<string name="url_label">URL</string>
@@ -60,39 +73,59 @@
<string name="chapters_label">Chapters</string>
<string name="shownotes_label">Shownotes</string>
<string name="description_label">Description</string>
- <string name="most_recent_prefix">Most Recent Episode:\u0020</string>
+ <string name="most_recent_prefix">Most recent episode:\u0020</string>
<string name="episodes_suffix">\u0020episodes</string>
<string name="length_prefix">Length:\u0020</string>
<string name="size_prefix">Size:\u0020</string>
<string name="processing_label">Processing</string>
- <string name="loading_label">Loading...</string>
+ <string name="loading_label">Loading&#8230;</string>
<string name="save_username_password_label">Save username and password</string>
<string name="close_label">Close</string>
<string name="retry_label">Retry</string>
<string name="auto_download_label">Include in auto downloads</string>
+ <string name="auto_download_apply_to_items_title">Apply to Previous Episodes</string>
+ <string name="auto_download_apply_to_items_message">The new <i>Auto Download</i> setting will automatically be applied to new episodes.\nDo you also want to apply it to previously published episodes?</string>
+ <string name="auto_delete_label">Auto Delete Episode\n(override global default)</string>
<string name="parallel_downloads_suffix">\u0020parallel downloads</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Always</string>
+ <string name="feed_auto_download_never">Never</string>
+ <string name="send_label">Send&#8230;</string>
+ <string name="episode_cleanup_never">Never</string>
+ <string name="episode_cleanup_queue_removal">When not in queue</string>
+ <string name="episode_cleanup_after_listening">After finishing</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 day after finishing</item>
+ <item quantity="other">%d days after finishing</item>
+ </plurals>
<!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string>
<string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">Add Podcast by URL</string>
- <string name="podcastdirectories_label">Find podcast in directory</string>
+ <string name="podcastdirectories_label">Find Podcast in Directory</string>
<string name="podcastdirectories_descr">You can search for new podcasts by name, category or popularity in the gpodder.net directory, or search the iTunes store.</string>
<string name="browse_gpoddernet_label">Browse gpodder.net</string>
<!-- Actions on feeds -->
<string name="mark_all_read_label">Mark all as played</string>
- <string name="mark_all_read_msg">Marked all episodes as played</string>
+ <string name="mark_all_read_msg">Marked all Episodes as played</string>
<string name="mark_all_read_confirmation_msg">Please confirm that you want to mark all episodes as being played.</string>
<string name="mark_all_read_feed_confirmation_msg">Please confirm that you want to mark all episodes in this feed as being played.</string>
+ <string name="mark_all_seen_label">Mark all as seen</string>
<string name="show_info_label">Show information</string>
- <string name="remove_feed_label">Remove podcast</string>
- <string name="share_link_label">Share website link</string>
- <string name="share_source_label">Share feed link</string>
+ <string name="remove_feed_label">Remove Podcast</string>
+ <string name="share_label">Share&#8230;</string>
+ <string name="share_link_label">Share Link</string>
+ <string name="share_link_with_position_label">Share Link with Position</string>
+ <string name="share_feed_url_label">Share Feed URL</string>
+ <string name="share_item_url_label">Share Episode URL</string>
+ <string name="share_item_url_with_position_label">Share Episode URL with Position</string>
<string name="feed_delete_confirmation_msg">Please confirm that you want to delete this feed and ALL episodes of this feed that you have downloaded.</string>
- <string name="feed_remover_msg">Removing feed</string>
- <string name="load_complete_feed">Refresh complete feed</string>
- <string name="hide_episodes_title">Hide episodes</string>
+ <string name="feed_remover_msg">Removing Feed</string>
+ <string name="load_complete_feed">Refresh complete Feed</string>
+ <string name="hide_episodes_title">Hide Episodes</string>
+ <string name="episode_actions">Apply actions</string>
<string name="hide_unplayed_episodes_label">Unplayed</string>
<string name="hide_paused_episodes_label">Paused</string>
<string name="hide_played_episodes_label">Played</string>
@@ -101,7 +134,7 @@
<string name="hide_downloaded_episodes_label">Downloaded</string>
<string name="hide_not_downloaded_episodes_label">Not downloaded</string>
<string name="filtered_label">Filtered</string>
- <string name="refresh_failed_msg">{fa-exclamation-circle} Last refresh failed</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Last Refresh failed</string>
<!-- actions on feeditems -->
<string name="download_label">Download</string>
@@ -110,51 +143,59 @@
<string name="stop_label">Stop</string>
<string name="stream_label">Stream</string>
<string name="remove_label">Remove</string>
- <string name="remove_episode_lable">Remove episode</string>
+ <string name="remove_episode_lable">Remove Episode</string>
<string name="mark_read_label">Mark as played</string>
- <string name="mark_unread_label">Mark as unplayed</string>
<string name="marked_as_read_label">Marked as played</string>
+ <string name="mark_unread_label">Mark as unplayed</string>
<string name="add_to_queue_label">Add to Queue</string>
<string name="added_to_queue_label">Added to Queue</string>
<string name="remove_from_queue_label">Remove from Queue</string>
+ <string name="add_to_favorite_label">Add to Favorites</string>
+ <string name="added_to_favorites">Added to Favorites</string>
+ <string name="remove_from_favorite_label">Remove from Favorites</string>
+ <string name="removed_from_favorites">Removed from Favorites</string>
<string name="visit_website_label">Visit Website</string>
<string name="support_label">Flattr this</string>
<string name="enqueue_all_new">Enqueue all</string>
<string name="download_all">Download all</string>
<string name="skip_episode_label">Skip episode</string>
- <string name="activate_auto_download">Activate auto download</string>
- <string name="deactivate_auto_download">Deactivate auto download</string>
- <string name="reset_position">Reset playback position</string>
+ <string name="activate_auto_download">Activate Auto Download</string>
+ <string name="deactivate_auto_download">Deactivate Auto Download</string>
+ <string name="reset_position">Reset Playback Position</string>
+ <string name="removed_item">Item removed</string>
<!-- Download messages and labels -->
<string name="download_successful">successful</string>
<string name="download_failed">failed</string>
<string name="download_pending">Download pending</string>
<string name="download_running">Download running</string>
- <string name="download_error_device_not_found">Storage device not found</string>
- <string name="download_error_insufficient_space">Insufficient space</string>
- <string name="download_error_file_error">File error</string>
+ <string name="download_error_device_not_found">Storage Device not found</string>
+ <string name="download_error_insufficient_space">Insufficient Space</string>
+ <string name="download_error_file_error">File Error</string>
<string name="download_error_http_data_error">HTTP Data Error</string>
<string name="download_error_error_unknown">Unknown Error</string>
<string name="download_error_parser_exception">Parser Exception</string>
- <string name="download_error_unsupported_type">Unsupported Feed type</string>
- <string name="download_error_connection_error">Connection error</string>
- <string name="download_error_unknown_host">Unknown host</string>
- <string name="download_error_unauthorized">Authentication error</string>
+ <string name="download_error_unsupported_type">Unsupported Feed Type</string>
+ <string name="download_error_connection_error">Connection Error</string>
+ <string name="download_error_unknown_host">Unknown Host</string>
+ <string name="download_error_unauthorized">Authentication Error</string>
<string name="cancel_all_downloads_label">Cancel all downloads</string>
<string name="download_canceled_msg">Download canceled</string>
<string name="download_canceled_autodownload_enabled_msg">Download canceled\nDisabled <i>Auto Download</i> for this item</string>
<string name="download_report_title">Downloads completed with error(s)</string>
- <string name="download_report_content_title">Download report</string>
+ <string name="download_report_content_title">Download Report</string>
<string name="download_error_malformed_url">Malformed URL</string>
<string name="download_error_io_error">IO Error</string>
- <string name="download_error_request_error">Request error</string>
- <string name="download_error_db_access">Database access error</string>
- <string name="downloads_left">\u0020Downloads left</string>
+ <string name="download_error_request_error">Request Error</string>
+ <string name="download_error_db_access">Database Access Error</string>
+ <plurals name="downloads_left">
+ <item quantity="one">%d download left</item>
+ <item quantity="other">%d downloads left</item>
+ </plurals>
<string name="downloads_processing">Processing downloads</string>
<string name="download_notification_title">Downloading podcast data</string>
<string name="download_report_content">%1$d downloads succeeded, %2$d failed</string>
- <string name="download_log_title_unknown">Unknown title</string>
+ <string name="download_log_title_unknown">Unknown Title</string>
<string name="download_type_feed">Feed</string>
<string name="download_type_media">Media file</string>
<string name="download_type_image">Image</string>
@@ -162,10 +203,10 @@
<string name="authentication_notification_title">Authentication required</string>
<string name="authentication_notification_msg">The resource you requested requires a username and a password</string>
<string name="confirm_mobile_download_dialog_title">Confirm Mobile Download</string>
- <string name="confirm_mobile_download_dialog_message_not_in_queue">Downloading over mobile data connection is disabled in the settings.\n\nEnable temporarily or just add to queue?\n\n<small>Your choice will be remember for 10 minutes.</small></string>
- <string name="confirm_mobile_download_dialog_message">Downloading over mobile data connection is disabled in the settings.\n\nEnable temporarily?\n\n<small>Your choice will be remember for 10 minutes.</small></string>
- <string name="confirm_mobile_download_dialog_only_add_to_queue">Only add to Queue</string>
- <string name="confirm_mobile_download_dialog_enable_temporarily">Enable temporarily</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Downloading over mobile data connection is disabled in the settings.\n\nYou can choose to either only add the episode to the queue or you can allow downloading temporarily.\n\n<small>Your choice will be remembered for 10 minutes.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Downloading over mobile data connection is disabled in the settings.\n\nDo you want to allow downloading temporarily?\n\n<small>Your choice will be remembered for 10 minutes.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Enqueue</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Allow temporarily</string>
<!-- Mediaplayer messages -->
<string name="player_error_msg">Error!</string>
@@ -182,9 +223,11 @@
<string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string>
<!-- Queue operations -->
- <string name="lock_queue">Lock queue</string>
- <string name="unlock_queue">Unlock queue</string>
- <string name="clear_queue_label">Clear queue</string>
+ <string name="lock_queue">Lock Queue</string>
+ <string name="unlock_queue">Unlock Queue</string>
+ <string name="queue_locked">Queue locked</string>
+ <string name="queue_unlocked">Queue unlocked</string>
+ <string name="clear_queue_label">Clear Queue</string>
<string name="undo">Undo</string>
<string name="removed_from_queue">Item removed</string>
<string name="move_to_top_label">Move to top</string>
@@ -228,12 +271,14 @@
<!-- Variable Speed -->
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Not Installed</string>
- <string name="no_playback_plugin_msg">For variable speed playback to work, a third party library must be installed.\n\nTap \'Download Plugin\' to download a free plugin from the Play Store\n\nAny problems found using this plugin are not the responsibility of AntennaPod and should be reported to the plugin owner.</string>
+ <string name="no_playback_plugin_or_sonic_msg">For variable speed playback to work, we recommend to enable the built-in Sonic mediaplayer [Android 4.1+].\n\nAlternatively, you can download the third party plugin <i>Prestissimo</i> from the Play Store.\nAny problems with Prestissimo are not the responsibility of AntennaPod and should be reported to the plugin owner.</string>
<string name="set_playback_speed_label">Playback Speeds</string>
+ <string name="enable_sonic">Enable Sonic</string>
<!-- Empty list labels -->
<string name="no_items_label">There are no items in this list.</string>
<string name="no_feeds_label">You haven\'t subscribed to any feeds yet.</string>
+ <string name="no_chapters_label">This episode has no chapters.</string>
<!-- Preferences -->
<string name="other_pref">Other</string>
@@ -241,47 +286,67 @@
<string name="queue_label">Queue</string>
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pause playback when the headphones are disconnected</string>
+ <string name="pref_episode_cleanup_title">Episode Cleanup</string>
+ <string name="pref_episode_cleanup_summary">Episodes that aren\'t in the queue and aren\'t favorites should be eligible for removal if Auto Download needs space for new episodes</string>
+ <string name="pref_pauseOnDisconnect_sum">Pause playback when headphones or bluetooth are disconnected</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Resume playback when the headphones are reconnected</string>
+ <string name="pref_unpauseOnBluetoothReconnect_sum">Resume playback when bluetooth reconnects</string>
+ <string name="pref_hardwareForwardButtonSkips_title">Forward button skips</string>
+ <string name="pref_hardwareForwardButtonSkips_sum">When pressing a hardware forward button skip to the next episode instead of fast-forwarding</string>
<string name="pref_followQueue_sum">Jump to next queue item when playback completes</string>
<string name="pref_auto_delete_sum">Delete episode when playback completes</string>
<string name="pref_auto_delete_title">Auto Delete</string>
<string name="pref_smart_mark_as_played_sum">Mark episodes as played even if less than a certain amount of seconds of playing time is still left</string>
<string name="pref_smart_mark_as_played_title">Smart mark as played</string>
+ <string name="pref_skip_keeps_episodes_sum">Keep episodes when they are skipped</string>
+ <string name="pref_skip_keeps_episodes_title">Keep Skipped Episodes</string>
<string name="playback_pref">Playback</string>
<string name="network_pref">Network</string>
- <string name="pref_autoUpdateIntervall_title">Update interval</string>
- <string name="pref_autoUpdateIntervall_sum">Specify an interval in which the feeds are refreshed automatically or disable it</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Update Interval or Time of Day</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Specify an interval or a specific time of day to refresh the feeds automatically</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">You can set an <i>interval</i> like \"every 2 hours\", set a specific <i>time of day</i> like \"7:00 AM\" or <i>disable</i> automatic updates altogether.\n\n<small>Please note: Update times are inexact. You may encounter a short delay.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Disable</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Set Interval</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Set Time of Day</string>
+ <string name="pref_autoUpdateIntervallOrTime_every">every %1$s</string>
+ <string name="pref_autoUpdateIntervallOrTime_at">at %1$s</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Download media files only over WiFi</string>
- <string name="pref_followQueue_title">Continuous playback</string>
+ <string name="pref_followQueue_title">Continuous Playback</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi media download</string>
- <string name="pref_pauseOnHeadsetDisconnect_title">Headphones disconnect</string>
- <string name="pref_unpauseOnHeadsetReconnect_title">Headphones reconnect</string>
- <string name="pref_mobileUpdate_title">Mobile updates</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Headphones Disconnect</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">Headphones Reconnect</string>
+ <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth Reconnect</string>
+ <string name="pref_mobileUpdate_title">Mobile Updates</string>
<string name="pref_mobileUpdate_sum">Allow updates over the mobile data connection</string>
<string name="refreshing_label">Refreshing</string>
<string name="flattr_settings_label">Flattr settings</string>
<string name="pref_flattr_auth_title">Flattr sign-in</string>
<string name="pref_flattr_auth_sum">Sign in to your flattr account to flattr things directly from the app.</string>
- <string name="pref_flattr_this_app_title">Flattr this app</string>
+ <string name="pref_flattr_this_app_title">Flattr this App</string>
<string name="pref_flattr_this_app_sum">Support the development of AntennaPod by flattring it. Thanks!</string>
<string name="pref_revokeAccess_title">Revoke access</string>
<string name="pref_revokeAccess_sum">Revoke the access permission to your flattr account for this app.</string>
<string name="pref_auto_flattr_title">Automatic Flattr</string>
<string name="pref_auto_flattr_sum">Configure automatic flattring</string>
<string name="user_interface_label">User Interface</string>
- <string name="pref_set_theme_title">Select theme</string>
- <string name="pref_nav_drawer_items_title">Change navigation drawer</string>
+ <string name="pref_set_theme_title">Select Theme</string>
+ <string name="pref_nav_drawer_title">Customize Navigation Drawer</string>
+ <string name="pref_nav_drawer_sum">Customize the appearance of the navigation drawer.</string>
+ <string name="pref_nav_drawer_items_title">Set Navigation Drawer items</string>
<string name="pref_nav_drawer_items_sum">Change which items appear in the navigation drawer.</string>
+ <string name="pref_nav_drawer_feed_order_title">Set Subscription Order</string>
+ <string name="pref_nav_drawer_feed_order_sum">Change the order of your subscriptions</string>
+ <string name="pref_nav_drawer_feed_counter_title">Set Subscription Counter</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Change the information displayed by the subscription counter</string>
<string name="pref_set_theme_sum">Change the appearance of AntennaPod.</string>
- <string name="pref_automatic_download_title">Automatic download</string>
+ <string name="pref_automatic_download_title">Automatic Download</string>
<string name="pref_automatic_download_sum">Configure the automatic download of episodes.</string>
<string name="pref_autodl_wifi_filter_title">Enable Wi-Fi filter</string>
<string name="pref_autodl_wifi_filter_sum">Allow automatic download only for selected Wi-Fi networks.</string>
<string name="pref_automatic_download_on_battery_title">Download when not charging</string>
<string name="pref_automatic_download_on_battery_sum">Allow automatic download when the battery is not charging</string>
- <string name="pref_parallel_downloads_title">Parallel downloads</string>
- <string name="pref_episode_cache_title">Episode cache</string>
+ <string name="pref_parallel_downloads_title">Parallel Downloads</string>
+ <string name="pref_episode_cache_title">Episode Cache</string>
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>
<string name="pref_episode_cache_unlimited">Unlimited</string>
@@ -302,13 +367,25 @@
<string name="pref_gpodnet_sethostname_use_default_host">Use default host</string>
<string name="pref_expandNotify_title">Expand Notification</string>
<string name="pref_expandNotify_sum">Always expand the notification to show playback buttons.</string>
- <string name="pref_persistNotify_title">Persistent playback controls</string>
+ <string name="pref_persistNotify_title">Persistent Playback Controls</string>
<string name="pref_persistNotify_sum">Keep notification and lockscreen controls when playback is paused.</string>
+ <string name="pref_lockscreen_background_title">Set Lockscreen Background</string>
+ <string name="pref_lockscreen_background_sum">Set the lockscreen background to the current episode\'s image. As a side effect, this will also show the image in third party apps.</string>
+ <string name="pref_showDownloadReport_title">Show Download Report</string>
+ <string name="pref_showDownloadReport_sum">If downloads fail, generate a report that shows the details of the failure.</string>
<string name="pref_expand_notify_unsupport_toast">Android versions before 4.1 do not support expanded notifications.</string>
<string name="pref_queueAddToFront_sum">Add new episodes to the front of the queue.</string>
- <string name="pref_queueAddToFront_title">Enqueue at front.</string>
+ <string name="pref_queueAddToFront_title">Enqueue at Front</string>
<string name="pref_smart_mark_as_played_disabled">Disabled</string>
-
+ <string name="pref_image_cache_size_title">Image Cache Size</string>
+ <string name="pref_image_cache_size_sum">Size of the disk cache for images.</string>
+ <string name="crash_report_title">Crash Report</string>
+ <string name="crash_report_sum">Send the latest crash report via e-mail</string>
+ <string name="send_email">Send e-mail</string>
+ <string name="experimental_pref">Experimental</string>
+ <string name="pref_sonic_title">Sonic media player</string>
+ <string name="pref_sonic_message">Use built-in sonic media player as a replacement for Android\'s native mediaplayer and Prestissimo</string>
+ <string name="pref_current_value">Current value: %1$s</string>
<!-- Auto-Flattr dialog -->
<string name="auto_flattr_enable">Enable automatic flattring</string>
@@ -326,22 +403,24 @@
<!-- OPML import and export -->
<string name="opml_import_txtv_button_lable">OPML files allow you to move your podcasts from one podcatcher to another.</string>
+ <string name="opml_import_option">Option %1$d</string>
<string name="opml_import_explanation_1">Choose a specific file path from the local filesystem.</string>
<string name="opml_import_explanation_2">Use an external applications like Dropbox, Google Drive or your favourite file manager to open an OPML file.</string>
<string name="opml_import_explanation_3">Many applications like Google Mail, Dropbox, Google Drive and most file managers can <i>open</i> OPML files <i>with</i> AntennaPod.</string> <string name="start_import_label">Start import</string>
- <string name="opml_import_label">OPML import</string>
+ <string name="opml_import_label">OPML Import</string>
<string name="opml_directory_error">ERROR!</string>
<string name="reading_opml_label">Reading OPML file</string>
<string name="opml_reader_error">An error has occurred while reading the opml document:</string>
<string name="opml_import_error_dir_empty">The import directory is empty.</string>
<string name="select_all_label">Select all</string>
<string name="deselect_all_label">Deselect all</string>
+ <string name="select_options_label">Select&#8230;</string>
<string name="choose_file_from_filesystem">From local filesystem</string>
<string name="choose_file_from_external_application">Use external application</string>
<string name="opml_export_label">OPML export</string>
- <string name="exporting_label">Exporting...</string>
+ <string name="exporting_label">Exporting&#8230;</string>
<string name="export_error_label">Export error</string>
- <string name="opml_export_success_title">OPML export successful.</string>
+ <string name="opml_export_success_title">OPML Export successful.</string>
<string name="opml_export_success_sum">The .opml file was written to:\u0020</string>
<!-- Sleep timer -->
@@ -351,9 +430,24 @@
<string name="sleep_timer_label">Sleep timer</string>
<string name="time_left_label">Time left:\u0020</string>
<string name="time_dialog_invalid_input">Invalid input, time has to be an integer</string>
- <string name="time_unit_seconds">seconds</string>
- <string name="time_unit_minutes">minutes</string>
- <string name="time_unit_hours">hours</string>
+ <string name="timer_about_to_expire_label"><b>When timer is about to expire:</b></string>
+ <string name="shake_to_reset_label">Shake to reset timer</string>
+ <string name="timer_vibration_label">Vibrate</string>
+ <string name="time_seconds">seconds</string>
+ <string name="time_minutes">minutes</string>
+ <string name="time_hours">hours</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 second</item>
+ <item quantity="other">%d seconds</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minute</item>
+ <item quantity="other">%d minutes</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 hour</item>
+ <item quantity="other">%d hours</item>
+ </plurals>
<!-- gpodder.net -->
<string name="gpodnet_taglist_header">CATEGORIES</string>
@@ -389,24 +483,30 @@
<!-- Directory chooser -->
<string name="selected_folder_label">Selected folder:</string>
<string name="create_folder_label">Create folder</string>
- <string name="choose_data_directory">Choose data folder</string>
+ <string name="choose_data_directory">Choose Data Folder</string>
+ <string name="choose_data_directory_message">Please choose the base of your data folder. AntennaPod will create the appropriate sub-directories.</string>
<string name="create_folder_msg">Create new folder with name "%1$s"?</string>
<string name="create_folder_success">Created new folder</string>
<string name="create_folder_error_no_write_access">Cannot write to this folder</string>
<string name="create_folder_error_already_exists">Folder already exists</string>
<string name="create_folder_error">Could not create folder</string>
+ <string name="folder_does_not_exist_error">"%1$s" does not exist</string>
+ <string name="folder_not_readable_error">"%1$s" is not readable</string>
+ <string name="folder_not_writable_error">"%1$s" is not writable</string>
<string name="folder_not_empty_dialog_title">Folder is not empty</string>
<string name="folder_not_empty_dialog_msg">The folder you have selected is not empty. Media downloads and other files will be placed directly in this folder. Continue anyway?</string>
<string name="set_to_default_folder">Choose default folder</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pause playback instead of lowering volume when another app wants to play sounds</string>
- <string name="pref_pausePlaybackForFocusLoss_title">Pause for interruptions</string>
+ <string name="pref_pausePlaybackForFocusLoss_title">Pause for Interruptions</string>
<string name="pref_resumeAfterCall_sum">Resume playback after a phone call completes</string>
- <string name="pref_resumeAfterCall_title">Resume after call</string>
+ <string name="pref_resumeAfterCall_title">Resume after Call</string>
+ <string name="pref_restart_required">AntennaPod has to be restarted for this change to take effect.</string>
+
<!-- Online feed view -->
<string name="subscribe_label">Subscribe</string>
<string name="subscribed_label">Subscribed</string>
- <string name="downloading_label">Downloading...</string>
+ <string name="downloading_label">Downloading&#8230;</string>
<!-- Content descriptions for image buttons -->
<string name="show_chapters_label">Show chapters</string>
@@ -431,9 +531,59 @@
<!-- Feed information screen -->
<string name="authentication_label">Authentication</string>
<string name="authentication_descr">Change your username and password for this podcast and its episodes.</string>
+ <string name="auto_download_settings_label">Auto Download Settings</string>
+ <string name="episode_filters_label">Episode Filter</string>
+ <string name="episode_filters_description">List of terms used to decide if an episode should be included or excluded when auto downloading</string>
+ <string name="episode_filters_include">Include</string>
+ <string name="episode_filters_exclude">Exclude</string>
+ <string name="episode_filters_hint">Single words \n\"Multiple Words\"</string>
+ <string name="keep_updated">Keep Updated</string>
+
+ <!-- Progress information -->
+ <string name="progress_upgrading_database">Upgrading the database</string>
<!-- AntennaPodSP -->
<string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps&#8230;</string>
<string name="search_itunes_label">Search iTunes</string>
+
+ <string name="select_label"><b>Select&#8230;</b></string>
+ <string name="filter">Filter</string>
+ <string name="all_label">All</string>
+ <string name="selected_all_label">Selected all Episodes</string>
+ <string name="none_label">None</string>
+ <string name="deselected_all_label">Deselected all Episodes</string>
+ <string name="played_label">Played</string>
+ <string name="selected_played_label">Selected played Episodes</string>
+ <string name="unplayed_label">Unplayed</string>
+ <string name="selected_unplayed_label">Selected unplayed Episodes</string>
+ <string name="downloaded_label">Downloaded</string>
+ <string name="selected_downloaded_label">Selected downloaded Episodes</string>
+ <string name="not_downloaded_label">Not downloaded</string>
+ <string name="selected_not_downloaded_label">Selected not downloaded Episodes</string>
+ <string name="sort_title"><b>Sort by&#8230;</b></string>
+ <string name="sort_title_a_z">Title (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Title (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Date (New \u2192 Old)</string>
+ <string name="sort_date_old_new">Date (Old \u2192 New)</string>
+ <string name="sort_duration_short_long">Duration (Short \u2192 Long)</string>
+ <string name="sort_duration_long_short">Duration (Long \u2192 Short)</string>
+
+ <!-- Rating dialog -->
+ <string name="rating_title">Like AntennaPod?</string>
+ <string name="rating_message">We would appreciate it if you take the time to rate AntennaPod.</string>
+ <string name="rating_never_label">Leave me alone</string>
+ <string name="rating_later_label">Remind me later</string>
+ <string name="rating_now_label">Sure, let\'s do this!</string>
+
+ <!-- Audio controls -->
+ <string name="audio_controls">Audio controls</string>
+ <string name="playback_speed">Playback Speed</string>
+ <string name="volume">Volume</string>
+ <string name="left_short">L</string>
+ <string name="right_short">R</string>
+ <string name="audio_effects">Audio Effects</string>
+ <string name="stereo_to_mono">Downmix: Stereo to mono</string>
+ <string name="sonic_only">Sonic only</string>
+
</resources>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index 44e80da1d..eb2b6e12e 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.AntennaPod.Light" parent="@style/Theme.AppCompat.Light">
+ <style name="Theme.AntennaPod.Light" parent="Theme.AppCompat.Light">
<item name="colorPrimary">@color/primary_light</item>
- <item name="colorAccent">@color/color_accent</item>
+ <item name="colorAccent">@color/holo_blue_light</item>
+ <item name="progressBarTheme">@style/ProgressBarLight</item>
+ <item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Light</item>
+ <item name="attr/action_bar_icon_color">@color/grey600</item>
<item name="attr/action_about">@drawable/ic_info_grey600_24dp</item>
<item name="attr/action_search">@drawable/ic_search_grey600_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_grey600_24dp</item>
@@ -14,7 +18,6 @@
<item name="attr/av_rewind">@drawable/ic_fast_rewind_grey600_24dp</item>
<item name="attr/content_discard">@drawable/ic_delete_grey600_24dp</item>
<item name="attr/content_new">@drawable/ic_add_grey600_24dp</item>
- <item name="attr/device_access_time">@drawable/ic_timer_grey600_24dp</item>
<item name="attr/feed">@drawable/ic_feed_grey600_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_grey600_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_grey600_24dp</item>
@@ -31,7 +34,7 @@
<item name="attr/non_transparent_background">@color/white</item>
<item name="attr/overlay_background">@color/overlay_light</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable</item>
- <item name="attr/dragview_background">@drawable/ic_drag_handle</item>
+ <item name="attr/dragview_background">@drawable/ic_drag_vertical_grey600_48dp</item>
<item name="attr/dragview_float_background">@color/white</item>
<item name="attr/nav_drawer_background">@color/white</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_grey600_24dp</item>
@@ -42,15 +45,28 @@
<item name="attr/av_pause_big">@drawable/ic_pause_grey600_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_grey600_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_grey600_36dp</item>
+ <item name="attr/av_skip_big">@drawable/ic_skip_grey600_36dp</item>
+ <item name="attr/ic_fav">@drawable/ic_star_border_grey600_24dp</item>
+ <item name="attr/ic_unfav">@drawable/ic_star_grey600_24dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_grey600_24dp</item>
<item name="attr/ic_lock_open">@drawable/ic_lock_open_grey600_24dp</item>
<item name="attr/ic_lock_closed">@drawable/ic_lock_closed_grey600_24dp</item>
<item name="attr/ic_filter">@drawable/ic_filter_grey600_24dp</item>
+ <item name="attr/ic_sleep">@drawable/ic_sleep_grey600_24dp</item>
+ <item name="attr/ic_sleep_off">@drawable/ic_sleep_off_grey600_24dp</item>
+ <item name="attr/ic_check_box">@drawable/ic_check_box_grey600_24dp</item>
+ <item name="attr/ic_check_box_outline">@drawable/ic_check_box_outline_blank_grey600_24dp</item>
+ <item name="attr/ic_indeterminate_check_box">@drawable/ic_indeterminate_check_box_grey600_24dp</item>
+ <item name="attr/ic_sort">@drawable/ic_sort_grey600_24dp</item>
</style>
- <style name="Theme.AntennaPod.Dark" parent="@style/Theme.AppCompat">
- <item name="colorAccent">@color/color_accent</item>
- <item name="attr/action_about">@drawable/ic_info_white_24dp</item>
+ <style name="Theme.AntennaPod.Dark" parent="Theme.AppCompat">
+ <item name="colorAccent">@color/holo_blue_dark</item>
+ <item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="progressBarTheme">@style/ProgressBarDark</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Dark</item>
+ <item name="attr/action_bar_icon_color">@color/white</item>
+ <item name="attr/action_about">@drawable/ic_info_white_24dp</item>g
<item name="attr/action_search">@drawable/ic_search_white_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_white_24dp</item>
<item name="attr/av_download">@drawable/ic_file_download_white_24dp</item>
@@ -60,7 +76,6 @@
<item name="attr/av_rewind">@drawable/ic_fast_rewind_white_24dp</item>
<item name="attr/content_discard">@drawable/ic_delete_white_24dp</item>
<item name="attr/content_new">@drawable/ic_add_white_24dp</item>
- <item name="attr/device_access_time">@drawable/ic_timer_white_24dp</item>
<item name="attr/feed">@drawable/ic_feed_white_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_white_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_white_24dp</item>
@@ -77,7 +92,7 @@
<item name="attr/non_transparent_background">@color/black</item>
<item name="attr/overlay_background">@color/overlay_dark</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable_dark</item>
- <item name="attr/dragview_background">@drawable/ic_drag_handle_dark</item>
+ <item name="attr/dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
<item name="attr/dragview_float_background">@color/black</item>
<item name="attr/nav_drawer_background">#3B3B3B</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_white_24dp</item>
@@ -88,17 +103,29 @@
<item name="attr/av_pause_big">@drawable/ic_pause_white_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_white_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_white_36dp</item>
+ <item name="attr/av_skip_big">@drawable/ic_skip_white_36dp</item>
+ <item name="attr/ic_fav">@drawable/ic_star_border_white_24dp</item>
+ <item name="attr/ic_unfav">@drawable/ic_star_white_24dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_white_24dp</item>
<item name="attr/ic_lock_open">@drawable/ic_lock_open_white_24dp</item>
<item name="attr/ic_lock_closed">@drawable/ic_lock_closed_white_24dp</item>
<item name="attr/ic_filter">@drawable/ic_filter_white_24dp</item>
+ <item name="attr/ic_sleep">@drawable/ic_sleep_white_24dp</item>
+ <item name="attr/ic_sleep_off">@drawable/ic_sleep_off_white_24dp</item>
+ <item name="attr/ic_check_box">@drawable/ic_check_box_white_24dp</item>
+ <item name="attr/ic_check_box_outline">@drawable/ic_check_box_outline_blank_white_24dp</item>
+ <item name="attr/ic_indeterminate_check_box">@drawable/ic_indeterminate_check_box_white_24dp</item>
+ <item name="attr/ic_sort">@drawable/ic_sort_white_24dp</item>
</style>
- <style name="Theme.AntennaPod.Light.NoTitle" parent="@style/Theme.AppCompat.Light.NoActionBar">
+ <style name="Theme.AntennaPod.Light.NoTitle" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
+ <item name="progressBarTheme">@style/ProgressBarLight</item>
<item name="colorPrimary">@color/primary_light</item>
- <item name="colorAccent">@color/color_accent</item>
+ <item name="colorAccent">@color/holo_blue_light</item>
+ <item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Light</item>
<item name="attr/action_about">@drawable/ic_info_grey600_24dp</item>
<item name="attr/action_search">@drawable/ic_search_grey600_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_grey600_24dp</item>
@@ -109,7 +136,6 @@
<item name="attr/av_rewind">@drawable/ic_fast_rewind_grey600_24dp</item>
<item name="attr/content_discard">@drawable/ic_delete_grey600_24dp</item>
<item name="attr/content_new">@drawable/ic_add_grey600_24dp</item>
- <item name="attr/device_access_time">@drawable/ic_timer_grey600_24dp</item>
<item name="attr/feed">@drawable/ic_feed_grey600_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_grey600_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_grey600_24dp</item>
@@ -126,7 +152,7 @@
<item name="attr/non_transparent_background">@color/white</item>
<item name="attr/overlay_background">@color/overlay_light</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable</item>
- <item name="attr/dragview_background">@drawable/ic_drag_handle</item>
+ <item name="attr/dragview_background">@drawable/ic_drag_vertical_grey600_48dp</item>
<item name="attr/dragview_float_background">@color/white</item>
<item name="attr/nav_drawer_background">@color/white</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_grey600_24dp</item>
@@ -137,16 +163,28 @@
<item name="attr/av_pause_big">@drawable/ic_pause_grey600_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_grey600_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_grey600_36dp</item>
+ <item name="attr/av_skip_big">@drawable/ic_skip_grey600_36dp</item>
+ <item name="attr/ic_fav">@drawable/ic_star_border_grey600_24dp</item>
+ <item name="attr/ic_unfav">@drawable/ic_star_grey600_24dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_grey600_24dp</item>
<item name="attr/ic_lock_open">@drawable/ic_lock_open_grey600_24dp</item>
<item name="attr/ic_lock_closed">@drawable/ic_lock_closed_grey600_24dp</item>
<item name="attr/ic_filter">@drawable/ic_filter_grey600_24dp</item>
+ <item name="attr/ic_sleep">@drawable/ic_sleep_grey600_24dp</item>
+ <item name="attr/ic_sleep_off">@drawable/ic_sleep_off_grey600_24dp</item>
+ <item name="attr/ic_check_box">@drawable/ic_check_box_grey600_24dp</item>
+ <item name="attr/ic_check_box_outline">@drawable/ic_check_box_outline_blank_grey600_24dp</item>
+ <item name="attr/ic_indeterminate_check_box">@drawable/ic_indeterminate_check_box_grey600_24dp</item>
+ <item name="attr/ic_sort">@drawable/ic_sort_grey600_24dp</item>
</style>
- <style name="Theme.AntennaPod.Dark.NoTitle" parent="@style/Theme.AppCompat.NoActionBar">
+ <style name="Theme.AntennaPod.Dark.NoTitle" parent="Theme.AppCompat.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
- <item name="colorAccent">@color/color_accent</item>
+ <item name="progressBarTheme">@style/ProgressBarDark</item>
+ <item name="colorAccent">@color/holo_blue_dark</item>
+ <item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Dark</item>
<item name="attr/action_about">@drawable/ic_info_white_24dp</item>
<item name="attr/action_search">@drawable/ic_search_white_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_white_24dp</item>
@@ -157,7 +195,6 @@
<item name="attr/av_rewind">@drawable/ic_fast_rewind_white_24dp</item>
<item name="attr/content_discard">@drawable/ic_delete_white_24dp</item>
<item name="attr/content_new">@drawable/ic_add_white_24dp</item>
- <item name="attr/device_access_time">@drawable/ic_timer_white_24dp</item>
<item name="attr/feed">@drawable/ic_feed_white_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_white_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_white_24dp</item>
@@ -174,7 +211,7 @@
<item name="attr/non_transparent_background">@color/black</item>
<item name="attr/overlay_background">@color/overlay_dark</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable_dark</item>
- <item name="attr/dragview_background">@drawable/ic_drag_handle_dark</item>
+ <item name="attr/dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
<item name="attr/dragview_float_background">@color/black</item>
<item name="attr/nav_drawer_background">#3B3B3B</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_white_24dp</item>
@@ -185,54 +222,25 @@
<item name="attr/av_pause_big">@drawable/ic_pause_white_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_white_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_white_36dp</item>
+ <item name="attr/av_skip_big">@drawable/ic_skip_white_36dp</item>
+ <item name="attr/ic_fav">@drawable/ic_star_border_white_24dp</item>
+ <item name="attr/ic_unfav">@drawable/ic_star_white_24dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_white_24dp</item>
<item name="attr/ic_lock_open">@drawable/ic_lock_open_white_24dp</item>
<item name="attr/ic_lock_closed">@drawable/ic_lock_closed_white_24dp</item>
<item name="attr/ic_filter">@drawable/ic_filter_white_24dp</item>
+ <item name="attr/ic_sleep">@drawable/ic_sleep_white_24dp</item>
+ <item name="attr/ic_sleep_off">@drawable/ic_sleep_off_white_24dp</item>
+ <item name="attr/ic_check_box">@drawable/ic_check_box_white_24dp</item>
+ <item name="attr/ic_check_box_outline">@drawable/ic_check_box_outline_blank_white_24dp</item>
+ <item name="attr/ic_indeterminate_check_box">@drawable/ic_indeterminate_check_box_white_24dp</item>
+ <item name="attr/ic_sort">@drawable/ic_sort_white_24dp</item>
</style>
<style name="Theme.AntennaPod.VideoPlayer" parent="@style/Theme.AntennaPod.Dark">
<item name="windowActionBarOverlay">true</item>
</style>
- <style name="UndoBar">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">48dp</item>
- <item name="android:layout_gravity">bottom</item>
- <item name="android:layout_marginLeft">8dp</item>
- <item name="android:layout_marginRight">8dp</item>
- <item name="android:layout_marginBottom">16dp</item>
- <item name="android:orientation">horizontal</item>
- <item name="android:background">@drawable/undobar</item>
- <item name="android:clickable">true</item>
- <item name="android:divider">@drawable/undobar_divider</item>
- </style>
-
- <style name="UndoBarMessage">
- <item name="android:layout_width">0dp</item>
- <item name="android:layout_weight">1</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginLeft">16dp</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:layout_marginRight">16dp</item>
- <item name="android:textAppearance">?android:textAppearanceMedium</item>
- <item name="android:textColor">#fff</item>
- </style>
-
- <style name="UndoBarButton">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:paddingLeft">16dp</item>
- <item name="android:paddingRight">16dp</item>
- <item name="android:background">@drawable/undobar_button</item>
- <item name="android:drawableLeft">@drawable/ic_undobar_undo</item>
- <item name="android:drawablePadding">12dp</item>
- <item name="android:textAppearance">?android:textAppearanceSmall</item>
- <item name="android:textStyle">bold</item>
- <item name="android:textColor">#fff</item>
- <item name="android:text">@string/undo</item>
- </style>
-
<style name="AntennaPod.TextView.Heading" parent="@android:style/TextAppearance.Medium">
<item name="android:textSize">@dimen/text_size_large</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -245,6 +253,12 @@
<item name="android:ellipsize">end</item>
</style>
+ <style name="AntennaPod.TextView.ListItemPrimaryTitle2" parent="@android:style/TextAppearance.Small">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
<style name="AntennaPod.TextView.ListItemSecondaryTitle" parent="@android:style/TextAppearance.Small">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
@@ -265,10 +279,34 @@
<item name="android:text">@string/new_label</item>
</style>
+ <style name="Widget.AntennaPod.Button" parent="Widget.AppCompat.Button">
+ <item name="textAllCaps">false</item>
+ </style>
+
+ <style name="AntennaPod.Dialog.Light" parent="Theme.AppCompat.Light.Dialog">
+ <item name="colorAccent">@color/holo_blue_light</item>
+ </style>
+
+ <style name="AntennaPod.Dialog.Dark" parent="Theme.AppCompat.Dialog">
+ <item name="colorAccent">@color/holo_blue_dark</item>
+ </style>
+
<style name="BigBlurryBackground">
<item name="android:scaleType">centerCrop</item>
- <item name="android:tint">@color/image_readability_tint</item>
+ <!-- <item name="android:tint">@color/image_readability_tint</item> -->
+ <!-- Reactivate when Glide's tinting has been fixed for Android 5.x
+ Remove color filter from ItemlistFragment -->
+
+ </style>
+
+ <style name="ProgressBarLight">
+ <item name="android:indeterminateOnly">false</item>
+ <item name="android:progressDrawable">@drawable/progress_bar_horizontal_light</item>
+ </style>
+ <style name="ProgressBarDark">
+ <item name="android:indeterminateOnly">false</item>
+ <item name="android:progressDrawable">@drawable/progress_bar_horizontal_dark</item>
</style>
</resources>