summaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java97
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java30
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java1766
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java317
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java347
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java106
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java48
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java17
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java155
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/AlarmUpdateReceiver.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java241
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java56
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java899
-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.java724
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java1173
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java55
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java592
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java115
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java120
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java40
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java37
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java373
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java41
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java61
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java59
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java88
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java30
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java103
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Converter.java35
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntList.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java54
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Supplier.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java55
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java99
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java64
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java361
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java64
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java6
-rw-r--r--core/src/main/res/drawable-hdpi-v11/ic_stat_antenna_default.pngbin678 -> 541 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi-v11/ic_stat_authentication.pngbin467 -> 371 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_add_grey600_24dp.pngbin222 -> 125 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_add_white_24dp.pngbin223 -> 124 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_audiotrack_light.pngbin0 -> 417 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_av_fast_forward_80dp.pngbin1228 -> 429 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_av_pause_circle_outline_80dp.pngbin3309 -> 1100 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_av_play_circle_outline_80dp.pngbin3395 -> 1232 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_av_rewind_80dp.pngbin1277 -> 408 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cancel_grey600_24dp.pngbin522 -> 415 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cancel_white_24dp.pngbin510 -> 394 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_disabled_light.pngbin0 -> 770 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_disconnect_grey600_36dp.pngbin0 -> 1419 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_disconnect_white_36dp.pngbin0 -> 968 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_light.pngbin0 -> 975 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_off_light.pngbin0 -> 867 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_on_0_light.pngbin0 -> 961 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_on_1_light.pngbin0 -> 979 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_on_2_light.pngbin0 -> 976 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_cast_on_light.pngbin0 -> 982 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_grey600_24dp.pngbin584 -> 278 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_grey600_24dp.pngbin397 -> 180 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_outline_blank_white_24dp.pngbin340 -> 170 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_check_box_white_24dp.pngbin505 -> 260 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_close_light.pngbin0 -> 493 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_create_new_folder_grey600_24dp.pngbin198 -> 167 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_create_new_folder_white_24dp.pngbin191 -> 164 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_delete_grey600_24dp.pngbin248 -> 161 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_delete_white_24dp.pngbin246 -> 158 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_description_grey600_36dp.pngbin425 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_description_white_36dp.pngbin424 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_done_grey600_24dp.pngbin326 -> 175 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_done_white_24dp.pngbin309 -> 169 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_drag_vertical_grey600_48dp.9.pngbin389 -> 289 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_drag_vertical_white_48dp.9.pngbin361 -> 265 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_expand_more_grey600_36dp.pngbin348 -> 195 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_expand_more_white_36dp.pngbin324 -> 189 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_24dp.pngbin366 -> 256 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_36dp.pngbin460 -> 324 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_forward_white_24dp.pngbin360 -> 253 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_forward_white_36dp.pngbin451 -> 315 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_24dp.pngbin395 -> 267 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_36dp.pngbin480 -> 331 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_rewind_white_24dp.pngbin376 -> 261 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_fast_rewind_white_36dp.pngbin461 -> 321 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.pngbin1159 -> 511 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_white_24dp.pngbin727 -> 458 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_file_download_grey600_24dp.pngbin276 -> 154 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_file_download_white_24dp.pngbin277 -> 150 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.pngbin135 -> 111 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_filter_white_24dp.pngbin131 -> 110 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_folder_grey600_24dp.pngbin0 -> 227 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_folder_white_24dp.pngbin0 -> 224 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_hearing_grey600_18dp.pngbin583 -> 478 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_hearing_white_18dp.pngbin602 -> 449 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_history_grey600_24dp.pngbin636 -> 509 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_history_white_24dp.pngbin638 -> 511 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_grey600_24dp.pngbin405 -> 179 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_indeterminate_check_box_white_24dp.pngbin348 -> 171 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_info_grey600_24dp.pngbin448 -> 342 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_info_white_24dp.pngbin433 -> 325 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_launcher.pngbin3955 -> 3887 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_list_grey600_24dp.pngbin207 -> 115 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_list_white_24dp.pngbin207 -> 114 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.pngbin366 -> 315 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.pngbin358 -> 303 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.pngbin362 -> 314 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.pngbin356 -> 299 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.pngbin0 -> 2355 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_more_vert_grey600_24dp.pngbin221 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.pngbin219 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_new.pngbin891 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_new_dark.pngbin716 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_new_releases_grey600_24dp.pngbin501 -> 399 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_new_releases_white_24dp.pngbin491 -> 381 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.pngbin188 -> 103 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.pngbin235 -> 126 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_pause_light.pngbin0 -> 191 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_pause_white_24dp.pngbin188 -> 103 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_pause_white_36dp.pngbin234 -> 124 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.pngbin301 -> 195 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.pngbin355 -> 235 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.pngbin282 -> 194 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.pngbin348 -> 232 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_play_light.pngbin0 -> 562 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.pngbin508 -> 407 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_refresh_white_24dp.pngbin531 -> 367 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_remove_red_eye_grey600_18dp.pngbin470 -> 380 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_remove_red_eye_white_18dp.pngbin468 -> 358 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sd_storage_grey600_36dp.pngbin333 -> 259 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sd_storage_white_36dp.pngbin332 -> 247 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_search_grey600_24dp.pngbin522 -> 412 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_search_white_24dp.pngbin504 -> 386 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.pngbin572 -> 468 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_settings_input_antenna_grey600_24dp.pngbin713 -> 569 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_settings_input_antenna_white_24dp.pngbin703 -> 553 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_settings_white_24dp.pngbin561 -> 438 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_share_grey600_24dp.pngbin513 -> 413 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_share_white_24dp.pngbin506 -> 382 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_skip_grey600_36dp.pngbin302 -> 256 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_skip_white_36dp.pngbin304 -> 251 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_grey600_24dp.pngbin421 -> 180 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_off_grey600_24dp.pngbin659 -> 290 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_off_white_24dp.pngbin431 -> 264 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sleep_white_24dp.pngbin361 -> 179 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sort_grey600_24dp.pngbin264 -> 118 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_sort_white_24dp.pngbin238 -> 114 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_border_grey600_24dp.pngbin637 -> 530 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_border_white_24dp.pngbin637 -> 488 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_grey600_24dp.pngbin460 -> 389 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_star_white_24dp.pngbin454 -> 369 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_stat_antenna_default.pngbin649 -> 473 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_stat_authentication.pngbin648 -> 446 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_timer_grey600_24dp.pngbin640 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_timer_white_24dp.pngbin609 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_toc_grey600_36dp.pngbin229 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_toc_white_36dp.pngbin240 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_web_grey600_24dp.pngbin248 -> 162 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_web_white_24dp.pngbin245 -> 159 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_widget_preview.pngbin18320 -> 8210 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/navigation_up.pngbin2270 -> 318 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/navigation_up_dark.pngbin2221 -> 309 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/stat_notify_sync.pngbin421 -> 333 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/stat_notify_sync_error.pngbin436 -> 352 bytes
-rw-r--r--core/src/main/res/drawable-ldpi-v11/ic_stat_antenna_default.pngbin307 -> 259 bytes
-rw-r--r--core/src/main/res/drawable-ldpi/ic_launcher.pngbin1658 -> 1645 bytes
-rw-r--r--core/src/main/res/drawable-ldpi/ic_stat_antenna_default.pngbin271 -> 202 bytes
-rw-r--r--core/src/main/res/drawable-mdpi-v11/ic_stat_antenna_default.pngbin414 -> 349 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi-v11/ic_stat_authentication.pngbin293 -> 244 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_add_grey600_24dp.pngbin174 -> 90 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_add_white_24dp.pngbin174 -> 88 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_audiotrack_light.pngbin0 -> 333 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_av_fast_forward_80dp.pngbin760 -> 328 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_av_pause_circle_outline_80dp.pngbin1765 -> 768 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_av_play_circle_outline_80dp.pngbin1858 -> 820 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_av_rewind_80dp.pngbin853 -> 349 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cancel_grey600_24dp.pngbin401 -> 307 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cancel_white_24dp.pngbin393 -> 290 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_disabled_light.pngbin0 -> 536 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_disconnect_grey600_36dp.pngbin0 -> 1033 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_disconnect_white_36dp.pngbin0 -> 694 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_light.pngbin0 -> 693 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_off_light.pngbin0 -> 635 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_on_0_light.pngbin0 -> 684 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_on_1_light.pngbin0 -> 696 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_on_2_light.pngbin0 -> 690 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_cast_on_light.pngbin0 -> 694 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_grey600_24dp.pngbin397 -> 193 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_grey600_24dp.pngbin254 -> 116 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_outline_blank_white_24dp.pngbin230 -> 113 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_check_box_white_24dp.pngbin331 -> 179 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_close_light.pngbin0 -> 379 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_create_new_folder_grey600_24dp.pngbin152 -> 133 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_create_new_folder_white_24dp.pngbin149 -> 132 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_delete_grey600_24dp.pngbin199 -> 114 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_delete_white_24dp.pngbin197 -> 112 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_description_grey600_36dp.pngbin271 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_description_white_36dp.pngbin309 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_done_grey600_24dp.pngbin244 -> 132 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_done_white_24dp.pngbin243 -> 133 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_drag_vertical_grey600_48dp.9.pngbin253 -> 202 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_drag_vertical_white_48dp.9.pngbin231 -> 191 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_expand_more_grey600_36dp.pngbin291 -> 160 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_expand_more_white_36dp.pngbin290 -> 153 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_24dp.pngbin276 -> 162 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_36dp.pngbin366 -> 256 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_forward_white_24dp.pngbin265 -> 163 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_forward_white_36dp.pngbin360 -> 253 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_24dp.pngbin267 -> 167 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_36dp.pngbin395 -> 267 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_rewind_white_24dp.pngbin263 -> 162 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_fast_rewind_white_36dp.pngbin376 -> 261 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.pngbin773 -> 353 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_white_24dp.pngbin492 -> 321 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_file_download_grey600_24dp.pngbin213 -> 115 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_file_download_white_24dp.pngbin210 -> 113 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.pngbin111 -> 91 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_filter_white_24dp.pngbin111 -> 90 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_folder_grey600_24dp.pngbin0 -> 207 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_folder_white_24dp.pngbin0 -> 206 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_hearing_grey600_18dp.pngbin408 -> 324 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_hearing_white_18dp.pngbin397 -> 301 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_history_grey600_24dp.pngbin447 -> 345 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_history_white_24dp.pngbin447 -> 345 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_grey600_24dp.pngbin259 -> 118 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_indeterminate_check_box_white_24dp.pngbin241 -> 115 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_info_grey600_24dp.pngbin327 -> 233 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_info_white_24dp.pngbin319 -> 224 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_launcher.pngbin2382 -> 2359 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_list_grey600_24dp.pngbin178 -> 88 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_list_white_24dp.pngbin171 -> 86 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.pngbin242 -> 205 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.pngbin237 -> 199 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.pngbin242 -> 208 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.pngbin238 -> 198 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.pngbin0 -> 1467 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_more_vert_grey600_24dp.pngbin202 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.pngbin202 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_new.pngbin593 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_new_dark.pngbin484 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_new_releases_grey600_24dp.pngbin382 -> 288 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_new_releases_white_24dp.pngbin378 -> 277 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_pause_grey600_24dp.pngbin168 -> 84 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_pause_grey600_36dp.pngbin188 -> 103 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_pause_light.pngbin0 -> 280 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_pause_white_24dp.pngbin174 -> 83 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_pause_white_36dp.pngbin188 -> 103 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_24dp.pngbin248 -> 151 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_36dp.pngbin301 -> 195 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.pngbin257 -> 154 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.pngbin282 -> 194 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_play_light.pngbin0 -> 447 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.pngbin356 -> 261 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_refresh_white_24dp.pngbin346 -> 247 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_remove_red_eye_grey600_18dp.pngbin357 -> 265 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_remove_red_eye_white_18dp.pngbin371 -> 259 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sd_storage_grey600_36dp.pngbin220 -> 181 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sd_storage_white_36dp.pngbin214 -> 172 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_search_grey600_24dp.pngbin356 -> 263 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_search_white_24dp.pngbin346 -> 247 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_settings_input_antenna_grey600_24dp.pngbin491 -> 388 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_settings_input_antenna_white_24dp.pngbin473 -> 371 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_share_grey600_24dp.pngbin371 -> 269 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_share_white_24dp.pngbin361 -> 261 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_skip_grey600_36dp.pngbin218 -> 183 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_skip_white_36dp.pngbin216 -> 183 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_grey600_24dp.pngbin336 -> 140 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_off_grey600_24dp.pngbin472 -> 225 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_off_white_24dp.pngbin323 -> 203 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sleep_white_24dp.pngbin252 -> 139 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sort_grey600_24dp.pngbin193 -> 91 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_sort_white_24dp.pngbin192 -> 90 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_border_grey600_24dp.pngbin410 -> 354 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_border_white_24dp.pngbin410 -> 342 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_grey600_24dp.pngbin307 -> 274 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_star_white_24dp.pngbin302 -> 265 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_stat_antenna_default.pngbin412 -> 297 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_stat_authentication.pngbin460 -> 317 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_timer_grey600_24dp.pngbin442 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_timer_white_24dp.pngbin420 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_toc_grey600_36dp.pngbin205 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_toc_white_36dp.pngbin205 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_web_grey600_24dp.pngbin212 -> 123 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_web_white_24dp.pngbin211 -> 122 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/navigation_up.pngbin2123 -> 279 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/navigation_up_dark.pngbin2060 -> 267 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/stat_notify_sync.pngbin272 -> 227 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/stat_notify_sync_error.pngbin274 -> 241 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi-v11/ic_stat_antenna_default.pngbin1005 -> 783 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi-v11/ic_stat_authentication.pngbin529 -> 418 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_add_grey600_24dp.pngbin199 -> 109 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_add_white_24dp.pngbin198 -> 97 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_audiotrack_light.pngbin0 -> 447 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_av_fast_forward_80dp.pngbin1968 -> 575 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_av_pause_circle_outline_80dp.pngbin5191 -> 1889 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_av_play_circle_outline_80dp.pngbin5393 -> 1994 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_av_rewind_80dp.pngbin1992 -> 603 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cancel_grey600_24dp.pngbin661 -> 537 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cancel_white_24dp.pngbin645 -> 513 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.pngbin0 -> 976 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_disconnect_grey600_36dp.pngbin0 -> 1832 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_disconnect_white_36dp.pngbin0 -> 1348 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_light.pngbin0 -> 1328 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_off_light.pngbin0 -> 1161 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.pngbin0 -> 1286 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.pngbin0 -> 1308 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.pngbin0 -> 1309 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_cast_on_light.pngbin0 -> 1331 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_grey600_24dp.pngbin658 -> 305 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_grey600_24dp.pngbin406 -> 186 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_outline_blank_white_24dp.pngbin364 -> 165 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_check_box_white_24dp.pngbin526 -> 282 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_close_light.pngbin0 -> 526 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_create_new_folder_grey600_24dp.pngbin248 -> 198 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_create_new_folder_white_24dp.pngbin239 -> 188 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.pngbin271 -> 153 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_delete_white_24dp.pngbin270 -> 146 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_description_grey600_36dp.pngbin461 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_description_white_36dp.pngbin459 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_done_grey600_24dp.pngbin373 -> 189 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_done_white_24dp.pngbin363 -> 183 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_drag_vertical_grey600_48dp.9.pngbin548 -> 379 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_drag_vertical_white_48dp.9.pngbin517 -> 349 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_expand_more_grey600_36dp.pngbin411 -> 223 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_expand_more_white_36dp.pngbin406 -> 218 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_24dp.pngbin383 -> 252 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_36dp.pngbin497 -> 332 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_forward_white_24dp.pngbin386 -> 253 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_forward_white_36dp.pngbin496 -> 327 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_24dp.pngbin405 -> 279 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_36dp.pngbin523 -> 348 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_24dp.pngbin399 -> 263 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_36dp.pngbin511 -> 331 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.pngbin1420 -> 645 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_white_24dp.pngbin910 -> 572 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_file_download_grey600_24dp.pngbin283 -> 148 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.pngbin282 -> 143 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.pngbin141 -> 106 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.pngbin141 -> 103 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_folder_grey600_24dp.pngbin0 -> 284 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_folder_white_24dp.pngbin0 -> 273 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_hearing_grey600_18dp.pngbin759 -> 621 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_hearing_white_18dp.pngbin724 -> 588 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_history_grey600_24dp.pngbin769 -> 636 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_history_white_24dp.pngbin770 -> 634 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_grey600_24dp.pngbin407 -> 186 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_indeterminate_check_box_white_24dp.pngbin370 -> 166 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_info_grey600_24dp.pngbin547 -> 428 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_info_white_24dp.pngbin530 -> 411 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_launcher.pngbin5589 -> 5519 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_list_grey600_24dp.pngbin197 -> 104 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_list_white_24dp.pngbin196 -> 95 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.pngbin430 -> 355 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.pngbin421 -> 341 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.pngbin427 -> 355 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.pngbin420 -> 341 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.pngbin0 -> 4496 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_more_vert_grey600_24dp.pngbin252 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.pngbin269 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_new.pngbin1189 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_new_dark.pngbin989 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_new_releases_grey600_24dp.pngbin623 -> 496 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_new_releases_white_24dp.pngbin637 -> 464 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.pngbin193 -> 105 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.pngbin217 -> 109 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_pause_light.pngbin0 -> 221 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_pause_white_24dp.pngbin193 -> 90 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_pause_white_36dp.pngbin215 -> 92 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.pngbin319 -> 211 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.pngbin400 -> 270 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.pngbin318 -> 206 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.pngbin399 -> 270 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_play_light.pngbin0 -> 678 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.pngbin644 -> 511 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.pngbin637 -> 484 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_remove_red_eye_grey600_18dp.pngbin589 -> 492 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_remove_red_eye_white_18dp.pngbin585 -> 472 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sd_storage_grey600_36dp.pngbin403 -> 295 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sd_storage_white_36dp.pngbin404 -> 284 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_search_grey600_24dp.pngbin597 -> 483 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_search_white_24dp.pngbin591 -> 453 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.pngbin704 -> 581 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_grey600_24dp.pngbin834 -> 698 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_white_24dp.pngbin825 -> 675 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.pngbin737 -> 540 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.pngbin629 -> 496 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_share_white_24dp.pngbin625 -> 472 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_skip_grey600_36dp.pngbin364 -> 285 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_skip_white_36dp.pngbin368 -> 285 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_grey600_24dp.pngbin494 -> 224 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_off_grey600_24dp.pngbin769 -> 358 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_off_white_24dp.pngbin486 -> 326 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sleep_white_24dp.pngbin367 -> 208 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sort_grey600_24dp.pngbin215 -> 106 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_sort_white_24dp.pngbin212 -> 101 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_border_grey600_24dp.pngbin828 -> 694 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_border_white_24dp.pngbin821 -> 630 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.pngbin593 -> 487 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_star_white_24dp.pngbin582 -> 455 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.pngbin942 -> 693 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_stat_authentication.pngbin882 -> 584 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_timer_grey600_24dp.pngbin761 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_timer_white_24dp.pngbin810 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_toc_grey600_36dp.pngbin222 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_toc_white_36dp.pngbin229 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_undobar_undo.pngbin1558 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_web_grey600_24dp.pngbin282 -> 190 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_web_white_24dp.pngbin319 -> 182 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/navigation_up.pngbin2471 -> 409 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/navigation_up_dark.pngbin2445 -> 408 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/stat_notify_sync.pngbin475 -> 368 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/stat_notify_sync_error.pngbin533 -> 415 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/undobar.9.pngbin1665 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/undobar_button_focused.9.pngbin1141 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/undobar_button_pressed.9.pngbin1123 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/undobar_divider.9.pngbin963 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_add_grey600_24dp.pngbin223 -> 114 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_add_white_24dp.pngbin222 -> 97 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_audiotrack_light.pngbin0 -> 584 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_av_fast_forward_80dp.pngbin3207 -> 831 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_av_pause_circle_outline_80dp.pngbin8059 -> 2745 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_av_play_circle_outline_80dp.pngbin8216 -> 2987 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_av_rewind_80dp.pngbin3502 -> 757 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cancel_grey600_24dp.pngbin920 -> 756 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cancel_white_24dp.pngbin893 -> 716 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.pngbin0 -> 1429 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_grey600_36dp.pngbin0 -> 2068 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_white_36dp.pngbin0 -> 1953 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_light.pngbin0 -> 1952 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_off_light.pngbin0 -> 1679 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.pngbin0 -> 1832 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.pngbin0 -> 1893 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.pngbin0 -> 1910 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_cast_on_light.pngbin0 -> 1944 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_grey600_24dp.pngbin920 -> 415 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_grey600_24dp.pngbin582 -> 244 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_outline_blank_white_24dp.pngbin502 -> 214 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_check_box_white_24dp.pngbin800 -> 383 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_close_light.pngbin0 -> 673 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_create_new_folder_grey600_24dp.pngbin343 -> 262 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_create_new_folder_white_24dp.pngbin339 -> 251 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.pngbin341 -> 195 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.pngbin338 -> 189 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_description_grey600_36dp.pngbin635 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_description_white_36dp.pngbin626 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_done_grey600_24dp.pngbin451 -> 262 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_done_white_24dp.pngbin476 -> 252 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_drag_vertical_grey600_48dp.9.pngbin853 -> 593 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_drag_vertical_white_48dp.9.pngbin809 -> 544 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_expand_more_grey600_36dp.pngbin539 -> 278 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_expand_more_white_36dp.pngbin539 -> 273 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_24dp.pngbin497 -> 332 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_36dp.pngbin689 -> 520 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_24dp.pngbin496 -> 327 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_36dp.pngbin689 -> 518 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_24dp.pngbin523 -> 348 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_36dp.pngbin704 -> 547 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_24dp.pngbin511 -> 331 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.pngbin708 -> 548 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.pngbin2087 -> 956 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.pngbin1366 -> 843 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_file_download_grey600_24dp.pngbin353 -> 182 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.pngbin351 -> 177 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.pngbin189 -> 114 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.pngbin188 -> 107 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_folder_grey600_24dp.pngbin0 -> 356 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.pngbin0 -> 342 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_hearing_grey600_18dp.pngbin1041 -> 885 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_hearing_white_18dp.pngbin1034 -> 840 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_history_grey600_24dp.pngbin1121 -> 950 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_history_white_24dp.pngbin1123 -> 952 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_grey600_24dp.pngbin580 -> 247 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_indeterminate_check_box_white_24dp.pngbin514 -> 219 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_info_grey600_24dp.pngbin762 -> 611 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_info_white_24dp.pngbin736 -> 576 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_launcher.pngbin14262 -> 9483 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_list_grey600_24dp.pngbin223 -> 111 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_list_white_24dp.pngbin230 -> 94 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.pngbin630 -> 518 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.pngbin621 -> 494 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.pngbin627 -> 519 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.pngbin621 -> 496 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.pngbin0 -> 7096 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_more_vert_grey600_24dp.pngbin316 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.pngbin313 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_new.pngbin1759 -> 0 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_new_dark.pngbin1501 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_new_releases_grey600_24dp.pngbin881 -> 718 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_new_releases_white_24dp.pngbin858 -> 679 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.pngbin217 -> 109 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.pngbin294 -> 143 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_pause_light.pngbin0 -> 317 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.pngbin215 -> 92 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.pngbin293 -> 143 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.pngbin400 -> 270 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.pngbin544 -> 367 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.pngbin399 -> 270 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.pngbin543 -> 351 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_play_light.pngbin0 -> 955 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.pngbin882 -> 741 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.pngbin875 -> 689 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_grey600_18dp.pngbin826 -> 697 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_white_18dp.pngbin862 -> 669 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sd_storage_grey600_36dp.pngbin605 -> 439 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sd_storage_white_36dp.pngbin593 -> 416 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_search_grey600_24dp.pngbin881 -> 712 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_search_white_24dp.pngbin871 -> 669 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.pngbin994 -> 844 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_grey600_24dp.pngbin1231 -> 1063 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_white_24dp.pngbin1219 -> 1019 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.pngbin974 -> 790 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.pngbin866 -> 694 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_share_white_24dp.pngbin857 -> 660 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_skip_grey600_36dp.pngbin533 -> 369 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_skip_white_36dp.pngbin543 -> 361 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_grey600_24dp.pngbin737 -> 311 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_off_grey600_24dp.pngbin1113 -> 495 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_off_white_24dp.pngbin664 -> 449 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sleep_white_24dp.pngbin509 -> 280 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sort_grey600_24dp.pngbin244 -> 115 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.pngbin241 -> 103 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_border_grey600_24dp.pngbin1227 -> 1009 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_border_white_24dp.pngbin1222 -> 934 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.pngbin877 -> 703 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.pngbin870 -> 657 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_stat_authentication.pngbin1266 -> 848 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_timer_grey600_24dp.pngbin1074 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_timer_white_24dp.pngbin1063 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_toc_grey600_36dp.pngbin313 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_toc_white_36dp.pngbin312 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_web_grey600_24dp.pngbin402 -> 245 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_web_white_24dp.pngbin408 -> 234 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/stat_notify_sync.pngbin705 -> 545 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/stat_notify_sync_error.pngbin761 -> 577 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxxhdpi/ic_av_fast_forward_80dp.pngbin5878 -> 1332 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxxhdpi/ic_av_rewind_80dp.pngbin6299 -> 1362 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_close_light.pngbin0 -> 805 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_grey600_24dp.pngbin466 -> 331 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_white_24dp.pngbin463 -> 314 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.pngbin235 -> 123 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.pngbin234 -> 106 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxxhdpi/ic_folder_grey600_24dp.pngbin0 -> 527 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.pngbin0 -> 504 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.pngbin854 -> 675 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.pngbin839 -> 646 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.pngbin850 -> 674 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.pngbin838 -> 643 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_pause_light.pngbin0 -> 400 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_play_light.pngbin0 -> 1190 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sd_storage_grey600_36dp.pngbin890 -> 583 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sd_storage_white_36dp.pngbin890 -> 559 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_skip_grey600_36dp.pngbin737 -> 443 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_skip_white_36dp.pngbin749 -> 433 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_grey600_24dp.pngbin955 -> 403 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_off_grey600_24dp.pngbin1514 -> 637 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_off_white_24dp.pngbin882 -> 580 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_sleep_white_24dp.pngbin647 -> 358 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_border_grey600_24dp.pngbin1675 -> 1352 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_border_white_24dp.pngbin1684 -> 1248 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.pngbin1179 -> 916 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.pngbin1183 -> 856 bytes
-rw-r--r--core/src/main/res/drawable/undobar_button.xml22
-rw-r--r--core/src/main/res/values-ar/strings.xml127
-rw-r--r--core/src/main/res/values-az/strings.xml4
-rw-r--r--core/src/main/res/values-ca-rES/strings.xml38
-rw-r--r--core/src/main/res/values-ca/strings.xml35
-rw-r--r--core/src/main/res/values-cs-rCZ/strings.xml31
-rw-r--r--core/src/main/res/values-da/strings.xml4
-rw-r--r--core/src/main/res/values-de/strings.xml93
-rw-r--r--core/src/main/res/values-el/strings.xml343
-rw-r--r--core/src/main/res/values-es-rES/strings.xml4
-rw-r--r--core/src/main/res/values-es/strings.xml92
-rw-r--r--core/src/main/res/values-fi/strings.xml38
-rw-r--r--core/src/main/res/values-fr/strings.xml109
-rw-r--r--core/src/main/res/values-hi-rIN/strings.xml4
-rw-r--r--core/src/main/res/values-hu/strings.xml38
-rw-r--r--core/src/main/res/values-id/strings.xml133
-rw-r--r--core/src/main/res/values-it-rIT/strings.xml26
-rw-r--r--core/src/main/res/values-it/strings.xml276
-rw-r--r--core/src/main/res/values-iw-rIL/strings.xml4
-rw-r--r--core/src/main/res/values-ja/strings.xml94
-rw-r--r--core/src/main/res/values-kn-rIN/strings.xml106
-rw-r--r--core/src/main/res/values-ko-rKR/strings.xml38
-rw-r--r--core/src/main/res/values-ko/strings.xml74
-rw-r--r--core/src/main/res/values-nb/strings.xml5
-rw-r--r--core/src/main/res/values-nl/strings.xml115
-rw-r--r--core/src/main/res/values-no-rNB/strings.xml490
-rw-r--r--core/src/main/res/values-no/strings.xml38
-rw-r--r--core/src/main/res/values-pl-rPL/strings.xml4
-rw-r--r--core/src/main/res/values-pl/strings.xml99
-rw-r--r--core/src/main/res/values-pt-rBR/strings.xml120
-rw-r--r--core/src/main/res/values-pt/strings.xml112
-rw-r--r--core/src/main/res/values-ro-rRO/strings.xml4
-rw-r--r--core/src/main/res/values-ru/strings.xml4
-rw-r--r--core/src/main/res/values-sv-rSE/strings.xml94
-rw-r--r--core/src/main/res/values-tr/strings.xml4
-rw-r--r--core/src/main/res/values-uk-rUA/strings.xml31
-rw-r--r--core/src/main/res/values-vi-rVN/strings.xml38
-rw-r--r--core/src/main/res/values-vi/strings.xml65
-rw-r--r--core/src/main/res/values-zh-rCN/strings.xml67
-rw-r--r--core/src/main/res/values/arrays.xml7
-rw-r--r--core/src/main/res/values/attrs.xml5
-rw-r--r--core/src/main/res/values/colors.xml2
-rw-r--r--core/src/main/res/values/dimens.xml1
-rw-r--r--core/src/main/res/values/strings.xml103
-rw-r--r--core/src/main/res/values/styles.xml20
666 files changed, 9261 insertions, 2954 deletions
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 1064e98ac..3acc84e3b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ApplicationCallbacks.java
@@ -12,12 +12,12 @@ public interface ApplicationCallbacks {
/**
* Returns a non-null instance of the application class
*/
- public Application getApplicationInstance();
+ Application getApplicationInstance();
/**
* Returns a non-null intent that starts the storage error
* activity.
*/
- public Intent getStorageErrorActivity(Context context);
+ Intent getStorageErrorActivity(Context context);
}
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 6619e706b..9bbccbb82 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
@@ -1,5 +1,13 @@
package de.danoeh.antennapod.core;
+import android.content.Context;
+
+import de.danoeh.antennapod.core.cast.CastManager;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.danoeh.antennapod.core.util.NetworkUtils;
+
/**
* Stores callbacks for core classes like Services, DB classes etc. and other configuration variables.
* Apps using the core module of AntennaPod should register implementations of all interfaces here.
@@ -22,4 +30,20 @@ public class ClientConfig {
public static FlattrCallbacks flattrCallbacks;
public static DBTasksCallbacks dbTasksCallbacks;
+
+ private static boolean initialized = false;
+
+ public static synchronized void initialize(Context context) {
+ if(initialized) {
+ return;
+ }
+ PodDBAdapter.init(context);
+ UserPreferences.init(context);
+ UpdateManager.init(context);
+ PlaybackPreferences.init(context);
+ NetworkUtils.init(context);
+ CastManager.init(context);
+ initialized = true;
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java
index edf3e3199..11a6b2c9f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java
@@ -11,10 +11,10 @@ public interface DBTasksCallbacks {
/**
* Returns the client's implementation of the AutomaticDownloadAlgorithm interface.
*/
- public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm();
+ AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm();
/**
* Returns the client's implementation of the EpisodeCacheCleanupAlgorithm interface.
*/
- public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm();
+ EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java
index 286e830c5..e56445489 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java
@@ -19,7 +19,7 @@ public interface DownloadServiceCallbacks {
*
* @return A non-null PendingIntent for the notification.
*/
- public PendingIntent getNotificationContentIntent(Context context);
+ PendingIntent getNotificationContentIntent(Context context);
/**
* Returns a PendingIntent for a notification that tells the user to enter a username
@@ -30,7 +30,7 @@ public interface DownloadServiceCallbacks {
*
* @return A non-null PendingIntent for the notification.
*/
- public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request);
+ PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request);
/**
* Returns a PendingIntent for notification that notifies the user about the completion of downloads
@@ -40,19 +40,19 @@ public interface DownloadServiceCallbacks {
*
* @return A non-null PendingIntent for the notification or null if shouldCreateReport()==false
*/
- public PendingIntent getReportNotificationContentIntent(Context context);
+ PendingIntent getReportNotificationContentIntent(Context context);
/**
* Called by the FeedSyncThread after a feed has been downloaded and parsed.
*
* @param feed The non-null feed that has been parsed.
*/
- public void onFeedParsed(Context context, Feed feed);
+ void onFeedParsed(Context context, Feed feed);
/**
* Returns true if the DownloadService should create a report that shows the number of failed
* downloads when the service shuts down.
* */
- public boolean shouldCreateReport();
+ boolean shouldCreateReport();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java
index cee1029d8..5fa6faa13 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/FlattrCallbacks.java
@@ -15,7 +15,7 @@ public interface FlattrCallbacks {
* Returns if true if the flattr integration should be activated,
* false otherwise.
*/
- public boolean flattrEnabled();
+ boolean flattrEnabled();
/**
* Returns an intent that starts the activity that is responsible for
@@ -24,13 +24,13 @@ public interface FlattrCallbacks {
* @return The intent that starts the authentication activity or null
* if flattr integration is disabled (i.e. flattrEnabled() == false).
*/
- public Intent getFlattrAuthenticationActivityIntent(Context context);
+ Intent getFlattrAuthenticationActivityIntent(Context context);
- public PendingIntent getFlattrFailedNotificationContentIntent(Context context);
+ PendingIntent getFlattrFailedNotificationContentIntent(Context context);
- public String getFlattrAppKey();
+ String getFlattrAppKey();
- public String getFlattrAppSecret();
+ String getFlattrAppSecret();
- public void handleFlattrAuthenticationSuccess(AccessToken token);
+ void handleFlattrAuthenticationSuccess(AccessToken token);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java
index 6174bce29..10797ecfb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/GpodnetCallbacks.java
@@ -13,7 +13,7 @@ public interface GpodnetCallbacks {
* Returns if true if the gpodder.net integration should be activated,
* false otherwise.
*/
- public boolean gpodnetEnabled();
+ boolean gpodnetEnabled();
/**
* Returns a PendingIntent for the error notification of the GpodnetSyncService.
@@ -23,5 +23,5 @@ public interface GpodnetCallbacks {
* @return A PendingIntent for the notification or null if gpodder.net integration
* has been disabled (i.e. gpodnetEnabled() == false).
*/
- public PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(Context context);
+ PendingIntent getGpodnetSyncServiceErrorNotificationPendingIntent(Context context);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java
index fb01fa703..13a32ab8a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java
@@ -15,19 +15,20 @@ public interface PlaybackServiceCallbacks {
* type of media that is being played.
*
* @param mediaType The type of media that is being played.
+ * @param remotePlayback true if the media is played on a remote device.
* @return A non-null activity intent.
*/
- public Intent getPlayerActivityIntent(Context context, MediaType mediaType);
+ Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback);
/**
* Returns true if the PlaybackService should load new episodes from the queue when playback ends
* and false if the PlaybackService should ignore the queue and load no more episodes when playback
* finishes.
*/
- public boolean useQueue();
+ boolean useQueue();
/**
* Returns a drawable resource that is used for the notification of the playback service.
*/
- public int getNotificationIconResource(Context context);
+ int getNotificationIconResource(Context context);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
new file mode 100644
index 000000000..8362c4a4e
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
@@ -0,0 +1,97 @@
+package de.danoeh.antennapod.core;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.util.Log;
+
+import org.antennapod.audio.MediaPlayer;
+
+import java.io.File;
+import java.util.List;
+
+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.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
+
+/*
+ * This class's job is do perform maintenance tasks whenever the app has been updated
+ */
+public class UpdateManager {
+
+ public static final String TAG = UpdateManager.class.getSimpleName();
+
+ private static final String PREF_NAME = "app_version";
+ private static final String KEY_VERSION_CODE = "version_code";
+
+ private static int currentVersionCode;
+
+ private static Context context;
+ private static SharedPreferences prefs;
+
+ public static void init(Context context) {
+ UpdateManager.context = context;
+ prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ PackageManager pm = context.getPackageManager();
+ try {
+ PackageInfo info = pm.getPackageInfo(context.getPackageName(), 0);
+ currentVersionCode = info.versionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to obtain package info for package name: " + context.getPackageName(), e);
+ currentVersionCode = 0;
+ return;
+ }
+ final int oldVersionCode = getStoredVersionCode();
+ Log.d(TAG, "old: " + oldVersionCode + ", current: " + currentVersionCode);
+ if(oldVersionCode < currentVersionCode) {
+ onUpgrade(oldVersionCode, currentVersionCode);
+ setCurrentVersionCode();
+ }
+ }
+
+ public static int getStoredVersionCode() {
+ return prefs.getInt(KEY_VERSION_CODE, -1);
+ }
+
+ public static void setCurrentVersionCode() {
+ prefs.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply();
+ }
+
+ private static void onUpgrade(final int oldVersionCode, final int newVersionCode) {
+ if(oldVersionCode < 1030099) {
+ // delete the now obsolete image cache
+ // from now on, Glide will handle caching images
+ new Thread() {
+ public void run() {
+ List<Feed> feeds = DBReader.getFeedList();
+ for (Feed podcast : feeds) {
+ List<FeedItem> episodes = DBReader.getFeedItemList(podcast);
+ for (FeedItem episode : episodes) {
+ FeedImage image = episode.getImage();
+ if (image != null && image.isDownloaded() && image.getFile_url() != null) {
+ File imageFile = new File(image.getFile_url());
+ if (imageFile.exists()) {
+ imageFile.delete();
+ }
+ image.setFile_url(null); // calls setDownloaded(false)
+ DBWriter.setFeedImage(image);
+ }
+ }
+ }
+ }
+ }.start();
+ }
+ if(oldVersionCode < 1050004) {
+ if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) {
+ UserPreferences.enableSonic(true);
+ }
+ }
+ }
+
+}
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 7ff622f34..e475e696c 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
@@ -30,9 +30,7 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
try {
DBWriter.deleteFeed(context, feed.getId()).get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
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 ac032fcc0..3ddaba52e 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
@@ -17,6 +17,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
@@ -44,10 +45,10 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
private final Context context;
- public static enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS}
+ public enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS}
- private volatile int countFailed = 0;
- private volatile int countSuccess = 0;
+ private final AtomicInteger countFailed = new AtomicInteger();
+ private final AtomicInteger countSuccess = new AtomicInteger();
private volatile FlattrThing extraFlattrThing;
@@ -105,7 +106,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
return ExitCode.NO_THINGS;
}
- List<Future> dbFutures = new LinkedList<Future>();
+ List<Future> dbFutures = new LinkedList<>();
for (FlattrThing thing : flattrQueue) {
if (BuildConfig.DEBUG) Log.d(TAG, "Processing " + thing.getTitle());
@@ -114,12 +115,12 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
FlattrUtils.clickUrl(context, thing.getPaymentLink());
thing.getFlattrStatus().setFlattred();
publishProgress(R.string.flattr_click_success);
- countSuccess++;
+ countSuccess.incrementAndGet();
} catch (FlattrException e) {
e.printStackTrace();
- countFailed++;
- if (countFailed == 1) {
+ int failed = countFailed.incrementAndGet();
+ if (failed == 1) {
exception = e;
}
}
@@ -133,9 +134,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
for (Future f : dbFutures) {
try {
f.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
@@ -148,7 +147,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
super.onPostExecute(exitCode);
switch (exitCode) {
case EXIT_NORMAL:
- if (countFailed > 0) {
+ if (countFailed.get() > 0) {
postFlattrFailedNotification();
}
break;
@@ -190,7 +189,8 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
}
private void postFlattrFailedNotification() {
- if (countFailed == 0) {
+ int failed = countFailed.get();
+ if (failed == 0) {
return;
}
@@ -198,15 +198,15 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
String title;
String subtext;
- if (countFailed == 1) {
+ if (failed == 1) {
title = context.getString(R.string.flattrd_failed_label);
String exceptionMsg = (exception.getMessage() != null) ? exception.getMessage() : "";
subtext = context.getString(R.string.flattr_click_failure, extraFlattrThing.getTitle())
+ "\n" + exceptionMsg;
} else {
title = context.getString(R.string.flattrd_label);
- subtext = context.getString(R.string.flattr_click_success_count, countSuccess) + "\n"
- + context.getString(R.string.flattr_click_failure_count, countFailed);
+ subtext = context.getString(R.string.flattr_click_success_count, countSuccess.get()) + "\n"
+ + context.getString(R.string.flattr_click_failure_count, failed);
}
Notification notification = new NotificationCompat.Builder(context)
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 888591e89..4c084eaaf 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
@@ -2,15 +2,17 @@ package de.danoeh.antennapod.core.asynctask;
import android.content.Context;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+
import org.shredzone.flattr4j.exception.FlattrException;
import org.shredzone.flattr4j.model.Flattr;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+
/**
* Fetch list of flattred things and flattr status in database in a background thread.
*/
@@ -36,9 +38,7 @@ public class FlattrStatusFetcher extends Thread {
} catch (FlattrException e) {
e.printStackTrace();
Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage());
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java
index edd69f15b..992321441 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java
@@ -16,13 +16,13 @@ public interface ImageResource {
* <p/>
* For example implementations, see FeedMedia or ExternalMedia.
*/
- public static final String SCHEME_MEDIA = "media";
+ String SCHEME_MEDIA = "media";
/**
* Parameter key for an encoded fallback Uri. This Uri MUST point to a local image file
*/
- public static final String PARAM_FALLBACK = "fallback";
+ String PARAM_FALLBACK = "fallback";
/**
* Returns a Uri to the image or null if no image is available.
@@ -33,5 +33,5 @@ public interface ImageResource {
* The Uri can also have an optional fallback-URL if loading the default URL
* failed (see PARAM_FALLBACK).
*/
- public Uri getImageUri();
+ Uri getImageUri();
}
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 690fbdfc6..982015314 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
@@ -8,7 +8,6 @@ import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayOutputStream;
@@ -26,8 +25,8 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
+import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.opml.OpmlElement;
import de.danoeh.antennapod.core.opml.OpmlReader;
@@ -45,13 +44,13 @@ public class OpmlBackupAgent extends BackupAgentHelper {
addHelper(OPML_BACKUP_KEY, new OpmlBackupHelper(this));
}
- private static final void LOGD(String tag, String msg) {
+ private static void LOGD(String tag, String msg) {
if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, msg);
}
}
- private static final void LOGD(String tag, String msg, Throwable tr) {
+ private static void LOGD(String tag, String msg, Throwable tr) {
if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, msg, tr);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java b/core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java
new file mode 100644
index 000000000..213dd1875
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java
@@ -0,0 +1,11 @@
+package de.danoeh.antennapod.core.cast;
+
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer;
+
+public interface CastConsumer extends VideoCastConsumer{
+
+ /**
+ * Called when the stream's volume is changed.
+ */
+ void onStreamVolumeChanged(double value, boolean isMute);
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java b/core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java
new file mode 100644
index 000000000..5b1fdab61
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java
@@ -0,0 +1,1766 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Changes made by Domingos Lopes <domingos86lopes@gmail.com>
+ *
+ * original can be found at http://www.github.com/googlecast/CastCompanionLibrary-android
+ */
+
+package de.danoeh.antennapod.core.cast;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.media.MediaRouter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.gms.cast.Cast;
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.cast.CastMediaControlIntent;
+import com.google.android.gms.cast.CastStatusCodes;
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.gms.cast.RemoteMediaPlayer;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.CastConfiguration;
+import com.google.android.libraries.cast.companionlibrary.cast.MediaQueue;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.OnFailedListener;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.TimeUnit;
+
+import de.danoeh.antennapod.core.R;
+
+import static com.google.android.gms.cast.RemoteMediaPlayer.RESUME_STATE_PLAY;
+import static com.google.android.gms.cast.RemoteMediaPlayer.RESUME_STATE_UNCHANGED;
+
+/**
+ * A subclass of {@link BaseCastManager} that is suitable for casting video contents (it
+ * also provides a single custom data channel/namespace if an out-of-band communication is
+ * needed).
+ * <p>
+ * Clients need to initialize this class by calling
+ * {@link #init(android.content.Context)} in the Application's
+ * {@code onCreate()} method. To access the (singleton) instance of this class, clients
+ * need to call {@link #getInstance()}.
+ * <p>This
+ * class manages various states of the remote cast device. Client applications, however, can
+ * complement the default behavior of this class by hooking into various callbacks that it provides
+ * (see {@link CastConsumer}).
+ * Since the number of these callbacks is usually much larger than what a single application might
+ * be interested in, there is a no-op implementation of this interface (see
+ * {@link DefaultCastConsumer}) that applications can subclass to override only those methods that
+ * they are interested in. Since this library depends on the cast functionalities provided by the
+ * Google Play services, the library checks to ensure that the right version of that service is
+ * installed. It also provides a simple static method {@code checkGooglePlayServices()} that clients
+ * can call at an early stage of their applications to provide a dialog for users if they need to
+ * update/activate their Google Play Services library.
+ *
+ * @see CastConfiguration
+ */
+public class CastManager extends BaseCastManager implements OnFailedListener {
+ public static final String TAG = "CastManager";
+
+ public static final String CAST_APP_ID = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
+
+ public static final double DEFAULT_VOLUME_STEP = 0.05;
+ public static final long DEFAULT_LIVE_STREAM_DURATION_MS = TimeUnit.HOURS.toMillis(2);
+ private double volumeStep = DEFAULT_VOLUME_STEP;
+ private MediaQueue mediaQueue;
+ private MediaStatus mediaStatus;
+
+ private static CastManager INSTANCE;
+ private RemoteMediaPlayer remoteMediaPlayer;
+ private int state = MediaStatus.PLAYER_STATE_IDLE;
+ private int idleReason;
+ private final Set<CastConsumer> castConsumers = new CopyOnWriteArraySet<>();
+ private long liveStreamDuration = DEFAULT_LIVE_STREAM_DURATION_MS;
+ private MediaQueueItem preLoadingItem;
+
+ public static final int QUEUE_OPERATION_LOAD = 1;
+ public static final int QUEUE_OPERATION_INSERT_ITEMS = 2;
+ public static final int QUEUE_OPERATION_UPDATE_ITEMS = 3;
+ public static final int QUEUE_OPERATION_JUMP = 4;
+ public static final int QUEUE_OPERATION_REMOVE_ITEM = 5;
+ public static final int QUEUE_OPERATION_REMOVE_ITEMS = 6;
+ public static final int QUEUE_OPERATION_REORDER = 7;
+ public static final int QUEUE_OPERATION_MOVE = 8;
+ public static final int QUEUE_OPERATION_APPEND = 9;
+ public static final int QUEUE_OPERATION_NEXT = 10;
+ public static final int QUEUE_OPERATION_PREV = 11;
+ public static final int QUEUE_OPERATION_SET_REPEAT = 12;
+
+ private CastManager(Context context, CastConfiguration castConfiguration) {
+ super(context, castConfiguration);
+ Log.d(TAG, "CastManager is instantiated");
+ }
+
+ public static synchronized CastManager init(Context context) {
+ if (INSTANCE == null) {
+ //TODO also setup dialog factory if necessary
+ CastConfiguration castConfiguration = new CastConfiguration.Builder(CAST_APP_ID)
+ .enableDebug()
+ .enableAutoReconnect()
+ .enableWifiReconnection()
+ .setLaunchOptions(true, Locale.getDefault())
+ .build();
+ Log.d(TAG, "New instance of CastManager is created");
+ if (ConnectionResult.SUCCESS != GoogleApiAvailability.getInstance()
+ .isGooglePlayServicesAvailable(context)) {
+ Log.e(TAG, "Couldn't find the appropriate version of Google Play Services");
+ //TODO check whether creating an instance without google play services installed actually gives an exception
+ }
+ INSTANCE = new CastManager(context, castConfiguration);
+ }
+ return INSTANCE;
+ }
+
+ /**
+ * Returns a (singleton) instance of this class. Clients should call this method in order to
+ * get a hold of this singleton instance, only after it is initialized. If it is not initialized
+ * yet, an {@link IllegalStateException} will be thrown.
+ *
+ */
+ public static CastManager getInstance() {
+ if (INSTANCE == null) {
+ String msg = "No CastManager instance was found, did you forget to initialize it?";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+ return INSTANCE;
+ }
+
+ /**
+ * Returns the active {@link RemoteMediaPlayer} instance. Since there are a number of media
+ * control APIs that this library do not provide a wrapper for, client applications can call
+ * those methods directly after obtaining an instance of the active {@link RemoteMediaPlayer}.
+ */
+ public final RemoteMediaPlayer getRemoteMediaPlayer() {
+ return remoteMediaPlayer;
+ }
+
+ /**
+ * Determines if the media that is loaded remotely is a live stream or not.
+ *
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public final boolean isRemoteStreamLive() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ MediaInfo info = getRemoteMediaInformation();
+ return (info != null) && (info.getStreamType() == MediaInfo.STREAM_TYPE_LIVE);
+ }
+
+ /*
+ * A simple check to make sure remoteMediaPlayer is not null
+ */
+ private void checkRemoteMediaPlayerAvailable() throws NoConnectionException {
+ if (remoteMediaPlayer == null) {
+ throw new NoConnectionException();
+ }
+ }
+
+ /**
+ * Returns the url for the media that is currently playing on the remote device. If there is no
+ * connection, this will return <code>null</code>.
+ *
+ * @throws NoConnectionException If no connectivity to the device exists
+ * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
+ * a possibly transient loss of network
+ */
+ public String getRemoteMediaUrl() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ if (remoteMediaPlayer != null && remoteMediaPlayer.getMediaInfo() != null) {
+ MediaInfo info = remoteMediaPlayer.getMediaInfo();
+ remoteMediaPlayer.getMediaStatus().getPlayerState();
+ return info.getContentId();
+ }
+ throw new NoConnectionException();
+ }
+
+ /**
+ * Indicates if the remote media is currently playing (or buffering).
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public boolean isRemoteMediaPlaying() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ return state == MediaStatus.PLAYER_STATE_BUFFERING
+ || state == MediaStatus.PLAYER_STATE_PLAYING;
+ }
+
+ /**
+ * Returns <code>true</code> if the remote connected device is playing a movie.
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public boolean isRemoteMediaPaused() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ return state == MediaStatus.PLAYER_STATE_PAUSED;
+ }
+
+ /**
+ * Returns <code>true</code> only if there is a media on the remote being played, paused or
+ * buffered.
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public boolean isRemoteMediaLoaded() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ return isRemoteMediaPaused() || isRemoteMediaPlaying();
+ }
+
+ /**
+ * Returns the {@link MediaInfo} for the current media
+ *
+ * @throws NoConnectionException If no connectivity to the device exists
+ * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
+ * a possibly transient loss of network
+ */
+ public MediaInfo getRemoteMediaInformation() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ checkRemoteMediaPlayerAvailable();
+ return remoteMediaPlayer.getMediaInfo();
+ }
+
+ /**
+ * Gets the remote's system volume. It internally detects what type of volume is used.
+ *
+ * @throws NoConnectionException If no connectivity to the device exists
+ * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
+ * a possibly transient loss of network
+ */
+ public double getStreamVolume() throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ checkRemoteMediaPlayerAvailable();
+ return remoteMediaPlayer.getMediaStatus().getStreamVolume();
+ }
+
+ /**
+ * Sets the stream volume.
+ *
+ * @param volume Should be a value between 0 and 1, inclusive.
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ * @throws CastException If setting system volume fails
+ */
+ public void setStreamVolume(double volume) throws CastException,
+ TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (volume > 1.0) {
+ volume = 1.0;
+ } else if (volume < 0) {
+ volume = 0.0;
+ }
+
+ RemoteMediaPlayer mediaPlayer = getRemoteMediaPlayer();
+ if (mediaPlayer == null) {
+ throw new NoConnectionException();
+ }
+ mediaPlayer.setStreamVolume(mApiClient, volume).setResultCallback(
+ (result) -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_setting_volume,
+ result.getStatus().getStatusCode());
+ } else {
+ CastManager.this.onStreamVolumeChanged();
+ }
+ });
+ }
+
+ /**
+ * Returns <code>true</code> if remote Stream is muted.
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public boolean isStreamMute() throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ checkRemoteMediaPlayerAvailable();
+ return remoteMediaPlayer.getMediaStatus().isMute();
+ }
+
+ /**
+ * Returns <code>true</code> if remote device is muted.
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public boolean isMute() throws TransientNetworkDisconnectionException, NoConnectionException {
+ return isStreamMute() || isDeviceMute();
+ }
+
+ /**
+ * Mutes or un-mutes the stream volume.
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void setStreamMute(boolean mute) throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ checkRemoteMediaPlayerAvailable();
+ remoteMediaPlayer.setStreamMute(mApiClient, mute);
+ }
+
+ /**
+ * Returns the duration of the media that is loaded, in milliseconds.
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public long getMediaDuration() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ checkRemoteMediaPlayerAvailable();
+ return remoteMediaPlayer.getStreamDuration();
+ }
+
+ /**
+ * Returns the time left (in milliseconds) of the current media. If there is no
+ * {@code RemoteMediaPlayer}, it returns -1.
+ *
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public long getMediaTimeRemaining()
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ return -1;
+ }
+ return isRemoteStreamLive() ? liveStreamDuration : remoteMediaPlayer.getStreamDuration()
+ - remoteMediaPlayer.getApproximateStreamPosition();
+ }
+
+ /**
+ * Returns the current (approximate) position of the current media, in milliseconds.
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public long getCurrentMediaPosition() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ checkRemoteMediaPlayerAvailable();
+ return remoteMediaPlayer.getApproximateStreamPosition();
+ }
+
+ public int getApplicationStandbyState() throws IllegalStateException {
+ Log.d(TAG, "getApplicationStandbyState()");
+ return Cast.CastApi.getStandbyState(mApiClient);
+ }
+
+ private void onApplicationDisconnected(int errorCode) {
+ Log.d(TAG, "onApplicationDisconnected() reached with error code: " + errorCode);
+ mApplicationErrorCode = errorCode;
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onApplicationDisconnected(errorCode);
+ }
+ if (mMediaRouter != null) {
+ Log.d(TAG, "onApplicationDisconnected(): Cached RouteInfo: " + getRouteInfo());
+ Log.d(TAG, "onApplicationDisconnected(): Selected RouteInfo: "
+ + mMediaRouter.getSelectedRoute());
+ if (getRouteInfo() == null || mMediaRouter.getSelectedRoute().equals(getRouteInfo())) {
+ Log.d(TAG, "onApplicationDisconnected(): Setting route to default");
+ mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
+ }
+ }
+ onDeviceSelected(null /* CastDevice */, null /* RouteInfo */);
+ }
+
+ private void onApplicationStatusChanged() {
+ if (!isConnected()) {
+ return;
+ }
+ try {
+ String appStatus = Cast.CastApi.getApplicationStatus(mApiClient);
+ Log.d(TAG, "onApplicationStatusChanged() reached: " + appStatus);
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onApplicationStatusChanged(appStatus);
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "onApplicationStatusChanged()", e);
+ }
+ }
+
+ private void onDeviceVolumeChanged() {
+ Log.d(TAG, "onDeviceVolumeChanged() reached");
+ double volume;
+ try {
+ volume = getDeviceVolume();
+ boolean isMute = isDeviceMute();
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onVolumeChanged(volume, isMute);
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Failed to get volume", e);
+ }
+
+ }
+
+ private void onStreamVolumeChanged() {
+ Log.d(TAG, "onStreamVolumeChanged() reached");
+ double volume;
+ try {
+ volume = getStreamVolume();
+ boolean isMute = isStreamMute();
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onStreamVolumeChanged(volume, isMute);
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Failed to get volume", e);
+ }
+ }
+
+ @Override
+ protected void onApplicationConnected(ApplicationMetadata appMetadata,
+ String applicationStatus, String sessionId, boolean wasLaunched) {
+ Log.d(TAG, "onApplicationConnected() reached with sessionId: " + sessionId
+ + ", and mReconnectionStatus=" + mReconnectionStatus);
+ mApplicationErrorCode = NO_APPLICATION_ERROR;
+ if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) {
+ // we have tried to reconnect and successfully launched the app, so
+ // it is time to select the route and make the cast icon happy :-)
+ List<MediaRouter.RouteInfo> routes = mMediaRouter.getRoutes();
+ if (routes != null) {
+ String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID);
+ for (MediaRouter.RouteInfo routeInfo : routes) {
+ if (routeId.equals(routeInfo.getId())) {
+ // found the right route
+ Log.d(TAG, "Found the correct route during reconnection attempt");
+ mReconnectionStatus = RECONNECTION_STATUS_FINALIZED;
+ mMediaRouter.selectRoute(routeInfo);
+ break;
+ }
+ }
+ }
+ }
+ try {
+ //attachDataChannel();
+ attachMediaChannel();
+ mSessionId = sessionId;
+ // saving device for future retrieval; we only save the last session info
+ mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SESSION_ID, mSessionId);
+ remoteMediaPlayer.requestStatus(mApiClient).setResultCallback(result -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_status_request,
+ result.getStatus().getStatusCode());
+ }
+ });
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onApplicationConnected(appMetadata, mSessionId, wasLaunched);
+ }
+ } catch (TransientNetworkDisconnectionException e) {
+ Log.e(TAG, "Failed to attach media/data channel due to network issues", e);
+ onFailed(R.string.cast_failed_no_connection_trans, NO_STATUS_CODE);
+ } catch (NoConnectionException e) {
+ Log.e(TAG, "Failed to attach media/data channel due to network issues", e);
+ onFailed(R.string.cast_failed_no_connection, NO_STATUS_CODE);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager
+ * #onConnectivityRecovered()
+ */
+ @Override
+ public void onConnectivityRecovered() {
+ reattachMediaChannel();
+ //reattachDataChannel();
+ super.onConnectivityRecovered();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.android.gms.cast.CastClient.Listener#onApplicationStopFailed (int)
+ */
+ @Override
+ public void onApplicationStopFailed(int errorCode) {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onApplicationStopFailed(errorCode);
+ }
+ }
+
+ @Override
+ public void onApplicationConnectionFailed(int errorCode) {
+ Log.d(TAG, "onApplicationConnectionFailed() reached with errorCode: " + errorCode);
+ mApplicationErrorCode = errorCode;
+ if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) {
+ if (errorCode == CastStatusCodes.APPLICATION_NOT_RUNNING) {
+ // while trying to re-establish session, we found out that the app is not running
+ // so we need to disconnect
+ mReconnectionStatus = RECONNECTION_STATUS_INACTIVE;
+ onDeviceSelected(null /* CastDevice */, null /* RouteInfo */);
+ }
+ } else {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onApplicationConnectionFailed(errorCode);
+ }
+ onDeviceSelected(null /* CastDevice */, null /* RouteInfo */);
+ if (mMediaRouter != null) {
+ Log.d(TAG, "onApplicationConnectionFailed(): Setting route to default");
+ mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
+ }
+ }
+ }
+
+ /**
+ * Loads a media. For this to succeed, you need to have successfully launched the application.
+ *
+ * @param media The media to be loaded
+ * @param autoPlay If <code>true</code>, playback starts after load
+ * @param position Where to start the playback (only used if autoPlay is <code>true</code>.
+ * Units is milliseconds.
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void loadMedia(MediaInfo media, boolean autoPlay, int position)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ loadMedia(media, autoPlay, position, null);
+ }
+
+ /**
+ * Loads a media. For this to succeed, you need to have successfully launched the application.
+ *
+ * @param media The media to be loaded
+ * @param autoPlay If <code>true</code>, playback starts after load
+ * @param position Where to start the playback (only used if autoPlay is <code>true</code>).
+ * Units is milliseconds.
+ * @param customData Optional {@link JSONObject} data to be passed to the cast device
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void loadMedia(MediaInfo media, boolean autoPlay, int position, JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ loadMedia(media, null, autoPlay, position, customData);
+ }
+
+ /**
+ * Loads a media. For this to succeed, you need to have successfully launched the application.
+ *
+ * @param media The media to be loaded
+ * @param activeTracks An array containing the list of track IDs to be set active for this
+ * media upon a successful load
+ * @param autoPlay If <code>true</code>, playback starts after load
+ * @param position Where to start the playback (only used if autoPlay is <code>true</code>).
+ * Units is milliseconds.
+ * @param customData Optional {@link JSONObject} data to be passed to the cast device
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void loadMedia(MediaInfo media, final long[] activeTracks, boolean autoPlay,
+ int position, JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "loadMedia");
+ checkConnectivity();
+ if (media == null) {
+ return;
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to load a video with no active media session");
+ throw new NoConnectionException();
+ }
+
+ Log.d(TAG, "remoteMediaPlayer.load() with media=" + media.getMetadata().getString(MediaMetadata.KEY_TITLE)
+ + ", position=" + position + ", autoplay=" + autoPlay);
+ remoteMediaPlayer.load(mApiClient, media, autoPlay, position, activeTracks, customData)
+ .setResultCallback(result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaLoadResult(result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Loads and optionally starts playback of a new queue of media items.
+ *
+ * @param items Array of items to load, in the order that they should be played. Must not be
+ * {@code null} or empty.
+ * @param startIndex The array index of the item in the {@code items} array that should be
+ * played first (i.e., it will become the currentItem).If {@code repeatMode}
+ * is {@link MediaStatus#REPEAT_MODE_REPEAT_OFF} playback will end when the
+ * last item in the array is played.
+ * <p>
+ * This may be useful for continuation scenarios where the user was already
+ * using the sender application and in the middle decides to cast. This lets
+ * the sender application avoid mapping between the local and remote queue
+ * positions and/or avoid issuing an extra request to update the queue.
+ * <p>
+ * This value must be less than the length of {@code items}.
+ * @param repeatMode The repeat playback mode for the queue. One of
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_OFF},
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_ALL},
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_SINGLE} and
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE}.
+ * @param customData Custom application-specific data to pass along with the request, may be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueLoad(final MediaQueueItem[] items, final int startIndex, final int repeatMode,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueLoad");
+ checkConnectivity();
+ if (items == null || items.length == 0) {
+ return;
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to queue one or more videos with no active media session");
+ throw new NoConnectionException();
+ }
+ Log.d(TAG, "remoteMediaPlayer.queueLoad() with " + items.length + "items, starting at "
+ + startIndex);
+ remoteMediaPlayer
+ .queueLoad(mApiClient, items, startIndex, repeatMode, customData)
+ .setResultCallback(result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_LOAD,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Inserts a list of new media items into the queue.
+ *
+ * @param itemsToInsert List of items to insert into the queue, in the order that they should be
+ * played. The itemId field of the items should be unassigned or the
+ * request will fail with an INVALID_PARAMS error. Must not be {@code null}
+ * or empty.
+ * @param insertBeforeItemId ID of the item that will be located immediately after the inserted
+ * list. If the value is {@link MediaQueueItem#INVALID_ITEM_ID} or
+ * invalid, the inserted list will be appended to the end of the
+ * queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueInsertItems(final MediaQueueItem[] itemsToInsert, final int insertBeforeItemId,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueInsertItems");
+ checkConnectivity();
+ if (itemsToInsert == null || itemsToInsert.length == 0) {
+ throw new IllegalArgumentException("items cannot be empty or null");
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to insert into queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueInsertItems(mApiClient, itemsToInsert, insertBeforeItemId, customData)
+ .setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(
+ QUEUE_OPERATION_INSERT_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Updates properties of a subset of the existing items in the media queue.
+ *
+ * @param itemsToUpdate List of queue items to be updated. The items will retain the existing
+ * order and will be fully replaced with the ones provided, including the
+ * media information. Any other items currently in the queue will remain
+ * unchanged. The tracks information can not change once the item is loaded
+ * (if the item is the currentItem). If any of the items does not exist it
+ * will be ignored.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueUpdateItems(final MediaQueueItem[] itemsToUpdate, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueUpdateItems(mApiClient, itemsToUpdate, customData).setResultCallback(
+ result -> {
+ Log.d(TAG, "queueUpdateItems() " + result.getStatus() + result.getStatus()
+ .isSuccess());
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_UPDATE_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Plays the item with {@code itemId} in the queue.
+ * <p>
+ * If {@code itemId} is not found in the queue, this method will report success without sending
+ * a request to the receiver.
+ *
+ * @param itemId The ID of the item to which to jump.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueJumpToItem(int itemId, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ checkConnectivity();
+ if (itemId == MediaQueueItem.INVALID_ITEM_ID) {
+ throw new IllegalArgumentException("itemId is not valid");
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to jump in a queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueJumpToItem(mApiClient, itemId, customData).setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_JUMP,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Removes a list of items from the queue. If the remaining queue is empty, the media session
+ * will be terminated.
+ *
+ * @param itemIdsToRemove The list of media item IDs to remove. Must not be {@code null} or
+ * empty.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueRemoveItems(final int[] itemIdsToRemove, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ Log.d(TAG, "queueRemoveItems");
+ checkConnectivity();
+ if (itemIdsToRemove == null || itemIdsToRemove.length == 0) {
+ throw new IllegalArgumentException("itemIds cannot be empty or null");
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to remove items from queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueRemoveItems(mApiClient, itemIdsToRemove, customData).setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REMOVE_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Removes the item with {@code itemId} from the queue.
+ * <p>
+ * If {@code itemId} is not found in the queue, this method will silently return without sending
+ * a request to the receiver. A {@code itemId} may not be in the queue because it wasn't
+ * originally in the queue, or it was removed by another sender.
+ *
+ * @param itemId The ID of the item to be removed.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueRemoveItem(final int itemId, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ Log.d(TAG, "queueRemoveItem");
+ checkConnectivity();
+ if (itemId == MediaQueueItem.INVALID_ITEM_ID) {
+ throw new IllegalArgumentException("itemId is invalid");
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to remove an item from queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueRemoveItem(mApiClient, itemId, customData).setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REMOVE_ITEM,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Reorder a list of media items in the queue.
+ *
+ * @param itemIdsToReorder The list of media item IDs to reorder, in the new order. Any other
+ * items currently in the queue will maintain their existing order. The
+ * list will be inserted just before the item specified by
+ * {@code insertBeforeItemId}, or at the end of the queue if
+ * {@code insertBeforeItemId} is {@link MediaQueueItem#INVALID_ITEM_ID}.
+ * <p>
+ * For example:
+ * <p>
+ * If insertBeforeItemId is not specified <br>
+ * Existing queue: "A","D","G","H","B","E" <br>
+ * itemIds: "D","H","B" <br>
+ * New Order: "A","G","E","D","H","B" <br>
+ * <p>
+ * If insertBeforeItemId is "A" <br>
+ * Existing queue: "A","D","G","H","B" <br>
+ * itemIds: "D","H","B" <br>
+ * New Order: "D","H","B","A","G","E" <br>
+ * <p>
+ * If insertBeforeItemId is "G" <br>
+ * Existing queue: "A","D","G","H","B" <br>
+ * itemIds: "D","H","B" <br>
+ * New Order: "A","D","H","B","G","E" <br>
+ * <p>
+ * If any of the items does not exist it will be ignored.
+ * Must not be {@code null} or empty.
+ * @param insertBeforeItemId ID of the item that will be located immediately after the reordered
+ * list. If set to {@link MediaQueueItem#INVALID_ITEM_ID}, the
+ * reordered list will be appended at the end of the queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueReorderItems(final int[] itemIdsToReorder, final int insertBeforeItemId,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ Log.d(TAG, "queueReorderItems");
+ checkConnectivity();
+ if (itemIdsToReorder == null || itemIdsToReorder.length == 0) {
+ throw new IllegalArgumentException("itemIdsToReorder cannot be empty or null");
+ }
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to reorder items in a queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueReorderItems(mApiClient, itemIdsToReorder, insertBeforeItemId, customData)
+ .setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REORDER,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Moves the item with {@code itemId} to a new position in the queue.
+ * <p>
+ * If {@code itemId} is not found in the queue, either because it wasn't there originally or it
+ * was removed by another sender before calling this function, this function will silently
+ * return without sending a request to the receiver.
+ *
+ * @param itemId The ID of the item to be moved.
+ * @param newIndex The new index of the item. If the value is negative, an error will be
+ * returned. If the value is out of bounds, or becomes out of bounds because the
+ * queue was shortened by another sender while this request is in progress, the
+ * item will be moved to the end of the queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueMoveItemToNewIndex(int itemId, int newIndex, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueMoveItemToNewIndex");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to mote item to new index with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueMoveItemToNewIndex(mApiClient, itemId, newIndex, customData)
+ .setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_MOVE,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Appends a new media item to the end of the queue.
+ *
+ * @param item The item to append. Must not be {@code null}.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueAppendItem(MediaQueueItem item, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueAppendItem");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to append item with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueAppendItem(mApiClient, item, customData)
+ .setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_APPEND,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Jumps to the next item in the queue.
+ *
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueNext(final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueNext");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueNext(mApiClient, customData).setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_NEXT,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Jumps to the previous item in the queue.
+ *
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queuePrev(final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queuePrev");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queuePrev(mApiClient, customData).setResultCallback(
+ result -> {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_PREV,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Inserts an item in the queue and starts the playback of that newly inserted item. It is
+ * assumed that we are inserting before the "current item"
+ *
+ * @param item The item to be inserted
+ * @param insertBeforeItemId ID of the item that will be located immediately after the inserted
+ * and is assumed to be the "current item"
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueInsertBeforeCurrentAndPlay(MediaQueueItem item, int insertBeforeItemId,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueInsertBeforeCurrentAndPlay");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to insert into queue with no active media session");
+ throw new NoConnectionException();
+ }
+ if (item == null || insertBeforeItemId == MediaQueueItem.INVALID_ITEM_ID) {
+ throw new IllegalArgumentException(
+ "item cannot be empty or insertBeforeItemId cannot be invalid");
+ }
+ remoteMediaPlayer.queueInsertItems(mApiClient, new MediaQueueItem[]{item},
+ insertBeforeItemId, customData).setResultCallback(
+ result -> {
+ if (result.getStatus().isSuccess()) {
+
+ try {
+ queuePrev(customData);
+ } catch (TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ Log.e(TAG, "queuePrev() Failed to skip to previous", e);
+ }
+ }
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_INSERT_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Sets the repeat mode of the queue.
+ *
+ * @param repeatMode The repeat playback mode for the queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueSetRepeatMode(final int repeatMode, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "queueSetRepeatMode");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer
+ .queueSetRepeatMode(mApiClient, repeatMode, customData).setResultCallback(
+ result -> {
+ if (!result.getStatus().isSuccess()) {
+ Log.d(TAG, "Failed with status: " + result.getStatus());
+ }
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_SET_REPEAT,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Plays the loaded media.
+ *
+ * @param position Where to start the playback. Units is milliseconds.
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void play(int position) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ Log.d(TAG, "attempting to play media at position " + position + " seconds");
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to play a video with no active media session");
+ throw new NoConnectionException();
+ }
+ seekAndPlay(position);
+ }
+
+ /**
+ * Resumes the playback from where it was left (can be the beginning).
+ *
+ * @param customData Optional {@link JSONObject} data to be passed to the cast device
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void play(JSONObject customData) throws
+ TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "play(customData)");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to play a video with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer.play(mApiClient, customData)
+ .setResultCallback(result -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_to_play,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Resumes the playback from where it was left (can be the beginning).
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void play() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ play(null);
+ }
+
+ /**
+ * Stops the playback of media/stream
+ *
+ * @param customData Optional {@link JSONObject}
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void stop(JSONObject customData) throws
+ TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "stop()");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to stop a stream with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer.stop(mApiClient, customData).setResultCallback(
+ result -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_to_stop,
+ result.getStatus().getStatusCode());
+ }
+ }
+ );
+ }
+
+ /**
+ * Stops the playback of media/stream
+ *
+ * @throws CastException
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void stop() throws CastException,
+ TransientNetworkDisconnectionException, NoConnectionException {
+ stop(null);
+ }
+
+ /**
+ * Pauses the playback.
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void pause() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ pause(null);
+ }
+
+ /**
+ * Pauses the playback.
+ *
+ * @param customData Optional {@link JSONObject} data to be passed to the cast device
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void pause(JSONObject customData) throws
+ TransientNetworkDisconnectionException, NoConnectionException {
+ Log.d(TAG, "attempting to pause media");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to pause a video with no active media session");
+ throw new NoConnectionException();
+ }
+ remoteMediaPlayer.pause(mApiClient, customData)
+ .setResultCallback(result -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_to_pause,
+ result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Seeks to the given point without changing the state of the player, i.e. after seek is
+ * completed, it resumes what it was doing before the start of seek.
+ *
+ * @param position in milliseconds
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void seek(int position) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ Log.d(TAG, "attempting to seek media");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to seek a video with no active media session");
+ throw new NoConnectionException();
+ }
+ Log.d(TAG, "remoteMediaPlayer.seek() to position " + position);
+ remoteMediaPlayer.seek(mApiClient,
+ position,
+ RESUME_STATE_UNCHANGED).
+ setResultCallback(result -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_seek, result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Fast forwards the media by the given amount. If {@code lengthInMillis} is negative, it
+ * rewinds the media.
+ *
+ * @param lengthInMillis The amount to fast forward the media, given in milliseconds
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void forward(int lengthInMillis) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ Log.d(TAG, "forward(): attempting to forward media by " + lengthInMillis);
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to seek a video with no active media session");
+ throw new NoConnectionException();
+ }
+ long position = remoteMediaPlayer.getApproximateStreamPosition() + lengthInMillis;
+ seek((int) position);
+ }
+
+ /**
+ * Seeks to the given point and starts playback regardless of the starting state.
+ *
+ * @param position in milliseconds
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void seekAndPlay(int position) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ Log.d(TAG, "attempting to seek media");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ Log.e(TAG, "Trying to seekAndPlay a video with no active media session");
+ throw new NoConnectionException();
+ }
+ Log.d(TAG, "remoteMediaPlayer.seek() to position " + position + "and play");
+ remoteMediaPlayer.seek(mApiClient, position, RESUME_STATE_PLAY)
+ .setResultCallback(result -> {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.cast_failed_seek, result.getStatus().getStatusCode());
+ }
+ });
+ }
+
+ /**
+ * Toggles the playback of the media.
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void togglePlayback() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ boolean isPlaying = isRemoteMediaPlaying();
+ if (isPlaying) {
+ pause();
+ } else {
+ if (state == MediaStatus.PLAYER_STATE_IDLE
+ && idleReason == MediaStatus.IDLE_REASON_FINISHED) {
+ loadMedia(getRemoteMediaInformation(), true, 0);
+ } else {
+ play();
+ }
+ }
+ }
+
+ private void attachMediaChannel() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ Log.d(TAG, "attachMediaChannel()");
+ checkConnectivity();
+ if (remoteMediaPlayer == null) {
+ remoteMediaPlayer = new RemoteMediaPlayer();
+
+ remoteMediaPlayer.setOnStatusUpdatedListener(
+ () -> {
+ Log.d(TAG, "RemoteMediaPlayer::onStatusUpdated() is reached");
+ CastManager.this.onRemoteMediaPlayerStatusUpdated();
+ }
+ );
+
+ remoteMediaPlayer.setOnPreloadStatusUpdatedListener(
+ () -> {
+ Log.d(TAG, "RemoteMediaPlayer::onPreloadStatusUpdated() is reached");
+ CastManager.this.onRemoteMediaPreloadStatusUpdated();
+ });
+
+
+ remoteMediaPlayer.setOnMetadataUpdatedListener(
+ () -> {
+ Log.d(TAG, "RemoteMediaPlayer::onMetadataUpdated() is reached");
+ CastManager.this.onRemoteMediaPlayerMetadataUpdated();
+ }
+ );
+
+ remoteMediaPlayer.setOnQueueStatusUpdatedListener(
+ () -> {
+ Log.d(TAG, "RemoteMediaPlayer::onQueueStatusUpdated() is reached");
+ mediaStatus = remoteMediaPlayer.getMediaStatus();
+ if (mediaStatus != null
+ && mediaStatus.getQueueItems() != null) {
+ List<MediaQueueItem> queueItems = mediaStatus
+ .getQueueItems();
+ int itemId = mediaStatus.getCurrentItemId();
+ MediaQueueItem item = mediaStatus
+ .getQueueItemById(itemId);
+ int repeatMode = mediaStatus.getQueueRepeatMode();
+ onQueueUpdated(queueItems, item, repeatMode, false);
+ } else {
+ onQueueUpdated(null, null,
+ MediaStatus.REPEAT_MODE_REPEAT_OFF, false);
+ }
+ });
+
+ }
+ try {
+ Log.d(TAG, "Registering MediaChannel namespace");
+ Cast.CastApi.setMessageReceivedCallbacks(mApiClient, remoteMediaPlayer.getNamespace(),
+ remoteMediaPlayer);
+ } catch (IOException | IllegalStateException e) {
+ Log.e(TAG, "attachMediaChannel()", e);
+ }
+ }
+
+ private void reattachMediaChannel() {
+ if (remoteMediaPlayer != null && mApiClient != null) {
+ try {
+ Log.d(TAG, "Registering MediaChannel namespace");
+ Cast.CastApi.setMessageReceivedCallbacks(mApiClient,
+ remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
+ } catch (IOException | IllegalStateException e) {
+ Log.e(TAG, "reattachMediaChannel()", e);
+ }
+ }
+ }
+
+ private void detachMediaChannel() {
+ Log.d(TAG, "trying to detach media channel");
+ if (remoteMediaPlayer != null) {
+ try {
+ Cast.CastApi.removeMessageReceivedCallbacks(mApiClient,
+ remoteMediaPlayer.getNamespace());
+ } catch (IOException | IllegalStateException e) {
+ Log.e(TAG, "detachMediaChannel()", e);
+ }
+ remoteMediaPlayer = null;
+ }
+ }
+
+ /**
+ * Returns the playback status of the remote device.
+ *
+ * @return Returns one of the values
+ * <ul>
+ * <li> <code>MediaStatus.PLAYER_STATE_UNKNOWN</code></li>
+ * <li> <code>MediaStatus.PLAYER_STATE_IDLE</code></li>
+ * <li> <code>MediaStatus.PLAYER_STATE_PLAYING</code></li>
+ * <li> <code>MediaStatus.PLAYER_STATE_PAUSED</code></li>
+ * <li> <code>MediaStatus.PLAYER_STATE_BUFFERING</code></li>
+ * </ul>
+ */
+ public int getPlaybackStatus() {
+ return state;
+ }
+
+ /**
+ * Returns the latest retrieved value for the {@link MediaStatus}. This value is updated
+ * whenever the onStatusUpdated callback is called.
+ */
+ public final MediaStatus getMediaStatus() {
+ return mediaStatus;
+ }
+
+ /**
+ * Returns the Idle reason, defined in <code>MediaStatus.IDLE_*</code>. Note that the returned
+ * value is only meaningful if the status is truly <code>MediaStatus.PLAYER_STATE_IDLE
+ * </code>
+ *
+ * <p>Possible values are:
+ * <ul>
+ * <li>IDLE_REASON_NONE</li>
+ * <li>IDLE_REASON_FINISHED</li>
+ * <li>IDLE_REASON_CANCELED</li>
+ * <li>IDLE_REASON_INTERRUPTED</li>
+ * <li>IDLE_REASON_ERROR</li>
+ * </ul>
+ */
+ public int getIdleReason() {
+ return idleReason;
+ }
+
+ private void onMessageSendFailed(int errorCode) {
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onDataMessageSendFailed(errorCode);
+ }
+ }
+
+ /*
+ * This is called by onStatusUpdated() of the RemoteMediaPlayer
+ */
+ private void onRemoteMediaPlayerStatusUpdated() {
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated() reached");
+ if (mApiClient == null || remoteMediaPlayer == null) {
+ Log.d(TAG, "mApiClient or remoteMediaPlayer is null, so will not proceed");
+ return;
+ }
+ mediaStatus = remoteMediaPlayer.getMediaStatus();
+ if (mediaStatus == null) {
+ Log.d(TAG, "MediaStatus is null, so will not proceed");
+ return;
+ } else {
+ List<MediaQueueItem> queueItems = mediaStatus.getQueueItems();
+ if (queueItems != null) {
+ int itemId = mediaStatus.getCurrentItemId();
+ MediaQueueItem item = mediaStatus.getQueueItemById(itemId);
+ int repeatMode = mediaStatus.getQueueRepeatMode();
+ onQueueUpdated(queueItems, item, repeatMode, false);
+ } else {
+ onQueueUpdated(null, null, MediaStatus.REPEAT_MODE_REPEAT_OFF, false);
+ }
+ state = mediaStatus.getPlayerState();
+ idleReason = mediaStatus.getIdleReason();
+
+ if (state == MediaStatus.PLAYER_STATE_PLAYING) {
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = playing");
+ } else if (state == MediaStatus.PLAYER_STATE_PAUSED) {
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = paused");
+ } else if (state == MediaStatus.PLAYER_STATE_IDLE) {
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = IDLE with reason: "
+ + idleReason);
+ if (idleReason == MediaStatus.IDLE_REASON_ERROR) {
+ // something bad happened on the cast device
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): IDLE reason = ERROR");
+ onFailed(R.string.cast_failed_receiver_player_error, NO_STATUS_CODE);
+ }
+ } else if (state == MediaStatus.PLAYER_STATE_BUFFERING) {
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = buffering");
+ } else {
+ Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = unknown");
+ }
+ }
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onRemoteMediaPlayerStatusUpdated();
+ }
+ if (mediaStatus != null) {
+ double volume = mediaStatus.getStreamVolume();
+ boolean isMute = mediaStatus.isMute();
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onStreamVolumeChanged(volume, isMute);
+ }
+ }
+ }
+
+ private void onRemoteMediaPreloadStatusUpdated() {
+ MediaQueueItem item = null;
+ mediaStatus = remoteMediaPlayer.getMediaStatus();
+ if (mediaStatus != null) {
+ item = mediaStatus.getQueueItemById(mediaStatus.getPreloadedItemId());
+ }
+ preLoadingItem = item;
+ Log.d(TAG, "onRemoteMediaPreloadStatusUpdated() " + item);
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onRemoteMediaPreloadStatusUpdated(item);
+ }
+ }
+
+ public MediaQueueItem getPreLoadingItem() {
+ return preLoadingItem;
+ }
+
+ /*
+ * This is called by onQueueStatusUpdated() of RemoteMediaPlayer
+ */
+ private void onQueueUpdated(List<MediaQueueItem> queueItems, MediaQueueItem item,
+ int repeatMode, boolean shuffle) {
+ Log.d(TAG, "onQueueUpdated() reached");
+ Log.d(TAG, String.format("Queue Items size: %d, Item: %s, Repeat Mode: %d, Shuffle: %s",
+ queueItems == null ? 0 : queueItems.size(), item, repeatMode, shuffle));
+ if (queueItems != null) {
+ mediaQueue = new MediaQueue(new CopyOnWriteArrayList<>(queueItems), item, shuffle,
+ repeatMode);
+ } else {
+ mediaQueue = new MediaQueue(new CopyOnWriteArrayList<>(), null, false,
+ MediaStatus.REPEAT_MODE_REPEAT_OFF);
+ }
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onMediaQueueUpdated(queueItems, item, repeatMode, shuffle);
+ }
+ }
+
+ /*
+ * This is called by onMetadataUpdated() of RemoteMediaPlayer
+ */
+ public void onRemoteMediaPlayerMetadataUpdated() {
+ Log.d(TAG, "onRemoteMediaPlayerMetadataUpdated() reached");
+ for (CastConsumer consumer : castConsumers) {
+ consumer.onRemoteMediaPlayerMetadataUpdated();
+ }
+ }
+
+ /**
+ * Registers a {@link CastConsumer} interface with this class.
+ * Registered listeners will be notified of changes to a variety of
+ * lifecycle and media status changes through the callbacks that the interface provides.
+ *
+ * @see DefaultCastConsumer
+ */
+ public synchronized void addCastConsumer(CastConsumer listener) {
+ if (listener != null) {
+ addBaseCastConsumer(listener);
+ castConsumers.add(listener);
+ Log.d(TAG, "Successfully added the new CastConsumer listener " + listener);
+ }
+ }
+
+ /**
+ * Unregisters a {@link CastConsumer}.
+ */
+ public synchronized void removeCastConsumer(CastConsumer listener) {
+ if (listener != null) {
+ removeBaseCastConsumer(listener);
+ castConsumers.remove(listener);
+ }
+ }
+
+ @Override
+ protected void onDeviceUnselected() {
+ detachMediaChannel();
+ //removeDataChannel();
+ state = MediaStatus.PLAYER_STATE_IDLE;
+ mediaStatus = null;
+ }
+
+ @Override
+ protected Cast.CastOptions.Builder getCastOptionBuilder(CastDevice device) {
+ Cast.CastOptions.Builder builder = new Cast.CastOptions.Builder(mSelectedCastDevice, new CastListener());
+ if (isFeatureEnabled(CastConfiguration.FEATURE_DEBUGGING)) {
+ builder.setVerboseLoggingEnabled(true);
+ }
+ return builder;
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ super.onConnectionFailed(result);
+ state = MediaStatus.PLAYER_STATE_IDLE;
+ mediaStatus = null;
+ }
+
+ @Override
+ public void onDisconnected(boolean stopAppOnExit, boolean clearPersistedConnectionData,
+ boolean setDefaultRoute) {
+ super.onDisconnected(stopAppOnExit, clearPersistedConnectionData, setDefaultRoute);
+ state = MediaStatus.PLAYER_STATE_IDLE;
+ mediaStatus = null;
+ mediaQueue = null;
+ }
+
+ class CastListener extends Cast.Listener {
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.android.gms.cast.Cast.Listener#onApplicationDisconnected (int)
+ */
+ @Override
+ public void onApplicationDisconnected(int statusCode) {
+ CastManager.this.onApplicationDisconnected(statusCode);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.android.gms.cast.Cast.Listener#onApplicationStatusChanged ()
+ */
+ @Override
+ public void onApplicationStatusChanged() {
+ CastManager.this.onApplicationStatusChanged();
+ }
+
+ @Override
+ public void onVolumeChanged() {
+ CastManager.this.onDeviceVolumeChanged();
+ }
+ }
+
+ @Override
+ public void onFailed(int resourceId, int statusCode) {
+ Log.d(TAG, "onFailed: " + mContext.getString(resourceId) + ", code: " + statusCode);
+ super.onFailed(resourceId, statusCode);
+ }
+
+ /**
+ * Clients can call this method to delegate handling of the volume. Clients should override
+ * {@code dispatchEvent} and call this method:
+ * <pre>
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mCastManager.onDispatchVolumeKeyEvent(event, VOLUME_DELTA)) {
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+ * </pre>
+ * @param event The dispatched event.
+ * @param volumeDelta The amount by which volume should be increased or decreased in each step
+ * @return <code>true</code> if volume is handled by the library, <code>false</code> otherwise.
+ */
+ public boolean onDispatchVolumeKeyEvent(KeyEvent event, double volumeDelta) {
+ if (isConnected()) {
+ boolean isKeyDown = event.getAction() == KeyEvent.ACTION_DOWN;
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ return changeVolume(volumeDelta, isKeyDown);
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ return changeVolume(-volumeDelta, isKeyDown);
+ }
+ }
+ return false;
+ }
+
+ private boolean changeVolume(double volumeIncrement, boolean isKeyDown) {
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ && getPlaybackStatus() == MediaStatus.PLAYER_STATE_PLAYING
+ && isFeatureEnabled(CastConfiguration.FEATURE_LOCKSCREEN)) {
+ return false;
+ }
+
+ if (isKeyDown) {
+ try {
+ adjustDeviceVolume(volumeIncrement);
+ } catch (CastException | TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ Log.e(TAG, "Failed to change volume", e);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets the volume step, i.e. the fraction by which volume will increase or decrease each time
+ * user presses the hard volume buttons on the device.
+ *
+ * @param volumeStep Should be a double between 0 and 1, inclusive.
+ */
+ public CastManager setVolumeStep(double volumeStep) {
+ if ((volumeStep > 1) || (volumeStep < 0)) {
+ throw new IllegalArgumentException("Volume Step should be between 0 and 1, inclusive");
+ }
+ this.volumeStep = volumeStep;
+ return this;
+ }
+
+ /**
+ * Returns the volume step. The default value is {@code DEFAULT_VOLUME_STEP}.
+ */
+ public double getVolumeStep() {
+ return volumeStep;
+ }
+
+ public final MediaQueue getMediaQueue() {
+ return mediaQueue;
+ }
+
+ /**
+ * Checks whether the selected Cast Device has the specified audio or video capabilities.
+ *
+ * @param capability capability from:
+ * <ul>
+ * <li>{@link CastDevice#CAPABILITY_AUDIO_IN}</li>
+ * <li>{@link CastDevice#CAPABILITY_AUDIO_OUT}</li>
+ * <li>{@link CastDevice#CAPABILITY_VIDEO_IN}</li>
+ * <li>{@link CastDevice#CAPABILITY_VIDEO_OUT}</li>
+ * </ul>
+ * @param defaultVal value to return whenever there's no device selected.
+ * @return {@code true} if the selected device has the specified capability,
+ * {@code false} otherwise.
+ */
+ public boolean hasCapability(final int capability, final boolean defaultVal) {
+ if (mSelectedCastDevice != null) {
+ return mSelectedCastDevice.hasCapability(capability);
+ } else {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Adds and wires up the Switchable Media Router cast button. It returns a reference to the
+ * {@link SwitchableMediaRouteActionProvider} associated with the button if the caller needs
+ * such reference. It is assumed that the enclosing
+ * {@link android.app.Activity} inherits (directly or indirectly) from
+ * {@link android.support.v7.app.AppCompatActivity}.
+ *
+ * @param menuItem MenuItem of the Media Router cast button.
+ */
+ public final SwitchableMediaRouteActionProvider addMediaRouterButton(MenuItem menuItem) {
+ SwitchableMediaRouteActionProvider mediaRouteActionProvider = (SwitchableMediaRouteActionProvider)
+ MenuItemCompat.getActionProvider(menuItem);
+ mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
+ if (mCastConfiguration.getMediaRouteDialogFactory() != null) {
+ mediaRouteActionProvider.setDialogFactory(mCastConfiguration.getMediaRouteDialogFactory());
+ }
+ return mediaRouteActionProvider;
+ }
+
+ /* (non-Javadoc)
+ * These methods startReconnectionService and stopReconnectionService simply override the ones
+ * from BaseCastManager with empty implementations because we handle the service ourselves, but
+ * need to allow BaseCastManager to save current network information.
+ */
+ @Override
+ protected void startReconnectionService(long mediaDurationLeft) {
+ // Do nothing
+ }
+
+ @Override
+ protected void stopReconnectionService() {
+ // Do nothing
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java b/core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java
new file mode 100644
index 000000000..f0a7214c9
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java
@@ -0,0 +1,317 @@
+package de.danoeh.antennapod.core.cast;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.common.images.WebImage;
+
+import java.util.Calendar;
+import java.util.List;
+
+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.storage.DBReader;
+import de.danoeh.antennapod.core.util.playback.ExternalMedia;
+import de.danoeh.antennapod.core.util.playback.Playable;
+
+/**
+ * Helper functions for Cast support.
+ */
+public class CastUtils {
+ private static final String TAG = "CastUtils";
+
+ public static final String KEY_MEDIA_ID = "de.danoeh.antennapod.core.cast.MediaId";
+
+ public static final String KEY_EPISODE_IDENTIFIER = "de.danoeh.antennapod.core.cast.EpisodeId";
+ public static final String KEY_EPISODE_LINK = "de.danoeh.antennapod.core.cast.EpisodeLink";
+ public static final String KEY_FEED_URL = "de.danoeh.antennapod.core.cast.FeedUrl";
+ public static final String KEY_FEED_WEBSITE = "de.danoeh.antennapod.core.cast.FeedWebsite";
+ public static final String KEY_EPISODE_NOTES = "de.danoeh.antennapod.core.cast.EpisodeNotes";
+ public static final int EPISODE_NOTES_MAX_LENGTH = Integer.MAX_VALUE;
+
+ /**
+ * The field <code>AntennaPod.FormatVersion</code> specifies which version of MediaMetaData
+ * fields we're using. Future implementations should try to be backwards compatible with earlier
+ * versions, and earlier versions should be forward compatible until the version indicated by
+ * <code>MAX_VERSION_FORWARD_COMPATIBILITY</code>. If an update makes the format unreadable for
+ * an earlier version, then its version number should be greater than the
+ * <code>MAX_VERSION_FORWARD_COMPATIBILITY</code> value set on the earlier one, so that it
+ * doesn't try to parse the object.
+ */
+ public static final String KEY_FORMAT_VERSION = "de.danoeh.antennapod.core.cast.FormatVersion";
+ public static final int FORMAT_VERSION_VALUE = 1;
+ public static final int MAX_VERSION_FORWARD_COMPATIBILITY = 9999;
+
+ public static boolean isCastable(Playable media){
+ if (media == null || media instanceof ExternalMedia) {
+ return false;
+ }
+ if (media instanceof FeedMedia || media instanceof RemoteMedia){
+ String url = media.getStreamUrl();
+ if(url == null || url.isEmpty()){
+ return false;
+ }
+ switch (media.getMediaType()) {
+ case UNKNOWN:
+ return false;
+ case AUDIO:
+ return CastManager.getInstance().hasCapability(CastDevice.CAPABILITY_AUDIO_OUT, true);
+ case VIDEO:
+ return CastManager.getInstance().hasCapability(CastDevice.CAPABILITY_VIDEO_OUT, true);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Converts {@link FeedMedia} objects into a format suitable for sending to a Cast Device.
+ * Before using this method, one should make sure {@link #isCastable(Playable)} returns
+ * {@code true}.
+ *
+ * Unless media.{@link FeedMedia#loadMetadata() loadMetadata()} has already been called,
+ * this method should not run on the main thread.
+ *
+ * @param media The {@link FeedMedia} object to be converted.
+ * @return {@link MediaInfo} object in a format proper for casting.
+ */
+ public static MediaInfo convertFromFeedMedia(FeedMedia media){
+ if(media == null) {
+ return null;
+ }
+ MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC);
+ try{
+ media.loadMetadata();
+ } catch (Playable.PlayableException e) {
+ Log.e(TAG, "Unable to load FeedMedia metadata", e);
+ }
+ FeedItem feedItem = media.getItem();
+ if (feedItem != null) {
+ metadata.putString(MediaMetadata.KEY_TITLE, media.getEpisodeTitle());
+ String subtitle = media.getFeedTitle();
+ if (subtitle != null) {
+ metadata.putString(MediaMetadata.KEY_SUBTITLE, subtitle);
+ }
+ FeedImage image = feedItem.getImage();
+ if (image != null && !TextUtils.isEmpty(image.getDownload_url())) {
+ metadata.addImage(new WebImage(Uri.parse(image.getDownload_url())));
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(media.getItem().getPubDate());
+ metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar);
+ Feed feed = feedItem.getFeed();
+ if (feed != null) {
+ if (!TextUtils.isEmpty(feed.getAuthor())) {
+ metadata.putString(MediaMetadata.KEY_ARTIST, feed.getAuthor());
+ }
+ if (!TextUtils.isEmpty(feed.getDownload_url())) {
+ metadata.putString(KEY_FEED_URL, feed.getDownload_url());
+ }
+ if (!TextUtils.isEmpty(feed.getLink())) {
+ metadata.putString(KEY_FEED_WEBSITE, feed.getLink());
+ }
+ }
+ if (!TextUtils.isEmpty(feedItem.getItemIdentifier())) {
+ metadata.putString(KEY_EPISODE_IDENTIFIER, feedItem.getItemIdentifier());
+ } else {
+ metadata.putString(KEY_EPISODE_IDENTIFIER, media.getStreamUrl());
+ }
+ if (!TextUtils.isEmpty(feedItem.getLink())) {
+ metadata.putString(KEY_EPISODE_LINK, feedItem.getLink());
+ }
+ }
+ String notes = null;
+ try {
+ notes = media.loadShownotes().call();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to load FeedMedia notes", e);
+ }
+ if (notes != null) {
+ if (notes.length() > EPISODE_NOTES_MAX_LENGTH) {
+ notes = notes.substring(0, EPISODE_NOTES_MAX_LENGTH);
+ }
+ metadata.putString(KEY_EPISODE_NOTES, notes);
+ }
+ // This field only identifies the id on the device that has the original version.
+ // Idea is to perhaps, on a first approach, check if the version on the local DB with the
+ // same id matches the remote object, and if not then search for episode and feed identifiers.
+ // This at least should make media recognition for a single device much quicker.
+ metadata.putInt(KEY_MEDIA_ID, ((Long) media.getIdentifier()).intValue());
+ // A way to identify different casting media formats in case we change it in the future and
+ // senders with different versions share a casting device.
+ metadata.putInt(KEY_FORMAT_VERSION, FORMAT_VERSION_VALUE);
+
+ MediaInfo.Builder builder = new MediaInfo.Builder(media.getStreamUrl())
+ .setContentType(media.getMime_type())
+ .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
+ .setMetadata(metadata);
+ if (media.getDuration() > 0) {
+ builder.setStreamDuration(media.getDuration());
+ }
+ return builder.build();
+ }
+
+ //TODO make unit tests for all the conversion methods
+ /**
+ * Converts {@link MediaInfo} objects into the appropriate implementation of {@link Playable}.
+ *
+ * Unless <code>searchFeedMedia</code> is set to <code>false</code>, this method should not run
+ * on the GUI thread.
+ *
+ * @param media The {@link MediaInfo} object to be converted.
+ * @param searchFeedMedia If set to <code>true</code>, the database will be queried to find a
+ * {@link FeedMedia} instance that matches {@param media}.
+ * @return {@link Playable} object in a format proper for casting.
+ */
+ public static Playable getPlayable(MediaInfo media, boolean searchFeedMedia) {
+ Log.d(TAG, "getPlayable called with searchFeedMedia=" + searchFeedMedia);
+ if (media == null) {
+ Log.d(TAG, "MediaInfo object provided is null, not converting to any Playable instance");
+ return null;
+ }
+ MediaMetadata metadata = media.getMetadata();
+ int version = metadata.getInt(KEY_FORMAT_VERSION);
+ if (version <= 0 || version > MAX_VERSION_FORWARD_COMPATIBILITY) {
+ Log.w(TAG, "MediaInfo object obtained from the cast device is not compatible with this" +
+ "version of AntennaPod CastUtils, curVer=" + FORMAT_VERSION_VALUE +
+ ", object version=" + version);
+ return null;
+ }
+ Playable result = null;
+ if (searchFeedMedia) {
+ long mediaId = metadata.getInt(KEY_MEDIA_ID);
+ if (mediaId > 0) {
+ FeedMedia fMedia = DBReader.getFeedMedia(mediaId);
+ if (fMedia != null) {
+ try {
+ fMedia.loadMetadata();
+ if (matches(media, fMedia)) {
+ result = fMedia;
+ Log.d(TAG, "FeedMedia object obtained matches the MediaInfo provided. id=" + mediaId);
+ } else {
+ Log.d(TAG, "FeedMedia object obtained does NOT match the MediaInfo provided. id=" + mediaId);
+ }
+ } catch (Playable.PlayableException e) {
+ Log.e(TAG, "Unable to load FeedMedia metadata to compare with MediaInfo", e);
+ }
+ } else {
+ Log.d(TAG, "Unable to find in database a FeedMedia with id=" + mediaId);
+ }
+ }
+ if (result == null) {
+ FeedItem feedItem = DBReader.getFeedItem(metadata.getString(KEY_FEED_URL),
+ metadata.getString(KEY_EPISODE_IDENTIFIER));
+ if (feedItem != null) {
+ result = feedItem.getMedia();
+ Log.d(TAG, "Found episode that matches the MediaInfo provided. Using its media, if existing.");
+ }
+ }
+ }
+ if (result == null) {
+ List<WebImage> imageList = metadata.getImages();
+ String imageUrl = null;
+ if (!imageList.isEmpty()) {
+ imageUrl = imageList.get(0).getUrl().toString();
+ }
+ result = new RemoteMedia(media.getContentId(),
+ metadata.getString(KEY_EPISODE_IDENTIFIER),
+ metadata.getString(KEY_FEED_URL),
+ metadata.getString(MediaMetadata.KEY_SUBTITLE),
+ metadata.getString(MediaMetadata.KEY_TITLE),
+ metadata.getString(KEY_EPISODE_LINK),
+ metadata.getString(MediaMetadata.KEY_ARTIST),
+ imageUrl,
+ metadata.getString(KEY_FEED_WEBSITE),
+ media.getContentType(),
+ metadata.getDate(MediaMetadata.KEY_RELEASE_DATE).getTime());
+ String notes = metadata.getString(KEY_EPISODE_NOTES);
+ if (!TextUtils.isEmpty(notes)) {
+ ((RemoteMedia) result).setNotes(notes);
+ }
+ Log.d(TAG, "Converted MediaInfo into RemoteMedia");
+ }
+ if (result.getDuration() == 0 && media.getStreamDuration() > 0) {
+ result.setDuration((int) media.getStreamDuration());
+ }
+ return result;
+ }
+
+ /**
+ * Compares a {@link MediaInfo} instance with a {@link FeedMedia} one and evaluates whether they
+ * represent the same podcast episode.
+ *
+ * @param info the {@link MediaInfo} object to be compared.
+ * @param media the {@link FeedMedia} object to be compared.
+ * @return <true>true</true> if there's a match, <code>false</code> otherwise.
+ *
+ * @see RemoteMedia#equals(Object)
+ */
+ public static boolean matches(MediaInfo info, FeedMedia media) {
+ if (info == null || media == null) {
+ return false;
+ }
+ if (!TextUtils.equals(info.getContentId(), media.getStreamUrl())) {
+ return false;
+ }
+ MediaMetadata metadata = info.getMetadata();
+ FeedItem fi = media.getItem();
+ if (fi == null || metadata == null ||
+ !TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), fi.getItemIdentifier())) {
+ return false;
+ }
+ Feed feed = fi.getFeed();
+ return feed != null && TextUtils.equals(metadata.getString(KEY_FEED_URL), feed.getDownload_url());
+ }
+
+ /**
+ * Compares a {@link MediaInfo} instance with a {@link RemoteMedia} one and evaluates whether they
+ * represent the same podcast episode.
+ *
+ * @param info the {@link MediaInfo} object to be compared.
+ * @param media the {@link RemoteMedia} object to be compared.
+ * @return <true>true</true> if there's a match, <code>false</code> otherwise.
+ *
+ * @see RemoteMedia#equals(Object)
+ */
+ public static boolean matches(MediaInfo info, RemoteMedia media) {
+ if (info == null || media == null) {
+ return false;
+ }
+ if (!TextUtils.equals(info.getContentId(), media.getStreamUrl())) {
+ return false;
+ }
+ MediaMetadata metadata = info.getMetadata();
+ return metadata != null &&
+ TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), media.getEpisodeIdentifier()) &&
+ TextUtils.equals(metadata.getString(KEY_FEED_URL), media.getFeedUrl());
+ }
+
+ /**
+ * Compares a {@link MediaInfo} instance with a {@link Playable} and evaluates whether they
+ * represent the same podcast episode. Useful every time we get a MediaInfo from the Cast Device
+ * and want to avoid unnecessary conversions.
+ *
+ * @param info the {@link MediaInfo} object to be compared.
+ * @param media the {@link Playable} object to be compared.
+ * @return <true>true</true> if there's a match, <code>false</code> otherwise.
+ *
+ * @see RemoteMedia#equals(Object)
+ */
+ public static boolean matches(MediaInfo info, Playable media) {
+ if (info == null || media == null) {
+ return false;
+ }
+ if (media instanceof RemoteMedia) {
+ return matches(info, (RemoteMedia) media);
+ }
+ return media instanceof FeedMedia && matches(info, (FeedMedia) media);
+ }
+
+
+ //TODO Queue handling perhaps
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java b/core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java
new file mode 100644
index 000000000..fe4183d54
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java
@@ -0,0 +1,10 @@
+package de.danoeh.antennapod.core.cast;
+
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
+
+public class DefaultCastConsumer extends VideoCastConsumerImpl implements CastConsumer {
+ @Override
+ public void onStreamVolumeChanged(double value, boolean isMute) {
+ // no-op
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java b/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java
new file mode 100644
index 000000000..99f7b9496
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java
@@ -0,0 +1,347 @@
+package de.danoeh.antennapod.core.cast;
+
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.common.images.WebImage;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.Feed;
+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.storage.DBReader;
+import de.danoeh.antennapod.core.util.ChapterUtils;
+import de.danoeh.antennapod.core.util.playback.Playable;
+
+/**
+ * Playable implementation for media on a Cast Device for which a local version of
+ * {@link de.danoeh.antennapod.core.feed.FeedMedia} hasn't been found.
+ */
+public class RemoteMedia implements Playable {
+ public static final String TAG = "RemoteMedia";
+
+ public static final int PLAYABLE_TYPE_REMOTE_MEDIA = 3;
+
+ private String downloadUrl;
+ private String itemIdentifier;
+ private String feedUrl;
+ private String feedTitle;
+ private String episodeTitle;
+ private String episodeLink;
+ private String feedAuthor;
+ private String imageUrl;
+ private String feedLink;
+ private String mime_type;
+ private Date pubDate;
+ private String notes;
+ private List<Chapter> chapters;
+ private int duration;
+ private int position;
+ private long lastPlayedTime;
+
+ public RemoteMedia(String downloadUrl, String itemId, String feedUrl, String feedTitle,
+ String episodeTitle, String episodeLink, String feedAuthor,
+ String imageUrl, String feedLink, String mime_type, Date pubDate) {
+ this.downloadUrl = downloadUrl;
+ this.itemIdentifier = itemId;
+ this.feedUrl = feedUrl;
+ this.feedTitle = feedTitle;
+ this.episodeTitle = episodeTitle;
+ this.episodeLink = episodeLink;
+ this.feedAuthor = feedAuthor;
+ this.imageUrl = imageUrl;
+ this.feedLink = feedLink;
+ this.mime_type = mime_type;
+ this.pubDate = pubDate;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+ public MediaInfo extractMediaInfo() {
+ MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC);
+
+ metadata.putString(MediaMetadata.KEY_TITLE, episodeTitle);
+ metadata.putString(MediaMetadata.KEY_SUBTITLE, feedTitle);
+ if (!TextUtils.isEmpty(imageUrl)) {
+ metadata.addImage(new WebImage(Uri.parse(imageUrl)));
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(pubDate);
+ metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar);
+ if (!TextUtils.isEmpty(feedAuthor)) {
+ metadata.putString(MediaMetadata.KEY_ARTIST, feedAuthor);
+ }
+ if (!TextUtils.isEmpty(feedUrl)) {
+ metadata.putString(CastUtils.KEY_FEED_URL, feedUrl);
+ }
+ if (!TextUtils.isEmpty(feedLink)) {
+ metadata.putString(CastUtils.KEY_FEED_WEBSITE, feedLink);
+ }
+ if (!TextUtils.isEmpty(itemIdentifier)) {
+ metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, itemIdentifier);
+ } else {
+ metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, downloadUrl);
+ }
+ if (!TextUtils.isEmpty(episodeLink)) {
+ metadata.putString(CastUtils.KEY_EPISODE_LINK, episodeLink);
+ }
+ String notes = this.notes;
+ if (notes != null) {
+ if (notes.length() > CastUtils.EPISODE_NOTES_MAX_LENGTH) {
+ notes = notes.substring(0, CastUtils.EPISODE_NOTES_MAX_LENGTH);
+ }
+ metadata.putString(CastUtils.KEY_EPISODE_NOTES, notes);
+ }
+ // Default id value
+ metadata.putInt(CastUtils.KEY_MEDIA_ID, 0);
+ metadata.putInt(CastUtils.KEY_FORMAT_VERSION, CastUtils.FORMAT_VERSION_VALUE);
+
+ MediaInfo.Builder builder = new MediaInfo.Builder(downloadUrl)
+ .setContentType(mime_type)
+ .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
+ .setMetadata(metadata);
+ if (duration > 0) {
+ builder.setStreamDuration(duration);
+ }
+ return builder.build();
+ }
+
+ public String getEpisodeIdentifier() {
+ return itemIdentifier;
+ }
+
+ public String getFeedUrl() {
+ return feedUrl;
+ }
+
+ public FeedMedia lookForFeedMedia() {
+ FeedItem feedItem = DBReader.getFeedItem(feedUrl, itemIdentifier);
+ if (feedItem == null) {
+ return null;
+ }
+ return feedItem.getMedia();
+ }
+
+ @Override
+ public void writeToPreferences(SharedPreferences.Editor prefEditor) {
+ //it seems pointless to do it, since the session should be kept by the remote device.
+ }
+
+ @Override
+ public void loadMetadata() throws PlayableException {
+ //Already loaded
+ }
+
+ @Override
+ public void loadChapterMarks() {
+ ChapterUtils.loadChaptersFromStreamUrl(this);
+ }
+
+ @Override
+ public String getEpisodeTitle() {
+ return episodeTitle;
+ }
+
+ @Override
+ public List<Chapter> getChapters() {
+ return chapters;
+ }
+
+ @Override
+ public String getWebsiteLink() {
+ if (episodeLink != null) {
+ return episodeLink;
+ } else {
+ return feedUrl;
+ }
+ }
+
+ @Override
+ public String getPaymentLink() {
+ return null;
+ }
+
+ @Override
+ public String getFeedTitle() {
+ return feedTitle;
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return itemIdentifier + "@" + feedUrl;
+ }
+
+ @Override
+ public int getDuration() {
+ return duration;
+ }
+
+ @Override
+ public int getPosition() {
+ return position;
+ }
+
+ @Override
+ public long getLastPlayedTime() {
+ return lastPlayedTime;
+ }
+
+ @Override
+ public MediaType getMediaType() {
+ return MediaType.fromMimeType(mime_type);
+ }
+
+ @Override
+ public String getLocalMediaUrl() {
+ return null;
+ }
+
+ @Override
+ public String getStreamUrl() {
+ return downloadUrl;
+ }
+
+ @Override
+ public boolean localFileAvailable() {
+ return false;
+ }
+
+ @Override
+ public boolean streamAvailable() {
+ return true;
+ }
+
+ @Override
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp) {
+ //we're not saving playback information for this kind of items on preferences
+ setPosition(newPosition);
+ setLastPlayedTime(timestamp);
+ }
+
+ @Override
+ public void setPosition(int newPosition) {
+ position = newPosition;
+ }
+
+ @Override
+ public void setDuration(int newDuration) {
+ duration = newDuration;
+ }
+
+ @Override
+ public void setLastPlayedTime(long lastPlayedTimestamp) {
+ lastPlayedTime = lastPlayedTimestamp;
+ }
+
+ @Override
+ public void onPlaybackStart() {
+ // no-op
+ }
+
+ @Override
+ public void onPlaybackCompleted() {
+ // no-op
+ }
+
+ @Override
+ public int getPlayableType() {
+ return PLAYABLE_TYPE_REMOTE_MEDIA;
+ }
+
+ @Override
+ public void setChapters(List<Chapter> chapters) {
+ this.chapters = chapters;
+ }
+
+ @Override
+ public Uri getImageUri() {
+ if (imageUrl != null) {
+ return Uri.parse(imageUrl);
+ }
+ return null;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public Callable<String> loadShownotes() {
+ return () -> (notes != null) ? notes : "";
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(downloadUrl);
+ dest.writeString(itemIdentifier);
+ dest.writeString(feedUrl);
+ dest.writeString(feedTitle);
+ dest.writeString(episodeTitle);
+ dest.writeString(episodeLink);
+ dest.writeString(feedAuthor);
+ dest.writeString(imageUrl);
+ dest.writeString(feedLink);
+ dest.writeString(mime_type);
+ dest.writeLong(pubDate.getTime());
+ dest.writeString(notes);
+ dest.writeInt(duration);
+ dest.writeInt(position);
+ dest.writeLong(lastPlayedTime);
+ }
+
+ public static final Parcelable.Creator<RemoteMedia> CREATOR = new Parcelable.Creator<RemoteMedia>() {
+ @Override
+ public RemoteMedia createFromParcel(Parcel in) {
+ RemoteMedia result = new RemoteMedia(in.readString(), in.readString(), in.readString(),
+ in.readString(), in.readString(), in.readString(), in.readString(), in.readString(),
+ in.readString(), in.readString(), new Date(in.readLong()));
+ result.setNotes(in.readString());
+ result.setDuration(in.readInt());
+ result.setPosition(in.readInt());
+ result.setLastPlayedTime(in.readLong());
+ return result;
+ }
+
+ @Override
+ public RemoteMedia[] newArray(int size) {
+ return new RemoteMedia[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RemoteMedia) {
+ RemoteMedia rm = (RemoteMedia) other;
+ return TextUtils.equals(downloadUrl, rm.downloadUrl) &&
+ TextUtils.equals(feedUrl, rm.feedUrl) &&
+ TextUtils.equals(itemIdentifier, rm.itemIdentifier);
+ }
+ if (other instanceof FeedMedia) {
+ FeedMedia fm = (FeedMedia) other;
+ if (!TextUtils.equals(downloadUrl, fm.getStreamUrl())) {
+ return false;
+ }
+ FeedItem fi = fm.getItem();
+ if (fi == null || !TextUtils.equals(itemIdentifier, fi.getItemIdentifier())) {
+ return false;
+ }
+ Feed feed = fi.getFeed();
+ return feed != null && TextUtils.equals(feedUrl, feed.getDownload_url());
+ }
+ return false;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java b/core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java
new file mode 100644
index 000000000..f063cf5e3
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java
@@ -0,0 +1,106 @@
+package de.danoeh.antennapod.core.cast;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.app.MediaRouteActionProvider;
+import android.support.v7.app.MediaRouteChooserDialogFragment;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+import android.support.v7.media.MediaRouter;
+import android.util.Log;
+
+/**
+ * <p>Action Provider that extends {@link MediaRouteActionProvider} and allows the client to
+ * disable completely the button by calling {@link #setEnabled(boolean)}.</p>
+ *
+ * <p>It is disabled by default, so if a client wants to initially have it enabled it must call
+ * <code>setEnabled(true)</code>.</p>
+ */
+public class SwitchableMediaRouteActionProvider extends MediaRouteActionProvider {
+ public static final String TAG = "SwitchblMediaRtActProv";
+
+ private static final String CHOOSER_FRAGMENT_TAG =
+ "android.support.v7.mediarouter:MediaRouteChooserDialogFragment";
+ private static final String CONTROLLER_FRAGMENT_TAG =
+ "android.support.v7.mediarouter:MediaRouteControllerDialogFragment";
+ private boolean enabled;
+
+ public SwitchableMediaRouteActionProvider(Context context) {
+ super(context);
+ enabled = false;
+ }
+
+ /**
+ * <p>Sets whether the Media Router button should be allowed to become visible or not.</p>
+ *
+ * <p>It's invisible by default.</p>
+ */
+ public void setEnabled(boolean newVal) {
+ enabled = newVal;
+ refreshVisibility();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return enabled && super.isVisible();
+ }
+
+ @Override
+ public boolean onPerformDefaultAction() {
+ if (!super.onPerformDefaultAction()) {
+ // there is no button, but we should still show the dialog if it's the case.
+ if (!isVisible()) {
+ return false;
+ }
+ FragmentManager fm = getFragmentManager();
+ if (fm == null) {
+ return false;
+ }
+ MediaRouter.RouteInfo route = MediaRouter.getInstance(getContext()).getSelectedRoute();
+ if (route.isDefault() || !route.matchesSelector(getRouteSelector())) {
+ if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
+ Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+ return false;
+ }
+ MediaRouteChooserDialogFragment f =
+ getDialogFactory().onCreateChooserDialogFragment();
+ f.setRouteSelector(getRouteSelector());
+ f.show(fm, CHOOSER_FRAGMENT_TAG);
+ } else {
+ if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
+ Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+ return false;
+ }
+ MediaRouteControllerDialogFragment f =
+ getDialogFactory().onCreateControllerDialogFragment();
+ f.show(fm, CONTROLLER_FRAGMENT_TAG);
+ }
+ return true;
+
+ } else {
+ return true;
+ }
+ }
+
+ private FragmentManager getFragmentManager() {
+ Activity activity = getActivity();
+ if (activity instanceof FragmentActivity) {
+ return ((FragmentActivity)activity).getSupportFragmentManager();
+ }
+ return null;
+ }
+
+ private Activity getActivity() {
+ // Gross way of unwrapping the Activity so we can get the FragmentManager
+ Context context = getContext();
+ while (context instanceof ContextWrapper) {
+ if (context instanceof Activity) {
+ return (Activity)context;
+ }
+ context = ((ContextWrapper)context).getBaseContext();
+ }
+ return 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 abb75e5e7..266526d82 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
@@ -49,28 +49,10 @@ public abstract class ConfirmationDialog {
builder.setTitle(titleId);
builder.setMessage(messageId);
builder.setPositiveButton(positiveText != 0 ? positiveText : R.string.confirm_label,
- new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onConfirmButtonPressed(dialog);
- }
- });
+ (dialog, which) -> onConfirmButtonPressed(dialog));
builder.setNegativeButton(negativeText != 0 ? negativeText : R.string.cancel_label,
- new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onCancelButtonPressed(dialog);
- }
- });
- builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
-
- @Override
- public void onCancel(DialogInterface dialog) {
- onCancelButtonPressed(dialog);
- }
- });
+ (dialog, which) -> onCancelButtonPressed(dialog));
+ builder.setOnCancelListener(ConfirmationDialog.this::onCancelButtonPressed);
return builder.create();
}
}
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 b7e79431d..b1beac315 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,7 +1,6 @@
package de.danoeh.antennapod.core.dialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import de.danoeh.antennapod.core.R;
@@ -15,13 +14,7 @@ public class DownloadRequestErrorDialogCreator {
String errorMessage) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setNeutralButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
+ (dialog, which) -> dialog.dismiss())
.setTitle(R.string.download_error_request_error)
.setMessage(
context.getString(R.string.download_request_error_dialog_message_prefix)
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 7ccb742fb..dbad084c7 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
@@ -34,7 +34,7 @@ public class EventDistributor extends Observable {
private EventDistributor() {
this.handler = new Handler();
- events = new ConcurrentLinkedQueue<Integer>();
+ events = new ConcurrentLinkedQueue<>();
}
public static synchronized EventDistributor getInstance() {
@@ -54,13 +54,7 @@ public class EventDistributor extends Observable {
public void addEvent(Integer i) {
events.offer(i);
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- processEventQueue();
- }
- });
+ handler.post(EventDistributor.this::processEventQueue);
}
private void processEventQueue() {
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 d2d7cbc73..67ac2d280 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
@@ -112,7 +112,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
- this.items = new ArrayList<FeedItem>();
+ this.items = new ArrayList<>();
if(filter != null) {
this.itemfilter = new FeedItemFilter(filter);
} else {
@@ -235,7 +235,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
*/
public boolean hasUnplayedItems() {
for (FeedItem item : items) {
- if (false == item.isNew() && false == item.isPlayed()) {
+ if (!item.isNew() && !item.isPlayed()) {
return true;
}
}
@@ -324,12 +324,11 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
if (super.compareWithOther(other)) {
return true;
}
- if (!title.equals(other.title)) {
+ if (!TextUtils.equals(title, other.title)) {
return true;
}
if (other.feedIdentifier != null) {
- if (feedIdentifier == null
- || !feedIdentifier.equals(other.feedIdentifier)) {
+ if (feedIdentifier == null || !feedIdentifier.equals(other.feedIdentifier)) {
return true;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java
index 05115c1ea..90b5e50b7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java
@@ -54,9 +54,8 @@ public abstract class FeedComponent {
FeedComponent that = (FeedComponent) o;
- if (id != that.id) return false;
+ return id == that.id;
- return true;
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java
index 3dc58654b..ca9af058b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java
@@ -1,5 +1,7 @@
package de.danoeh.antennapod.core.feed;
+import android.text.TextUtils;
+
import java.io.File;
/**
@@ -54,7 +56,7 @@ public abstract class FeedFile extends FeedComponent {
if (super.compareWithOther(other)) {
return true;
}
- if (!download_url.equals(other.download_url)) {
+ if (!TextUtils.equals(download_url, other.download_url)) {
return true;
}
return false;
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 d8c32f55e..4921c0576 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
@@ -171,9 +171,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
String itemIdentifier = cursor.getString(indexItemIdentifier);
long autoDownload = cursor.getLong(indexAutoDownload);
- FeedItem item = new FeedItem(id, title, link, pubDate, paymentLink, feedId, flattrStatus,
+ return new FeedItem(id, title, link, pubDate, paymentLink, feedId, flattrStatus,
hasChapters, null, state, itemIdentifier, autoDownload);
- return item;
}
public void updateFromOther(FeedItem other) {
@@ -196,6 +195,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
if (other.media != null) {
if (media == null) {
setMedia(other.media);
+ // reset to new if feed item did link to a file before
+ setNew();
} else if (media.compareWithOther(other.media)) {
media.updateFromOther(other.media);
}
@@ -359,24 +360,16 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
private boolean isPlaying() {
- if (media != null) {
- return media.isPlaying();
- }
- return false;
+ return media != null && media.isPlaying();
}
@Override
public Callable<String> loadShownotes() {
- return new Callable<String>() {
- @Override
- public String call() throws Exception {
-
- if (contentEncoded == null || description == null) {
- DBReader.loadExtraInformationOfFeedItem(FeedItem.this);
-
- }
- return (contentEncoded != null) ? contentEncoded : description;
+ return () -> {
+ if (contentEncoded == null || description == null) {
+ DBReader.loadExtraInformationOfFeedItem(FeedItem.this);
}
+ return (contentEncoded != null) ? contentEncoded : description;
};
}
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 6b11e162e..7f064fff3 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
@@ -8,12 +8,12 @@ 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.cast.RemoteMedia;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
@@ -153,25 +153,10 @@ public class FeedMedia extends FeedFile implements Playable {
* Uses mimetype to determine the type of media.
*/
public MediaType getMediaType() {
- if (mime_type == null || mime_type.isEmpty()) {
- return MediaType.UNKNOWN;
- } else {
- if (mime_type.startsWith("audio")) {
- return MediaType.AUDIO;
- } else if (mime_type.startsWith("video")) {
- return MediaType.VIDEO;
- } else if (mime_type.equals("application/ogg")) {
- return MediaType.AUDIO;
- }
- }
- return MediaType.UNKNOWN;
+ return MediaType.fromMimeType(mime_type);
}
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;
@@ -499,20 +484,17 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public Callable<String> loadShownotes() {
- return new Callable<String>() {
- @Override
- public String call() throws Exception {
- if (item == null) {
- item = DBReader.getFeedItem(
- itemID);
- }
- if (item.getContentEncoded() == null || item.getDescription() == null) {
- DBReader.loadExtraInformationOfFeedItem(
- item);
+ return () -> {
+ if (item == null) {
+ item = DBReader.getFeedItem(
+ itemID);
+ }
+ if (item.getContentEncoded() == null || item.getDescription() == null) {
+ DBReader.loadExtraInformationOfFeedItem(
+ item);
- }
- return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
}
+ return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
};
}
@@ -587,4 +569,12 @@ public class FeedMedia extends FeedFile implements Playable {
hasEmbeddedPicture = Boolean.FALSE;
}
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof RemoteMedia) {
+ return o.equals(this);
+ }
+ return super.equals(o);
+ }
}
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 faf23a37a..e2b5dd7c2 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
@@ -68,7 +68,7 @@ public class FeedPreferences {
/**
* @return the filter for this feed
*/
- public FeedFilter getFilter() {
+ public @NonNull FeedFilter getFilter() {
return filter;
}
@@ -95,8 +95,9 @@ public class FeedPreferences {
* @return True if the two objects are different.
*/
public boolean compareWithOther(FeedPreferences other) {
- if (other == null)
+ if (other == null) {
return true;
+ }
if (!TextUtils.equals(username, other.username)) {
return true;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java b/core/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java
index 7b3cb829d..83ac031bf 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/MediaType.java
@@ -1,5 +1,20 @@
package de.danoeh.antennapod.core.feed;
+import android.text.TextUtils;
+
public enum MediaType {
- AUDIO, VIDEO, UNKNOWN
+ AUDIO, VIDEO, UNKNOWN;
+
+ public static MediaType fromMimeType(String mime_type) {
+ if (TextUtils.isEmpty(mime_type)) {
+ return MediaType.UNKNOWN;
+ } else if (mime_type.startsWith("audio")) {
+ return MediaType.AUDIO;
+ } else if (mime_type.startsWith("video")) {
+ return MediaType.VIDEO;
+ } else if (mime_type.equals("application/ogg")) {
+ return MediaType.AUDIO;
+ }
+ return MediaType.UNKNOWN;
+ }
}
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
index 86baa459c..513264a05 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
@@ -18,7 +18,6 @@ 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;
@@ -107,7 +106,6 @@ public class ApOkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
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)) {
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 48f234917..9f716e546 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
@@ -80,7 +80,7 @@ public class GpodnetService {
String response = executeRequest(request);
try {
JSONArray jsonTagList = new JSONArray(response);
- List<GpodnetTag> tagList = new ArrayList<GpodnetTag>(
+ List<GpodnetTag> tagList = new ArrayList<>(
jsonTagList.length());
for (int i = 0; i < jsonTagList.length(); i++) {
JSONObject jObj = jsonTagList.getJSONObject(i);
@@ -318,8 +318,7 @@ public class GpodnetService {
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/subscriptions/%s.opml", username), null).toURL();
Request.Builder request = new Request.Builder().url(url);
- String response = executeRequest(request);
- return response;
+ return executeRequest(request);
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
@@ -660,7 +659,7 @@ public class GpodnetService {
private List<GpodnetPodcast> readPodcastListFromJSONArray(@NonNull JSONArray array)
throws JSONException {
- List<GpodnetPodcast> result = new ArrayList<GpodnetPodcast>(
+ List<GpodnetPodcast> result = new ArrayList<>(
array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readPodcastFromJSONObject(array.getJSONObject(i)));
@@ -712,7 +711,7 @@ public class GpodnetService {
private List<GpodnetDevice> readDeviceListFromJSONArray(@NonNull JSONArray array)
throws JSONException {
- List<GpodnetDevice> result = new ArrayList<GpodnetDevice>(
+ List<GpodnetDevice> result = new ArrayList<>(
array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readDeviceFromJSONObject(array.getJSONObject(i)));
@@ -732,7 +731,7 @@ public class GpodnetService {
private GpodnetSubscriptionChange readSubscriptionChangesFromJSONObject(
@NonNull JSONObject object) throws JSONException {
- List<String> added = new LinkedList<String>();
+ List<String> added = new LinkedList<>();
JSONArray jsonAdded = object.getJSONArray("add");
for (int i = 0; i < jsonAdded.length(); i++) {
String addedUrl = jsonAdded.getString(i);
@@ -741,7 +740,7 @@ public class GpodnetService {
added.add(addedUrl);
}
- List<String> removed = new LinkedList<String>();
+ List<String> removed = new LinkedList<>();
JSONArray jsonRemoved = object.getJSONArray("remove");
for (int i = 0; i < jsonRemoved.length(); i++) {
String removedUrl = jsonRemoved.getString(i);
@@ -757,7 +756,7 @@ public class GpodnetService {
private GpodnetEpisodeActionGetResponse readEpisodeActionsFromJSONObject(
@NonNull JSONObject object) throws JSONException {
- List<GpodnetEpisodeAction> episodeActions = new ArrayList<GpodnetEpisodeAction>();
+ List<GpodnetEpisodeAction> episodeActions = new ArrayList<>();
long timestamp = object.getLong("timestamp");
JSONArray jsonActions = object.getJSONArray("actions");
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 2d49c170a..79eb33cb5 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
@@ -25,7 +25,7 @@ public class GpodnetDevice {
+ type + ", subscriptions=" + subscriptions + "]";
}
- public static enum DeviceType {
+ public enum DeviceType {
DESKTOP, LAPTOP, MOBILE, SERVER, OTHER;
static DeviceType fromString(String s) {
@@ -33,16 +33,17 @@ public class GpodnetDevice {
return OTHER;
}
- if (s.equals("desktop")) {
- return DESKTOP;
- } else if (s.equals("laptop")) {
- return LAPTOP;
- } else if (s.equals("mobile")) {
- return MOBILE;
- } else if (s.equals("server")) {
- return SERVER;
- } else {
- return OTHER;
+ switch (s) {
+ case "desktop":
+ return DESKTOP;
+ case "laptop":
+ return LAPTOP;
+ case "mobile":
+ return MOBILE;
+ case "server":
+ return SERVER;
+ default:
+ return OTHER;
}
}
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 2d174a6bc..9627ecae6 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
@@ -61,14 +61,13 @@ public class GpodnetEpisodeAction {
String deviceId = fields[2];
try {
Action action = Action.valueOf(fields[3]);
- GpodnetEpisodeAction result = new Builder(podcast, episode, action)
+ return new Builder(podcast, episode, action)
.deviceId(deviceId)
- .timestamp(new Date(Long.valueOf(fields[4])))
- .started(Integer.valueOf(fields[5]))
- .position(Integer.valueOf(fields[6]))
- .total(Integer.valueOf(fields[7]))
+ .timestamp(new Date(Long.parseLong(fields[4])))
+ .started(Integer.parseInt(fields[5]))
+ .position(Integer.parseInt(fields[6]))
+ .total(Integer.parseInt(fields[7]))
.build();
- return result;
} catch(IllegalArgumentException e) {
Log.e(TAG, "readFromString(" + s + "): " + e.getMessage());
return null;
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 5f096db14..03c33c9a1 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
@@ -38,7 +38,7 @@ public class GpodnetEpisodeActionPostResponse {
final JSONObject object = new JSONObject(objectString);
final long timestamp = object.getLong("timestamp");
JSONArray urls = object.getJSONArray("update_urls");
- Map<String, String> updatedUrls = new ArrayMap<String, String>(urls.length());
+ Map<String, String> updatedUrls = new ArrayMap<>(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/menuhandler/MenuItemUtils.java b/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java
index b8d17bcce..f63c9aeb0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java
@@ -32,7 +32,7 @@ public class MenuItemUtils {
}
}
- public static interface UpdateRefreshMenuItemChecker {
- public boolean isRefreshing();
+ public interface UpdateRefreshMenuItemChecker {
+ boolean isRefreshing();
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java b/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java
index 775129d09..17afc7904 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/opml/OpmlReader.java
@@ -1,7 +1,7 @@
package de.danoeh.antennapod.core.opml;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
@@ -10,6 +10,8 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
+import de.danoeh.antennapod.core.BuildConfig;
+
/** Reads OPML documents. */
public class OpmlReader {
private static final String TAG = "OpmlReader";
@@ -27,7 +29,7 @@ public class OpmlReader {
*/
public ArrayList<OpmlElement> readDocument(Reader reader)
throws XmlPullParserException, IOException {
- elementList = new ArrayList<OpmlElement>();
+ elementList = new ArrayList<>();
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
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 edd7b807a..6d4d3baa6 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
@@ -189,7 +189,7 @@ public class GpodnetPreferences {
public static Set<String> getAddedFeedsCopy() {
ensurePreferencesLoaded();
- Set<String> copy = new HashSet<String>();
+ Set<String> copy = new HashSet<>();
feedListLock.lock();
copy.addAll(addedFeeds);
feedListLock.unlock();
@@ -206,7 +206,7 @@ public class GpodnetPreferences {
public static Set<String> getRemovedFeedsCopy() {
ensurePreferencesLoaded();
- Set<String> copy = new HashSet<String>();
+ Set<String> copy = new HashSet<>();
feedListLock.lock();
copy.addAll(removedFeeds);
feedListLock.unlock();
@@ -232,7 +232,7 @@ public class GpodnetPreferences {
public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {
ensurePreferencesLoaded();
- List<GpodnetEpisodeAction> copy = new ArrayList();
+ List<GpodnetEpisodeAction> copy = new ArrayList<>();
feedListLock.lock();
copy.addAll(queuedEpisodeActions);
feedListLock.unlock();
@@ -272,7 +272,7 @@ public class GpodnetPreferences {
}
private static Set<String> readListFromString(String s) {
- Set<String> result = new HashSet<String>();
+ Set<String> result = new HashSet<>();
for (String item : s.split(" ")) {
result.add(item);
}
@@ -290,7 +290,7 @@ public class GpodnetPreferences {
private static List<GpodnetEpisodeAction> readEpisodeActionsFromString(String s) {
String[] lines = s.split("\n");
- List<GpodnetEpisodeAction> result = new ArrayList<GpodnetEpisodeAction>(lines.length);
+ List<GpodnetEpisodeAction> result = new ArrayList<>(lines.length);
for(String line : lines) {
if(TextUtils.isEmpty(line)) {
GpodnetEpisodeAction action = GpodnetEpisodeAction.readFromString(line);
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 6c0aff15e..b5bbb0350 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
@@ -17,6 +17,7 @@ import org.json.JSONException;
import java.io.File;
import java.io.IOException;
+import java.net.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -25,10 +26,12 @@ import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
+import de.danoeh.antennapod.core.service.download.ProxyConfig;
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;
+import de.danoeh.antennapod.core.util.Converter;
/**
* Provides access to preferences set by the user in the settings screen. A
@@ -49,9 +52,11 @@ public class UserPreferences {
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_COMPACT_NOTIFICATION_BUTTONS = "prefCompactNotificationButtons";
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";
@@ -78,6 +83,11 @@ public class UserPreferences {
public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
public static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
+ public static final String PREF_PROXY_TYPE = "prefProxyType";
+ public static final String PREF_PROXY_HOST = "prefProxyHost";
+ public static final String PREF_PROXY_PORT = "prefProxyPort";
+ public static final String PREF_PROXY_USER = "prefProxyUser";
+ public static final String PREF_PROXY_PASSWORD = "prefProxyPassword";
// Services
public static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
@@ -101,11 +111,15 @@ public class UserPreferences {
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 String PREF_CAST_ENABLED = "prefCast"; //Used for enabling Chromecast support
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 final int NOTIFICATION_BUTTON_REWIND = 0;
+ private static final int NOTIFICATION_BUTTON_FAST_FORWARD = 1;
+ private static final int NOTIFICATION_BUTTON_SKIP = 2;
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
public static int FEED_ORDER_COUNTER = 0;
public static int FEED_ORDER_ALPHABETICAL = 1;
@@ -156,14 +170,50 @@ public class UserPreferences {
return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenItems, ",")));
}
+ public static List<Integer> getCompactNotificationButtons() {
+ String[] buttons = TextUtils.split(
+ prefs.getString(PREF_COMPACT_NOTIFICATION_BUTTONS,
+ String.valueOf(NOTIFICATION_BUTTON_SKIP)),
+ ",");
+ List<Integer> notificationButtons = new ArrayList<>();
+ for (int i=0; i<buttons.length; i++) {
+ notificationButtons.add(Integer.parseInt(buttons[i]));
+ }
+ return notificationButtons;
+ }
+
+ /**
+ * Helper function to return whether the specified button should be shown on compact
+ * notifications.
+ *
+ * @param buttonId Either NOTIFICATION_BUTTON_REWIND, NOTIFICATION_BUTTON_FAST_FORWARD or
+ * NOTIFICATION_BUTTON_SKIP.
+ * @return {@code true} if button should be shown, {@code false} otherwise
+ */
+ private static boolean showButtonOnCompactNotification(int buttonId) {
+ return getCompactNotificationButtons().contains(buttonId);
+ }
+
+ public static boolean showRewindOnCompactNotification() {
+ return showButtonOnCompactNotification(NOTIFICATION_BUTTON_REWIND);
+ }
+
+ public static boolean showFastForwardOnCompactNotification() {
+ return showButtonOnCompactNotification(NOTIFICATION_BUTTON_FAST_FORWARD);
+ }
+
+ public static boolean showSkipOnCompactNotification() {
+ return showButtonOnCompactNotification(NOTIFICATION_BUTTON_SKIP);
+ }
+
public static int getFeedOrder() {
String value = prefs.getString(PREF_DRAWER_FEED_ORDER, "0");
- return Integer.valueOf(value);
+ return Integer.parseInt(value);
}
public static int getFeedCounterSetting() {
String value = prefs.getString(PREF_DRAWER_FEED_COUNTER, "0");
- return Integer.valueOf(value);
+ return Integer.parseInt(value);
}
/**
@@ -189,9 +239,9 @@ public class UserPreferences {
}
/**
- * Returns true if notifications are persistent
+ * Returns true if the lockscreen background should be set to the current episode's image
*
- * @return {@code true} if notifications are persistent, {@code false} otherwise
+ * @return {@code true} if the lockscreen background should be set, {@code false} otherwise
*/
public static boolean setLockscreenBackground() {
return prefs.getBoolean(PREF_LOCKSCREEN_BACKGROUND, true);
@@ -243,7 +293,7 @@ public class UserPreferences {
}
public static int getSmartMarkAsPlayedSecs() {
- return Integer.valueOf(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
+ return Integer.parseInt(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
}
public static boolean isAutoFlattr() {
@@ -260,20 +310,20 @@ public class UserPreferences {
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)));
- }
+ return Converter.getVolumeFromPercentage(volume);
}
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)));
- }
+ return Converter.getVolumeFromPercentage(volume);
+ }
+
+ public static int getLeftVolumePercentage() {
+ return prefs.getInt(PREF_LEFT_VOLUME, 100);
+ }
+
+ public static int getRightVolumePercentage() {
+ return prefs.getInt(PREF_RIGHT_VOLUME, 100);
}
public static boolean shouldPauseForFocusLoss() {
@@ -284,7 +334,7 @@ public class UserPreferences {
public static long getUpdateInterval() {
String updateInterval = prefs.getString(PREF_UPDATE_INTERVAL, "0");
- if(false == updateInterval.contains(":")) {
+ if(!updateInterval.contains(":")) {
return readUpdateInterval(updateInterval);
} else {
return 0;
@@ -295,8 +345,8 @@ public class UserPreferences {
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]);
+ int hourOfDay = Integer.parseInt(parts[0]);
+ int minute = Integer.parseInt(parts[1]);
return new int[] { hourOfDay, minute };
} else {
return new int[0];
@@ -308,7 +358,7 @@ public class UserPreferences {
}
public static int getParallelDownloads() {
- return Integer.valueOf(prefs.getString(PREF_PARALLEL_DOWNLOADS, "4"));
+ return Integer.parseInt(prefs.getString(PREF_PARALLEL_DOWNLOADS, "4"));
}
public static int getEpisodeCacheSizeUnlimited() {
@@ -338,12 +388,12 @@ public class UserPreferences {
public static int getImageCacheSize() {
String cacheSizeString = prefs.getString(PREF_IMAGE_CACHE_SIZE, IMAGE_CACHE_DEFAULT_VALUE);
- int cacheSizeInt = Integer.valueOf(cacheSizeString);
+ int cacheSizeInt = Integer.parseInt(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);
+ cacheSizeInt = Integer.parseInt(IMAGE_CACHE_DEFAULT_VALUE);
}
int cacheSizeMB = cacheSizeInt * 1024 * 1024;
return cacheSizeMB;
@@ -371,6 +421,41 @@ public class UserPreferences {
return TextUtils.split(selectedNetWorks, ",");
}
+ public static void setProxyConfig(ProxyConfig config) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(PREF_PROXY_TYPE, config.type.name());
+ if(TextUtils.isEmpty(config.host)) {
+ editor.remove(PREF_PROXY_HOST);
+ } else {
+ editor.putString(PREF_PROXY_HOST, config.host);
+ }
+ if(config.port <= 0 || config.port > 65535) {
+ editor.remove(PREF_PROXY_PORT);
+ } else {
+ editor.putInt(PREF_PROXY_PORT, config.port);
+ }
+ if(TextUtils.isEmpty(config.username)) {
+ editor.remove(PREF_PROXY_USER);
+ } else {
+ editor.putString(PREF_PROXY_USER, config.username);
+ }
+ if(TextUtils.isEmpty(config.password)) {
+ editor.remove(PREF_PROXY_PASSWORD);
+ } else {
+ editor.putString(PREF_PROXY_PASSWORD, config.password);
+ }
+ editor.apply();
+ }
+
+ public static ProxyConfig getProxyConfig() {
+ Proxy.Type type = Proxy.Type.valueOf(prefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name()));
+ String host = prefs.getString(PREF_PROXY_HOST, null);
+ int port = prefs.getInt(PREF_PROXY_PORT, 0);
+ String username = prefs.getString(PREF_PROXY_USER, null);
+ String password = prefs.getString(PREF_PROXY_PASSWORD, null);
+ return new ProxyConfig(type, host, port, username, password);
+ }
+
public static boolean shouldResumeAfterCall() {
return prefs.getBoolean(PREF_RESUME_AFTER_CALL, true);
}
@@ -468,6 +553,13 @@ public class UserPreferences {
.apply();
}
+ public static void setCompactNotificationButtons(List<Integer> items) {
+ String str = TextUtils.join(",", items);
+ prefs.edit()
+ .putString(PREF_COMPACT_NOTIFICATION_BUTTONS, str)
+ .apply();
+ }
+
public static void setQueueLocked(boolean locked) {
prefs.edit()
.putBoolean(PREF_QUEUE_LOCKED, locked)
@@ -494,7 +586,7 @@ public class UserPreferences {
if (valueFromPrefs.equals(context.getString(R.string.pref_episode_cache_unlimited))) {
return EPISODE_CACHE_SIZE_UNLIMITED;
} else {
- return Integer.valueOf(valueFromPrefs);
+ return Integer.parseInt(valueFromPrefs);
}
}
@@ -502,15 +594,7 @@ public class UserPreferences {
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()]);
+ selectedSpeeds = new String[] { "1.00", "1.25", "1.50", "1.75", "2.00" };
} else {
try {
JSONArray jsonArray = new JSONArray(valueFromPrefs);
@@ -548,7 +632,7 @@ public class UserPreferences {
public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() {
- int cleanupValue = Integer.valueOf(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
+ int cleanupValue = Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
if (cleanupValue == EPISODE_CLEANUP_QUEUE) {
return new APQueueCleanupAlgorithm();
} else if (cleanupValue == EPISODE_CLEANUP_NULL) {
@@ -717,4 +801,11 @@ public class UserPreferences {
public static int readEpisodeCacheSize(String valueFromPrefs) {
return readEpisodeCacheSizeInternal(valueFromPrefs);
}
+
+ /**
+ * Evaluates whether Cast support (Chromecast, Audio Cast, etc) is enabled on the preferences.
+ */
+ public static boolean isCastEnabled() {
+ return prefs.getBoolean(PREF_CAST_ENABLED, false);
+ }
}
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 ce5004a98..33c15883b 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
@@ -6,7 +6,7 @@ import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
-import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
/** Listens for events that make it necessary to reset the update alarm. */
@@ -22,8 +22,7 @@ public class AlarmUpdateReceiver extends BroadcastReceiver {
} else if (TextUtils.equals(intent.getAction(), Intent.ACTION_PACKAGE_REPLACED)) {
Log.d(TAG, "Resetting update alarm after app upgrade");
}
- PlaybackPreferences.init(context);
- UserPreferences.init(context);
+ ClientConfig.initialize(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 b959c7301..e0abe0efb 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.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.NetworkUtils;
@@ -19,6 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent");
+ ClientConfig.initialize(context);
if (NetworkUtils.isDownloadAllowed()) {
DBTasks.refreshAllFeeds(context, null);
} else {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
index a900248d2..0bfeb1150 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
@@ -5,25 +5,27 @@ import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
-import de.danoeh.antennapod.core.BuildConfig;
+
+import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
/** Receives media button events. */
public class MediaButtonReceiver extends BroadcastReceiver {
private static final String TAG = "MediaButtonReceiver";
public static final String EXTRA_KEYCODE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.KEYCODE";
+ public static final String EXTRA_SOURCE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.SOURCE";
public static final String NOTIFY_BUTTON_RECEIVER = "de.danoeh.antennapod.NOTIFY_BUTTON_RECEIVER";
@Override
public void onReceive(Context context, Intent intent) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Received intent");
- KeyEvent event = (KeyEvent) intent.getExtras().get(
- Intent.EXTRA_KEY_EVENT);
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ Log.d(TAG, "Received intent");
+ KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
+ if (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount()==0) {
+ ClientConfig.initialize(context);
Intent serviceIntent = new Intent(context, PlaybackService.class);
- int keycode = event.getKeyCode();
- serviceIntent.putExtra(EXTRA_KEYCODE, keycode);
+ serviceIntent.putExtra(EXTRA_KEYCODE, event.getKeyCode());
+ serviceIntent.putExtra(EXTRA_SOURCE, event.getSource());
context.startService(serviceIntent);
}
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 d939c1007..9704d2111 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
@@ -111,12 +111,17 @@ public class GpodnetSyncService extends Service {
stopSelf();
return;
}
+ boolean initialSync = GpodnetPreferences.getLastSubscriptionSyncTimestamp() == 0 &&
+ GpodnetPreferences.getLastEpisodeActionsSyncTimestamp() == 0;
if(syncSubscriptions) {
syncSubscriptionChanges();
syncSubscriptions = false;
}
if(syncActions) {
- syncEpisodeActions();
+ // we only sync episode actions after the subscriptions have been added to the database
+ if(!initialSync) {
+ syncEpisodeActions();
+ }
syncActions = false;
}
stopSelf();
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 b23819ef7..5dd1e2dfa 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
@@ -2,8 +2,10 @@ package de.danoeh.antennapod.core.service.download;
import android.os.Build;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
+import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
@@ -14,7 +16,10 @@ import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
import java.net.Socket;
+import java.net.SocketAddress;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.concurrent.TimeUnit;
@@ -23,6 +28,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBWriter;
/**
@@ -44,12 +50,15 @@ public class AntennapodHttpClient {
*/
public static synchronized OkHttpClient getHttpClient() {
if (httpClient == null) {
-
httpClient = newHttpClient();
}
return httpClient;
}
+ public static synchronized void reinit() {
+ httpClient = newHttpClient();
+ }
+
/**
* Creates a new HTTP client. Most users should just use
* getHttpClient() to get the standard AntennaPod client,
@@ -69,13 +78,13 @@ public class AntennapodHttpClient {
client.networkInterceptors().add(chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
- if(response.code() == HttpURLConnection.HTTP_MOVED_PERM ||
+ 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
+ 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://") &&
+ } else if (!location.toLowerCase().startsWith("http://") &&
!location.toLowerCase().startsWith("https://")) {
// Reference is relative to current path
URL url = request.url();
@@ -106,6 +115,21 @@ public class AntennapodHttpClient {
client.setFollowRedirects(true);
client.setFollowSslRedirects(true);
+ ProxyConfig config = UserPreferences.getProxyConfig();
+ if (config.type != Proxy.Type.DIRECT) {
+ int port = config.port > 0 ? config.port : ProxyConfig.DEFAULT_PORT;
+ SocketAddress address = InetSocketAddress.createUnresolved(config.host, port);
+ Proxy proxy = new Proxy(config.type, address);
+ client.setProxy(proxy);
+ if (!TextUtils.isEmpty(config.username)) {
+ String credentials = Credentials.basic(config.username, config.password);
+ client.interceptors().add(chain -> {
+ Request request = chain.request().newBuilder()
+ .header("Proxy-Authorization", credentials).build();
+ return chain.proceed(request);
+ });
+ }
+ }
if(16 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 21) {
client.setSslSocketFactory(new CustomSslSocketFactory());
}
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 7f40ea6e2..de91916a9 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
@@ -128,7 +128,8 @@ public class DownloadRequest implements Parcelable {
DownloadRequest that = (DownloadRequest) o;
- if (lastModified != that.lastModified) return false;
+ if (lastModified != null ? !lastModified.equals(that.lastModified) : that.lastModified != null)
+ return false;
if (deleteOnFailure != that.deleteOnFailure) return false;
if (feedfileId != that.feedfileId) return false;
if (feedfileType != that.feedfileType) return false;
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 090c48ca0..00b0e6db3 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
@@ -41,11 +41,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -64,6 +61,7 @@ import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
@@ -188,7 +186,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()) == 416) {
+ && Integer.parseInt(status.getReasonDetailed()) == 416) {
Log.d(TAG, "Requested invalid range, restarting download from the beginning");
FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination()));
@@ -207,8 +205,10 @@ public class DownloadService extends Service {
FeedItem item = media.getItem();
boolean httpNotFound = status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR
&& String.valueOf(HttpURLConnection.HTTP_NOT_FOUND).equals(status.getReasonDetailed());
+ boolean forbidden = status.getReason() == DownloadError.ERROR_FORBIDDEN
+ && String.valueOf(HttpURLConnection.HTTP_FORBIDDEN).equals(status.getReasonDetailed());
boolean notEnoughSpace = status.getReason() == DownloadError.ERROR_NOT_ENOUGH_SPACE;
- if (httpNotFound || notEnoughSpace) {
+ if (httpNotFound || forbidden || notEnoughSpace) {
DBWriter.saveFeedItemAutoDownloadFailed(item).get();
}
// to make lists reload the failed item, we fake an item update
@@ -252,54 +252,35 @@ public class DownloadService extends Service {
Log.d(TAG, "Service started");
isRunning = true;
handler = new Handler();
- reportQueue = Collections.synchronizedList(new ArrayList<DownloadStatus>());
- downloads = Collections.synchronizedList(new ArrayList<Downloader>());
+ reportQueue = Collections.synchronizedList(new ArrayList<>());
+ downloads = Collections.synchronizedList(new ArrayList<>());
numberOfDownloads = new AtomicInteger(0);
IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
- syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
+ syncExecutor = Executors.newSingleThreadExecutor(r -> {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
});
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
- downloadExecutor = new ExecutorCompletionService<Downloader>(
+ downloadExecutor = new ExecutorCompletionService<>(
Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
- 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;
}
)
);
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- }, new RejectedExecutionHandler() {
-
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG, "SchedEx rejected submission of new task");
- }
- }
+ r -> {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }, (r, executor) -> Log.w(TAG, "SchedEx rejected submission of new task")
);
downloadCompletionThread.start();
feedSyncThread = new FeedSyncThread();
@@ -338,6 +319,14 @@ public class DownloadService extends Service {
cancelNotificationUpdater();
unregisterReceiver(cancelDownloadReceiver);
+ // if this was the initial gpodder sync, i.e. we just synced the feeds successfully,
+ // it is now time to sync the episode actions
+ if(GpodnetPreferences.loggedIn() &&
+ GpodnetPreferences.getLastSubscriptionSyncTimestamp() > 0 &&
+ GpodnetPreferences.getLastEpisodeActionsSyncTimestamp() == 0) {
+ GpodnetSyncService.sendSyncActionsIntent(this);
+ }
+
// start auto download in case anything new has shown up
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
}
@@ -383,16 +372,16 @@ public class DownloadService extends Service {
if (i > 0) {
bigText.append("\n");
}
- bigText.append("\u2022 " + request.getTitle());
+ bigText.append("\u2022 ").append(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()
- + "%)");
+ bigText.append("\u2022 ").append(request.getTitle())
+ .append(" (").append(request.getProgressPercent())
+ .append("%)");
}
}
@@ -489,16 +478,13 @@ public class DownloadService extends Service {
* DownloadService list.
*/
private void removeDownload(final Downloader d) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Removing downloader: "
- + d.getDownloadRequest().getSource());
- boolean rc = downloads.remove(d);
- Log.d(TAG, "Result of downloads.remove: " + rc);
- DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
- postDownloaders();
- }
+ handler.post(() -> {
+ Log.d(TAG, "Removing downloader: "
+ + d.getDownloadRequest().getSource());
+ boolean rc = downloads.remove(d);
+ Log.d(TAG, "Result of downloads.remove: " + rc);
+ DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
+ postDownloaders();
});
}
@@ -574,12 +560,7 @@ public class DownloadService extends Service {
* used from a thread other than the main thread.
*/
void queryDownloadsAsync() {
- handler.post(new Runnable() {
- public void run() {
- queryDownloads();
- ;
- }
- });
+ handler.post(DownloadService.this::queryDownloads);
}
/**
@@ -598,27 +579,24 @@ public class DownloadService extends Service {
}
private void postAuthenticationNotification(final DownloadRequest downloadRequest) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- final String resourceTitle = (downloadRequest.getTitle() != null)
- ? downloadRequest.getTitle() : downloadRequest.getSource();
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this);
- builder.setTicker(getText(R.string.authentication_notification_title))
- .setContentTitle(getText(R.string.authentication_notification_title))
- .setContentText(getText(R.string.authentication_notification_msg))
- .setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.authentication_notification_msg)
- + ": " + resourceTitle))
- .setSmallIcon(R.drawable.ic_stat_authentication)
- .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_stat_authentication))
- .setAutoCancel(true)
- .setContentIntent(ClientConfig.downloadServiceCallbacks.getAuthentificationNotificationContentIntent(DownloadService.this, downloadRequest))
- .setVisibility(Notification.VISIBILITY_PUBLIC);
- Notification n = builder.build();
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(downloadRequest.getSource().hashCode(), n);
- }
+ handler.post(() -> {
+ final String resourceTitle = (downloadRequest.getTitle() != null)
+ ? downloadRequest.getTitle() : downloadRequest.getSource();
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this);
+ builder.setTicker(getText(R.string.authentication_notification_title))
+ .setContentTitle(getText(R.string.authentication_notification_title))
+ .setContentText(getText(R.string.authentication_notification_msg))
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.authentication_notification_msg)
+ + ": " + resourceTitle))
+ .setSmallIcon(R.drawable.ic_stat_authentication)
+ .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_stat_authentication))
+ .setAutoCancel(true)
+ .setContentIntent(ClientConfig.downloadServiceCallbacks.getAuthentificationNotificationContentIntent(DownloadService.this, downloadRequest))
+ .setVisibility(Notification.VISIBILITY_PUBLIC);
+ Notification n = builder.build();
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(downloadRequest.getSource().hashCode(), n);
});
}
@@ -651,8 +629,8 @@ public class DownloadService extends Service {
class FeedSyncThread extends Thread {
private static final String TAG = "FeedSyncThread";
- private BlockingQueue<DownloadRequest> completedRequests = new LinkedBlockingDeque<DownloadRequest>();
- private CompletionService<Pair<DownloadRequest, FeedHandlerResult>> parserService = new ExecutorCompletionService<Pair<DownloadRequest, FeedHandlerResult>>(Executors.newSingleThreadExecutor());
+ private BlockingQueue<DownloadRequest> completedRequests = new LinkedBlockingDeque<>();
+ private CompletionService<Pair<DownloadRequest, FeedHandlerResult>> parserService = new ExecutorCompletionService<>(Executors.newSingleThreadExecutor());
private ExecutorService dbService = Executors.newSingleThreadExecutor();
private Future<?> dbUpdateFuture;
private volatile boolean isActive = true;
@@ -668,7 +646,7 @@ public class DownloadService extends Service {
* @return Collected feeds or null if the method has been interrupted during the first waiting period.
*/
private List<Pair<DownloadRequest, FeedHandlerResult>> collectCompletedRequests() {
- List<Pair<DownloadRequest, FeedHandlerResult>> results = new LinkedList<Pair<DownloadRequest, FeedHandlerResult>>();
+ List<Pair<DownloadRequest, FeedHandlerResult>> results = new LinkedList<>();
DownloadRequester requester = DownloadRequester.getInstance();
int tasks = 0;
@@ -712,11 +690,9 @@ public class DownloadService extends Service {
if (result != null) {
results.add(result);
}
- } catch (InterruptedException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
}
}
@@ -751,41 +727,36 @@ public class DownloadService extends Service {
if (dbUpdateFuture != null) {
try {
dbUpdateFuture.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
- dbUpdateFuture = dbService.submit(new Runnable() {
- @Override
- public void run() {
- Feed[] savedFeeds = DBTasks.updateFeed(DownloadService.this, getFeeds(results));
-
- for (int i = 0; i < savedFeeds.length; i++) {
- Feed savedFeed = savedFeeds[i];
-
- // 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) {
- try {
- feed.setId(savedFeed.getId());
- DBTasks.loadNextPageOfFeed(DownloadService.this, savedFeed, true);
- } catch (DownloadRequestException e) {
- Log.e(TAG, "Error trying to load next page", e);
- }
+ dbUpdateFuture = dbService.submit(() -> {
+ Feed[] savedFeeds = DBTasks.updateFeed(DownloadService.this, getFeeds(results));
+
+ for (int i = 0; i < savedFeeds.length; i++) {
+ Feed savedFeed = savedFeeds[i];
+
+ // 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) {
+ try {
+ feed.setId(savedFeed.getId());
+ DBTasks.loadNextPageOfFeed(DownloadService.this, savedFeed, true);
+ } catch (DownloadRequestException e) {
+ Log.e(TAG, "Error trying to load next page", e);
}
-
- ClientConfig.downloadServiceCallbacks.onFeedParsed(DownloadService.this,
- savedFeed);
-
- numberOfDownloads.decrementAndGet();
}
- queryDownloadsAsync();
+ ClientConfig.downloadServiceCallbacks.onFeedParsed(DownloadService.this,
+ savedFeed);
+
+ numberOfDownloads.decrementAndGet();
}
+
+ queryDownloadsAsync();
});
}
@@ -847,21 +818,11 @@ public class DownloadService extends Service {
try {
result = feedHandler.parseFeed(feed);
Log.d(TAG, feed.getTitle() + " parsed");
- if (checkFeedData(feed) == false) {
+ if (!checkFeedData(feed)) {
throw new InvalidFeedException();
}
- } catch (SAXException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (IOException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (ParserConfigurationException e) {
+ } catch (SAXException | IOException | ParserConfigurationException e) {
successful = false;
e.printStackTrace();
reason = DownloadError.ERROR_PARSER_EXCEPTION;
@@ -884,7 +845,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(feed);
- if(log.size() > 0 && log.get(0).isSuccessful() == false) {
+ if(log.size() > 0 && !log.get(0).isSuccessful()) {
saveDownloadStatus(new DownloadStatus(feed,
feed.getHumanReadableIdentifier(), DownloadError.SUCCESS, successful,
reasonDetailed));
@@ -1017,9 +978,7 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination());
try {
DBWriter.setFeedMedia(media).get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
@@ -1045,8 +1004,8 @@ public class DownloadService extends Service {
public void run() {
FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
if (media == null) {
- throw new IllegalStateException(
- "Could not find downloaded media object in database");
+ Log.e(TAG, "Could not find downloaded media object in database");
+ return;
}
media.setDownloaded(true);
media.setFile_url(request.getDestination());
@@ -1087,10 +1046,7 @@ public class DownloadService extends Service {
if (item != null && !DBTasks.isInQueue(DownloadService.this, item.getId())) {
DBWriter.addQueueItem(DownloadService.this, item).get();
}
- } catch (ExecutionException e) {
- e.printStackTrace();
- status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage());
- } catch (InterruptedException e) {
+ } catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage());
}
@@ -1134,14 +1090,11 @@ public class DownloadService extends Service {
private class NotificationUpdater implements Runnable {
public void run() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- Notification n = updateNotifications();
- if (n != null) {
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NOTIFICATION_ID, n);
- }
+ handler.post(() -> {
+ Notification n = updateNotifications();
+ if (n != null) {
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, n);
}
});
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java
index 2d9347b0a..b0829f084 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java
@@ -6,5 +6,5 @@ package de.danoeh.antennapod.core.service.download;
*/
public interface DownloaderCallback {
- public void onDownloadCompleted(Downloader downloader);
+ void onDownloadCompleted(Downloader downloader);
}
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 e3a195253..556c008d4 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
@@ -21,12 +21,13 @@ import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.FeedImage;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.StorageUtils;
@@ -67,6 +68,12 @@ public class HttpDownloader extends Downloader {
final URI uri = URIUtil.getURIFromRequestUrl(request.getSource());
Request.Builder httpReq = new Request.Builder().url(uri.toURL())
.header("User-Agent", ClientConfig.USER_AGENT);
+ if(request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ // set header explicitly so that okhttp doesn't do transparent gzip
+ Log.d(TAG, "addHeader(\"Accept-Encoding\", \"identity\")");
+ httpReq.addHeader("Accept-Encoding", "identity");
+ }
+
if(!TextUtils.isEmpty(request.getLastModified())) {
String lastModified = request.getLastModified();
Date lastModifiedDate = DateUtils.parse(lastModified);
@@ -77,9 +84,8 @@ public class HttpDownloader extends Downloader {
httpReq.addHeader("If-Modified-Since", lastModified);
}
} else {
- String eTag = lastModified;
- Log.d(TAG, "addHeader(\"If-None-Match\", \"" + eTag + "\")");
- httpReq.addHeader("If-None-Match", eTag);
+ Log.d(TAG, "addHeader(\"If-None-Match\", \"" + lastModified + "\")");
+ httpReq.addHeader("If-None-Match", lastModified);
}
}
@@ -104,19 +110,20 @@ public class HttpDownloader extends Downloader {
Log.d(TAG, "Adding range header: " + request.getSoFar());
}
- Response response = null;
+ Response response;
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));
+ httpClient.setProtocols(Collections.singletonList(Protocol.HTTP_1_1));
response = httpClient.newCall(httpReq.build()).execute();
}
else {
throw e;
}
}
+
responseBody = response.body();
String contentEncodingHeader = response.header("Content-Encoding");
boolean isGzip = false;
@@ -159,6 +166,9 @@ public class HttpDownloader extends Downloader {
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
error = DownloadError.ERROR_UNAUTHORIZED;
details = String.valueOf(response.code());
+ } else if(response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
+ error = DownloadError.ERROR_FORBIDDEN;
+ details = String.valueOf(response.code());
} else {
error = DownloadError.ERROR_HTTP_DATA_ERROR;
details = String.valueOf(response.code());
@@ -172,6 +182,16 @@ public class HttpDownloader extends Downloader {
return;
}
+ if(request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ String contentType = response.header("Content-Type");
+ Log.d(TAG, "content type: " + contentType);
+ if(!contentType.startsWith("audio/") && !contentType.startsWith("video/") &&
+ !contentType.equals("application/octet-stream")) {
+ onFail(DownloadError.ERROR_FILE_TYPE, null);
+ return;
+ }
+ }
+
connection = new BufferedInputStream(responseBody.byteStream());
String contentRangeHeader = (fileExists) ? response.header("Content-Range") : null;
@@ -180,7 +200,7 @@ public class HttpDownloader extends Downloader {
&& !TextUtils.isEmpty(contentRangeHeader)) {
String start = contentRangeHeader.substring("bytes ".length(),
contentRangeHeader.indexOf("-"));
- request.setSoFar(Long.valueOf(start));
+ request.setSoFar(Long.parseLong(start));
Log.d(TAG, "Starting download at position " + request.getSoFar());
out = new RandomAccessFile(destination, "rw");
@@ -204,21 +224,18 @@ public class HttpDownloader extends Downloader {
long freeSpace = StorageUtils.getFreeSpaceAvailable();
Log.d(TAG, "Free space is " + freeSpace);
- if (request.getSize() != DownloadStatus.SIZE_UNKNOWN
- && request.getSize() > freeSpace) {
+ if (request.getSize() != DownloadStatus.SIZE_UNKNOWN && request.getSize() > freeSpace) {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
return;
}
Log.d(TAG, "Starting download");
try {
- while (!cancelled
- && (count = connection.read(buffer)) != -1) {
+ 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));
+ int progressPercent = (int)(100.0 * request.getSoFar() / request.getSize());
+ request.setProgressPercent(progressPercent);
}
} catch(IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
@@ -230,12 +247,8 @@ public class HttpDownloader extends Downloader {
// written file. This check cannot be made if compression was used
if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN &&
request.getSoFar() != request.getSize()) {
- onFail(DownloadError.ERROR_IO_ERROR,
- "Download completed but size: " +
- request.getSoFar() +
- " does not equal expected size " +
- request.getSize()
- );
+ onFail(DownloadError.ERROR_IO_ERROR, "Download completed but size: " +
+ request.getSoFar() + " does not equal expected size " + request.getSize());
return;
} else if(request.getSize() > 0 && request.getSoFar() == 0){
onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read");
@@ -279,7 +292,8 @@ public class HttpDownloader extends Downloader {
}
private void onFail(DownloadError reason, String reasonDetailed) {
- Log.d(TAG, "Download failed");
+ Log.d(TAG, "onFail() called with: " + "reason = [" + reason + "], " +
+ "reasonDetailed = [" + reasonDetailed + "]");
result.setFailed(reason, reasonDetailed);
if (request.isDeleteOnFailure()) {
cleanup();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java
new file mode 100644
index 000000000..6eb1f4118
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java
@@ -0,0 +1,32 @@
+package de.danoeh.antennapod.core.service.download;
+
+import android.support.annotation.Nullable;
+
+import java.net.Proxy;
+
+public class ProxyConfig {
+
+ public final Proxy.Type type;
+ @Nullable public final String host;
+ public final int port;
+ @Nullable public final String username;
+ @Nullable public final String password;
+
+ public final static int DEFAULT_PORT = 8080;
+
+ public static ProxyConfig direct() {
+ return new ProxyConfig(Proxy.Type.DIRECT, null, 0, null, null);
+ }
+
+ public static ProxyConfig http(String host, int port, String username, String password) {
+ return new ProxyConfig(Proxy.Type.HTTP, host, port, username, password);
+ }
+
+ public ProxyConfig(Proxy.Type type, String host, int port, String username, String password) {
+ this.type = type;
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
new file mode 100644
index 000000000..8bacac1ef
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
@@ -0,0 +1,899 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.PowerManager;
+import android.support.annotation.NonNull;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.Pair;
+import android.view.SurfaceHolder;
+
+import org.antennapod.audio.MediaPlayer;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+
+import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+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;
+import de.danoeh.antennapod.core.util.playback.VideoPlayer;
+
+/**
+ * Manages the MediaPlayer object of the PlaybackService.
+ */
+public class LocalPSMP extends PlaybackServiceMediaPlayer {
+ public static final String TAG = "LclPlaybackSvcMPlayer";
+
+ private final AudioManager audioManager;
+
+ private volatile PlayerStatus statusBeforeSeeking;
+ private volatile IPlayer mediaPlayer;
+ private volatile Playable media;
+
+ private volatile boolean stream;
+ private volatile MediaType mediaType;
+ private volatile AtomicBoolean startWhenPrepared;
+ private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
+ private volatile Pair<Integer, Integer> videoSize;
+
+ /**
+ * Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
+ * have to wait until these operations have finished.
+ */
+ private final ReentrantLock playerLock;
+ private CountDownLatch seekLatch;
+
+ private final ThreadPoolExecutor executor;
+
+ public LocalPSMP(@NonNull Context context,
+ @NonNull PSMPCallback callback) {
+ super(context, callback);
+
+ this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ this.playerLock = new ReentrantLock();
+ this.startWhenPrepared = new AtomicBoolean(false);
+ executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
+ (r, executor) -> Log.d(TAG, "Rejected execution of runnable"));
+
+ mediaPlayer = null;
+ statusBeforeSeeking = null;
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ mediaType = MediaType.UNKNOWN;
+ videoSize = null;
+ }
+
+ /**
+ * Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
+ * episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
+ * not do anything.
+ * Whether playback starts immediately depends on the given parameters. See below for more details.
+ * <p/>
+ * States:
+ * During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters.
+ * <p/>
+ * If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If
+ * 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state.
+ * <p/>
+ * If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object
+ * will enter the ERROR state.
+ * <p/>
+ * This method is executed on an internal executor service.
+ *
+ * @param playable The Playable object that is supposed to be played. This parameter must not be null.
+ * @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via
+ * getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by
+ * the Android MediaPlayer via getStreamUrl.
+ * @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the
+ * episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared
+ * for playback immediately (see 'prepareImmediately' parameter for more details)
+ * @param prepareImmediately Set to true if the method should also prepare the episode for playback.
+ */
+ @Override
+ public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
+ Log.d(TAG, "playMediaObject(...)");
+ executor.submit(() -> {
+ playerLock.lock();
+ try {
+ playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ throw e;
+ } finally {
+ playerLock.unlock();
+ }
+ });
+ }
+
+ /**
+ * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
+ * the given playable parameter is the same object as the currently playing media.
+ * <p/>
+ * This method requires the playerLock and is executed on the caller's thread.
+ *
+ * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean)
+ */
+ private void playMediaObject(@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");
+ }
+
+
+ if (media != null) {
+ if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())
+ && playerStatus == PlayerStatus.PLAYING) {
+ // episode is already playing -> ignore method call
+ Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
+ return;
+ } else {
+ // stop playback of this episode
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
+ mediaPlayer.stop();
+ }
+ // set temporarily to pause in order to update list with current position
+ if (playerStatus == PlayerStatus.PLAYING) {
+ setPlayerStatus(PlayerStatus.PAUSED, media);
+ }
+
+ smartMarkAsPlayed(media);
+
+ setPlayerStatus(PlayerStatus.INDETERMINATE, null);
+ }
+ }
+
+ this.media = playable;
+ this.stream = stream;
+ this.mediaType = media.getMediaType();
+ this.videoSize = null;
+ createMediaPlayer();
+ LocalPSMP.this.startWhenPrepared.set(startWhenPrepared);
+ setPlayerStatus(PlayerStatus.INITIALIZING, media);
+ try {
+ media.loadMetadata();
+ callback.onMediaChanged(false);
+ if (stream) {
+ mediaPlayer.setDataSource(media.getStreamUrl());
+ } else {
+ mediaPlayer.setDataSource(media.getLocalMediaUrl());
+ }
+ setPlayerStatus(PlayerStatus.INITIALIZED, media);
+
+ if (prepareImmediately) {
+ setPlayerStatus(PlayerStatus.PREPARING, media);
+ mediaPlayer.prepare();
+ onPrepared(startWhenPrepared);
+ }
+
+ } catch (Playable.PlayableException | IOException | IllegalStateException e) {
+ e.printStackTrace();
+ setPlayerStatus(PlayerStatus.ERROR, null);
+ }
+ }
+
+ /**
+ * Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
+ * nothing will happen.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void resume() {
+ executor.submit(() -> {
+ playerLock.lock();
+ resumeSync();
+ playerLock.unlock();
+ });
+ }
+
+ private void resumeSync() {
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
+ int focusGained = audioManager.requestAudioFocus(
+ audioFocusChangeListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ acquireWifiLockIfNecessary();
+ 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) {
+ int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
+ media.getPosition(),
+ media.getLastPlayedTime());
+ seekToSync(newPosition);
+ }
+ mediaPlayer.start();
+
+ setPlayerStatus(PlayerStatus.PLAYING, media);
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ media.onPlaybackStart();
+
+ } else {
+ Log.e(TAG, "Failed to request audio focus");
+ }
+ } else {
+ Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
+ }
+ }
+
+
+ /**
+ * Saves the current position and pauses playback. Note that, if audiofocus
+ * is abandoned, the lockscreen controls will also disapear.
+ * <p/>
+ * This method is executed on an internal executor service.
+ *
+ * @param abandonFocus is true if the service should release audio focus
+ * @param reinit is true if service should reinit after pausing if the media
+ * file is being streamed
+ */
+ @Override
+ public void pause(final boolean abandonFocus, final boolean reinit) {
+ executor.submit(() -> {
+ playerLock.lock();
+ releaseWifiLockIfNecessary();
+ if (playerStatus == PlayerStatus.PLAYING) {
+ Log.d(TAG, "Pausing playback.");
+ mediaPlayer.pause();
+ setPlayerStatus(PlayerStatus.PAUSED, media);
+
+ if (abandonFocus) {
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ }
+ if (stream && reinit) {
+ reinit();
+ }
+ } else {
+ Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
+ }
+
+ playerLock.unlock();
+ });
+ }
+
+ /**
+ * Prepares media player for playback if the service is in the INITALIZED
+ * state.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void prepare() {
+ executor.submit(() -> {
+ playerLock.lock();
+
+ if (playerStatus == PlayerStatus.INITIALIZED) {
+ Log.d(TAG, "Preparing media player");
+ setPlayerStatus(PlayerStatus.PREPARING, media);
+ try {
+ mediaPlayer.prepare();
+ onPrepared(startWhenPrepared.get());
+ } catch (IOException e) {
+ e.printStackTrace();
+ setPlayerStatus(PlayerStatus.ERROR, null);
+ }
+ }
+ playerLock.unlock();
+
+ });
+ }
+
+ /**
+ * Called after media player has been prepared. This method is executed on the caller's thread.
+ */
+ void onPrepared(final boolean startWhenPrepared) {
+ playerLock.lock();
+
+ if (playerStatus != PlayerStatus.PREPARING) {
+ playerLock.unlock();
+ throw new IllegalStateException("Player is not in PREPARING state");
+ }
+
+ Log.d(TAG, "Resource prepared");
+
+ if (mediaType == MediaType.VIDEO) {
+ VideoPlayer vp = (VideoPlayer) mediaPlayer;
+ videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
+ }
+
+ if (media.getPosition() > 0) {
+ seekToSync(media.getPosition());
+ }
+
+ if (media.getDuration() == 0) {
+ Log.d(TAG, "Setting duration of media");
+ media.setDuration(mediaPlayer.getDuration());
+ }
+ setPlayerStatus(PlayerStatus.PREPARED, media);
+
+ if (startWhenPrepared) {
+ resumeSync();
+ }
+
+ playerLock.unlock();
+ }
+
+ /**
+ * Resets the media player and moves it into INITIALIZED state.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void reinit() {
+ executor.submit(() -> {
+ playerLock.lock();
+ Log.d(TAG, "reinit()");
+ releaseWifiLockIfNecessary();
+ if (media != null) {
+ playMediaObject(media, true, stream, startWhenPrepared.get(), false);
+ } else if (mediaPlayer != null) {
+ mediaPlayer.reset();
+ } else {
+ Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
+ }
+ playerLock.unlock();
+ });
+ }
+
+
+ /**
+ * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
+ *
+ * @param t The position to seek to in milliseconds. t < 0 will be interpreted as t = 0
+ * <p/>
+ * This method is executed on the caller's thread.
+ */
+ private void seekToSync(int t) {
+ if (t < 0) {
+ t = 0;
+ }
+ playerLock.lock();
+
+ if (playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.PREPARED) {
+ if (!stream) {
+ statusBeforeSeeking = playerStatus;
+ setPlayerStatus(PlayerStatus.SEEKING, media);
+ }
+ 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);
+ prepare();
+ }
+ playerLock.unlock();
+ }
+
+ /**
+ * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
+ * Invalid time values (< 0) will be ignored.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void seekTo(final int t) {
+ executor.submit(() -> seekToSync(t));
+ }
+
+ /**
+ * Seek a specific position from the current position
+ *
+ * @param d offset from current position (positive or negative)
+ */
+ @Override
+ public void seekDelta(final int d) {
+ executor.submit(() -> {
+ playerLock.lock();
+ int currentPosition = getPosition();
+ if (currentPosition != INVALID_TIME) {
+ seekToSync(currentPosition + d);
+ } else {
+ Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
+ }
+
+ playerLock.unlock();
+ });
+ }
+
+ /**
+ * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
+ */
+ @Override
+ public int getDuration() {
+ if (!playerLock.tryLock()) {
+ return INVALID_TIME;
+ }
+
+ int retVal = INVALID_TIME;
+ if (playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.PREPARED) {
+ retVal = mediaPlayer.getDuration();
+ } else if (media != null && media.getDuration() > 0) {
+ retVal = media.getDuration();
+ }
+
+ playerLock.unlock();
+ return retVal;
+ }
+
+ /**
+ * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
+ */
+ @Override
+ public int getPosition() {
+ try {
+ if (!playerLock.tryLock(50, TimeUnit.MILLISECONDS)) {
+ return INVALID_TIME;
+ }
+ } catch (InterruptedException e) {
+ return INVALID_TIME;
+ }
+
+ int retVal = INVALID_TIME;
+ if (playerStatus.isAtLeast(PlayerStatus.PREPARED)) {
+ retVal = mediaPlayer.getCurrentPosition();
+ }
+ if (retVal <= 0 && media != null && media.getPosition() >= 0) {
+ retVal = media.getPosition();
+ }
+
+ playerLock.unlock();
+ Log.d(TAG, "getPosition() -> " + retVal);
+ return retVal;
+ }
+
+ @Override
+ public boolean isStartWhenPrepared() {
+ return startWhenPrepared.get();
+ }
+
+ @Override
+ public void setStartWhenPrepared(boolean startWhenPrepared) {
+ this.startWhenPrepared.set(startWhenPrepared);
+ }
+
+ /**
+ * Returns true if the playback speed can be adjusted.
+ */
+ @Override
+ public boolean canSetSpeed() {
+ boolean retVal = false;
+ if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ retVal = (mediaPlayer).canSetSpeed();
+ }
+ return retVal;
+ }
+
+ /**
+ * Sets the playback speed.
+ * This method is executed on the caller's thread.
+ */
+ private void setSpeedSync(float speed) {
+ playerLock.lock();
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ if (mediaPlayer.canSetSpeed()) {
+ mediaPlayer.setPlaybackSpeed(speed);
+ Log.d(TAG, "Playback speed was set to " + speed);
+ callback.playbackSpeedChanged(speed);
+ }
+ }
+ playerLock.unlock();
+ }
+
+ /**
+ * Sets the playback speed.
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void setSpeed(final float speed) {
+ executor.submit(() -> setSpeedSync(speed));
+ }
+
+ /**
+ * Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
+ */
+ @Override
+ public float getPlaybackSpeed() {
+ if (!playerLock.tryLock()) {
+ return 1;
+ }
+
+ float retVal = 1;
+ if ((playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.PREPARED) && mediaPlayer.canSetSpeed()) {
+ retVal = mediaPlayer.getCurrentSpeedMultiplier();
+ }
+ playerLock.unlock();
+ return retVal;
+ }
+
+ /**
+ * Sets the playback volume.
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void setVolume(final float volumeLeft, float volumeRight) {
+ executor.submit(() -> setVolumeSync(volumeLeft, volumeRight));
+ }
+
+ /**
+ * Sets the playback volume.
+ * 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
+ */
+ @Override
+ public boolean canDownmix() {
+ boolean retVal = false;
+ if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ retVal = mediaPlayer.canDownmix();
+ }
+ return retVal;
+ }
+
+ @Override
+ 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();
+ }
+
+ @Override
+ public MediaType getCurrentMediaType() {
+ return mediaType;
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return stream;
+ }
+
+
+ /**
+ * Releases internally used resources. This method should only be called when the object is not used anymore.
+ */
+ @Override
+ public void shutdown() {
+ executor.shutdown();
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ }
+ releaseWifiLockIfNecessary();
+ }
+
+ /**
+ * Releases internally used resources. This method should only be called when the object is not used anymore.
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void shutdownQuietly() {
+ executor.submit(this::shutdown);
+ executor.shutdown();
+ }
+
+ @Override
+ public void setVideoSurface(final SurfaceHolder surface) {
+ executor.submit(() -> {
+ playerLock.lock();
+ if (mediaPlayer != null) {
+ mediaPlayer.setDisplay(surface);
+ }
+ playerLock.unlock();
+ });
+ }
+
+ @Override
+ public void resetVideoSurface() {
+ executor.submit(() -> {
+ playerLock.lock();
+ if (mediaType == MediaType.VIDEO) {
+ Log.d(TAG, "Resetting video surface");
+ mediaPlayer.setDisplay(null);
+ reinit();
+ } else {
+ Log.e(TAG, "Resetting video surface for media of Audio type");
+ }
+ playerLock.unlock();
+ });
+ }
+
+ /**
+ * Return width and height of the currently playing video as a pair.
+ *
+ * @return Width and height as a Pair or null if the video size could not be determined. The method might still
+ * return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return
+ * invalid values.
+ */
+ @Override
+ public Pair<Integer, Integer> getVideoSize() {
+ if (!playerLock.tryLock()) {
+ // use cached value if lock can't be aquired
+ return videoSize;
+ }
+ Pair<Integer, Integer> res;
+ if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) {
+ res = null;
+ } else {
+ VideoPlayer vp = (VideoPlayer) mediaPlayer;
+ videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
+ res = videoSize;
+ }
+ playerLock.unlock();
+ return res;
+ }
+
+ /**
+ * Returns 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
+ */
+ @Override
+ public Playable getPlayable() {
+ return media;
+ }
+
+ @Override
+ protected void setPlayable(Playable playable) {
+ media = playable;
+ }
+
+ private IPlayer createMediaPlayer() {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ }
+ if (media == null || media.getMediaType() == MediaType.VIDEO) {
+ mediaPlayer = new VideoPlayer();
+ } else {
+ mediaPlayer = new AudioPlayer(context);
+ }
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
+ return setMediaPlayerListeners(mediaPlayer);
+ }
+
+ private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
+
+ @Override
+ public void onAudioFocusChange(final int focusChange) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+
+ // If there is an incoming call, playback should be paused permanently
+ TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final int callState = (tm != null) ? tm.getCallState() : 0;
+ Log.i(TAG, "Call state:" + callState);
+
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
+ (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) {
+ Log.d(TAG, "Lost audio focus");
+ pause(true, false);
+ callback.shouldStop();
+ } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ Log.d(TAG, "Gained audio focus");
+ if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
+ resume();
+ } else { // we ducked => raise audio level back
+ setVolumeSync(UserPreferences.getLeftVolume(),
+ UserPreferences.getRightVolume());
+ }
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ if (playerStatus == PlayerStatus.PLAYING) {
+ if (!UserPreferences.shouldPauseForFocusLoss()) {
+ Log.d(TAG, "Lost audio focus temporarily. Ducking...");
+ final float DUCK_FACTOR = 0.25f;
+ setVolumeSync(DUCK_FACTOR * UserPreferences.getLeftVolume(),
+ DUCK_FACTOR * UserPreferences.getRightVolume());
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ } else {
+ Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ }
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+ if (playerStatus == PlayerStatus.PLAYING) {
+ Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ }
+ playerLock.unlock();
+ }
+ });
+ }
+ };
+
+
+ @Override
+ public void endPlayback(final boolean wasSkipped, boolean switchingPlayers) {
+ executor.submit(() -> {
+ playerLock.lock();
+ releaseWifiLockIfNecessary();
+
+ boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
+
+ if (playerStatus != PlayerStatus.INDETERMINATE) {
+ setPlayerStatus(PlayerStatus.INDETERMINATE, media);
+ }
+ if (mediaPlayer != null) {
+ mediaPlayer.reset();
+
+ }
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers);
+
+ playerLock.unlock();
+ });
+ }
+
+ /**
+ * Moves the LocalPSMP into STOPPED state. This call is only valid if the player is currently in
+ * INDETERMINATE state, for example after a call to endPlayback.
+ * This method will only take care of changing the PlayerStatus of this object! Other tasks like
+ * abandoning audio focus have to be done with other methods.
+ */
+ @Override
+ public void stop() {
+ executor.submit(() -> {
+ playerLock.lock();
+ releaseWifiLockIfNecessary();
+
+ if (playerStatus == PlayerStatus.INDETERMINATE) {
+ setPlayerStatus(PlayerStatus.STOPPED, null);
+ } else {
+ Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
+ }
+ playerLock.unlock();
+
+ });
+ }
+
+ @Override
+ protected boolean shouldLockWifi(){
+ return stream;
+ }
+
+ private IPlayer setMediaPlayerListeners(IPlayer mp) {
+ if (mp != null && media != null) {
+ if (media.getMediaType() == MediaType.AUDIO) {
+ ((AudioPlayer) mp)
+ .setOnCompletionListener(audioCompletionListener);
+ ((AudioPlayer) mp)
+ .setOnSeekCompleteListener(audioSeekCompleteListener);
+ ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
+ ((AudioPlayer) mp)
+ .setOnBufferingUpdateListener(audioBufferingUpdateListener);
+ ((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
+ ((AudioPlayer) mp).setOnSpeedAdjustmentAvailableChangedListener(audioSetSpeedAbilityListener);
+ } else {
+ ((VideoPlayer) mp)
+ .setOnCompletionListener(videoCompletionListener);
+ ((VideoPlayer) mp)
+ .setOnSeekCompleteListener(videoSeekCompleteListener);
+ ((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
+ ((VideoPlayer) mp)
+ .setOnBufferingUpdateListener(videoBufferingUpdateListener);
+ ((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
+ }
+ }
+ return mp;
+ }
+
+ private final MediaPlayer.OnCompletionListener audioCompletionListener =
+ mp -> genericOnCompletion();
+
+ private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener =
+ mp -> genericOnCompletion();
+
+ private void genericOnCompletion() {
+ endPlayback(false, false);
+ }
+
+ private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =
+ (mp, percent) -> genericOnBufferingUpdate(percent);
+
+ private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener =
+ (mp, percent) -> genericOnBufferingUpdate(percent);
+
+ private void genericOnBufferingUpdate(int percent) {
+ callback.onBufferingUpdate(percent);
+ }
+
+ private final MediaPlayer.OnInfoListener audioInfoListener =
+ (mp, what, extra) -> genericInfoListener(what);
+
+ private final android.media.MediaPlayer.OnInfoListener videoInfoListener =
+ (mp, what, extra) -> genericInfoListener(what);
+
+ private boolean genericInfoListener(int what) {
+ return callback.onMediaPlayerInfo(what, 0);
+ }
+
+ private final MediaPlayer.OnSpeedAdjustmentAvailableChangedListener audioSetSpeedAbilityListener =
+ (arg0, speedAdjustmentAvailable) -> callback.setSpeedAbilityChanged();
+
+
+ private final MediaPlayer.OnErrorListener audioErrorListener =
+ (mp, what, extra) -> {
+ if(mp.canFallback()) {
+ mp.fallback();
+ return true;
+ } else {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnErrorListener videoErrorListener = this::genericOnError;
+
+ private boolean genericOnError(Object inObj, int what, int extra) {
+ return callback.onMediaPlayerError(inObj, what, extra);
+ }
+
+ private final MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener =
+ mp -> genericSeekCompleteListener();
+
+ private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener =
+ mp -> genericSeekCompleteListener();
+
+ private void genericSeekCompleteListener() {
+ 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();
+ }
+}
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
deleted file mode 100644
index 7d06390f2..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/MediaButtonIntentReceiver.java
+++ /dev/null
@@ -1,26 +0,0 @@
-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 2be075a92..e6d41879c 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
@@ -6,6 +6,7 @@ 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;
@@ -14,25 +15,42 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.MediaPlayer;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Vibrator;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.NotificationCompat;
+import android.support.v7.media.MediaRouter;
import android.text.TextUtils;
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 android.widget.Toast;
import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.target.Target;
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
import java.util.List;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.cast.CastConsumer;
+import de.danoeh.antennapod.core.cast.CastManager;
+import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -47,8 +65,10 @@ 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.NetworkUtils;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
/**
@@ -67,6 +87,10 @@ public class PlaybackService extends Service {
*/
public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra";
/**
+ * True if cast session should disconnect.
+ */
+ public static final String EXTRA_CAST_DISCONNECT = "extra.de.danoeh.antennapod.core.service.castDisconnect";
+ /**
* True if media should be streamed.
*/
public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.core.service.shouldStream";
@@ -116,6 +140,7 @@ public class PlaybackService extends Service {
*/
public static final int EXTRA_CODE_AUDIO = 1;
public static final int EXTRA_CODE_VIDEO = 2;
+ public static final int EXTRA_CODE_CAST = 3;
public static final int NOTIFICATION_TYPE_ERROR = 0;
public static final int NOTIFICATION_TYPE_INFO = 1;
@@ -142,12 +167,28 @@ public class PlaybackService extends Service {
public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8;
/**
+ * Ability to set the playback speed has changed
+ */
+ public static final int NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED = 9;
+
+ /**
+ * Send a message to the user (with provided String resource id)
+ */
+ public static final int NOTIFICATION_TYPE_SHOW_TOAST = 10;
+
+ /**
* Returned by getPositionSafe() or getDurationSafe() if the playbackService
* is in an invalid state.
*/
public static final int INVALID_TIME = -1;
/**
+ * Time in seconds during which the CastManager will try to reconnect to the Cast Device after
+ * the Wifi Connection is regained.
+ */
+ private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15;
+
+ /**
* Is true if service is running.
*/
public static boolean isRunning = false;
@@ -159,12 +200,30 @@ public class PlaybackService extends Service {
* Is true if the service was running, but paused due to headphone disconnect
*/
public static boolean transientPause = false;
+ /**
+ * Is true if a Cast Device is connected to the service.
+ */
+ private static volatile boolean isCasting = false;
+ /**
+ * Stores the state of the cast playback just before it disconnects.
+ */
+ private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection;
+
+ private boolean wifiConnectivity = true;
+ private BroadcastReceiver wifiBroadcastReceiver;
private static final int NOTIFICATION_ID = 1;
private PlaybackServiceMediaPlayer mediaPlayer;
private PlaybackServiceTaskManager taskManager;
+ private CastManager castManager;
+ private MediaRouter mediaRouter;
+ /**
+ * Only used for Lollipop notifications.
+ */
+ private MediaSessionCompat mediaSession;
+
private int startPosition;
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
@@ -190,12 +249,12 @@ public class PlaybackService extends Service {
*/
public static Intent getPlayerActivityIntent(Context context) {
if (isRunning) {
- return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, currentMediaType);
+ return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, currentMediaType, isCasting);
} else {
if (PlaybackPreferences.getCurrentEpisodeIsVideo()) {
- return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.VIDEO);
+ return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.VIDEO, isCasting);
} else {
- return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.AUDIO);
+ return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.AUDIO, isCasting);
}
}
}
@@ -206,7 +265,7 @@ public class PlaybackService extends Service {
*/
public static Intent getPlayerActivityIntent(Context context, Playable media) {
MediaType mt = media.getMediaType();
- return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, mt);
+ return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, mt, isCasting);
}
@Override
@@ -232,8 +291,45 @@ public class PlaybackService extends Service {
registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(
ACTION_RESUME_PLAY_CURRENT_EPISODE));
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
- mediaPlayer = new PlaybackServiceMediaPlayer(this, mediaPlayerCallback);
+ mediaRouter = MediaRouter.getInstance(getApplicationContext());
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .registerOnSharedPreferenceChangeListener(prefListener);
+
+ ComponentName eventReceiver = new ComponentName(getApplicationContext(),
+ MediaButtonReceiver.class);
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(eventReceiver);
+ PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent);
+
+ try {
+ mediaSession.setCallback(sessionCallback);
+ mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ } 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();
+ }
+
+ castManager = CastManager.getInstance();
+ castManager.addCastConsumer(castConsumer);
+ isCasting = castManager.isConnected();
+ if (isCasting) {
+ if (UserPreferences.isCastEnabled()) {
+ onCastAppConnected(false);
+ } else {
+ castManager.disconnect();
+ }
+ } else {
+ mediaPlayer = new LocalPSMP(this, mediaPlayerCallback);
+ }
+
+ mediaSession.setActive(true);
}
@Override
@@ -244,6 +340,11 @@ public class PlaybackService extends Service {
started = false;
currentMediaType = MediaType.UNKNOWN;
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .unregisterOnSharedPreferenceChangeListener(prefListener);
+ if (mediaSession != null) {
+ mediaSession.release();
+ }
unregisterReceiver(headsetDisconnected);
unregisterReceiver(shutdownReceiver);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
@@ -253,6 +354,8 @@ public class PlaybackService extends Service {
unregisterReceiver(skipCurrentEpisodeReceiver);
unregisterReceiver(pausePlayCurrentEpisodeReceiver);
unregisterReceiver(pauseResumeCurrentEpisodeReceiver);
+ castManager.removeCastConsumer(castConsumer);
+ unregisterWifiBroadcastReceiver();
mediaPlayer.shutdown();
taskManager.shutdown();
}
@@ -269,10 +372,12 @@ public class PlaybackService extends Service {
Log.d(TAG, "OnStartCommand called");
final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
+ final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false);
final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
- if (keycode == -1 && playable == null) {
+ if (keycode == -1 && playable == null && !castDisconnect) {
Log.e(TAG, "PlaybackService was started with no arguments");
stopSelf();
+ return Service.START_REDELIVER_INTENT;
}
if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
@@ -282,7 +387,10 @@ public class PlaybackService extends Service {
if (keycode != -1) {
Log.d(TAG, "Received media button event");
- handleKeycode(keycode);
+ handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE,
+ InputDevice.SOURCE_CLASS_NONE));
+ } else if (castDisconnect) {
+ castManager.disconnect();
} else {
started = true;
boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
@@ -290,6 +398,10 @@ public class PlaybackService extends Service {
boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ //If the user asks to play External Media, the casting session, if on, should end.
+ if (playable instanceof ExternalMedia) {
+ castManager.disconnect();
+ }
mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
}
}
@@ -300,7 +412,7 @@ public class PlaybackService extends Service {
/**
* Handles media button events
*/
- private void handleKeycode(int keycode) {
+ private void handleKeycode(int keycode, int source) {
Log.d(TAG, "Handling keycode: " + keycode);
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
@@ -308,11 +420,7 @@ public class PlaybackService extends Service {
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (status == PlayerStatus.PLAYING) {
- if (UserPreferences.isPersistNotify()) {
- mediaPlayer.pause(false, true);
- } else {
- mediaPlayer.pause(true, true);
- }
+ mediaPlayer.pause(!UserPreferences.isPersistNotify(), true);
} else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
mediaPlayer.resume();
} else if (status == PlayerStatus.PREPARING) {
@@ -332,17 +440,21 @@ public class PlaybackService extends Service {
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
if (status == PlayerStatus.PLAYING) {
- mediaPlayer.pause(false, true);
- }
- if (UserPreferences.isPersistNotify()) {
- mediaPlayer.pause(false, true);
- } else {
- mediaPlayer.pause(true, true);
+ mediaPlayer.pause(!UserPreferences.isPersistNotify(), true);
}
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
- mediaPlayer.endPlayback(true);
+ if(source == InputDevice.SOURCE_CLASS_NONE ||
+ UserPreferences.shouldHardwareButtonSkip()) {
+ // assume the skip command comes from a notification or the lockscreen
+ // a >| skip button should actually skip
+ mediaPlayer.endPlayback(true, false);
+ } else {
+ // assume skip command comes from a (bluetooth) media button
+ // user actually wants to fast-forward
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
+ }
break;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000);
@@ -360,6 +472,7 @@ public class PlaybackService extends Service {
stopForeground(true); // gets rid of persistent notification
break;
default:
+ Log.d(TAG, "Unhandled key code: " + keycode);
if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
String message = String.format(getResources().getString(R.string.unknown_media_key), keycode);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
@@ -386,7 +499,7 @@ public class PlaybackService extends Service {
}
public void notifyVideoSurfaceAbandoned() {
- stopForeground(true);
+ stopForeground(!UserPreferences.isPersistNotify());
mediaPlayer.resetVideoSurface();
}
@@ -434,6 +547,7 @@ public class PlaybackService extends Service {
@Override
public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) {
currentMediaType = mediaPlayer.getCurrentMediaType();
+ updateMediaSession(newInfo.playerStatus);
switch (newInfo.playerStatus) {
case INITIALIZED:
writePlaybackPreferences();
@@ -447,12 +561,13 @@ public class PlaybackService extends Service {
taskManager.cancelPositionSaver();
saveCurrentPosition(false, 0);
taskManager.cancelWidgetUpdater();
- if (UserPreferences.isPersistNotify() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ if ((UserPreferences.isPersistNotify() || isCasting) &&
+ android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// do not remove notification on pause based on user pref and whether android version supports expanded notifications
// Change [Play] button to [Pause]
setupNotification(newInfo);
- } else if (!UserPreferences.isPersistNotify()) {
- // remove notifcation on pause
+ } else if (!UserPreferences.isPersistNotify() && !isCasting) {
+ // remove notification on pause
stopForeground(true);
}
writePlayerStatusPlaybackPreferences();
@@ -512,8 +627,11 @@ public class PlaybackService extends Service {
@Override
public void playbackSpeedChanged(float s) {
- sendNotificationBroadcast(
- NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
+ sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
+ }
+
+ public void setSpeedAbilityChanged() {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED, 0);
}
@Override
@@ -522,7 +640,16 @@ public class PlaybackService extends Service {
}
@Override
- public boolean onMediaPlayerInfo(int code) {
+ public void onMediaChanged(boolean reloadUI) {
+ Log.d(TAG, "reloadUI callback reached");
+ if (reloadUI) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ }
+ PlaybackService.this.updateMediaSessionMetadata(getPlayable());
+ }
+
+ @Override
+ public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) {
switch (code) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
@@ -530,6 +657,12 @@ public class PlaybackService extends Service {
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0);
return true;
+ case RemotePSMP.CAST_ERROR:
+ sendNotificationBroadcast(NOTIFICATION_TYPE_SHOW_TOAST, resourceId);
+ return true;
+ case RemotePSMP.CAST_ERROR_PRIORITY_HIGH:
+ Toast.makeText(PlaybackService.this, resourceId, Toast.LENGTH_SHORT).show();
+ return true;
default:
return false;
}
@@ -549,16 +682,15 @@ public class PlaybackService extends Service {
}
@Override
- public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) {
- PlaybackService.this.endPlayback(playNextEpisode, wasSkipped);
+ public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
+ PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers);
return true;
}
};
- private void endPlayback(boolean playNextEpisode, boolean wasSkipped) {
- Log.d(TAG, "Playback ended");
+ private void endPlayback(final Playable playable, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
+ Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": ""));
- final Playable playable = mediaPlayer.getPlayable();
if (playable == null) {
Log.e(TAG, "Cannot end playback: media was null");
return;
@@ -569,30 +701,39 @@ public class PlaybackService extends Service {
boolean isInQueue = false;
FeedItem nextItem = null;
- if (playable instanceof FeedMedia) {
+ if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) {
FeedMedia media = (FeedMedia) playable;
FeedItem item = media.getItem();
- try {
- final List<FeedItem> queue = taskManager.getQueue();
- isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
- nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
- } catch (InterruptedException e) {
- e.printStackTrace();
- // isInQueue remains false
- }
+ if (!switchingPlayers) {
+ try {
+ final List<FeedItem> queue = taskManager.getQueue();
+ isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
+ nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ // isInQueue remains false
+ }
+
+ boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode();
- 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 (!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);
+ }
- if (isInQueue) {
- DBWriter.removeQueueItem(PlaybackService.this, item, true);
+ // Delete episode if enabled
+ if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
+ DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
+ Log.d(TAG, "Episode Deleted");
+ }
}
}
+
DBWriter.addItemToPlaybackHistory(media);
// auto-flattr if enabled
@@ -600,12 +741,6 @@ public class PlaybackService extends Service {
DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item);
}
- // Delete episode if enabled
- if(item.getFeed().getPreferences().getCurrentAutoDelete() && !shouldKeep ) {
- DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
- Log.d(TAG, "Episode Deleted");
- }
-
// gpodder play action
if(GpodnetPreferences.loggedIn()) {
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
@@ -619,46 +754,49 @@ public class PlaybackService extends Service {
}
}
- // Load next episode if previous episode was in the queue and if there
- // is an episode in the queue left.
- // Start playback immediately if continuous playback is enabled
- Playable nextMedia = null;
- boolean loadNextItem = ClientConfig.playbackServiceCallbacks.useQueue() &&
- isInQueue &&
- nextItem != null;
-
- playNextEpisode = playNextEpisode &&
- loadNextItem &&
- UserPreferences.isFollowQueue();
-
- if (loadNextItem) {
- Log.d(TAG, "Loading next item in queue");
- nextMedia = nextItem.getMedia();
- }
- final boolean prepareImmediately;
- final boolean startWhenPrepared;
- final boolean stream;
+ if (!switchingPlayers) {
+ // Load next episode if previous episode was in the queue and if there
+ // is an episode in the queue left.
+ // Start playback immediately if continuous playback is enabled
+ Playable nextMedia = null;
+ boolean loadNextItem = ClientConfig.playbackServiceCallbacks.useQueue() &&
+ isInQueue &&
+ nextItem != null;
+
+ playNextEpisode = playNextEpisode &&
+ loadNextItem &&
+ UserPreferences.isFollowQueue();
+
+ if (loadNextItem) {
+ Log.d(TAG, "Loading next item in queue");
+ nextMedia = nextItem.getMedia();
+ }
+ final boolean prepareImmediately;
+ final boolean startWhenPrepared;
+ final boolean stream;
- if (playNextEpisode) {
- Log.d(TAG, "Playback of next episode will start immediately.");
- prepareImmediately = startWhenPrepared = true;
- } else {
- Log.d(TAG, "No more episodes available to play");
- prepareImmediately = startWhenPrepared = false;
- stopForeground(true);
- stopWidgetUpdater();
- }
+ if (playNextEpisode) {
+ Log.d(TAG, "Playback of next episode will start immediately.");
+ prepareImmediately = startWhenPrepared = true;
+ } else {
+ Log.d(TAG, "No more episodes available to play");
+ prepareImmediately = startWhenPrepared = false;
+ stopForeground(true);
+ stopWidgetUpdater();
+ }
- writePlaybackPreferencesNoMediaPlaying();
- if (nextMedia != null) {
- stream = !nextMedia.localFileAvailable();
- mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
- (nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
- } else {
- sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
- mediaPlayer.stop();
- //stopSelf();
+ writePlaybackPreferencesNoMediaPlaying();
+ if (nextMedia != null) {
+ stream = !nextMedia.localFileAvailable();
+ mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
+ isCasting ? EXTRA_CODE_CAST :
+ (nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
+ } else {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
+ mediaPlayer.stop();
+ //stopSelf();
+ }
}
}
@@ -783,6 +921,112 @@ public class PlaybackService extends Service {
}
/**
+ * Updates the Media Session for the corresponding status.
+ * @param playerStatus the current {@link PlayerStatus}
+ */
+ private void updateMediaSession(final PlayerStatus playerStatus) {
+ PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder();
+
+ int state;
+ if (playerStatus != null) {
+ switch (playerStatus) {
+ case PLAYING:
+ state = PlaybackStateCompat.STATE_PLAYING;
+ break;
+ case PREPARED:
+ case PAUSED:
+ state = PlaybackStateCompat.STATE_PAUSED;
+ break;
+ case STOPPED:
+ state = PlaybackStateCompat.STATE_STOPPED;
+ break;
+ case SEEKING:
+ state = PlaybackStateCompat.STATE_FAST_FORWARDING;
+ break;
+ case PREPARING:
+ case INITIALIZING:
+ state = PlaybackStateCompat.STATE_CONNECTING;
+ break;
+ case INITIALIZED:
+ case INDETERMINATE:
+ state = PlaybackStateCompat.STATE_NONE;
+ break;
+ case ERROR:
+ state = PlaybackStateCompat.STATE_ERROR;
+ break;
+ default:
+ state = PlaybackStateCompat.STATE_NONE;
+ break;
+ }
+ } else {
+ state = PlaybackStateCompat.STATE_NONE;
+ }
+ sessionState.setState(state, mediaPlayer.getPosition(), mediaPlayer.getPlaybackSpeed());
+ sessionState.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE
+ | PlaybackStateCompat.ACTION_REWIND
+ | PlaybackStateCompat.ACTION_FAST_FORWARD
+ | PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
+ mediaSession.setPlaybackState(sessionState.build());
+ }
+
+ /**
+ * Used by updateMediaSessionMetadata to load notification data in another thread.
+ */
+ private Thread mediaSessionSetupThread;
+
+ private void updateMediaSessionMetadata(final Playable p) {
+ if (p == null || mediaSession == null) {
+ return;
+ }
+ if (mediaSessionSetupThread != null) {
+ mediaSessionSetupThread.interrupt();
+ }
+
+ Runnable mediaSessionSetupTask = () -> {
+ 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 {
+ if (isCasting) {
+ Bitmap art = Glide.with(this)
+ .load(p.getImageUri())
+ .asBitmap()
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ .get();
+ builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art);
+ } else {
+ WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Bitmap art = Glide.with(this)
+ .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));
+ }
+ }
+ if (!Thread.currentThread().isInterrupted() && started) {
+ mediaSession.setMetadata(builder.build());
+ }
+ };
+
+ mediaSessionSetupThread = new Thread(mediaSessionSetupTask);
+ mediaSessionSetupThread.start();
+ }
+
+ /**
* Used by setupNotification to load notification data in another thread.
*/
private Thread notificationSetupThread;
@@ -816,8 +1060,8 @@ public class PlaybackService extends Service {
.centerCrop()
.into(iconSize, iconSize)
.get();
- } catch(Throwable tr) {
- Log.e(TAG, Log.getStackTraceString(tr));
+ } catch (Throwable tr) {
+ Log.e(TAG, "Error loading the media icon for the notification", tr);
}
}
}
@@ -835,7 +1079,7 @@ public class PlaybackService extends Service {
if (!Thread.currentThread().isInterrupted() && started && info.playable != null) {
String contentText = info.playable.getEpisodeTitle();
String contentTitle = info.playable.getFeedTitle();
- Notification notification = null;
+ Notification notification;
// Builder is v7, even if some not overwritten methods return its parent's v4 interface
NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(
@@ -850,15 +1094,28 @@ public class PlaybackService extends Service {
.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
+ if (isCasting) {
+ Intent stopCastingIntent = new Intent(PlaybackService.this, PlaybackService.class);
+ stopCastingIntent.putExtra(EXTRA_CAST_DISCONNECT, true);
+ PendingIntent stopCastingPendingIntent = PendingIntent.getService(PlaybackService.this,
+ numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ notificationBuilder.addAction(R.drawable.ic_media_cast_disconnect,
+ getString(R.string.cast_disconnect_label),
+ stopCastingPendingIntent);
+ numActions++;
+ }
+
// 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);
+ if(UserPreferences.showRewindOnCompactNotification()) {
+ compactActionList.add(numActions);
+ }
numActions++;
if (playerStatus == PlayerStatus.PLAYING) {
@@ -883,6 +1140,9 @@ public class PlaybackService extends Service {
notificationBuilder.addAction(android.R.drawable.ic_media_ff,
getString(R.string.fast_forward_label),
ffButtonPendingIntent);
+ if(UserPreferences.showFastForwardOnCompactNotification()) {
+ compactActionList.add(numActions);
+ }
numActions++;
if (UserPreferences.isFollowQueue()) {
@@ -891,13 +1151,16 @@ public class PlaybackService extends Service {
notificationBuilder.addAction(android.R.drawable.ic_media_next,
getString(R.string.skip_episode_label),
skipButtonPendingIntent);
- compactActionList.add(numActions++);
+ if(UserPreferences.showSkipOnCompactNotification()) {
+ compactActionList.add(numActions);
+ }
+ numActions++;
}
PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_STOP, numActions);
notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle()
- .setMediaSession(mediaPlayer.getSessionToken())
+ .setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(compactActionList.toArray())
.setShowCancelButton(true)
.setCancelButtonIntent(stopButtonPendingIntent))
@@ -908,7 +1171,8 @@ public class PlaybackService extends Service {
if (playerStatus == PlayerStatus.PLAYING ||
playerStatus == PlayerStatus.PREPARING ||
- playerStatus == PlayerStatus.SEEKING) {
+ playerStatus == PlayerStatus.SEEKING ||
+ isCasting) {
startForeground(NOTIFICATION_ID, notification);
} else {
stopForeground(false);
@@ -960,11 +1224,10 @@ public class PlaybackService extends Service {
DBTasks.flattrItemIfLoggedIn(this, item);
}
}
- playable.saveCurrentPosition(PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext()),
+ playable.saveCurrentPosition(
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext()),
position,
- System.currentTimeMillis()
- );
+ System.currentTimeMillis());
}
}
@@ -1073,11 +1336,7 @@ public class PlaybackService extends Service {
if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
transientPause = true;
}
- if (UserPreferences.isPersistNotify()) {
- mediaPlayer.pause(false, true);
- } else {
- mediaPlayer.pause(true, true);
- }
+ mediaPlayer.pause(!UserPreferences.isPersistNotify(), true);
}
}
@@ -1116,7 +1375,7 @@ public class PlaybackService extends Service {
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
- mediaPlayer.endPlayback(true);
+ mediaPlayer.endPlayback(true, false);
}
}
};
@@ -1145,6 +1404,10 @@ public class PlaybackService extends Service {
return currentMediaType;
}
+ public static boolean isCasting() {
+ return isCasting;
+ }
+
public void resume() {
mediaPlayer.resume();
}
@@ -1233,7 +1496,7 @@ public class PlaybackService extends Service {
}
/**
- * @see de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer#seekToChapter(de.danoeh.antennapod.core.feed.Chapter)
+ * @see LocalPSMP#seekToChapter(de.danoeh.antennapod.core.feed.Chapter)
*/
public void seekToChapter(Chapter c) {
mediaPlayer.seekToChapter(c);
@@ -1271,4 +1534,247 @@ public class PlaybackService extends Service {
return false;
}
}
+
+ private CastConsumer castConsumer = new DefaultCastConsumer() {
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
+ PlaybackService.this.onCastAppConnected(wasLaunched);
+ }
+
+ @Override
+ public void onDisconnectionReason(int reason) {
+ Log.d(TAG, "onDisconnectionReason() with code " + reason);
+ // This is our final chance to update the underlying stream position
+ // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer
+ // is disconnected and hence we update our local value of stream position
+ // to the latest position.
+ if (mediaPlayer != null) {
+ saveCurrentPosition(false, 0);
+ infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo();
+ if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT &&
+ infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) {
+ // If it's NOT based on user action, we shouldn't automatically resume local playback
+ infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED;
+ }
+ }
+ }
+
+ @Override
+ public void onDisconnected() {
+ Log.d(TAG, "onDisconnected()");
+ isCasting = false;
+ PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection;
+ infoBeforeCastDisconnection = null;
+ if (info == null && mediaPlayer != null) {
+ info = mediaPlayer.getPSMPInfo();
+ }
+ if (info == null) {
+ info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null);
+ }
+ switchMediaPlayer(new LocalPSMP(PlaybackService.this, mediaPlayerCallback),
+ info, true);
+ if (info.playable != null) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
+ info.playable.getMediaType() == MediaType.AUDIO ? EXTRA_CODE_AUDIO : EXTRA_CODE_VIDEO);
+ } else {
+ Log.d(TAG, "Cast session disconnected, but no current media");
+ sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
+ }
+ // hardware volume buttons control the local device volume
+ mediaRouter.setMediaSessionCompat(null);
+ unregisterWifiBroadcastReceiver();
+ PlayerStatus status = info.playerStatus;
+ if ((status == PlayerStatus.PLAYING ||
+ status == PlayerStatus.SEEKING ||
+ status == PlayerStatus.PREPARING ||
+ UserPreferences.isPersistNotify()) &&
+ android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ setupNotification(info);
+ } else if (!UserPreferences.isPersistNotify()){
+ stopForeground(true);
+ }
+ }
+ };
+
+ private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
+
+ private static final String TAG = "MediaSessionCompat";
+
+ @Override
+ public void onPlay() {
+ Log.d(TAG, "onPlay()");
+ PlayerStatus status = getStatus();
+ if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
+ resume();
+ } else if (status == PlayerStatus.INITIALIZED) {
+ setStartWhenPrepared(true);
+ prepare();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ Log.d(TAG, "onPause()");
+ if (getStatus() == PlayerStatus.PLAYING) {
+ pause(false, true);
+ }
+ if (UserPreferences.isPersistNotify()) {
+ pause(false, true);
+ } else {
+ pause(true, true);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ Log.d(TAG, "onStop()");
+ mediaPlayer.stop();
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ Log.d(TAG, "onSkipToPrevious()");
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ }
+
+ @Override
+ public void onRewind() {
+ Log.d(TAG, "onRewind()");
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ }
+
+ @Override
+ public void onFastForward() {
+ Log.d(TAG, "onFastForward()");
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
+ }
+
+ @Override
+ public void onSkipToNext() {
+ Log.d(TAG, "onSkipToNext()");
+ if(UserPreferences.shouldHardwareButtonSkip()) {
+ mediaPlayer.endPlayback(true, false);
+ } else {
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
+ }
+ }
+
+
+ @Override
+ public void onSeekTo(long pos) {
+ Log.d(TAG, "onSeekTo()");
+ seekTo((int) pos);
+ }
+
+ @Override
+ public boolean onMediaButtonEvent(final Intent mediaButton) {
+ Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
+ if (mediaButton != null) {
+ KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT);
+ if (keyEvent != null &&
+ keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
+ keyEvent.getRepeatCount() == 0){
+ handleKeycode(keyEvent.getKeyCode(), keyEvent.getSource());
+ }
+ }
+ return false;
+ }
+ };
+
+ private void onCastAppConnected(boolean wasLaunched) {
+ Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined"));
+ isCasting = true;
+ PlaybackServiceMediaPlayer.PSMPInfo info = null;
+ if (mediaPlayer != null) {
+ info = mediaPlayer.getPSMPInfo();
+ if (info.playerStatus == PlayerStatus.PLAYING) {
+ // could be pause, but this way we make sure the new player will get the correct position,
+ // since pause runs asynchronously and we could be directing the new player to play even before
+ // the old player gives us back the position.
+ saveCurrentPosition(false, 0);
+ }
+ }
+ if (info == null) {
+ info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null);
+ }
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, EXTRA_CODE_CAST);
+ switchMediaPlayer(new RemotePSMP(PlaybackService.this, mediaPlayerCallback),
+ info,
+ wasLaunched);
+ // hardware volume buttons control the remote device volume
+ mediaRouter.setMediaSessionCompat(mediaSession);
+ registerWifiBroadcastReceiver();
+ setupNotification(info);
+ }
+
+ private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer,
+ @NonNull PlaybackServiceMediaPlayer.PSMPInfo info,
+ boolean wasLaunched) {
+ if (mediaPlayer != null) {
+ mediaPlayer.endPlayback(true, true);
+ mediaPlayer.shutdownQuietly();
+ }
+ mediaPlayer = newPlayer;
+ Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName());
+ if (!wasLaunched) {
+ PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo();
+ if (candidate.playable != null &&
+ candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) {
+ // do not automatically send new media to cast device
+ info.playable = null;
+ }
+ }
+ if (info.playable != null) {
+ mediaPlayer.playMediaObject(info.playable,
+ !info.playable.localFileAvailable(),
+ info.playerStatus == PlayerStatus.PLAYING,
+ info.playerStatus.isAtLeast(PlayerStatus.PREPARING));
+ }
+ }
+
+ private void registerWifiBroadcastReceiver() {
+ if (wifiBroadcastReceiver != null) {
+ return;
+ }
+ wifiBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ boolean isConnected = info.isConnected();
+ //apparently this method gets called twice when a change happens, but one run is enough.
+ if (isConnected && !wifiConnectivity) {
+ wifiConnectivity = true;
+ castManager.startCastDiscovery();
+ castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid());
+ } else {
+ wifiConnectivity = isConnected;
+ }
+ }
+ }
+ };
+ registerReceiver(wifiBroadcastReceiver,
+ new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));
+ }
+
+ private void unregisterWifiBroadcastReceiver() {
+ if (wifiBroadcastReceiver != null) {
+ unregisterReceiver(wifiBroadcastReceiver);
+ wifiBroadcastReceiver = null;
+ }
+ }
+
+ private SharedPreferences.OnSharedPreferenceChangeListener prefListener =
+ (sharedPreferences, key) -> {
+ if (UserPreferences.PREF_CAST_ENABLED.equals(key)) {
+ if (!UserPreferences.isCastEnabled()) {
+ if (castManager.isConnecting() || castManager.isConnected()) {
+ Log.d(TAG, "Disconnecting cast device due to a change in user preferences");
+ castManager.disconnect();
+ }
+ }
+ } else if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) {
+ updateMediaSessionMetadata(getPlayable());
+ }
+ };
}
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 a82e82506..206eccb65 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,56 +1,31 @@
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.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.support.annotation.StringRes;
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 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;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.ReentrantLock;
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.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;
-import de.danoeh.antennapod.core.util.playback.VideoPlayer;
+
+/*
+ * An inconvenience of an implementation like this is that some members and methods that once were
+ * private are now protected, allowing for access from classes of the same package, namely
+ * PlaybackService. A workaround would be to move this to a dedicated package.
+ */
/**
- * Manages the MediaPlayer object of the PlaybackService.
+ * Abstract class that allows for different implementations of the PlaybackServiceMediaPlayer for local
+ * and remote (cast devices) playback.
*/
-public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPreferenceChangeListener {
+public abstract class PlaybackServiceMediaPlayer {
public static final String TAG = "PlaybackSvcMediaPlayer";
/**
@@ -58,93 +33,22 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
*/
public static final int INVALID_TIME = -1;
- private final AudioManager audioManager;
-
- private volatile PlayerStatus playerStatus;
- private volatile PlayerStatus statusBeforeSeeking;
- private volatile IPlayer mediaPlayer;
- private volatile Playable media;
- /**
- * Only used for Lollipop notifications.
- */
- private final MediaSessionCompat mediaSession;
-
- private volatile boolean stream;
- private volatile MediaType mediaType;
- private volatile AtomicBoolean startWhenPrepared;
- private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
- private volatile Pair<Integer, Integer> videoSize;
-
- /**
- * Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
- * have to wait until these operations have finished.
- */
- private final ReentrantLock playerLock;
- private CountDownLatch seekLatch;
-
- private final PSMPCallback callback;
- private final Context context;
-
- private final ThreadPoolExecutor executor;
+ protected volatile PlayerStatus playerStatus;
/**
* A wifi-lock that is acquired if the media file is being streamed.
*/
private WifiManager.WifiLock wifiLock;
+ protected final PSMPCallback callback;
+ protected final Context context;
+
public PlaybackServiceMediaPlayer(@NonNull Context context,
- @NonNull PSMPCallback callback) {
+ @NonNull PSMPCallback callback){
this.context = context;
this.callback = callback;
- this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- this.playerLock = new ReentrantLock();
- this.startWhenPrepared = new AtomicBoolean(false);
- executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>(),
- new RejectedExecutionHandler() {
- @Override
- public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
- Log.d(TAG, "Rejected execution of runnable");
- }
- }
- );
- 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;
- pausedBecauseOfTransientAudiofocusLoss = false;
- 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();
- }
}
/**
@@ -173,148 +77,7 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* 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(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
- Log.d(TAG, "playMediaObject(...)");
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- try {
- playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
- } catch (RuntimeException e) {
- e.printStackTrace();
- throw e;
- } finally {
- playerLock.unlock();
- }
- }
- });
- }
-
- /**
- * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
- * the given playable parameter is the same object as the currently playing media.
- * <p/>
- * This method requires the playerLock and is executed on the caller's thread.
- *
- * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean)
- */
- private void playMediaObject(@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");
- }
-
-
- if (media != null) {
- if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())
- && playerStatus == PlayerStatus.PLAYING) {
- // episode is already playing -> ignore method call
- Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
- return;
- } else {
- // stop playback of this episode
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
- mediaPlayer.stop();
- }
- // set temporarily to pause in order to update list with current position
- if (playerStatus == PlayerStatus.PLAYING) {
- setPlayerStatus(PlayerStatus.PAUSED, media);
- }
-
- // smart mark as played
- if(media != null && media instanceof FeedMedia) {
- FeedMedia oldMedia = (FeedMedia) media;
- if(oldMedia.hasAlmostEnded()) {
- Log.d(TAG, "smart mark as read");
- FeedItem item = oldMedia.getItem();
- DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
- DBWriter.removeQueueItem(context, item, false);
- DBWriter.addItemToPlaybackHistory(oldMedia);
- if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
- Log.d(TAG, "Delete " + oldMedia.toString());
- DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
- }
- }
- }
-
- setPlayerStatus(PlayerStatus.INDETERMINATE, null);
- }
- }
-
- this.media = playable;
- this.stream = stream;
- this.mediaType = media.getMediaType();
- this.videoSize = null;
- createMediaPlayer();
- PlaybackServiceMediaPlayer.this.startWhenPrepared.set(startWhenPrepared);
- setPlayerStatus(PlayerStatus.INITIALIZING, media);
- try {
- media.loadMetadata();
- updateMediaSessionMetadata();
- if (stream) {
- mediaPlayer.setDataSource(media.getStreamUrl());
- } else {
- mediaPlayer.setDataSource(media.getLocalMediaUrl());
- }
- setPlayerStatus(PlayerStatus.INITIALIZED, media);
-
- if (mediaType == MediaType.VIDEO) {
- VideoPlayer vp = (VideoPlayer) mediaPlayer;
- // vp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
- }
-
- if (prepareImmediately) {
- setPlayerStatus(PlayerStatus.PREPARING, media);
- mediaPlayer.prepare();
- onPrepared(startWhenPrepared);
- }
-
- } catch (Playable.PlayableException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- } catch (IOException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- }
- }
-
- 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());
- });
- }
-
+ public abstract void playMediaObject(@NonNull Playable playable, boolean stream, boolean startWhenPrepared, boolean prepareImmediately);
/**
* Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
@@ -322,51 +85,7 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* <p/>
* This method is executed on an internal executor service.
*/
- public void resume() {
- executor.submit(() -> {
- playerLock.lock();
- resumeSync();
- playerLock.unlock();
- });
- }
-
- private void resumeSync() {
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- int focusGained = audioManager.requestAudioFocus(
- audioFocusChangeListener, AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
- if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- acquireWifiLockIfNecessary();
- 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) {
- int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
- media.getPosition(),
- media.getLastPlayedTime());
- seekToSync(newPosition);
- }
- mediaPlayer.start();
-
- setPlayerStatus(PlayerStatus.PLAYING, media);
- pausedBecauseOfTransientAudiofocusLoss = false;
- media.onPlaybackStart();
-
- } else {
- Log.e(TAG, "Failed to request audio focus");
- }
- } else {
- Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
- }
- }
-
+ public abstract void resume();
/**
* Saves the current position and pauses playback. Note that, if audiofocus
@@ -378,32 +97,7 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* @param reinit is true if service should reinit after pausing if the media
* file is being streamed
*/
- public void pause(final boolean abandonFocus, final boolean reinit) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
- if (playerStatus == PlayerStatus.PLAYING) {
- Log.d(TAG, "Pausing playback.");
- mediaPlayer.pause();
- setPlayerStatus(PlayerStatus.PAUSED, media);
-
- if (abandonFocus) {
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- pausedBecauseOfTransientAudiofocusLoss = false;
- }
- if (stream && reinit) {
- reinit();
- }
- } else {
- Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
- }
-
- playerLock.unlock();
- }
- });
- }
+ public abstract void pause(boolean abandonFocus, boolean reinit);
/**
* Prepared media player for playback if the service is in the INITALIZED
@@ -411,129 +105,14 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* <p/>
* This method is executed on an internal executor service.
*/
- public void prepare() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
-
- if (playerStatus == PlayerStatus.INITIALIZED) {
- Log.d(TAG, "Preparing media player");
- setPlayerStatus(PlayerStatus.PREPARING, media);
- try {
- mediaPlayer.prepare();
- onPrepared(startWhenPrepared.get());
- } catch (IOException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- }
- }
- playerLock.unlock();
-
- }
- });
- }
-
- /**
- * Called after media player has been prepared. This method is executed on the caller's thread.
- */
- void onPrepared(final boolean startWhenPrepared) {
- playerLock.lock();
-
- if (playerStatus != PlayerStatus.PREPARING) {
- playerLock.unlock();
- throw new IllegalStateException("Player is not in PREPARING state");
- }
-
- Log.d(TAG, "Resource prepared");
-
- if (mediaType == MediaType.VIDEO) {
- VideoPlayer vp = (VideoPlayer) mediaPlayer;
- videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
- }
-
- if (media.getPosition() > 0) {
- seekToSync(media.getPosition());
- }
-
- if (media.getDuration() == 0) {
- Log.d(TAG, "Setting duration of media");
- media.setDuration(mediaPlayer.getDuration());
- }
- setPlayerStatus(PlayerStatus.PREPARED, media);
-
- if (startWhenPrepared) {
- resumeSync();
- }
-
- playerLock.unlock();
- }
+ public abstract void prepare();
/**
* Resets the media player and moves it into INITIALIZED state.
* <p/>
* This method is executed on an internal executor service.
*/
- public void reinit() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
- if (media != null) {
- playMediaObject(media, true, stream, startWhenPrepared.get(), false);
- } else if (mediaPlayer != null) {
- mediaPlayer.reset();
- } else {
- Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
- }
- playerLock.unlock();
- }
- });
- }
-
-
- /**
- * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
- *
- * @param t The position to seek to in milliseconds. t < 0 will be interpreted as t = 0
- * <p/>
- * This method is executed on the caller's thread.
- */
- private void seekToSync(int t) {
- if (t < 0) {
- t = 0;
- }
- playerLock.lock();
-
- if (playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) {
- if (!stream) {
- statusBeforeSeeking = playerStatus;
- setPlayerStatus(PlayerStatus.SEEKING, media);
- }
- 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);
- prepare();
- }
- playerLock.unlock();
- }
+ public abstract void reinit();
/**
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
@@ -541,36 +120,14 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* <p/>
* This method is executed on an internal executor service.
*/
- public void seekTo(final int t) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- seekToSync(t);
- }
- });
- }
+ public abstract void seekTo(int t);
/**
* Seek a specific position from the current position
*
* @param d offset from current position (positive or negative)
*/
- public void seekDelta(final int d) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- int currentPosition = getPosition();
- if (currentPosition != INVALID_TIME) {
- seekToSync(currentPosition + d);
- } else {
- Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
- }
-
- playerLock.unlock();
- }
- });
- }
+ public abstract void seekDelta(int d);
/**
* Seek to the start of the specified chapter.
@@ -582,205 +139,64 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
/**
* Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
*/
- public int getDuration() {
- if (!playerLock.tryLock()) {
- return INVALID_TIME;
- }
-
- int retVal = INVALID_TIME;
- if (playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) {
- retVal = mediaPlayer.getDuration();
- } else if (media != null && media.getDuration() > 0) {
- retVal = media.getDuration();
- }
-
- playerLock.unlock();
- return retVal;
- }
+ public abstract int getDuration();
/**
* Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
*/
- public int getPosition() {
- if (!playerLock.tryLock()) {
- return INVALID_TIME;
- }
+ public abstract int getPosition();
- int retVal = INVALID_TIME;
- if (playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || 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();
- }
+ public abstract boolean isStartWhenPrepared();
- playerLock.unlock();
- Log.d(TAG, "getPosition() -> " + retVal);
- return retVal;
- }
-
- public boolean isStartWhenPrepared() {
- return startWhenPrepared.get();
- }
-
- public void setStartWhenPrepared(boolean startWhenPrepared) {
- this.startWhenPrepared.set(startWhenPrepared);
- }
+ public abstract void setStartWhenPrepared(boolean startWhenPrepared);
/**
* Returns true if the playback speed can be adjusted.
*/
- public boolean canSetSpeed() {
- boolean retVal = false;
- if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
- retVal = (mediaPlayer).canSetSpeed();
- }
- return retVal;
- }
-
- /**
- * Sets the playback speed.
- * This method is executed on the caller's thread.
- */
- private void setSpeedSync(float speed) {
- playerLock.lock();
- if (media != null && media.getMediaType() == MediaType.AUDIO) {
- if (mediaPlayer.canSetSpeed()) {
- mediaPlayer.setPlaybackSpeed((float) speed);
- Log.d(TAG, "Playback speed was set to " + speed);
- callback.playbackSpeedChanged(speed);
- }
- }
- playerLock.unlock();
- }
+ public abstract boolean canSetSpeed();
/**
* Sets the playback speed.
* This method is executed on an internal executor service.
*/
- public void setSpeed(final float speed) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- setSpeedSync(speed);
- }
- });
- }
+ public abstract void setSpeed(float speed);
/**
* Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
*/
- public float getPlaybackSpeed() {
- if (!playerLock.tryLock()) {
- return 1;
- }
-
- float retVal = 1;
- if ((playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) && mediaPlayer.canSetSpeed()) {
- retVal = mediaPlayer.getCurrentSpeedMultiplier();
- }
- playerLock.unlock();
- return retVal;
- }
+ public abstract float getPlaybackSpeed();
/**
- * Sets the playback speed.
+ * Sets the playback volume.
* This method is executed on an internal executor service.
*/
- public void setVolume(final float volumeLeft, float volumeRight) {
- executor.submit(() -> setVolumeSync(volumeLeft, volumeRight));
- }
-
- /**
- * 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();
- }
+ public abstract void setVolume(float volumeLeft, float volumeRight);
/**
* 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 abstract boolean canDownmix();
- 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 abstract void setDownmix(boolean enable);
- public MediaType getCurrentMediaType() {
- return mediaType;
- }
+ public abstract MediaType getCurrentMediaType();
- public boolean isStreaming() {
- return stream;
- }
+ public abstract boolean isStreaming();
+ /**
+ * Releases internally used resources. This method should only be called when the object is not used anymore.
+ */
+ public abstract void shutdown();
/**
* Releases internally used resources. This method should only be called when the object is not used anymore.
+ * This method is executed on an internal executor service.
*/
- public void shutdown() {
- executor.shutdown();
- if (mediaPlayer != null) {
- mediaPlayer.release();
- }
- if (mediaSession != null) {
- mediaSession.release();
- }
- releaseWifiLockIfNecessary();
- }
+ public abstract void shutdownQuietly();
- public void setVideoSurface(final SurfaceHolder surface) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- if (mediaPlayer != null) {
- mediaPlayer.setDisplay(surface);
- }
- playerLock.unlock();
- }
- });
- }
+ public abstract void setVideoSurface(SurfaceHolder surface);
- public void resetVideoSurface() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- Log.d(TAG, "Resetting video surface");
- mediaPlayer.setDisplay(null);
- reinit();
- playerLock.unlock();
- }
- });
- }
+ public abstract void resetVideoSurface();
/**
* Return width and height of the currently playing video as a pair.
@@ -789,30 +205,15 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return
* invalid values.
*/
- public Pair<Integer, Integer> getVideoSize() {
- if (!playerLock.tryLock()) {
- // use cached value if lock can't be aquired
- return videoSize;
- }
- Pair<Integer, Integer> res;
- if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) {
- res = null;
- } else {
- VideoPlayer vp = (VideoPlayer) mediaPlayer;
- videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
- res = videoSize;
- }
- playerLock.unlock();
- return res;
- }
+ public abstract Pair<Integer, Integer> getVideoSize();
/**
* Returns a PSMInfo object that contains information about the current state of the PSMP object.
*
* @return The PSMPInfo object.
*/
- public synchronized PSMPInfo getPSMPInfo() {
- return new PSMPInfo(playerStatus, media);
+ public final synchronized PSMPInfo getPSMPInfo() {
+ return new PSMPInfo(playerStatus, getPlayable());
}
/**
@@ -831,198 +232,27 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
* 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;
- }
+ public abstract Playable getPlayable();
- /**
- * Returns a token to this object's MediaSession. The MediaSession should only be used for notifications
- * at the moment.
- *
- * @return The MediaSessionCompat.Token object.
- */
- public MediaSessionCompat.Token getSessionToken() {
- return mediaSession.getSessionToken();
- }
+ protected abstract void setPlayable(Playable playable);
- /**
- * Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
- * so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
- * <p/>
- * This method will notify the callback about the change of the player status (even if the new status is the same
- * as the old one).
- *
- * @param newStatus The new PlayerStatus. This must not be null.
- * @param newMedia The new playable object of the PSMP object. This can be null.
- */
- private synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
- Log.d(TAG, "Setting player status to " + newStatus);
-
- this.playerStatus = newStatus;
- this.media = newMedia;
-
- PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder();
-
- int state;
- if (playerStatus != null) {
- Log.d(TAG, "playerStatus: " + playerStatus.toString());
- switch (playerStatus) {
- case PLAYING:
- state = PlaybackStateCompat.STATE_PLAYING;
- break;
- case PREPARED:
- case PAUSED:
- state = PlaybackStateCompat.STATE_PAUSED;
- break;
- case STOPPED:
- state = PlaybackStateCompat.STATE_STOPPED;
- break;
- case SEEKING:
- state = PlaybackStateCompat.STATE_FAST_FORWARDING;
- break;
- case PREPARING:
- case INITIALIZING:
- state = PlaybackStateCompat.STATE_CONNECTING;
- break;
- case INITIALIZED:
- case INDETERMINATE:
- state = PlaybackStateCompat.STATE_NONE;
- break;
- case ERROR:
- state = PlaybackStateCompat.STATE_ERROR;
- break;
- default:
- state = PlaybackStateCompat.STATE_NONE;
- break;
- }
- } else {
- state = PlaybackStateCompat.STATE_NONE;
- }
- 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));
- }
-
- private IPlayer createMediaPlayer() {
- if (mediaPlayer != null) {
- mediaPlayer.release();
- }
- if (media == null || media.getMediaType() == MediaType.VIDEO) {
- mediaPlayer = new VideoPlayer();
- } else {
- mediaPlayer = new AudioPlayer(context);
- }
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
- return setMediaPlayerListeners(mediaPlayer);
- }
-
- private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
-
- @Override
- public void onAudioFocusChange(final int focusChange) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
-
- // If there is an incoming call, playback should be paused permanently
- TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- final int callState = (tm != null) ? tm.getCallState() : 0;
- Log.i(TAG, "Call state:" + callState);
-
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
- (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) {
- Log.d(TAG, "Lost audio focus");
- pause(true, false);
- callback.shouldStop();
- } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- Log.d(TAG, "Gained audio focus");
- if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
- resume();
- } else { // we ducked => raise audio level back
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_RAISE, 0);
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
- if (playerStatus == PlayerStatus.PLAYING) {
- if (!UserPreferences.shouldPauseForFocusLoss()) {
- Log.d(TAG, "Lost audio focus temporarily. Ducking...");
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_LOWER, 0);
- pausedBecauseOfTransientAudiofocusLoss = false;
- } else {
- Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
- if (playerStatus == PlayerStatus.PLAYING) {
- Log.d(TAG, "Lost audio focus temporarily. Pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- }
- playerLock.unlock();
- }
- });
- }
- };
-
-
- public void endPlayback(final boolean wasSkipped) {
- executor.submit(() -> {
- playerLock.lock();
- releaseWifiLockIfNecessary();
-
- boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
-
- if (playerStatus != PlayerStatus.INDETERMINATE) {
- setPlayerStatus(PlayerStatus.INDETERMINATE, media);
- }
- if (mediaPlayer != null) {
- mediaPlayer.reset();
-
- }
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- callback.endPlayback(isPlaying, wasSkipped);
-
- playerLock.unlock();
- });
- }
+ public abstract void endPlayback(boolean wasSkipped, boolean switchingPlayers);
/**
- * Moves the PlaybackServiceMediaPlayer into STOPPED state. This call is only valid if the player is currently in
+ * Moves the PSMP into STOPPED state. This call is only valid if the player is currently in
* INDETERMINATE state, for example after a call to endPlayback.
* This method will only take care of changing the PlayerStatus of this object! Other tasks like
* abandoning audio focus have to be done with other methods.
*/
- public void stop() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
-
- if (playerStatus == PlayerStatus.INDETERMINATE) {
- setPlayerStatus(PlayerStatus.STOPPED, null);
- } else {
- Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
- }
- playerLock.unlock();
+ public abstract void stop();
- }
- });
- }
+ /**
+ * @return {@code true} if the WifiLock feature should be used, {@code false} otherwise.
+ */
+ protected abstract boolean shouldLockWifi();
- private synchronized void acquireWifiLockIfNecessary() {
- if (stream) {
+ protected final synchronized void acquireWifiLockIfNecessary() {
+ if (shouldLockWifi()) {
if (wifiLock == null) {
wifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
@@ -1032,270 +262,85 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
}
}
- private synchronized void releaseWifiLockIfNecessary() {
+ protected final synchronized void releaseWifiLockIfNecessary() {
if (wifiLock != null && wifiLock.isHeld()) {
wifiLock.release();
}
}
/**
- * Holds information about a PSMP object.
+ * Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
+ * so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
+ * <p/>
+ * This method will notify the callback about the change of the player status (even if the new status is the same
+ * as the old one).
+ *
+ * @param newStatus The new PlayerStatus. This must not be null.
+ * @param newMedia The new playable object of the PSMP object. This can be null.
*/
- public class PSMPInfo {
- public PlayerStatus playerStatus;
- public Playable playable;
-
- public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
- this.playerStatus = playerStatus;
- this.playable = playable;
- }
- }
-
- public interface PSMPCallback {
- void statusChanged(PSMPInfo newInfo);
-
- void shouldStop();
-
- void playbackSpeedChanged(float s);
-
- void onBufferingUpdate(int percent);
+ protected synchronized final void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
+ Log.d(TAG, this.getClass().getSimpleName() + ": Setting player status to " + newStatus);
- boolean onMediaPlayerInfo(int code);
-
- boolean onMediaPlayerError(Object inObj, int what, int extra);
-
- boolean endPlayback(boolean playNextEpisode, boolean wasSkipped);
- }
-
- private IPlayer setMediaPlayerListeners(IPlayer mp) {
- if (mp != null && media != null) {
- if (media.getMediaType() == MediaType.AUDIO) {
- ((AudioPlayer) mp)
- .setOnCompletionListener(audioCompletionListener);
- ((AudioPlayer) mp)
- .setOnSeekCompleteListener(audioSeekCompleteListener);
- ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
- ((AudioPlayer) mp)
- .setOnBufferingUpdateListener(audioBufferingUpdateListener);
- ((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
- } else {
- ((VideoPlayer) mp)
- .setOnCompletionListener(videoCompletionListener);
- ((VideoPlayer) mp)
- .setOnSeekCompleteListener(videoSeekCompleteListener);
- ((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
- ((VideoPlayer) mp)
- .setOnBufferingUpdateListener(videoBufferingUpdateListener);
- ((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
- }
- }
- return mp;
- }
-
- private final org.antennapod.audio.MediaPlayer.OnCompletionListener audioCompletionListener = new org.antennapod.audio.MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(org.antennapod.audio.MediaPlayer mp) {
- genericOnCompletion();
- }
- };
+ this.playerStatus = newStatus;
+ setPlayable(newMedia);
- private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(android.media.MediaPlayer mp) {
- genericOnCompletion();
+ if (playerStatus != null) {
+ Log.d(TAG, "playerStatus: " + playerStatus.toString());
}
- };
- private void genericOnCompletion() {
- endPlayback(false);
+ callback.statusChanged(new PSMPInfo(playerStatus, getPlayable()));
}
- private final org.antennapod.audio.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new org.antennapod.audio.MediaPlayer.OnBufferingUpdateListener() {
- @Override
- public void onBufferingUpdate(org.antennapod.audio.MediaPlayer mp,
- int percent) {
- genericOnBufferingUpdate(percent);
- }
- };
-
- private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
- @Override
- public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
- genericOnBufferingUpdate(percent);
+ protected void smartMarkAsPlayed(Playable media) {
+ if(media != null && media instanceof FeedMedia) {
+ FeedMedia oldMedia = (FeedMedia) media;
+ if(oldMedia.hasAlmostEnded()) {
+ Log.d(TAG, "smart mark as read");
+ FeedItem item = oldMedia.getItem();
+ if (item == null) {
+ return;
+ }
+ DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
+ DBWriter.removeQueueItem(context, item, false);
+ DBWriter.addItemToPlaybackHistory(oldMedia);
+ if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
+ Log.d(TAG, "Delete " + oldMedia.toString());
+ DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
+ }
+ }
}
- };
-
- private void genericOnBufferingUpdate(int percent) {
- callback.onBufferingUpdate(percent);
}
- private final org.antennapod.audio.MediaPlayer.OnInfoListener audioInfoListener = new org.antennapod.audio.MediaPlayer.OnInfoListener() {
- @Override
- public boolean onInfo(org.antennapod.audio.MediaPlayer mp, int what,
- int extra) {
- return genericInfoListener(what);
- }
- };
+ public interface PSMPCallback {
+ void statusChanged(PSMPInfo newInfo);
- private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
- @Override
- public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
- return genericInfoListener(what);
- }
- };
+ void shouldStop();
- private boolean genericInfoListener(int what) {
- return callback.onMediaPlayerInfo(what);
- }
+ void playbackSpeedChanged(float s);
- private final org.antennapod.audio.MediaPlayer.OnErrorListener audioErrorListener = new org.antennapod.audio.MediaPlayer.OnErrorListener() {
- @Override
- 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);
- }
- }
- };
+ void setSpeedAbilityChanged();
- private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
- return genericOnError(mp, what, extra);
- }
- };
+ void onBufferingUpdate(int percent);
- private boolean genericOnError(Object inObj, int what, int extra) {
- return callback.onMediaPlayerError(inObj, what, extra);
- }
+ void onMediaChanged(boolean reloadUI);
- private final org.antennapod.audio.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new org.antennapod.audio.MediaPlayer.OnSeekCompleteListener() {
- @Override
- public void onSeekComplete(org.antennapod.audio.MediaPlayer mp) {
- genericSeekCompleteListener();
- }
- };
+ boolean onMediaPlayerInfo(int code, @StringRes int resourceId);
- private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
- @Override
- public void onSeekComplete(android.media.MediaPlayer mp) {
- genericSeekCompleteListener();
- }
- };
+ boolean onMediaPlayerError(Object inObj, int what, int extra);
- private final void genericSeekCompleteListener() {
- 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();
+ boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
}
- private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
-
- private static final String TAG = "MediaSessionCompat";
-
- @Override
- 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);
- }
- return false;
- }
- };
+ /**
+ * Holds information about a PSMP object.
+ */
+ public static class PSMPInfo {
+ public PlayerStatus playerStatus;
+ public Playable playable;
- 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;
- }
+ public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
+ this.playerStatus = playerStatus;
+ this.playable = playable;
}
- 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 680fb8777..8a0964d36 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
@@ -6,12 +6,10 @@ import android.support.annotation.NonNull;
import android.util.Log;
import java.util.List;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.event.QueueEvent;
@@ -65,13 +63,10 @@ public class PlaybackServiceTaskManager {
@NonNull PSTMCallback callback) {
this.context = context;
this.callback = callback;
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, r -> {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
});
loadQueue();
EventBus.getDefault().register(this);
@@ -95,12 +90,7 @@ public class PlaybackServiceTaskManager {
private synchronized void loadQueue() {
if (!isQueueLoaderActive()) {
- queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
- @Override
- public List<FeedItem> call() throws Exception {
- return DBReader.getQueue();
- }
- });
+ queueFuture = schedExecutor.submit(DBReader::getQueue);
}
}
@@ -112,9 +102,7 @@ public class PlaybackServiceTaskManager {
if (queueFuture.isDone()) {
try {
return queueFuture.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
@@ -137,12 +125,7 @@ public class PlaybackServiceTaskManager {
*/
public synchronized void startPositionSaver() {
if (!isPositionSaverActive()) {
- Runnable positionSaver = new Runnable() {
- @Override
- public void run() {
- callback.positionSaverTick();
- }
- };
+ Runnable positionSaver = callback::positionSaverTick;
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
@@ -174,12 +157,7 @@ public class PlaybackServiceTaskManager {
*/
public synchronized void startWidgetUpdater() {
if (!isWidgetUpdaterActive()) {
- Runnable widgetUpdater = new Runnable() {
- @Override
- public void run() {
- callback.onWidgetUpdaterTick();
- }
- };
+ Runnable widgetUpdater = callback::onWidgetUpdaterTick;
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
@@ -279,18 +257,15 @@ public class PlaybackServiceTaskManager {
cancelChapterLoader();
}
- Runnable chapterLoader = new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Chapter loader started");
- if (media.getChapters() == null) {
- media.loadChapterMarks();
- if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
- callback.onChapterLoaded(media);
- }
+ Runnable chapterLoader = () -> {
+ Log.d(TAG, "Chapter loader started");
+ if (media.getChapters() == null) {
+ media.loadChapterMarks();
+ if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
+ callback.onChapterLoaded(media);
}
- Log.d(TAG, "Chapter loader stopped");
}
+ Log.d(TAG, "Chapter loader stopped");
};
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java
index 7c666abd5..8a222d7ec 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java
@@ -1,24 +1,33 @@
package de.danoeh.antennapod.core.service.playback;
public enum PlayerStatus {
- INDETERMINATE, // player is currently changing its state, listeners should wait until the player has left this state.
- ERROR,
- PREPARING,
- PAUSED,
- PLAYING,
- STOPPED,
- PREPARED,
- SEEKING,
- INITIALIZING, // playback service is loading the Playable's metadata
- INITIALIZED; // playback service was started, data source of media player was set.
+ INDETERMINATE(0), // player is currently changing its state, listeners should wait until the player has left this state.
+ ERROR(-1),
+ PREPARING(19),
+ PAUSED(30),
+ PLAYING(40),
+ STOPPED(5),
+ PREPARED(20),
+ SEEKING(29),
+ INITIALIZING(9), // playback service is loading the Playable's metadata
+ INITIALIZED(10); // playback service was started, data source of media player was set.
+ private int statusValue;
private static final PlayerStatus[] fromOrdinalLookup;
static {
fromOrdinalLookup = PlayerStatus.values();
}
+ PlayerStatus(int val) {
+ statusValue = val;
+ }
+
public static PlayerStatus fromOrdinal(int o) {
return fromOrdinalLookup[o];
}
+
+ public boolean isAtLeast(PlayerStatus other) {
+ return other == null || this.statusValue>=other.statusValue;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java
new file mode 100644
index 000000000..4262b8a70
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java
@@ -0,0 +1,592 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.util.Pair;
+import android.view.SurfaceHolder;
+
+import com.google.android.gms.cast.Cast;
+import com.google.android.gms.cast.CastStatusCodes;
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.cast.CastConsumer;
+import de.danoeh.antennapod.core.cast.CastManager;
+import de.danoeh.antennapod.core.cast.CastUtils;
+import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
+import de.danoeh.antennapod.core.cast.RemoteMedia;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
+import de.danoeh.antennapod.core.util.playback.Playable;
+
+/**
+ * Implementation of PlaybackServiceMediaPlayer suitable for remote playback on Cast Devices.
+ */
+public class RemotePSMP extends PlaybackServiceMediaPlayer {
+
+ public static final String TAG = "RemotePSMP";
+
+ public static final int CAST_ERROR = 3001;
+
+ public static final int CAST_ERROR_PRIORITY_HIGH = 3005;
+
+ private final CastManager castMgr;
+
+ private volatile Playable media;
+ private volatile MediaInfo remoteMedia;
+ private volatile MediaType mediaType;
+
+ private final AtomicBoolean isBuffering;
+
+ private final AtomicBoolean startWhenPrepared;
+
+ public RemotePSMP(@NonNull Context context, @NonNull PSMPCallback callback) {
+ super(context, callback);
+
+ castMgr = CastManager.getInstance();
+ media = null;
+ mediaType = null;
+ startWhenPrepared = new AtomicBoolean(false);
+ isBuffering = new AtomicBoolean(false);
+
+ try {
+ if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) {
+ // updates the state, but does not start playing new media if it was going to
+ onRemoteMediaPlayerStatusUpdated(
+ ((p, playNextEpisode, wasSkipped, switchingPlayers) ->
+ this.callback.endPlayback(p, false, wasSkipped, switchingPlayers)));
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to do initial check for loaded media", e);
+ }
+
+ castMgr.addCastConsumer(castConsumer);
+ //TODO
+ }
+
+ private CastConsumer castConsumer = new DefaultCastConsumer() {
+ @Override
+ public void onRemoteMediaPlayerMetadataUpdated() {
+ RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
+ }
+
+ @Override
+ public void onRemoteMediaPlayerStatusUpdated() {
+ RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
+ }
+
+ @Override
+ public void onMediaLoadResult(int statusCode) {
+ if (playerStatus == PlayerStatus.PREPARING) {
+ if (statusCode == CastStatusCodes.SUCCESS) {
+ setPlayerStatus(PlayerStatus.PREPARED, media);
+ if (media.getDuration() == 0) {
+ Log.d(TAG, "Setting duration of media");
+ try {
+ media.setDuration((int) castMgr.getMediaDuration());
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to get remote media's duration");
+ }
+ }
+ } else if (statusCode != CastStatusCodes.REPLACED){
+ Log.d(TAG, "Remote media failed to load");
+ setPlayerStatus(PlayerStatus.INITIALIZED, media);
+ }
+ } else {
+ Log.d(TAG, "onMediaLoadResult called, but Player Status wasn't in preparing state, so we ignore the result");
+ }
+ }
+
+ @Override
+ public void onApplicationStatusChanged(String appStatus) {
+ if (playerStatus != PlayerStatus.PLAYING) {
+ Log.d(TAG, "onApplicationStatusChanged, but no media was playing");
+ return;
+ }
+ boolean playbackEnded = false;
+ try {
+ int standbyState = castMgr.getApplicationStandbyState();
+ Log.d(TAG, "standbyState: " + standbyState);
+ playbackEnded = standbyState == Cast.STANDBY_STATE_YES;
+ } catch (IllegalStateException e) {
+ Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()");
+ }
+ if (playbackEnded) {
+ setPlayerStatus(PlayerStatus.INDETERMINATE, media);
+ callback.endPlayback(media, true, false, false);
+ }
+ }
+
+ @Override
+ public void onFailed(int resourceId, int statusCode) {
+ callback.onMediaPlayerInfo(CAST_ERROR, resourceId);
+ }
+ };
+
+ private void setBuffering(boolean buffering) {
+ if (buffering && isBuffering.compareAndSet(false, true)) {
+ callback.onMediaPlayerInfo(MediaPlayer.MEDIA_INFO_BUFFERING_START, 0);
+ } else if (!buffering && isBuffering.compareAndSet(true, false)) {
+ callback.onMediaPlayerInfo(MediaPlayer.MEDIA_INFO_BUFFERING_END, 0);
+ }
+ }
+
+ private Playable localVersion(MediaInfo info){
+ if (info == null) {
+ return null;
+ }
+ if (CastUtils.matches(info, media)) {
+ return media;
+ }
+ return CastUtils.getPlayable(info, true);
+ }
+
+ private MediaInfo remoteVersion(Playable playable) {
+ if (playable == null) {
+ return null;
+ }
+ if (CastUtils.matches(remoteMedia, playable)) {
+ return remoteMedia;
+ }
+ if (playable instanceof FeedMedia) {
+ return CastUtils.convertFromFeedMedia((FeedMedia) playable);
+ }
+ if (playable instanceof RemoteMedia) {
+ return ((RemoteMedia) playable).extractMediaInfo();
+ }
+ return null;
+ }
+
+ private void onRemoteMediaPlayerStatusUpdated(@NonNull EndPlaybackCall endPlaybackCall) {
+ MediaStatus status = castMgr.getMediaStatus();
+ if (status == null) {
+ Log.d(TAG, "Received null MediaStatus");
+ //setBuffering(false);
+ //setPlayerStatus(PlayerStatus.INDETERMINATE, null);
+ return;
+ } else {
+ Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState());
+ }
+ Playable currentMedia = localVersion(status.getMediaInfo());
+ boolean updateUI = currentMedia != media;
+ if (currentMedia != null) {
+ long position = status.getStreamPosition();
+ if (position > 0 && currentMedia.getPosition() == 0) {
+ currentMedia.setPosition((int) position);
+ }
+ }
+ int state = status.getPlayerState();
+ setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING);
+ switch (state) {
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ setPlayerStatus(PlayerStatus.PLAYING, currentMedia);
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ setPlayerStatus(PlayerStatus.PAUSED, currentMedia);
+ break;
+ case MediaStatus.PLAYER_STATE_BUFFERING:
+ setPlayerStatus(playerStatus, currentMedia);
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ int reason = status.getIdleReason();
+ switch (reason) {
+ case MediaStatus.IDLE_REASON_CANCELED:
+ // check if we're already loading something else
+ if (!updateUI || media == null) {
+ setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
+ } else {
+ updateUI = false;
+ }
+ break;
+ case MediaStatus.IDLE_REASON_INTERRUPTED:
+ // check if we're already loading something else
+ if (!updateUI || media == null) {
+ setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
+ } else {
+ updateUI = false;
+ }
+ break;
+ case MediaStatus.IDLE_REASON_NONE:
+ setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
+ break;
+ case MediaStatus.IDLE_REASON_FINISHED:
+ boolean playing = playerStatus == PlayerStatus.PLAYING;
+ setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
+ endPlaybackCall.endPlayback(currentMedia,playing, false, false);
+ // endPlayback already updates the UI, so no need to trigger it ourselves
+ updateUI = false;
+ break;
+ case MediaStatus.IDLE_REASON_ERROR:
+ Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode...");
+ setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
+ callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH,
+ R.string.cast_failed_media_error_skipping);
+ endPlaybackCall.endPlayback(currentMedia, startWhenPrepared.get(), true, false);
+ // endPlayback already updates the UI, so no need to trigger it ourselves
+ updateUI = false;
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_UNKNOWN:
+ //is this right?
+ setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
+ break;
+ default:
+ Log.e(TAG, "Remote media state undetermined!");
+ setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
+ }
+ if (updateUI) {
+ callback.onMediaChanged(true);
+ }
+ }
+
+ @Override
+ public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
+ Log.d(TAG, "playMediaObject() called");
+ playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
+ }
+
+ /**
+ * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
+ * the given playable parameter is the same object as the currently playing media.
+ *
+ * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean)
+ */
+ private void playMediaObject(@NonNull final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
+ if (!CastUtils.isCastable(playable)) {
+ Log.d(TAG, "media provided is not compatible with cast device");
+ callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable);
+ try {
+ playable.loadMetadata();
+ } catch (Playable.PlayableException e) {
+ Log.e(TAG, "Unable to load metadata of playable", e);
+ }
+ callback.endPlayback(playable, startWhenPrepared, true, false);
+ return;
+ }
+
+ if (media != null) {
+ if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())
+ && playerStatus == PlayerStatus.PLAYING) {
+ // episode is already playing -> ignore method call
+ Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
+ return;
+ } else {
+ // set temporarily to pause in order to update list with current position
+ try {
+ if (castMgr.isRemoteMediaPlaying()) {
+ setPlayerStatus(PlayerStatus.PAUSED, media);
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to determine whether media was playing, falling back to stored player status", e);
+ // this might end up just being pointless if we need to query the remote device for the position
+ if (playerStatus == PlayerStatus.PLAYING) {
+ setPlayerStatus(PlayerStatus.PAUSED, media);
+ }
+ }
+ smartMarkAsPlayed(media);
+
+
+ setPlayerStatus(PlayerStatus.INDETERMINATE, null);
+ }
+ }
+
+ this.media = playable;
+ remoteMedia = remoteVersion(playable);
+ //this.stream = stream;
+ this.mediaType = media.getMediaType();
+ this.startWhenPrepared.set(startWhenPrepared);
+ setPlayerStatus(PlayerStatus.INITIALIZING, media);
+ try {
+ media.loadMetadata();
+ callback.onMediaChanged(true);
+ setPlayerStatus(PlayerStatus.INITIALIZED, media);
+ if (prepareImmediately) {
+ prepare();
+ }
+ } catch (Playable.PlayableException e) {
+ Log.e(TAG, "Error while loading media metadata", e);
+ setPlayerStatus(PlayerStatus.STOPPED, null);
+ }
+ }
+
+ @Override
+ public void resume() {
+ try {
+ // TODO see comment on prepare()
+ // setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume());
+ if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
+ int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
+ media.getPosition(),
+ media.getLastPlayedTime());
+ castMgr.play(newPosition);
+ }
+ castMgr.play();
+ } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to resume remote playback", e);
+ }
+ }
+
+ @Override
+ public void pause(boolean abandonFocus, boolean reinit) {
+ try {
+ if (castMgr.isRemoteMediaPlaying()) {
+ castMgr.pause();
+ }
+ } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to pause", e);
+ }
+ }
+
+ @Override
+ public void prepare() {
+ if (playerStatus == PlayerStatus.INITIALIZED) {
+ Log.d(TAG, "Preparing media player");
+ setPlayerStatus(PlayerStatus.PREPARING, media);
+ try {
+ int position = media.getPosition();
+ if (position > 0) {
+ position = RewindAfterPauseUtils.calculatePositionWithRewind(
+ position,
+ media.getLastPlayedTime());
+ }
+ // TODO We're not supporting user set stream volume yet, as we need to make a UI
+ // that doesn't allow changing playback speed or have different values for left/right
+ //setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume());
+ castMgr.loadMedia(remoteMedia, startWhenPrepared.get(), position);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Error loading media", e);
+ setPlayerStatus(PlayerStatus.INITIALIZED, media);
+ }
+ }
+ }
+
+ @Override
+ public void reinit() {
+ Log.d(TAG, "reinit() called");
+ if (media != null) {
+ playMediaObject(media, true, false, startWhenPrepared.get(), false);
+ } else {
+ Log.d(TAG, "Call to reinit was ignored: media was null");
+ }
+ }
+
+ @Override
+ public void seekTo(int t) {
+ //TODO check other seek implementations and see if there's no issue with sending too many seek commands to the remote media player
+ try {
+ if (castMgr.isRemoteMediaLoaded()) {
+ setPlayerStatus(PlayerStatus.SEEKING, media);
+ castMgr.seek(t);
+ } else if (media != null && playerStatus == PlayerStatus.INITIALIZED){
+ media.setPosition(t);
+ startWhenPrepared.set(false);
+ prepare();
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to seek", e);
+ }
+ }
+
+ @Override
+ public void seekDelta(int d) {
+ int position = getPosition();
+ if (position != INVALID_TIME) {
+ seekTo(position + d);
+ } else {
+ Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
+ }
+ }
+
+ @Override
+ public int getDuration() {
+ int retVal = INVALID_TIME;
+ boolean prepared;
+ try {
+ prepared = castMgr.isRemoteMediaLoaded();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to check if remote media is loaded", e);
+ prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED);
+ }
+ if (prepared) {
+ try {
+ retVal = (int) castMgr.getMediaDuration();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to determine remote media's duration", e);
+ }
+ }
+ if(retVal == INVALID_TIME && media != null && media.getDuration() > 0) {
+ retVal = media.getDuration();
+ }
+ Log.d(TAG, "getDuration() -> " + retVal);
+ return retVal;
+ }
+
+ @Override
+ public int getPosition() {
+ int retVal = INVALID_TIME;
+ boolean prepared;
+ try {
+ prepared = castMgr.isRemoteMediaLoaded();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to check if remote media is loaded", e);
+ prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED);
+ }
+ if (prepared) {
+ try {
+ retVal = (int) castMgr.getCurrentMediaPosition();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Unable to determine remote media's position", e);
+ }
+ }
+ if(retVal <= 0 && media != null && media.getPosition() >= 0) {
+ retVal = media.getPosition();
+ }
+ Log.d(TAG, "getPosition() -> " + retVal);
+ return retVal;
+ }
+
+ @Override
+ public boolean isStartWhenPrepared() {
+ return startWhenPrepared.get();
+ }
+
+ @Override
+ public void setStartWhenPrepared(boolean startWhenPrepared) {
+ this.startWhenPrepared.set(startWhenPrepared);
+ }
+
+ //TODO I believe some parts of the code make the same decision skipping this check, so that
+ //should be changed as well
+ @Override
+ public boolean canSetSpeed() {
+ return false;
+ }
+
+ @Override
+ public void setSpeed(float speed) {
+ throw new UnsupportedOperationException("Setting playback speed unsupported for Remote Playback");
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ return 1;
+ }
+
+ @Override
+ public void setVolume(float volumeLeft, float volumeRight) {
+ Log.d(TAG, "Setting the Stream volume on Remote Media Player");
+ double volume = (volumeLeft+volumeRight)/2;
+ if (volume > 1.0) {
+ volume = 1.0;
+ }
+ if (volume < 0.0) {
+ volume = 0.0;
+ }
+ try {
+ castMgr.setStreamVolume(volume);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException | CastException e) {
+ Log.e(TAG, "Unable to set the volume", e);
+ }
+ }
+
+ @Override
+ public boolean canDownmix() {
+ return false;
+ }
+
+ @Override
+ public void setDownmix(boolean enable) {
+ throw new UnsupportedOperationException("Setting downmix unsupported in Remote Media Player");
+ }
+
+ @Override
+ public MediaType getCurrentMediaType() {
+ return mediaType;
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return true;
+ }
+
+ @Override
+ public void shutdown() {
+ castMgr.removeCastConsumer(castConsumer);
+ }
+
+ @Override
+ public void shutdownQuietly() {
+ shutdown();
+ }
+
+ @Override
+ public void setVideoSurface(SurfaceHolder surface) {
+ throw new UnsupportedOperationException("Setting Video Surface unsupported in Remote Media Player");
+ }
+
+ @Override
+ public void resetVideoSurface() {
+ Log.e(TAG, "Resetting Video Surface unsupported in Remote Media Player");
+ }
+
+ @Override
+ public Pair<Integer, Integer> getVideoSize() {
+ return null;
+ }
+
+ @Override
+ public Playable getPlayable() {
+ return media;
+ }
+
+ @Override
+ protected void setPlayable(Playable playable) {
+ if (playable != media) {
+ media = playable;
+ remoteMedia = remoteVersion(playable);
+ }
+ }
+
+ @Override
+ public void endPlayback(boolean wasSkipped, boolean switchingPlayers) {
+ Log.d(TAG, "endPlayback() called");
+ boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
+ try {
+ isPlaying = castMgr.isRemoteMediaPlaying();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ Log.e(TAG, "Could not determine if media is playing", e);
+ }
+ // TODO make sure we stop playback whenever there's no next episode.
+ if (playerStatus != PlayerStatus.INDETERMINATE) {
+ setPlayerStatus(PlayerStatus.INDETERMINATE, media);
+ }
+ callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers);
+ }
+
+ @Override
+ public void stop() {
+ if (playerStatus == PlayerStatus.INDETERMINATE) {
+ setPlayerStatus(PlayerStatus.STOPPED, null);
+ } else {
+ Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
+ }
+ }
+
+ @Override
+ protected boolean shouldLockWifi() {
+ return false;
+ }
+
+ private interface EndPlaybackCall {
+ boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
+ }
+}
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 80703e22d..e94874453 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
@@ -13,8 +13,6 @@ 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.
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 26dc027bf..aa97b321a 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
@@ -33,73 +33,70 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
*/
@Override
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()
- && UserPreferences.isEnableAutodownload();
-
- // true if we should auto download based on power status
- boolean powerShouldAutoDl = PowerUtils.deviceCharging(context)
- || UserPreferences.isEnableAutodownloadOnBattery();
-
- // we should only auto download if both network AND power are happy
- if (networkShouldAutoDl && powerShouldAutoDl) {
-
- Log.d(TAG, "Performing auto-dl of undownloaded episodes");
-
- List<FeedItem> candidates;
- 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);
- }
- }
-
- // filter items that are not auto downloadable
- Iterator<FeedItem> it = candidates.iterator();
- while(it.hasNext()) {
- FeedItem item = it.next();
- if(item.isAutoDownloadable() == false) {
- it.remove();
- }
+ return () -> {
+
+ // true if we should auto download based on network status
+ boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable()
+ && UserPreferences.isEnableAutodownload();
+
+ // true if we should auto download based on power status
+ boolean powerShouldAutoDl = PowerUtils.deviceCharging(context)
+ || UserPreferences.isEnableAutodownloadOnBattery();
+
+ // we should only auto download if both network AND power are happy
+ if (networkShouldAutoDl && powerShouldAutoDl) {
+
+ Log.d(TAG, "Performing auto-dl of undownloaded episodes");
+
+ List<FeedItem> candidates;
+ final List<FeedItem> queue = DBReader.getQueue();
+ final List<FeedItem> newItems = DBReader.getNewItemsList();
+ candidates = new ArrayList<>(queue.size() + newItems.size());
+ candidates.addAll(queue);
+ for(FeedItem newItem : newItems) {
+ FeedPreferences feedPrefs = newItem.getFeed().getPreferences();
+ FeedFilter feedFilter = feedPrefs.getFilter();
+ if(!candidates.contains(newItem) && feedFilter.shouldAutoDownload(newItem)) {
+ candidates.add(newItem);
}
+ }
- int autoDownloadableEpisodes = candidates.size();
- int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes();
- int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm()
- .makeRoomForEpisodes(context, autoDownloadableEpisodes);
- boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
- .getEpisodeCacheSizeUnlimited();
- int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
-
- int episodeSpaceLeft;
- if (cacheIsUnlimited ||
- episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
- episodeSpaceLeft = autoDownloadableEpisodes;
- } else {
- episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes);
+ // filter items that are not auto downloadable
+ Iterator<FeedItem> it = candidates.iterator();
+ while(it.hasNext()) {
+ FeedItem item = it.next();
+ if(!item.isAutoDownloadable()) {
+ it.remove();
}
+ }
- FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft)
- .toArray(new FeedItem[episodeSpaceLeft]);
+ int autoDownloadableEpisodes = candidates.size();
+ int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes();
+ int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm()
+ .makeRoomForEpisodes(context, autoDownloadableEpisodes);
+ boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
+ .getEpisodeCacheSizeUnlimited();
+ int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
+
+ int episodeSpaceLeft;
+ if (cacheIsUnlimited ||
+ episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
+ episodeSpaceLeft = autoDownloadableEpisodes;
+ } else {
+ episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes);
+ }
- Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download");
+ FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft)
+ .toArray(new FeedItem[episodeSpaceLeft]);
- try {
- DBTasks.downloadFeedItems(false, context, itemsToDownload);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
+ Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download");
+ try {
+ DBTasks.downloadFeedItems(false, context, itemsToDownload);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
}
+
}
};
}
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
index baa9a986e..04b200699 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
@@ -5,15 +5,12 @@ import android.support.annotation.NonNull;
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
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 72c68ddb6..dbb77e19c 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
@@ -14,5 +14,5 @@ public interface AutomaticDownloadAlgorithm {
* @param context Used for accessing the DB.
* @return A Runnable that will be submitted to an ExecutorService.
*/
- public Runnable autoDownloadUndownloadedItems(Context context);
+ 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 68187306d..869ec5f9d 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
@@ -4,6 +4,7 @@ import android.database.Cursor;
import android.support.v4.util.ArrayMap;
import android.util.Log;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -590,17 +591,19 @@ public final class DBReader {
FeedItem item = null;
Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId));
- if (itemCursor.moveToFirst()) {
- List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
- if (list.size() > 0) {
- item = list.get(0);
- loadAdditionalFeedItemListData(list);
- if (item.hasChapters()) {
- loadChaptersOfFeedItem(adapter, item);
- }
- }
+ if (!itemCursor.moveToFirst()) {
+ itemCursor.close();
+ return null;
}
+ List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
itemCursor.close();
+ if (list.size() > 0) {
+ item = list.get(0);
+ loadAdditionalFeedItemListData(list);
+ if (item.hasChapters()) {
+ loadChaptersOfFeedItem(adapter, item);
+ }
+ }
return item;
}
@@ -676,7 +679,7 @@ public final class DBReader {
* as well as chapter marks of the FeedItems will also be loaded from the database.
*/
public static List<FeedItem> getFeedItems(final long... itemIds) {
- Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + itemIds + "]");
+ Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + Arrays.toString(itemIds) + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> items = getFeedItems(adapter, itemIds);
@@ -898,25 +901,108 @@ public final class DBReader {
adapter.open();
Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId);
- FeedMedia media = null;
- if (mediaCursor.moveToFirst()) {
- int indexFeedItem = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
- final long itemId = mediaCursor.getLong(indexFeedItem);
- media = FeedMedia.fromCursor(mediaCursor);
+ if (!mediaCursor.moveToFirst()) {
+ mediaCursor.close();
+ return null;
+ }
+
+ int indexFeedItem = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
+ long itemId = mediaCursor.getLong(indexFeedItem);
+ FeedMedia media = FeedMedia.fromCursor(mediaCursor);
+ mediaCursor.close();
+
+ if(media != null) {
FeedItem item = getFeedItem(itemId);
- if (media != null && item != null) {
+ if (item != null) {
media.setItem(item);
item.setMedia(media);
}
}
- mediaCursor.close();
adapter.close();
return media;
}
/**
+ * Searches the DB for statistics
+ *
+ * @return The StatisticsInfo object
+ */
+ public static StatisticsData getStatistics() {
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+
+ long totalTime = 0;
+ List<StatisticsItem> feedTime = new ArrayList<>();
+
+ List<Feed> feeds = getFeedList();
+ for (Feed feed : feeds) {
+ long feedPlayedTime = 0;
+ long feedTotalTime = 0;
+ long episodes = 0;
+ long episodesStarted = 0;
+ List<FeedItem> items = getFeed(feed.getId()).getItems();
+ for(FeedItem item : items) {
+ FeedMedia media = item.getMedia();
+ if(media == null) {
+ continue;
+ }
+
+ feedPlayedTime += media.getPlayedDuration() / 1000;
+ if(media.getPlayedDuration() > 0) {
+ episodesStarted++;
+ }
+ feedTotalTime += media.getDuration() / 1000;
+ episodes++;
+ }
+ feedTime.add(new StatisticsItem(
+ feed, feedTotalTime, feedPlayedTime, episodes, episodesStarted));
+ totalTime += feedPlayedTime;
+ }
+
+ Collections.sort(feedTime, (item1, item2) -> {
+ if(item1.timePlayed > item2.timePlayed) {
+ return -1;
+ } else if(item1.timePlayed < item2.timePlayed) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+
+ adapter.close();
+ return new StatisticsData(totalTime, feedTime);
+ }
+
+ public static class StatisticsData {
+ public long totalTime;
+ public List<StatisticsItem> feedTime;
+
+ public StatisticsData(long totalTime, List<StatisticsItem> feedTime) {
+ this.totalTime = totalTime;
+ this.feedTime = feedTime;
+ }
+ }
+
+ public static class StatisticsItem {
+ public Feed feed;
+ public long time;
+ public long timePlayed;
+ public long episodes;
+ public long episodesStarted;
+
+ public StatisticsItem(Feed feed, long time, long timePlayed,
+ long episodes, long episodesStarted) {
+ this.feed = feed;
+ this.time = time;
+ this.timePlayed = timePlayed;
+ this.episodes = episodes;
+ this.episodesStarted = episodesStarted;
+ }
+ }
+
+ /**
* Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems.
*
* @return The flattr queue as a List.
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 ed593bb82..75bbd4adc 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
@@ -48,13 +48,10 @@ public final class DBTasks {
private static ExecutorService autodownloadExec;
static {
- autodownloadExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
+ autodownloadExec = Executors.newSingleThreadExecutor(r -> {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
});
}
@@ -85,9 +82,7 @@ public final class DBTasks {
if (feedID != 0) {
try {
DBWriter.deleteFeed(context, feedID).get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
} else {
@@ -114,7 +109,7 @@ public final class DBTasks {
boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) {
try {
if (!shouldStream) {
- if (media.fileExists() == false) {
+ if (!media.fileExists()) {
throw new MediaFileNotFoundException(
"No episode was found at " + media.getFile_url(),
media);
@@ -518,8 +513,8 @@ public final class DBTasks {
*/
public static synchronized Feed[] updateFeed(final Context context,
final Feed... newFeeds) {
- List<Feed> newFeedsList = new ArrayList<Feed>();
- List<Feed> updatedFeedsList = new ArrayList<Feed>();
+ List<Feed> newFeedsList = new ArrayList<>();
+ List<Feed> updatedFeedsList = new ArrayList<>();
Feed[] resultFeeds = new Feed[newFeeds.length];
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -582,12 +577,13 @@ public final class DBTasks {
item.setAutoDownload(savedFeed.getPreferences().getAutoDownload());
savedFeed.getItems().add(idx, item);
- // only mark the item new if it actually occurs
- // before the most recent item (before we started adding things)
+ // only mark the item new if it was published after or at the same time
+ // as the most recent item
// (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())) {
+ priorMostRecentDate.before(item.getPubDate()) ||
+ priorMostRecentDate.equals(item.getPubDate())) {
Log.d(TAG, "Marking item published on " + item.getPubDate() +
" new, prior most recent date = " + priorMostRecentDate);
item.setNew();
@@ -611,9 +607,7 @@ public final class DBTasks {
try {
DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[newFeedsList.size()])).get();
DBWriter.setCompleteFeed(updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
@@ -633,7 +627,7 @@ public final class DBTasks {
*/
public static FutureTask<List<FeedItem>> searchFeedItemTitle(final Context context,
final long feedID, final String query) {
- return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemTitles(feedID,
@@ -657,7 +651,7 @@ public final class DBTasks {
*/
public static FutureTask<List<FeedItem>> searchFeedItemDescription(final Context context,
final long feedID, final String query) {
- return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemDescriptions(feedID,
@@ -681,7 +675,7 @@ public final class DBTasks {
*/
public static FutureTask<List<FeedItem>> searchFeedItemContentEncoded(final Context context,
final long feedID, final String query) {
- return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemContentEncoded(feedID,
@@ -704,7 +698,7 @@ public final class DBTasks {
*/
public static FutureTask<List<FeedItem>> searchFeedItemChapters(final Context context,
final long feedID, final String query) {
- return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemChapters(feedID,
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 e728abc3b..6f299ee35 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
@@ -14,7 +14,6 @@ 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;
@@ -132,7 +131,7 @@ public class DBWriter {
}
}
Log.d(TAG, "Deleting File. Result: " + result);
- EventBus.getDefault().post(FeedItemEvent.deletedMedia(Arrays.asList(media.getItem())));
+ EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem())));
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
@@ -327,6 +326,7 @@ public class DBWriter {
adapter.setQueue(queue);
item.addTag(FeedItem.TAG_QUEUE);
EventBus.getDefault().post(QueueEvent.added(item, index));
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
if (item.isNew()) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
}
@@ -372,7 +372,8 @@ public class DBWriter {
if (queue != null) {
boolean queueModified = false;
LongList markAsUnplayedIds = new LongList();
- List<QueueEvent> events = new ArrayList<QueueEvent>();
+ List<QueueEvent> events = new ArrayList<>();
+ List<FeedItem> updatedItems = new ArrayList<>();
for (int i = 0; i < itemIds.length; i++) {
if (!itemListContains(queue, itemIds[i])) {
final FeedItem item = DBReader.getFeedItem(itemIds[i]);
@@ -388,6 +389,8 @@ public class DBWriter {
queue.add(item);
events.add(QueueEvent.added(item, queue.size() - 1));
}
+ item.addTag(FeedItem.TAG_QUEUE);
+ updatedItems.add(item);
queueModified = true;
if (item.isNew()) {
markAsUnplayedIds.add(item.getId());
@@ -400,6 +403,7 @@ public class DBWriter {
for (QueueEvent event : events) {
EventBus.getDefault().post(event);
}
+ EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
if (markAsUnplayedIds.size() > 0) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
}
@@ -449,6 +453,7 @@ public class DBWriter {
adapter.setQueue(queue);
item.removeTag(FeedItem.TAG_QUEUE);
EventBus.getDefault().post(QueueEvent.removed(item));
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
} else {
Log.w(TAG, "Queue was not modified by call to removeQueueItem");
}
@@ -470,6 +475,7 @@ public class DBWriter {
adapter.close();
item.addTag(FeedItem.TAG_FAVORITE);
EventBus.getDefault().post(FavoritesEvent.added(item));
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
});
}
@@ -485,6 +491,7 @@ public class DBWriter {
adapter.close();
item.addTag(FeedItem.TAG_FAVORITE);
EventBus.getDefault().post(FavoritesEvent.added(item));
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
});
}
@@ -495,6 +502,7 @@ public class DBWriter {
adapter.close();
item.removeTag(FeedItem.TAG_FAVORITE);
EventBus.getDefault().post(FavoritesEvent.removed(item));
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
});
}
@@ -545,9 +553,7 @@ public class DBWriter {
*/
public static Future<?> moveQueueItem(final int from,
final int to, final boolean broadcastUpdate) {
- return dbExec.submit(() -> {
- moveQueueItemHelper(from, to, broadcastUpdate);
- });
+ return dbExec.submit(() -> moveQueueItemHelper(from, to, broadcastUpdate));
}
/**
@@ -586,18 +592,33 @@ public class DBWriter {
/*
* Sets the 'read'-attribute of all specified FeedItems
*
- * @param context A context that is used for opening a database connection.
* @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<?> markItemPlayed(final int played, final long... itemIds) {
+ return markItemPlayed(played, true, itemIds);
+ }
+
+ /*
+ * Sets the 'read'-attribute of all specified FeedItems
+ *
+ * @param played New value of the 'read'-attribute, one of FeedItem.PLAYED, FeedItem.NEW,
+ * FeedItem.UNPLAYED
+ * @param broadcastUpdate true if this operation should trigger a UnreadItemsUpdate broadcast.
+ * This option is usually set to true
+ * @param itemIds IDs of the FeedItems.
+ */
+ public static Future<?> markItemPlayed(final int played, final boolean broadcastUpdate,
+ final long... itemIds) {
return dbExec.submit(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedItemRead(played, itemIds);
adapter.close();
- EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ if(broadcastUpdate) {
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
});
}
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 22c9538ca..04afc504b 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
@@ -51,7 +51,7 @@ public class DownloadRequester {
private Map<String, DownloadRequest> downloads;
private DownloadRequester() {
- downloads = new ConcurrentHashMap<String, DownloadRequest>();
+ downloads = new ConcurrentHashMap<>();
}
public static synchronized DownloadRequester getInstance() {
@@ -268,10 +268,7 @@ public class DownloadRequester {
* Checks if feedfile is in the downloads list
*/
public synchronized boolean isDownloadingFile(FeedFile item) {
- if (item.getDownload_url() != null) {
- return downloads.containsKey(item.getDownload_url());
- }
- return false;
+ return item.getDownload_url() != null && downloads.containsKey(item.getDownload_url());
}
public synchronized DownloadRequest getDownload(String downloadUrl) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
index 3a63685ba..d7c9e9108 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
@@ -1,10 +1,6 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.SearchResult;
-import de.danoeh.antennapod.core.util.comparator.SearchResultValueComparator;
import java.util.ArrayList;
import java.util.Collections;
@@ -12,6 +8,11 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.SearchResult;
+import de.danoeh.antennapod.core.util.comparator.SearchResultValueComparator;
+
/**
* Performs search on Feeds and FeedItems
*/
@@ -30,7 +31,7 @@ public class FeedSearcher {
context.getString(R.string.found_in_chapters_label),
context.getString(R.string.found_in_title_label)};
- List<SearchResult> result = new ArrayList<SearchResult>();
+ List<SearchResult> result = new ArrayList<>();
FutureTask<List<FeedItem>>[] tasks = new FutureTask[4];
(tasks[0] = DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query)).run();
@@ -46,9 +47,7 @@ public class FeedSearcher {
}
}
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
+ } catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
Collections.sort(result, new SearchResultValueComparator());
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 5b7f5f720..92d1e790c 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
@@ -10,12 +10,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.media.MediaMetadataRetriever;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.ProgressEvent;
@@ -303,7 +305,7 @@ public class PodDBAdapter {
private static SQLiteDatabase db;
private static Context context;
private static PodDBHelper dbHelper;
- private static int counter = 0;
+ private static AtomicInteger counter = new AtomicInteger(0);
public static void init(Context context) {
PodDBAdapter.context = context.getApplicationContext();
@@ -318,11 +320,15 @@ public class PodDBAdapter {
private PodDBAdapter() {}
- public PodDBAdapter open() {
+ public synchronized PodDBAdapter open() {
+ int adapters = counter.incrementAndGet();
+ Log.v(TAG, "Opening DB #" + adapters);
if (db == null || !db.isOpen() || db.isReadOnly()) {
- Log.v(TAG, "Opening DB");
try {
db = dbHelper.getWritableDatabase();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.enableWriteAheadLogging();
+ }
} catch (SQLException ex) {
Log.e(TAG, Log.getStackTraceString(ex));
db = dbHelper.getReadableDatabase();
@@ -331,8 +337,13 @@ public class PodDBAdapter {
return this;
}
- public void close() {
- // do nothing
+ public synchronized void close() {
+ int adapters = counter.decrementAndGet();
+ Log.v(TAG, "Closing DB #" + adapters);
+ if(adapters == 0) {
+ Log.v(TAG, "Closing DB, really");
+ db.close();
+ }
}
public static boolean deleteDatabase() {
@@ -425,34 +436,46 @@ public class PodDBAdapter {
*/
public long setImage(FeedImage image) {
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());
- values.put(KEY_DOWNLOADED, image.isDownloaded());
- values.put(KEY_FILE_URL, image.getFile_url());
- if (image.getId() == 0) {
- image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
- } else {
- db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
+ try {
+ if (!db.inTransaction()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
+ }
+ startedTransaction = true;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, image.getTitle());
+ values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
+ values.put(KEY_DOWNLOADED, image.isDownloaded());
+ values.put(KEY_FILE_URL, image.getFile_url());
+ if (image.getId() == 0) {
+ image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
new String[]{String.valueOf(image.getId())});
- }
+ }
- final FeedComponent owner = image.getOwner();
- if (owner != null && owner.getId() != 0) {
- values.clear();
- values.put(KEY_IMAGE, image.getId());
- if (owner instanceof Feed) {
- db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())});
+ final FeedComponent owner = image.getOwner();
+ if (owner != null && owner.getId() != 0) {
+ values.clear();
+ values.put(KEY_IMAGE, image.getId());
+ if (owner instanceof Feed) {
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())});
+ }
+ }
+ if (startedTransaction) {
+ db.setTransactionSuccessful();
+ }
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ if (startedTransaction) {
+ db.endTransaction();
}
- }
- if(startedTransaction) {
- db.setTransactionSuccessful();
- db.endTransaction();
}
return image.getId();
}
@@ -522,20 +545,29 @@ public class PodDBAdapter {
* transaction
*/
public void setCompleteFeed(Feed... feeds) {
- db.beginTransaction();
- for (Feed feed : feeds) {
- setFeed(feed);
- if (feed.getItems() != null) {
- for (FeedItem item : feed.getItems()) {
- setFeedItem(item, false);
- }
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
}
- if (feed.getPreferences() != null) {
- setFeedPreferences(feed.getPreferences());
+ for (Feed feed : feeds) {
+ setFeed(feed);
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ setFeedItem(item, false);
+ }
+ }
+ if (feed.getPreferences() != null) {
+ setFeedPreferences(feed.getPreferences());
+ }
}
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- db.endTransaction();
}
/**
@@ -598,19 +630,38 @@ public class PodDBAdapter {
}
public void setFeedItemlist(List<FeedItem> items) {
- db.beginTransaction();
- for (FeedItem item : items) {
- setFeedItem(item, true);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
+ }
+ for (FeedItem item : items) {
+ setFeedItem(item, true);
+ }
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- db.endTransaction();
}
public long setSingleFeedItem(FeedItem item) {
- db.beginTransaction();
- long result = setFeedItem(item, true);
- db.setTransactionSuccessful();
- db.endTransaction();
+ long result = 0;
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
+ }
+ result = setFeedItem(item, true);
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
+ }
return result;
}
@@ -728,20 +779,29 @@ public class PodDBAdapter {
public void setFeedItemRead(int played, long itemId, long mediaId,
boolean resetMediaPosition) {
- db.beginTransaction();
- ContentValues values = new ContentValues();
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
+ }
+ ContentValues values = new ContentValues();
- values.put(KEY_READ, played);
- db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)});
+ values.put(KEY_READ, played);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)});
- if (resetMediaPosition) {
- values.clear();
- values.put(KEY_POSITION, 0);
- db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
- }
+ if (resetMediaPosition) {
+ values.clear();
+ values.put(KEY_POSITION, 0);
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ }
- db.setTransactionSuccessful();
- db.endTransaction();
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
+ }
}
/**
@@ -750,15 +810,24 @@ public class PodDBAdapter {
* @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) {
- values.clear();
- values.put(KEY_READ, read);
- db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)});
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
+ }
+ ContentValues values = new ContentValues();
+ for (long id : itemIds) {
+ values.clear();
+ values.put(KEY_READ, read);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)});
+ }
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- db.endTransaction();
}
public void setChapters(FeedItem item) {
@@ -822,17 +891,26 @@ public class PodDBAdapter {
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);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ 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();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- db.endTransaction();
}
/**
@@ -880,17 +958,26 @@ public class PodDBAdapter {
public void setQueue(List<FeedItem> queue) {
ContentValues values = new ContentValues();
- db.beginTransaction();
- db.delete(TABLE_NAME_QUEUE, null, null);
- for (int i = 0; i < queue.size(); i++) {
- FeedItem item = queue.get(i);
- values.put(KEY_ID, i);
- values.put(KEY_FEEDITEM, item.getId());
- values.put(KEY_FEED, item.getFeed().getId());
- db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
+ }
+ db.delete(TABLE_NAME_QUEUE, null, null);
+ for (int i = 0; i < queue.size(); i++) {
+ FeedItem item = queue.get(i);
+ values.put(KEY_ID, i);
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_FEED, item.getFeed().getId());
+ db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ }
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
}
- db.setTransactionSuccessful();
- db.endTransaction();
}
public void clearQueue() {
@@ -937,23 +1024,32 @@ public class PodDBAdapter {
* Remove a feed with all its FeedItems and Media entries.
*/
public void removeFeed(Feed feed) {
- db.beginTransaction();
- if (feed.getImage() != null) {
- removeFeedImage(feed.getImage());
- }
- if (feed.getItems() != null) {
- for (FeedItem item : feed.getItems()) {
- removeFeedItem(item);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ db.beginTransactionNonExclusive();
+ } else {
+ db.beginTransaction();
}
- }
- // 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) });
+ if (feed.getImage() != null) {
+ removeFeedImage(feed.getImage());
+ }
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ 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 + "=?",
+ db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())});
- db.setTransactionSuccessful();
- db.endTransaction();
+ db.setTransactionSuccessful();
+ } catch (SQLException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ } finally {
+ db.endTransaction();
+ }
}
public void clearPlaybackHistory() {
@@ -972,9 +1068,8 @@ public class PodDBAdapter {
* @return The cursor of the query
*/
public final Cursor getAllFeedsCursor() {
- Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, null, null, null, null,
+ return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, null, null, null, null,
KEY_TITLE + " COLLATE NOCASE ASC");
- return c;
}
public final Cursor getFeedCursorDownloadUrls() {
@@ -992,22 +1087,19 @@ public class PodDBAdapter {
}
public final Cursor getAllItemsOfFeedCursor(final long feedId) {
- Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED
+ return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED
+ "=?", new String[]{String.valueOf(feedId)}, null, null,
- null
- );
- return c;
+ null);
}
/**
* Return a cursor with the SEL_FI_EXTRA selection of a single feeditem.
*/
public final Cursor getExtraInformationOfItem(final FeedItem item) {
- Cursor c = db
+ return db
.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?",
new String[]{String.valueOf(item.getId())}, null,
null, null);
- return c;
}
/**
@@ -1017,10 +1109,9 @@ public class PodDBAdapter {
* @return The cursor of the query
*/
public final Cursor getFeedMediaOfItemCursor(final FeedItem item) {
- Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
+ return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
new String[]{String.valueOf(item.getMedia().getId())}, null,
null, null);
- return c;
}
/**
@@ -1065,25 +1156,22 @@ public class PodDBAdapter {
}
public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
- Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
+ return db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
+ "=?", new String[]{String.valueOf(item.getId())}, null,
null, null
);
- return c;
}
public final Cursor getDownloadLog(final int feedFileType, final long feedFileId) {
final String query = "SELECT * FROM " + TABLE_NAME_DOWNLOAD_LOG +
" WHERE " + KEY_FEEDFILE + "=" + feedFileId + " AND " + KEY_FEEDFILETYPE + "=" + feedFileType
+ " ORDER BY " + KEY_ID + " DESC";
- Cursor c = db.rawQuery(query, null);
- return c;
+ return db.rawQuery(query, null);
}
public final Cursor getDownloadLogCursor(final int limit) {
- Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
+ return db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit);
- return c;
}
/**
@@ -1099,13 +1187,11 @@ public class PodDBAdapter {
TABLE_NAME_QUEUE + "." + KEY_FEEDITEM,
TABLE_NAME_QUEUE + "." + KEY_ID };
String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s", args);
- Cursor c = db.rawQuery(query, null);
- return c;
+ return db.rawQuery(query, null);
}
public Cursor getQueueIDCursor() {
- Cursor c = db.query(TABLE_NAME_QUEUE, new String[]{KEY_FEEDITEM}, null, null, null, null, KEY_ID + " ASC", null);
- return c;
+ return db.query(TABLE_NAME_QUEUE, new String[]{KEY_FEEDITEM}, null, null, null, null, KEY_ID + " ASC", null);
}
@@ -1117,8 +1203,7 @@ public class PodDBAdapter {
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;
+ return db.rawQuery(query, null);
}
/**
@@ -1126,9 +1211,8 @@ public class PodDBAdapter {
* 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
+ return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_READ
+ "<" + FeedItem.PLAYED, null, null, null, KEY_PUBDATE + " DESC");
- return c;
}
/**
@@ -1141,8 +1225,7 @@ public class PodDBAdapter {
+ " WHERE " + KEY_FEED + "=" + feedId
+ " AND " + KEY_READ + "=" + FeedItem.NEW
+ " ORDER BY " + KEY_PUBDATE + " DESC";
- Cursor c = db.rawQuery(query, null);
- return c;
+ return db.rawQuery(query, null);
}
/**
@@ -1160,13 +1243,11 @@ public class PodDBAdapter {
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;
+ return db.rawQuery(query, null);
}
public final Cursor getRecentlyPublishedItemsCursor(int limit) {
- Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, null, null, null, null, KEY_PUBDATE + " DESC LIMIT " + limit);
- return c;
+ return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, null, null, null, null, KEY_PUBDATE + " DESC LIMIT " + limit);
}
public Cursor getDownloadedItemsCursor() {
@@ -1175,8 +1256,7 @@ public class PodDBAdapter {
+ " 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;
+ return db.rawQuery(query, null);
}
/**
@@ -1192,10 +1272,9 @@ public class PodDBAdapter {
throw new IllegalArgumentException("Limit must be >= 0");
}
- Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null,
+ return db.query(TABLE_NAME_FEED_MEDIA, null,
KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
null, String.format("%s DESC LIMIT %d", KEY_PLAYBACK_COMPLETION_DATE, limit));
- return c;
}
public final Cursor getSingleFeedMediaCursor(long id) {
@@ -1244,18 +1323,17 @@ public class PodDBAdapter {
if (size == 1) {
return "(?)";
}
- StringBuffer buffer = new StringBuffer("(");
+ StringBuilder builder = new StringBuilder("(");
for (int i = 0; i < size - 1; i++) {
- buffer.append("?,");
+ builder.append("?,");
}
- buffer.append("?)");
- return buffer.toString();
+ builder.append("?)");
+ return builder.toString();
}
public final Cursor getFeedCursor(final long id) {
- Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_ID + "=" + id, null,
+ return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_ID + "=" + id, null,
null, null, null);
- return c;
}
public final Cursor getFeedItemCursor(final String id) {
@@ -1276,14 +1354,17 @@ public class PodDBAdapter {
}
public final Cursor getFeedItemCursor(final String podcastUrl, final String episodeUrl) {
- String downloadUrl = DatabaseUtils.sqlEscapeString(podcastUrl);
- String itemIdentifier = DatabaseUtils.sqlEscapeString(episodeUrl);
+ String escapedPodcastUrl = DatabaseUtils.sqlEscapeString(podcastUrl);
+ String escapedEpisodeUrl = 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;
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA
+ + " ON " + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + "=" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ + " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOAD_URL + "=" + escapedEpisodeUrl
+ + " AND " + TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL + "=" + escapedPodcastUrl;
+ Log.d(TAG, "SQL: " + query);
return db.rawQuery(query, null);
}
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 9280db8a3..66513a12e 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
@@ -47,10 +47,10 @@ public class HandlerState {
public HandlerState(Feed feed) {
this.feed = feed;
alternateUrls = new ArrayMap<>();
- items = new ArrayList<FeedItem>();
- tagstack = new Stack<SyndElement>();
+ items = new ArrayList<>();
+ tagstack = new Stack<>();
namespaces = new ArrayMap<>();
- defaultNamespaces = new Stack<Namespace>();
+ defaultNamespaces = new Stack<>();
tempObjects = new ArrayMap<>();
}
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 4d56e1365..f84d6ee96 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
@@ -41,27 +41,28 @@ public class TypeGetter {
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
String tag = xpp.getName();
- if (tag.equals(ATOM_ROOT)) {
- feed.setType(Feed.TYPE_ATOM1);
- Log.d(TAG, "Recognized type Atom");
- return Type.ATOM;
- } else if (tag.equals(RSS_ROOT)) {
- String strVersion = xpp.getAttributeValue(null, "version");
- if (strVersion != null) {
- if (strVersion.equals("2.0")) {
- feed.setType(Feed.TYPE_RSS2);
- Log.d(TAG, "Recognized type RSS 2.0");
- return Type.RSS20;
- } else if (strVersion.equals("0.91")
- || strVersion.equals("0.92")) {
- Log.d(TAG, "Recognized type RSS 0.91/0.92");
- return Type.RSS091;
+ switch (tag) {
+ case ATOM_ROOT:
+ feed.setType(Feed.TYPE_ATOM1);
+ Log.d(TAG, "Recognized type Atom");
+ return Type.ATOM;
+ case RSS_ROOT:
+ String strVersion = xpp.getAttributeValue(null, "version");
+ if (strVersion != null) {
+ if (strVersion.equals("2.0")) {
+ feed.setType(Feed.TYPE_RSS2);
+ Log.d(TAG, "Recognized type RSS 2.0");
+ return Type.RSS20;
+ } else if (strVersion.equals("0.91")
+ || strVersion.equals("0.92")) {
+ Log.d(TAG, "Recognized type RSS 0.91/0.92");
+ return Type.RSS091;
+ }
}
- }
- throw new UnsupportedFeedtypeException(Type.INVALID);
- } else {
- Log.d(TAG, "Type is invalid");
- throw new UnsupportedFeedtypeException(Type.INVALID, tag);
+ throw new UnsupportedFeedtypeException(Type.INVALID);
+ default:
+ Log.d(TAG, "Type is invalid");
+ throw new UnsupportedFeedtypeException(Type.INVALID, tag);
}
} else {
eventType = xpp.next();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java
index 71bf69ffa..306b79c15 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSContent.java
@@ -1,8 +1,9 @@
package de.danoeh.antennapod.core.syndication.namespace;
-import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import org.xml.sax.Attributes;
+import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+
public class NSContent extends Namespace {
public static final String NSTAG = "content";
public static final String NSURI = "http://purl.org/rss/1.0/modules/content/";
@@ -17,7 +18,8 @@ public class NSContent extends Namespace {
@Override
public void handleElementEnd(String localName, HandlerState state) {
- if (localName.equals(ENCODED)) {
+ if (ENCODED.equals(localName) && state.getCurrentItem() != null &&
+ state.getContentBuf() != null) {
state.getCurrentItem().setContentEncoded(state.getContentBuf().toString());
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
index 23f76186b..59d66a97e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.syndication.namespace;
import org.xml.sax.Attributes;
+import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.util.DateUtils;
@@ -21,17 +22,16 @@ public class NSDublinCore extends Namespace {
@Override
public void handleElementEnd(String localName, HandlerState state) {
- if(state.getTagstack().size() >= 2
- && state.getContentBuf() != null) {
- String content = state.getContentBuf().toString();
- SyndElement topElement = state.getTagstack().peek();
- String top = topElement.getName();
- SyndElement secondElement = state.getSecondTag();
- String second = secondElement.getName();
- if (top.equals(DATE) && second.equals(ITEM)) {
- state.getCurrentItem().setPubDate(
- DateUtils.parse(content));
+ if (state.getCurrentItem() != null && state.getContentBuf() != null &&
+ state.getTagstack() != null && state.getTagstack().size() >= 2) {
+ FeedItem currentItem = state.getCurrentItem();
+ String top = state.getTagstack().peek().getName();
+ String second = state.getSecondTag().getName();
+ if (DATE.equals(top) && ITEM.equals(second)) {
+ String content = state.getContentBuf().toString();
+ currentItem.setPubDate(DateUtils.parse(content));
}
}
}
+
}
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 99c4cd67a..1c424c6b5 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,6 +1,7 @@
package de.danoeh.antennapod.core.syndication.namespace;
import android.text.TextUtils;
+import android.util.Log;
import org.xml.sax.Attributes;
@@ -10,6 +11,7 @@ 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";
@@ -26,69 +28,82 @@ public class NSITunes extends Namespace {
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
- if (localName.equals(IMAGE)) {
+ if (IMAGE.equals(localName)) {
FeedImage image = new FeedImage();
image.setTitle(IMAGE_TITLE);
image.setDownload_url(attributes.getValue(IMAGE_HREF));
if (state.getCurrentItem() != null) {
// this is an items image
- image.setTitle(state.getCurrentItem().getTitle()+IMAGE_TITLE);
+ image.setTitle(state.getCurrentItem().getTitle() + IMAGE_TITLE);
image.setOwner(state.getCurrentItem());
state.getCurrentItem().setImage(image);
-
- } else {
+ } else {
// this is the feed image
// prefer to all other images
- if(!TextUtils.isEmpty(image.getDownload_url())) {
+ if (!TextUtils.isEmpty(image.getDownload_url())) {
image.setOwner(state.getFeed());
state.getFeed().setImage(image);
}
}
-
}
-
return new SyndElement(localName, this);
}
@Override
public void handleElementEnd(String localName, HandlerState state) {
- if (localName.equals(AUTHOR)) {
- state.getFeed().setAuthor(state.getContentBuf().toString());
- } else if (localName.equals(DURATION)) {
- String[] parts = state.getContentBuf().toString().trim().split(":");
+ if(state.getContentBuf() == null) {
+ return;
+ }
+ if (AUTHOR.equals(localName)) {
+ if (state.getFeed() != null) {
+ String author = state.getContentBuf().toString();
+ state.getFeed().setAuthor(author);
+ }
+ } else if (DURATION.equals(localName)) {
+ String durationStr = state.getContentBuf().toString();
+ if(TextUtils.isEmpty(durationStr)) {
+ return;
+ }
+ String[] parts = durationStr.trim().split(":");
try {
- int duration = 0;
+ int durationMs = 0;
if (parts.length == 2) {
- duration += TimeUnit.MINUTES.toMillis(Long.valueOf(parts[0])) +
- TimeUnit.SECONDS.toMillis(Long.valueOf(parts[1]));
+ durationMs += TimeUnit.MINUTES.toMillis(Long.parseLong(parts[0])) +
+ TimeUnit.SECONDS.toMillis((long)Float.parseFloat(parts[1]));
} else if (parts.length >= 3) {
- duration += TimeUnit.HOURS.toMillis(Long.valueOf(parts[0])) +
- TimeUnit.MINUTES.toMillis(Long.valueOf(parts[1])) +
- TimeUnit.SECONDS.toMillis(Long.valueOf(parts[2]));
+ durationMs += TimeUnit.HOURS.toMillis(Long.parseLong(parts[0])) +
+ TimeUnit.MINUTES.toMillis(Long.parseLong(parts[1])) +
+ TimeUnit.SECONDS.toMillis((long)Float.parseFloat(parts[2]));
} else {
return;
}
- state.getTempObjects().put(DURATION, duration);
+ state.getTempObjects().put(DURATION, durationMs);
} catch (NumberFormatException e) {
- e.printStackTrace();
+ Log.e(NSTAG, "Duration \"" + durationStr + "\" could not be parsed");
}
- } else if (localName.equals(SUBTITLE)) {
+ } else if (SUBTITLE.equals(localName)) {
String subtitle = state.getContentBuf().toString();
+ if (TextUtils.isEmpty(subtitle)) {
+ return;
+ }
if (state.getCurrentItem() != null) {
if (TextUtils.isEmpty(state.getCurrentItem().getDescription())) {
state.getCurrentItem().setDescription(subtitle);
}
} else {
- if (TextUtils.isEmpty(state.getFeed().getDescription())) {
+ if (state.getFeed() != null && TextUtils.isEmpty(state.getFeed().getDescription())) {
state.getFeed().setDescription(subtitle);
}
}
- } else if (localName.equals(SUMMARY)) {
+ } else if (SUMMARY.equals(localName)) {
String summary = state.getContentBuf().toString();
+ if (TextUtils.isEmpty(summary)) {
+ return;
+ }
if (state.getCurrentItem() != null) {
state.getCurrentItem().setDescription(summary);
- } else {
+ } else if (state.getFeed() != null) {
state.getFeed().setDescription(summary);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
index 7f03f1139..7a8b2bc03 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
@@ -1,14 +1,16 @@
package de.danoeh.antennapod.core.syndication.namespace;
+import android.text.TextUtils;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.syndication.handler.HandlerState;
-import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
+
import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
+
/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */
public class NSMedia extends Namespace {
private static final String TAG = "NSMedia";
@@ -25,36 +27,41 @@ public class NSMedia extends Namespace {
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
- if (localName.equals(CONTENT)) {
+ if (CONTENT.equals(localName)) {
String url = attributes.getValue(DOWNLOAD_URL);
String type = attributes.getValue(MIME_TYPE);
- if (state.getCurrentItem().getMedia() == null
- && url != null
- && (SyndTypeUtils.enclosureTypeValid(type) || ((type = SyndTypeUtils
- .getValidMimeTypeFromUrl(url)) != null))) {
-
+ boolean validType;
+ if(SyndTypeUtils.enclosureTypeValid(type)) {
+ validType = true;
+ } else {
+ type = SyndTypeUtils.getValidMimeTypeFromUrl(url);
+ validType = type != null;
+ }
+ if (state.getCurrentItem() != null && state.getCurrentItem().getMedia() == null &&
+ url != null && validType) {
long size = 0;
+ String sizeStr = attributes.getValue(SIZE);
try {
- size = Long.parseLong(attributes.getValue(SIZE));
+ size = Long.parseLong(sizeStr);
} catch (NumberFormatException e) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Length attribute could not be parsed.");
+ Log.e(TAG, "Size \"" + sizeStr + "\" could not be parsed.");
}
-
- int duration = 0;
- try {
- String durationStr = attributes.getValue(DURATION);
- if (durationStr != null) {
- duration = (int) TimeUnit.MILLISECONDS.convert(
- Long.parseLong(durationStr), TimeUnit.SECONDS);
+
+ int durationMs = 0;
+ String durationStr = attributes.getValue(DURATION);
+ if (!TextUtils.isEmpty(durationStr)) {
+ try {
+ long duration = Long.parseLong(durationStr);
+ durationMs = (int) TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Duration \"" + durationStr + "\" could not be parsed");
}
- } catch (NumberFormatException e) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Duration attribute could not be parsed");
}
-
- state.getCurrentItem().setMedia(
- new FeedMedia(state.getCurrentItem(), url, size, type));
+ FeedMedia media = new FeedMedia(state.getCurrentItem(), url, size, type);
+ if(durationMs > 0) {
+ media.setDuration(durationMs);
+ }
+ state.getCurrentItem().setMedia(media);
}
}
return new SyndElement(localName, this);
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 7e19213be..1c7952a56 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
@@ -1,10 +1,10 @@
package de.danoeh.antennapod.core.syndication.namespace;
+import android.text.TextUtils;
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;
@@ -43,18 +43,23 @@ public class NSRSS20 extends Namespace {
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
- if (localName.equals(ITEM)) {
+ if (ITEM.equals(localName)) {
state.setCurrentItem(new FeedItem());
state.getItems().add(state.getCurrentItem());
state.getCurrentItem().setFeed(state.getFeed());
- } else if (localName.equals(ENCLOSURE)) {
+ } else if (ENCLOSURE.equals(localName)) {
String type = attributes.getValue(ENC_TYPE);
String url = attributes.getValue(ENC_URL);
- if (state.getCurrentItem().getMedia() == null
- && (SyndTypeUtils.enclosureTypeValid(type) || ((type = SyndTypeUtils
- .getValidMimeTypeFromUrl(url)) != null))) {
-
+ boolean validType;
+ if(SyndTypeUtils.enclosureTypeValid(type)) {
+ validType = true;
+ } else {
+ type = type = SyndTypeUtils.getValidMimeTypeFromUrl(url);
+ validType = type != null;
+ }
+ if (state.getCurrentItem() != null && state.getCurrentItem().getMedia() == null &&
+ validType) {
long size = 0;
try {
size = Long.parseLong(attributes.getValue(ENC_LEN));
@@ -63,19 +68,18 @@ public class NSRSS20 extends Namespace {
size = 0;
}
} catch (NumberFormatException e) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Length attribute could not be parsed.");
+ Log.d(TAG, "Length attribute could not be parsed.");
}
state.getCurrentItem().setMedia(
new FeedMedia(state.getCurrentItem(), url, size, type));
}
- } else if (localName.equals(IMAGE)) {
+ } else if (IMAGE.equals(localName)) {
if (state.getTagstack().size() >= 1) {
String parent = state.getTagstack().peek().getName();
- if (parent.equals(CHANNEL)) {
+ if (CHANNEL.equals(parent)) {
Feed feed = state.getFeed();
- if(feed.getImage() == null) {
+ if(feed != null && feed.getImage() == null) {
feed.setImage(new FeedImage());
feed.getImage().setOwner(state.getFeed());
}
@@ -87,26 +91,26 @@ public class NSRSS20 extends Namespace {
@Override
public void handleElementEnd(String localName, HandlerState state) {
- if (localName.equals(ITEM)) {
+ if (ITEM.equals(localName)) {
if (state.getCurrentItem() != null) {
+ FeedItem currentItem = state.getCurrentItem();
// the title tag is optional in RSS 2.0. The description is used
// as a
// title if the item has no title-tag.
- if (state.getCurrentItem().getTitle() == null) {
- state.getCurrentItem().setTitle(
- state.getCurrentItem().getDescription());
+ if (currentItem.getTitle() == null) {
+ currentItem.setTitle(currentItem.getDescription());
}
if (state.getTempObjects().containsKey(NSITunes.DURATION)) {
- if (state.getCurrentItem().hasMedia()) {
- state.getCurrentItem().getMedia().setDuration((Integer) state.getTempObjects().get(NSITunes.DURATION));
+ if (currentItem.hasMedia()) {
+ Integer duration = (Integer) state.getTempObjects().get(NSITunes.DURATION);
+ currentItem.getMedia().setDuration(duration);
}
state.getTempObjects().remove(NSITunes.DURATION);
}
}
state.setCurrentItem(null);
- } else if (state.getTagstack().size() >= 2
- && state.getContentBuf() != null) {
+ } else if (state.getTagstack().size() >= 2 && state.getContentBuf() != null) {
String content = state.getContentBuf().toString();
SyndElement topElement = state.getTagstack().peek();
String top = topElement.getName();
@@ -116,46 +120,44 @@ public class NSRSS20 extends Namespace {
if (state.getTagstack().size() >= 3) {
third = state.getThirdTag().getName();
}
-
- if (top.equals(GUID) && second.equals(ITEM)) {
+ if (GUID.equals(top) && ITEM.equals(second)) {
// some feed creators include an empty or non-standard guid-element in their feed, which should be ignored
- if (!content.isEmpty()) {
+ if (!TextUtils.isEmpty(content) && state.getCurrentItem() != null) {
state.getCurrentItem().setItemIdentifier(content);
}
- } else if (top.equals(TITLE)) {
+ } else if (TITLE.equals(top)) {
String title = content.trim();
- if (second.equals(ITEM)) {
+ if (ITEM.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setTitle(title);
- } else if (second.equals(CHANNEL)) {
+ } else if (CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setTitle(title);
- } else if (second.equals(IMAGE) && third != null
- && third.equals(CHANNEL)) {
- if(state.getFeed().getImage().getTitle() == null) {
+ } else if (IMAGE.equals(second) && CHANNEL.equals(third)) {
+ if(state.getFeed() != null && state.getFeed().getImage() != null &&
+ state.getFeed().getImage().getTitle() == null) {
state.getFeed().getImage().setTitle(title);
}
}
- } else if (top.equals(LINK)) {
- if (second.equals(CHANNEL)) {
+ } else if (LINK.equals(top)) {
+ if (CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setLink(content);
- } else if (second.equals(ITEM)) {
+ } else if (ITEM.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setLink(content);
}
- } else if (top.equals(PUBDATE) && second.equals(ITEM)) {
- state.getCurrentItem().setPubDate(
- DateUtils.parse(content));
- } else if (top.equals(URL) && second.equals(IMAGE) && third != null
- && third.equals(CHANNEL)) {
- if(state.getFeed().getImage().getDownload_url() == null) { // prefer itunes:image
+ } else if (PUBDATE.equals(top) && ITEM.equals(second) && state.getCurrentItem() != null) {
+ state.getCurrentItem().setPubDate(DateUtils.parse(content));
+ } else if (URL.equals(top) && IMAGE.equals(second) && CHANNEL.equals(third)) {
+ // prefer itunes:image
+ if(state.getFeed() != null && state.getFeed().getImage() != null &&
+ state.getFeed().getImage().getDownload_url() == null) {
state.getFeed().getImage().setDownload_url(content);
}
- } else if (localName.equals(DESCR)) {
- if (second.equals(CHANNEL)) {
+ } else if (DESCR.equals(localName)) {
+ if (CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setDescription(content);
- } else if (second.equals(ITEM)) {
+ } else if (ITEM.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setDescription(content);
}
-
- } else if (localName.equals(LANGUAGE)) {
+ } else if (LANGUAGE.equals(localName) && state.getFeed() != null) {
state.getFeed().setLanguage(content.toLowerCase());
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
index 64b82100e..703817a35 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
@@ -6,8 +6,7 @@ import org.xml.sax.Attributes;
import java.util.ArrayList;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.util.DateUtils;
@@ -27,21 +26,22 @@ public class NSSimpleChapters extends Namespace {
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
- if (localName.equals(CHAPTERS)) {
- state.getCurrentItem().setChapters(new ArrayList<Chapter>());
- } else if (localName.equals(CHAPTER)) {
- try {
- state.getCurrentItem()
- .getChapters()
- .add(new SimpleChapter(DateUtils
- .parseTimeString(attributes.getValue(START)),
- attributes.getValue(TITLE), state.getCurrentItem(),
- attributes.getValue(HREF)));
- } catch (NumberFormatException e) {
- if (BuildConfig.DEBUG) Log.w(TAG, "Unable to read chapter", e);
+ FeedItem currentItem = state.getCurrentItem();
+ if(currentItem != null) {
+ if (localName.equals(CHAPTERS)) {
+ currentItem.setChapters(new ArrayList<>());
+ } else if (localName.equals(CHAPTER)) {
+ try {
+ long start = DateUtils.parseTimeString(attributes.getValue(START));
+ String title = attributes.getValue(TITLE);
+ String link = attributes.getValue(HREF);
+ SimpleChapter chapter = new SimpleChapter(start, title, currentItem, link);
+ currentItem.getChapters().add(chapter);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to read chapter", e);
+ }
}
}
-
return new SyndElement(localName, this);
}
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 b23a142af..7b5abf053 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
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.syndication.namespace.atom;
+import android.text.TextUtils;
import android.util.Log;
import org.xml.sax.Attributes;
@@ -65,21 +66,21 @@ public class NSAtom extends Namespace {
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
- if (localName.equals(ENTRY)) {
+ if (ENTRY.equals(localName)) {
state.setCurrentItem(new FeedItem());
state.getItems().add(state.getCurrentItem());
state.getCurrentItem().setFeed(state.getFeed());
} else if (localName.matches(isText)) {
String type = attributes.getValue(TEXT_TYPE);
return new AtomText(localName, this, type);
- } else if (localName.equals(LINK)) {
+ } else if (LINK.equals(localName)) {
String href = attributes.getValue(LINK_HREF);
String rel = attributes.getValue(LINK_REL);
SyndElement parent = state.getTagstack().peek();
if (parent.getName().matches(isFeedItem)) {
- if (rel == null || rel.equals(LINK_REL_ALTERNATE)) {
+ if (LINK_REL_ALTERNATE.equals(rel)) {
state.getCurrentItem().setLink(href);
- } else if (rel.equals(LINK_REL_ENCLOSURE)) {
+ } else if (LINK_REL_ENCLOSURE.equals(rel)) {
String strSize = attributes.getValue(LINK_LENGTH);
long size = 0;
try {
@@ -90,40 +91,45 @@ public class NSAtom extends Namespace {
Log.d(TAG, "Length attribute could not be parsed.");
}
String type = attributes.getValue(LINK_TYPE);
- if (SyndTypeUtils.enclosureTypeValid(type)
- || (type = SyndTypeUtils.getValidMimeTypeFromUrl(href)) != null) {
+ boolean validType;
+ if(SyndTypeUtils.enclosureTypeValid(type)) {
+ validType = true;
+ } else {
+ type = SyndTypeUtils.getValidMimeTypeFromUrl(href);
+ validType = type != null;
+ }
+ if (validType) {
FeedItem currItem = state.getCurrentItem();
- if(!currItem.hasMedia()) {
+ if(currItem != null && !currItem.hasMedia()) {
currItem.setMedia(new FeedMedia(currItem, href, size, type));
}
}
- } else if (rel.equals(LINK_REL_PAYMENT)) {
+ } else if (LINK_REL_PAYMENT.equals(rel)) {
state.getCurrentItem().setPaymentLink(href);
}
} else if (parent.getName().matches(isFeed)) {
- if (rel == null || rel.equals(LINK_REL_ALTERNATE)) {
+ if (LINK_REL_ALTERNATE.equals(rel)) {
String type = attributes.getValue(LINK_TYPE);
/*
* Use as link if a) no type-attribute is given and
* feed-object has no link yet b) type of link is
* LINK_TYPE_HTML or LINK_TYPE_XHTML
*/
- if ((type == null && state.getFeed().getLink() == null)
- || (type != null && (type.equals(LINK_TYPE_HTML)
- || type.equals(LINK_TYPE_XHTML)))) {
+ if (state.getFeed() != null &&
+ ((type == null && state.getFeed().getLink() == null) ||
+ (LINK_TYPE_HTML.equals(type) || LINK_TYPE_XHTML.equals(type)))) {
state.getFeed().setLink(href);
- } else if (type != null && (type.equals(LINK_TYPE_ATOM)
- || type.equals(LINK_TYPE_RSS))) {
+ } else if (LINK_TYPE_ATOM.equals(type) || LINK_TYPE_RSS.equals(type)) {
// treat as podlove alternate feed
String title = attributes.getValue(LINK_TITLE);
- if (title == null) {
+ if (TextUtils.isEmpty(title)) {
title = href;
}
state.addAlternateFeedUrl(title, href);
}
- } else if (rel.equals(LINK_REL_PAYMENT)) {
+ } else if (LINK_REL_PAYMENT.equals(rel) && state.getFeed() != null) {
state.getFeed().setPaymentLink(href);
- } else if (rel.equals(LINK_REL_NEXT)) {
+ } else if (LINK_REL_NEXT.equals(rel) && state.getFeed() != null) {
state.getFeed().setPaged(true);
state.getFeed().setNextPageLink(href);
}
@@ -134,11 +140,13 @@ public class NSAtom extends Namespace {
@Override
public void handleElementEnd(String localName, HandlerState state) {
- if (localName.equals(ENTRY)) {
+ if (ENTRY.equals(localName)) {
if (state.getCurrentItem() != null &&
state.getTempObjects().containsKey(NSITunes.DURATION)) {
- if (state.getCurrentItem().hasMedia()) {
- state.getCurrentItem().getMedia().setDuration((Integer) state.getTempObjects().get(NSITunes.DURATION));
+ FeedItem currentItem = state.getCurrentItem();
+ if (currentItem.hasMedia()) {
+ Integer duration = (Integer) state.getTempObjects().get(NSITunes.DURATION);
+ currentItem.getMedia().setDuration(duration);
}
state.getTempObjects().remove(NSITunes.DURATION);
}
@@ -163,47 +171,32 @@ public class NSAtom extends Namespace {
textElement.setContent(content);
}
- if (top.equals(ID)) {
- if (second.equals(FEED)) {
+ if (ID.equals(top)) {
+ if (FEED.equals(second) && state.getFeed() != null) {
state.getFeed().setFeedIdentifier(content);
- } else if (second.equals(ENTRY)) {
+ } else if (ENTRY.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setItemIdentifier(content);
}
- } else if (top.equals(TITLE)) {
-
- if (second.equals(FEED)) {
+ } else if (TITLE.equals(top) && textElement != null) {
+ if (FEED.equals(second) && state.getFeed() != null) {
state.getFeed().setTitle(textElement.getProcessedContent());
- } else if (second.equals(ENTRY)) {
- state.getCurrentItem().setTitle(
- textElement.getProcessedContent());
- }
- } else if (top.equals(SUBTITLE)) {
- if (second.equals(FEED)) {
- state.getFeed().setDescription(
- textElement.getProcessedContent());
- }
- } else if (top.equals(CONTENT)) {
- if (second.equals(ENTRY)) {
- state.getCurrentItem().setDescription(
- textElement.getProcessedContent());
- }
- } else if (top.equals(UPDATED)) {
- if (second.equals(ENTRY)
- && state.getCurrentItem().getPubDate() == null) {
- state.getCurrentItem().setPubDate(
- DateUtils.parse(content));
- }
- } else if (top.equals(PUBLISHED)) {
- if (second.equals(ENTRY)) {
- state.getCurrentItem().setPubDate(
- DateUtils.parse(content));
- }
- } else if (top.equals(IMAGE)) {
- if(state.getFeed().getImage() == null) {
- state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ } else if (ENTRY.equals(second) && state.getCurrentItem() != null) {
+ state.getCurrentItem().setTitle(textElement.getProcessedContent());
}
+ } else if (SUBTITLE.equals(top) && FEED.equals(second) && textElement != null &&
+ state.getFeed() != null) {
+ state.getFeed().setDescription(textElement.getProcessedContent());
+ } else if (CONTENT.equals(top) && ENTRY.equals(second) && textElement != null &&
+ state.getCurrentItem() != null) {
+ state.getCurrentItem().setDescription(textElement.getProcessedContent());
+ } else if (UPDATED.equals(top) && ENTRY.equals(second) && state.getCurrentItem() != null &&
+ state.getCurrentItem().getPubDate() == null) {
+ state.getCurrentItem().setPubDate(DateUtils.parse(content));
+ } else if (PUBLISHED.equals(top) && ENTRY.equals(second) && state.getCurrentItem() != null) {
+ state.getCurrentItem().setPubDate(DateUtils.parse(content));
+ } else if (IMAGE.equals(top) && state.getFeed() != null && state.getFeed().getImage() == null) {
+ state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
}
-
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java
index 8d1d8ffde..e84361fb2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndTypeUtils.java
@@ -30,8 +30,7 @@ public class SyndTypeUtils {
if (url != null) {
String extension = FilenameUtils.getExtension(url);
if (extension != null) {
- String type = MimeTypeMap.getSingleton()
- .getMimeTypeFromExtension(extension);
+ String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (type != null && enclosureTypeValid(type)) {
return type;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java
index 8dd9ffe4b..5169f7e76 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java
@@ -10,7 +10,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
@@ -63,11 +62,7 @@ public class ChapterUtils {
} else {
Log.i(TAG, "ChapterReader could not find any ID3 chapters");
}
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ID3ReaderException e) {
+ } catch (IOException | ID3ReaderException e) {
e.printStackTrace();
} finally {
if (in != null) {
@@ -116,9 +111,7 @@ public class ChapterUtils {
Log.i(TAG,
"ChapterReader could not find any ID3 chapters");
}
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ID3ReaderException e) {
+ } catch (IOException | ID3ReaderException e) {
e.printStackTrace();
} finally {
if (in != null) {
@@ -144,8 +137,6 @@ public class ChapterUtils {
if (input != null) {
readOggChaptersFromInputStream(media, input);
}
- } catch (MalformedURLException e) {
- e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
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 1b929b214..70a180913 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
@@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.util.Log;
+import java.util.Locale;
+
import de.danoeh.antennapod.core.R;
/** Provides methods for converting various units. */
@@ -88,9 +90,9 @@ public final class Converter {
if (parts.length != 3) {
return 0;
}
- return Integer.valueOf(parts[0]) * 3600 * 1000 +
- Integer.valueOf(parts[1]) * 60 * 1000 +
- Integer.valueOf(parts[2]) * 1000;
+ return Integer.parseInt(parts[0]) * 3600 * 1000 +
+ Integer.parseInt(parts[1]) * 60 * 1000 +
+ Integer.parseInt(parts[2]) * 1000;
}
/** Converts short duration string (HH:MM) to milliseconds. */
@@ -99,8 +101,8 @@ public final class Converter {
if (parts.length != 2) {
return 0;
}
- return Integer.valueOf(parts[0]) * 3600 * 1000 +
- Integer.valueOf(parts[1]) * 1000 * 60;
+ return Integer.parseInt(parts[0]) * 3600 * 1000 +
+ Integer.parseInt(parts[1]) * 1000 * 60;
}
/** Converts milliseconds to a localized string containing hours and minutes */
@@ -118,5 +120,26 @@ public final class Converter {
result += minutes;
return result;
}
-
+
+ /**
+ * Converts seconds to a localized representation
+ * @param time The time in seconds
+ * @return "HH:MM hours"
+ */
+ public static String shortLocalizedDuration(Context context, long time) {
+ float hours = (float) time / 3600f;
+ return String.format(Locale.getDefault(), "%.1f ", hours) + context.getString(R.string.time_hours);
+ }
+
+ /**
+ * Converts the volume as read as the progress from a SeekBar scaled to 100 and as saved in
+ * UserPreferences to the format taken by setVolume methods.
+ * @param progress integer between 0 to 100 taken from the SeekBar progress
+ * @return the appropriate volume as float taken by setVolume methods
+ */
+ public static float getVolumeFromPercentage(int progress){
+ if (progress==100)
+ return 1f;
+ return (float) (1 - (Math.log(101 - progress) / Math.log(101)));
+ }
}
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 155ef519b..314062e52 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
@@ -115,13 +115,13 @@ public class DateUtils {
int idx = 0;
if (parts.length == 3) {
// string has hours
- result += Integer.valueOf(parts[idx]) * 3600000L;
+ result += Integer.parseInt(parts[idx]) * 3600000L;
idx++;
}
if (parts.length >= 2) {
- result += Integer.valueOf(parts[idx]) * 60000L;
+ result += Integer.parseInt(parts[idx]) * 60000L;
idx++;
- result += (Float.valueOf(parts[idx])) * 1000L;
+ result += (Float.parseFloat(parts[idx])) * 1000L;
}
return result;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java
index 602c221bf..7779158e5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java
@@ -19,12 +19,15 @@ public enum DownloadError {
ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host),
ERROR_REQUEST_ERROR(12, R.string.download_error_request_error),
ERROR_DB_ACCESS_ERROR(13, R.string.download_error_db_access),
- ERROR_UNAUTHORIZED(14, R.string.download_error_unauthorized);
+ ERROR_UNAUTHORIZED(14, R.string.download_error_unauthorized),
+ ERROR_FILE_TYPE(15, R.string.download_error_file_type_type),
+ ERROR_FORBIDDEN(16, R.string.download_error_forbidden);
+
private final int code;
private final int resId;
- private DownloadError(int code, int resId) {
+ DownloadError(int code, int resId) {
this.code = code;
this.resId = resId;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
index 029e7fe84..89edd7dbe 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
@@ -13,7 +13,7 @@ public class EpisodeFilter {
/** Return a copy of the itemlist without items which have no media. */
public static ArrayList<FeedItem> getEpisodeList(List<FeedItem> items) {
- ArrayList<FeedItem> episodes = new ArrayList<FeedItem>(items);
+ ArrayList<FeedItem> episodes = new ArrayList<>(items);
for (FeedItem item : items) {
if (item.getMedia() == null) {
episodes.remove(item);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
index 673c81235..f48b9169b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
@@ -35,7 +35,7 @@ public final class IntList {
int hashCode = 1;
for (int i = 0; i < size; i++) {
int value = values[i];
- hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ hashCode = 31 * hashCode + value;
}
return hashCode;
}
@@ -62,7 +62,7 @@ public final class IntList {
@Override
public String toString() {
- StringBuffer sb = new StringBuffer(size * 5 + 10);
+ StringBuilder sb = new StringBuilder(size * 5 + 10);
sb.append("IntList{");
for (int i = 0; i < size; i++) {
if (i != 0) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
index 33fd252eb..c5ac44bf5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
@@ -1,6 +1,8 @@
package de.danoeh.antennapod.core.util;
+import java.util.Arrays;
+
/**
* Fast and memory efficient long to long map
*/
@@ -197,6 +199,15 @@ public class LongIntMap {
size = 0;
}
+ /**
+ * Returns a copy of the values contained in this map.
+ *
+ * @return a copy of the values contained in this map
+ */
+ public int[] values() {
+ return Arrays.copyOf(values, size);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
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 6ed8b820e..fdc244517 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
@@ -73,7 +73,7 @@ public final class LongList {
@Override
public String toString() {
- StringBuffer sb = new StringBuffer(size * 5 + 10);
+ StringBuilder sb = new StringBuilder(size * 5 + 10);
sb.append("LongList{");
for (int i = 0; i < size; i++) {
if (i != 0) {
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 c2cd273b8..55b608dce 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
@@ -92,11 +92,23 @@ public class NetworkUtils {
return mWifi.isConnected();
}
+ /**
+ * Returns the SSID of the wifi connection, or <code>null</code> if there is no wifi.
+ */
+ public static String getWifiSsid() {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+ if (wifiInfo != null) {
+ return wifiInfo.getSSID();
+ }
+ return null;
+ }
+
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()) {
+ if (!NetworkUtils.isDownloadAllowed()) {
subscriber.onNext(0L);
subscriber.onCompleted();
return;
@@ -107,7 +119,7 @@ public class NetworkUtils {
if (mediaFile.exists()) {
size = mediaFile.length();
}
- } else if (false == media.checkedOnSizeButUnknown()) {
+ } else if (!media.checkedOnSizeButUnknown()) {
// only query the network if we haven't already checked
String url = media.getDownload_url();
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 71d6040ba..5dc194dbd 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
@@ -2,12 +2,12 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
+import java.util.Comparator;
+
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.DBWriter;
-import java.util.Comparator;
-
/**
* Provides method for sorting the queue according to rules.
*/
@@ -26,59 +26,39 @@ public class QueueSorter {
switch (rule) {
case ALPHA_ASC:
- comparator = new Comparator<FeedItem>() {
- public int compare(FeedItem f1, FeedItem f2) {
- return f1.getTitle().compareTo(f2.getTitle());
- }
- };
+ comparator = (f1, f2) -> f1.getTitle().compareTo(f2.getTitle());
break;
case ALPHA_DESC:
- comparator = new Comparator<FeedItem>() {
- public int compare(FeedItem f1, FeedItem f2) {
- return f2.getTitle().compareTo(f1.getTitle());
- }
- };
+ comparator = (f1, f2) -> f2.getTitle().compareTo(f1.getTitle());
break;
case DATE_ASC:
- comparator = new Comparator<FeedItem>() {
- public int compare(FeedItem f1, FeedItem f2) {
- return f1.getPubDate().compareTo(f2.getPubDate());
- }
- };
+ comparator = (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate());
break;
case DATE_DESC:
- comparator = new Comparator<FeedItem>() {
- public int compare(FeedItem f1, FeedItem f2) {
- return f2.getPubDate().compareTo(f1.getPubDate());
- }
- };
+ comparator = (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate());
break;
case DURATION_ASC:
- comparator = new Comparator<FeedItem>() {
- public int compare(FeedItem f1, FeedItem f2) {
- FeedMedia f1Media = f1.getMedia();
- FeedMedia f2Media = f2.getMedia();
- int duration1 = f1Media != null ? f1Media.getDuration() : -1;
- int duration2 = f2Media != null ? f2Media.getDuration() : -1;
+ comparator = (f1, f2) -> {
+ FeedMedia f1Media = f1.getMedia();
+ FeedMedia f2Media = f2.getMedia();
+ int duration1 = f1Media != null ? f1Media.getDuration() : -1;
+ int duration2 = f2Media != null ? f2Media.getDuration() : -1;
- if (duration1 == -1 || duration2 == -1)
- return duration2 - duration1;
- else
- return duration1 - duration2;
- }
+ if (duration1 == -1 || duration2 == -1)
+ return duration2 - duration1;
+ else
+ return duration1 - duration2;
};
break;
case DURATION_DESC:
- comparator = new Comparator<FeedItem>() {
- public int compare(FeedItem f1, FeedItem f2) {
+ comparator = (f1, f2) -> {
FeedMedia f1Media = f1.getMedia();
FeedMedia f2Media = f2.getMedia();
int duration1 = f1Media != null ? f1Media.getDuration() : -1;
int duration2 = f2Media != null ? f2Media.getDuration() : -1;
return -1 * (duration1 - duration2);
- }
- };
+ };
default:
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java
index 7e7c6c08b..a4cd83f70 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShownotesProvider.java
@@ -11,6 +11,6 @@ public interface ShownotesProvider {
* database, it should be done in a separate thread. After the shownotes
* have been loaded, callback.onShownotesLoaded should be called.
*/
- public Callable<String> loadShownotes();
+ Callable<String> loadShownotes();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Supplier.java b/core/src/main/java/de/danoeh/antennapod/core/util/Supplier.java
new file mode 100644
index 000000000..796b03154
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Supplier.java
@@ -0,0 +1,5 @@
+package de.danoeh.antennapod.core.util;
+
+public interface Supplier<T> {
+ T get();
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java
index 092c06b4a..e093dc766 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/URIUtil.java
@@ -1,13 +1,14 @@
package de.danoeh.antennapod.core.util;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import de.danoeh.antennapod.core.BuildConfig;
+
/**
* Utility methods for dealing with URL encoding.
*/
@@ -26,9 +27,7 @@ public class URIUtil {
try {
URL url = new URL(source);
return new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException(e);
- } catch (URISyntaxException e) {
+ } catch (MalformedURLException | URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java
index e4818214e..97958eea7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java
@@ -1,11 +1,13 @@
package de.danoeh.antennapod.core.util.flattr;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
+
import org.shredzone.flattr4j.FlattrFactory;
import org.shredzone.flattr4j.FlattrService;
import org.shredzone.flattr4j.oauth.AccessToken;
+import de.danoeh.antennapod.core.BuildConfig;
+
/** Ensures that only one instance of the FlattrService class exists at a time */
public class FlattrServiceCreator {
@@ -13,11 +15,14 @@ public class FlattrServiceCreator {
private static volatile FlattrService flattrService;
- public static FlattrService getService(AccessToken token) {
- return FlattrFactory.getInstance().createFlattrService(token);
+ public synchronized static FlattrService getService(AccessToken token) {
+ if (flattrService == null) {
+ flattrService = FlattrFactory.getInstance().createFlattrService(token);
+ }
+ return flattrService;
}
- public static void deleteFlattrService() {
+ public synchronized static void deleteFlattrService() {
if (BuildConfig.DEBUG) Log.d(TAG, "Deleting service instance");
flattrService = null;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java
deleted file mode 100644
index 17581d3e9..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/FeedItemUndoToken.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package de.danoeh.antennapod.core.util.gui;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
-/**
- * Used by an UndoBarController for saving a removed FeedItem
- */
-public class FeedItemUndoToken implements Parcelable {
- private long itemId;
- private long feedId;
- private int position;
-
- public FeedItemUndoToken(FeedItem item, int position) {
- this.itemId = item.getId();
- this.feedId = item.getFeed().getId();
- this.position = position;
- }
-
- private FeedItemUndoToken(Parcel in) {
- itemId = in.readLong();
- feedId = in.readLong();
- position = in.readInt();
- }
-
- public static final Parcelable.Creator<FeedItemUndoToken> CREATOR = new Parcelable.Creator<FeedItemUndoToken>() {
- public FeedItemUndoToken createFromParcel(Parcel in) {
- return new FeedItemUndoToken(in);
- }
-
- public FeedItemUndoToken[] newArray(int size) {
- return new FeedItemUndoToken[size];
- }
- };
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeLong(itemId);
- out.writeLong(feedId);
- out.writeInt(position);
- }
-
- public long getFeedItemId() {
- return itemId;
- }
-
- public int getPosition() {
- return position;
- }
-}
-
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java
index d56871fd1..386f46724 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java
@@ -19,12 +19,9 @@ public class MoreContentListFooterUtil {
public MoreContentListFooterUtil(View root) {
this.root = root;
- root.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (listener != null && !loading) {
- listener.onClick();
- }
+ root.setOnClickListener(v -> {
+ if (listener != null && !loading) {
+ listener.onClick();
}
});
}
@@ -46,8 +43,8 @@ public class MoreContentListFooterUtil {
listener = l;
}
- public static interface Listener {
- public void onClick();
+ public interface Listener {
+ void onClick();
}
public View getRoot() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java
index 9f3c4c6d5..1807421b0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java
@@ -1,11 +1,6 @@
package de.danoeh.antennapod.core.util.id3reader;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.ID3Chapter;
-import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
-import de.danoeh.antennapod.core.util.id3reader.model.TagHeader;
import java.io.IOException;
import java.io.InputStream;
@@ -13,6 +8,12 @@ import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
+import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.ID3Chapter;
+import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
+import de.danoeh.antennapod.core.util.id3reader.model.TagHeader;
+
public class ChapterReader extends ID3Reader {
private static final String TAG = "ID3ChapterReader";
@@ -25,7 +26,7 @@ public class ChapterReader extends ID3Reader {
@Override
public int onStartTagHeader(TagHeader header) {
- chapters = new ArrayList<Chapter>();
+ chapters = new ArrayList<>();
System.out.println(header.toString());
return ID3Reader.ACTION_DONT_SKIP;
}
@@ -34,49 +35,53 @@ public class ChapterReader extends ID3Reader {
public int onStartFrameHeader(FrameHeader header, InputStream input)
throws IOException, ID3ReaderException {
System.out.println(header.toString());
- if (header.getId().equals(FRAME_ID_CHAPTER)) {
- if (currentChapter != null) {
- if (!hasId3Chapter(currentChapter)) {
- chapters.add(currentChapter);
- if (BuildConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter);
- currentChapter = null;
+ switch (header.getId()) {
+ case FRAME_ID_CHAPTER:
+ if (currentChapter != null) {
+ if (!hasId3Chapter(currentChapter)) {
+ chapters.add(currentChapter);
+ if (BuildConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter);
+ currentChapter = null;
+ }
}
- }
- StringBuffer elementId = new StringBuffer();
- readISOString(elementId, input, Integer.MAX_VALUE);
- char[] startTimeSource = readBytes(input, 4);
- long startTime = ((int) startTimeSource[0] << 24)
- | ((int) startTimeSource[1] << 16)
- | ((int) startTimeSource[2] << 8) | startTimeSource[3];
- currentChapter = new ID3Chapter(elementId.toString(), startTime);
- skipBytes(input, 12);
- return ID3Reader.ACTION_DONT_SKIP;
- } else if (header.getId().equals(FRAME_ID_TITLE)) {
- if (currentChapter != null && currentChapter.getTitle() == null) {
- StringBuffer title = new StringBuffer();
- readString(title, input, header.getSize());
- currentChapter
- .setTitle(title.toString());
- if (BuildConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle());
-
+ StringBuffer elementId = new StringBuffer();
+ readISOString(elementId, input, Integer.MAX_VALUE);
+ char[] startTimeSource = readBytes(input, 4);
+ long startTime = ((int) startTimeSource[0] << 24)
+ | ((int) startTimeSource[1] << 16)
+ | ((int) startTimeSource[2] << 8) | startTimeSource[3];
+ currentChapter = new ID3Chapter(elementId.toString(), startTime);
+ skipBytes(input, 12);
return ID3Reader.ACTION_DONT_SKIP;
- }
- } else if (header.getId().equals(FRAME_ID_LINK)) {
- if (currentChapter != null) {
- // skip description
- int descriptionLength = readString(null, input, header.getSize());
- StringBuffer link = new StringBuffer();
- readISOString(link, input, header.getSize() - descriptionLength);
- String decodedLink = URLDecoder.decode(link.toString(), "UTF-8");
-
- currentChapter.setLink(decodedLink);
-
- if (BuildConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink());
- return ID3Reader.ACTION_DONT_SKIP;
- }
- } else if (header.getId().equals("APIC")) {
- Log.d(TAG, header.toString());
- }
+ case FRAME_ID_TITLE:
+ if (currentChapter != null && currentChapter.getTitle() == null) {
+ StringBuffer title = new StringBuffer();
+ readString(title, input, header.getSize());
+ currentChapter
+ .setTitle(title.toString());
+ if (BuildConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle());
+
+ return ID3Reader.ACTION_DONT_SKIP;
+ }
+ break;
+ case FRAME_ID_LINK:
+ if (currentChapter != null) {
+ // skip description
+ int descriptionLength = readString(null, input, header.getSize());
+ StringBuffer link = new StringBuffer();
+ readISOString(link, input, header.getSize() - descriptionLength);
+ String decodedLink = URLDecoder.decode(link.toString(), "UTF-8");
+
+ currentChapter.setLink(decodedLink);
+
+ if (BuildConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink());
+ return ID3Reader.ACTION_DONT_SKIP;
+ }
+ break;
+ case "APIC":
+ Log.d(TAG, header.toString());
+ break;
+ }
return super.onStartFrameHeader(header, input);
}
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 f0850e6df..846733882 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
@@ -1,8 +1,11 @@
package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.util.Log;
import android.view.SurfaceHolder;
+
import org.antennapod.audio.MediaPlayer;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -12,8 +15,17 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
public AudioPlayer(Context context) {
super(context);
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .registerOnSharedPreferenceChangeListener(sonicListener);
}
+ private final SharedPreferences.OnSharedPreferenceChangeListener sonicListener =
+ (sharedPreferences, key) -> {
+ if (key.equals(UserPreferences.PREF_SONIC)) {
+ checkMpi();
+ }
+ };
+
@Override
public void setScreenOnWhilePlaying(boolean screenOn) {
Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
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 ec50dce7c..412a27b95 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
@@ -6,13 +6,14 @@ import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.MediaType;
-import de.danoeh.antennapod.core.util.ChapterUtils;
import java.util.List;
import java.util.concurrent.Callable;
+import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.util.ChapterUtils;
+
/** Represents a media file that is stored on the local storage device. */
public class ExternalMedia implements Playable {
@@ -106,12 +107,7 @@ public class ExternalMedia implements Playable {
@Override
public Callable<String> loadShownotes() {
- return new Callable<String>() {
- @Override
- public String call() throws Exception {
- return "";
- }
- };
+ return () -> "";
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java
index 0650225f0..5ba7f11d6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java
@@ -6,7 +6,7 @@ import de.danoeh.antennapod.core.R;
/** Utility class for MediaPlayer errors. */
public class MediaPlayerError {
-
+
/** Get a human-readable string for a specific error code. */
public static String getErrorString(Context context, int code) {
int resId;
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 86ec4fbd0..201efbc81 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
@@ -8,6 +8,7 @@ import android.util.Log;
import java.util.List;
import de.danoeh.antennapod.core.asynctask.ImageResource;
+import de.danoeh.antennapod.core.cast.RemoteMedia;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
@@ -26,7 +27,7 @@ public interface Playable extends Parcelable,
* Implementations must NOT call commit() after they have written the values
* to the preferences file.
*/
- public void writeToPreferences(SharedPreferences.Editor prefEditor);
+ void writeToPreferences(SharedPreferences.Editor prefEditor);
/**
* This method is called from a separate thread by the PlaybackService.
@@ -34,88 +35,88 @@ public interface Playable extends Parcelable,
* should execute as quickly as possible and NOT load chapter marks if no
* local file is available.
*/
- public void loadMetadata() throws PlayableException;
+ void loadMetadata() throws PlayableException;
/**
* This method is called from a separate thread by the PlaybackService.
* Playable objects should load their chapter marks in this method if no
* local file was available when loadMetadata() was called.
*/
- public void loadChapterMarks();
+ void loadChapterMarks();
/**
* Returns the title of the episode that this playable represents
*/
- public String getEpisodeTitle();
+ String getEpisodeTitle();
/**
* Returns a list of chapter marks or null if this Playable has no chapters.
*/
- public List<Chapter> getChapters();
+ List<Chapter> getChapters();
/**
* Returns a link to a website that is meant to be shown in a browser
*/
- public String getWebsiteLink();
+ String getWebsiteLink();
- public String getPaymentLink();
+ String getPaymentLink();
/**
* Returns the title of the feed this Playable belongs to.
*/
- public String getFeedTitle();
+ String getFeedTitle();
/**
* Returns a unique identifier, for example a file url or an ID from a
* database.
*/
- public Object getIdentifier();
+ Object getIdentifier();
/**
* Return duration of object or 0 if duration is unknown.
*/
- public int getDuration();
+ int getDuration();
/**
* Return position of object or 0 if position is unknown.
*/
- public int getPosition();
+ int getPosition();
/**
* Returns last time (in ms) when this playable was played or 0
* if last played time is unknown.
*/
- public long getLastPlayedTime();
+ long getLastPlayedTime();
/**
* Returns the type of media. This method should return the correct value
* BEFORE loadMetadata() is called.
*/
- public MediaType getMediaType();
+ MediaType getMediaType();
/**
* Returns an url to a local file that can be played or null if this file
* does not exist.
*/
- public String getLocalMediaUrl();
+ String getLocalMediaUrl();
/**
* Returns an url to a file that can be streamed by the player or null if
* this url is not known.
*/
- public String getStreamUrl();
+ String getStreamUrl();
/**
* Returns true if a local file that can be played is available. getFileUrl
* MUST return a non-null string if this method returns true.
*/
- public boolean localFileAvailable();
+ boolean localFileAvailable();
/**
* Returns true if a streamable file is available. getStreamUrl MUST return
* a non-null string if this method returns true.
*/
- public boolean streamAvailable();
+ boolean streamAvailable();
/**
* Saves the current position of this object. Implementations can use the
@@ -126,40 +127,40 @@ public interface Playable extends Parcelable,
* @param newPosition new playback position in ms
* @param timestamp current time in ms
*/
- public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp);
+ void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp);
- public void setPosition(int newPosition);
+ void setPosition(int newPosition);
- public void setDuration(int newDuration);
+ void setDuration(int newDuration);
/**
* @param lastPlayedTimestamp timestamp in ms
*/
- public void setLastPlayedTime(long lastPlayedTimestamp);
+ void setLastPlayedTime(long lastPlayedTimestamp);
/**
* Is called by the PlaybackService when playback starts.
*/
- public void onPlaybackStart();
+ void onPlaybackStart();
/**
* Is called by the PlaybackService when playback is completed.
*/
- public void onPlaybackCompleted();
+ void onPlaybackCompleted();
/**
* Returns an integer that must be unique among all Playable classes. The
* return value is later used by PlayableUtils to determine the type of the
* Playable object that is restored.
*/
- public int getPlayableType();
+ int getPlayableType();
- public void setChapters(List<Chapter> chapters);
+ void setChapters(List<Chapter> chapters);
/**
* Provides utility methods for Playable objects.
*/
- public static class PlayableUtils {
+ class PlayableUtils {
private static final String TAG = "PlayableUtils";
/**
@@ -183,6 +184,9 @@ public interface Playable extends Parcelable,
case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
result = createExternalMediaInstance(pref);
break;
+ case RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA:
+ result = createRemoteMediaInstance(pref);
+ break;
}
if (result == null) {
Log.e(TAG, "Could not restore Playable object from preferences");
@@ -211,9 +215,15 @@ public interface Playable extends Parcelable,
}
return result;
}
+
+ private static Playable createRemoteMediaInstance(SharedPreferences pref) {
+ //TODO there's probably no point in restoring RemoteMedia from preferences, because we
+ //only care about it while it's playing on the cast device.
+ return null;
+ }
}
- public static class PlayableException extends Exception {
+ class PlayableException extends Exception {
private static final long serialVersionUID = 1L;
public PlayableException() {
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 27935978c..7870c747e 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
@@ -10,7 +10,6 @@ import android.content.ServiceConnection;
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;
@@ -19,7 +18,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
-import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -42,6 +40,10 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.playback.Playable.PlayableUtils;
+import rx.Observable;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
/**
* Communicates with the playback service. GUI classes should use this class to
@@ -51,28 +53,30 @@ public abstract class PlaybackController {
private static final String TAG = "PlaybackController";
- public static final int INVALID_TIME = -1;
+ private static final int INVALID_TIME = -1;
private final Activity activity;
private PlaybackService playbackService;
- protected Playable media;
+ private Playable media;
private PlayerStatus status;
- private ScheduledThreadPoolExecutor schedExecutor;
+ private final ScheduledThreadPoolExecutor schedExecutor;
private static final int SCHED_EX_POOLSIZE = 1;
- protected MediaPositionObserver positionObserver;
- protected ScheduledFuture positionObserverFuture;
+ private MediaPositionObserver positionObserver;
+ private ScheduledFuture positionObserverFuture;
private boolean mediaInfoLoaded = false;
private boolean released = false;
+ private Subscription serviceBinder;
+
/**
* True if controller should reinit playback service if 'pause' button is
* pressed.
*/
- private boolean reinitOnPause;
+ private final boolean reinitOnPause;
public PlaybackController(@NonNull Activity activity, boolean reinitOnPause) {
@@ -87,8 +91,7 @@ public abstract class PlaybackController {
@Override
public void rejectedExecution(Runnable r,
ThreadPoolExecutor executor) {
- Log.w(TAG,
- "Rejected execution of runnable in schedExecutor");
+ Log.w(TAG, "Rejected execution of runnable in schedExecutor");
}
}
);
@@ -111,8 +114,7 @@ public abstract class PlaybackController {
if (!released) {
bindToService();
} else {
- throw new IllegalStateException(
- "Can't call init() after release() has been called");
+ throw new IllegalStateException("Can't call init() after release() has been called");
}
checkMediaInfoLoaded();
}
@@ -136,6 +138,9 @@ public abstract class PlaybackController {
// ignore
}
+ if(serviceBinder != null) {
+ serviceBinder.unsubscribe();
+ }
try {
activity.unbindService(mConnection);
} catch (IllegalArgumentException e) {
@@ -168,34 +173,33 @@ public abstract class PlaybackController {
*/
private void bindToService() {
Log.d(TAG, "Trying to connect to service");
- AsyncTask<Void, Void, Intent> intentLoader = new AsyncTask<Void, Void, Intent>() {
- @Override
- protected Intent doInBackground(Void... voids) {
- return getPlayLastPlayedMediaIntent();
- }
-
- @Override
- protected void onPostExecute(Intent serviceIntent) {
- boolean bound = false;
- if (!PlaybackService.started) {
- if (serviceIntent != null) {
- Log.d(TAG, "Calling start service");
- activity.startService(serviceIntent);
- bound = activity.bindService(serviceIntent, mConnection, 0);
+ if(serviceBinder != null) {
+ serviceBinder.unsubscribe();
+ }
+ serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(intent -> {
+ boolean bound = false;
+ if (!PlaybackService.started) {
+ if (intent != null) {
+ Log.d(TAG, "Calling start service");
+ activity.startService(intent);
+ bound = activity.bindService(intent, mConnection, 0);
+ } else {
+ status = PlayerStatus.STOPPED;
+ setupGUI();
+ handleStatus();
+ }
} else {
- status = PlayerStatus.STOPPED;
- setupGUI();
- handleStatus();
+ Log.d(TAG, "PlaybackService is running, trying to connect without start command.");
+ bound = activity.bindService(new Intent(activity, PlaybackService.class),
+ mConnection, 0);
}
- } else {
- Log.d(TAG, "PlaybackService is running, trying to connect without start command.");
- bound = activity.bindService(new Intent(activity,
- PlaybackService.class), mConnection, 0);
- }
- Log.d(TAG, "Result for service binding: " + bound);
- }
- };
- intentLoader.execute();
+ Log.d(TAG, "Result for service binding: " + bound);
+ }, error -> {
+ Log.e(TAG, Log.getStackTraceString(error));
+ });
}
/**
@@ -204,27 +208,21 @@ public abstract class PlaybackController {
*/
private Intent getPlayLastPlayedMediaIntent() {
Log.d(TAG, "Trying to restore last played media");
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(activity.getApplicationContext());
- long currentlyPlayingMedia = PlaybackPreferences
- .getCurrentlyPlayingMedia();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+ activity.getApplicationContext());
+ long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
Playable media = PlayableUtils.createInstanceFromPreferences(activity,
(int) currentlyPlayingMedia, prefs);
if (media != null) {
- Intent serviceIntent = new Intent(activity,
- PlaybackService.class);
+ Intent serviceIntent = new Intent(activity, PlaybackService.class);
serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- serviceIntent.putExtra(
- PlaybackService.EXTRA_START_WHEN_PREPARED, false);
- serviceIntent.putExtra(
- PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
+ serviceIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, false);
+ serviceIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
boolean fileExists = media.localFileAvailable();
- boolean lastIsStream = PlaybackPreferences
- .getCurrentEpisodeIsStream();
+ boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
- DBTasks.notifyMissingFeedMediaFile(
- activity, (FeedMedia) media);
+ DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
}
serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
lastIsStream || !fileExists);
@@ -238,10 +236,9 @@ public abstract class PlaybackController {
private void setupPositionObserver() {
- if ((positionObserverFuture != null && positionObserverFuture
- .isCancelled())
- || (positionObserverFuture != null && positionObserverFuture
- .isDone()) || positionObserverFuture == null) {
+ if (positionObserverFuture == null ||
+ positionObserverFuture.isCancelled() ||
+ positionObserverFuture.isDone()) {
Log.d(TAG, "Setting up position observer");
positionObserver = new MediaPositionObserver();
@@ -259,7 +256,7 @@ public abstract class PlaybackController {
}
}
- private ServiceConnection mConnection = new ServiceConnection() {
+ private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
playbackService = ((PlaybackService.LocalBinder) service)
.getService();
@@ -267,7 +264,8 @@ public abstract class PlaybackController {
queryService();
Log.d(TAG, "Connection to Service established");
} else {
- Log.i(TAG, "Connection to playback service has been established, but controller has already been released");
+ Log.i(TAG, "Connection to playback service has been established, " +
+ "but controller has already been released");
}
}
@@ -278,7 +276,7 @@ public abstract class PlaybackController {
}
};
- protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
+ private final BroadcastReceiver statusUpdate = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received statusUpdate Intent.");
@@ -288,66 +286,67 @@ public abstract class PlaybackController {
media = info.playable;
handleStatus();
} else {
- Log.w(TAG,
- "Couldn't receive status update: playbackService was null");
+ Log.w(TAG, "Couldn't receive status update: playbackService was null");
bindToService();
}
}
};
- protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver notificationReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (isConnectedToPlaybackService()) {
- int type = intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_TYPE, -1);
- int code = intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_CODE, -1);
- if (code != -1 && type != -1) {
- switch (type) {
- case PlaybackService.NOTIFICATION_TYPE_ERROR:
- handleError(code);
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
- float progress = ((float) code) / 100;
- onBufferUpdate(progress);
- break;
- case PlaybackService.NOTIFICATION_TYPE_RELOAD:
- cancelPositionObserver();
- mediaInfoLoaded = false;
- queryService();
- onReloadNotification(intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
- break;
- case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
- onSleepTimerUpdate();
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_START:
- onBufferStart();
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_END:
- onBufferEnd();
- break;
- case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
- onPlaybackEnd();
- break;
- case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
- onPlaybackSpeedChange();
- break;
- }
-
- } else {
- Log.d(TAG, "Bad arguments. Won't handle intent");
- }
- } else {
+ if (!isConnectedToPlaybackService()) {
bindToService();
+ return;
+ }
+ int type = intent.getIntExtra(PlaybackService.EXTRA_NOTIFICATION_TYPE, -1);
+ int code = intent.getIntExtra(PlaybackService.EXTRA_NOTIFICATION_CODE, -1);
+ if(code == -1 || type == -1) {
+ Log.d(TAG, "Bad arguments. Won't handle intent");
+ return;
+ }
+ switch (type) {
+ case PlaybackService.NOTIFICATION_TYPE_ERROR:
+ handleError(code);
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
+ float progress = ((float) code) / 100;
+ onBufferUpdate(progress);
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_RELOAD:
+ cancelPositionObserver();
+ mediaInfoLoaded = false;
+ queryService();
+ onReloadNotification(intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
+ onSleepTimerUpdate();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_START:
+ onBufferStart();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_END:
+ onBufferEnd();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
+ onPlaybackEnd();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
+ onPlaybackSpeedChange();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED:
+ onSetSpeedAbilityChanged();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_SHOW_TOAST:
+ postStatusMsg(code, true);
}
}
};
- private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -361,31 +360,33 @@ public abstract class PlaybackController {
}
};
- public void setupGUI() {};
+ public void setupGUI() {}
+
+ public void onPositionObserverUpdate() {}
- public void onPositionObserverUpdate() {};
+ public void onPlaybackSpeedChange() {}
- public void onPlaybackSpeedChange() {};
+ public void onSetSpeedAbilityChanged() {}
- public void onShutdownNotification() {};
+ public void onShutdownNotification() {}
/**
* Called when the currently displayed information should be refreshed.
*/
- public void onReloadNotification(int code) {};
+ public void onReloadNotification(int code) {}
- public void onBufferStart() {};
+ public void onBufferStart() {}
- public void onBufferEnd() {};
+ public void onBufferEnd() {}
- public void onBufferUpdate(float progress) {};
+ public void onBufferUpdate(float progress) {}
- public void onSleepTimerUpdate() {};
+ public void onSleepTimerUpdate() {}
- public void handleError(int code) {};
+ public void handleError(int code) {}
- public void onPlaybackEnd() {};
+ public void onPlaybackEnd() {}
public void repeatHandleStatus() {
if (status != null && playbackService != null) {
@@ -394,7 +395,7 @@ public abstract class PlaybackController {
}
/**
- * Is called whenever the PlaybackService changes it's status. This method
+ * Is called whenever the PlaybackService changes its status. This method
* should be used to update the GUI or start/cancel background threads.
*/
private void handleStatus() {
@@ -403,7 +404,8 @@ public abstract class PlaybackController {
final CharSequence playText = activity.getString(R.string.play_label);
final CharSequence pauseText = activity.getString(R.string.pause_label);
- if (PlaybackService.getCurrentMediaType() == MediaType.AUDIO) {
+ if (PlaybackService.getCurrentMediaType() == MediaType.AUDIO ||
+ PlaybackService.isCasting()) {
TypedArray res = activity.obtainStyledAttributes(new int[]{
R.attr.av_play_big, R.attr.av_pause_big});
playResource = res.getResourceId(0, R.drawable.ic_play_arrow_grey600_36dp);
@@ -417,22 +419,25 @@ public abstract class PlaybackController {
Log.d(TAG, "status: " + status.toString());
switch (status) {
case ERROR:
- postStatusMsg(R.string.player_error_msg);
+ postStatusMsg(R.string.player_error_msg, false);
handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN);
break;
case PAUSED:
clearStatusMsg();
checkMediaInfoLoaded();
cancelPositionObserver();
+ onPositionObserverUpdate();
updatePlayButtonAppearance(playResource, playText);
- if (PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
+ if (!PlaybackService.isCasting() &&
+ PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
setScreenOn(false);
}
break;
case PLAYING:
clearStatusMsg();
checkMediaInfoLoaded();
- if (PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
+ if (!PlaybackService.isCasting() &&
+ PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
onAwaitingVideoSurface();
setScreenOn(true);
}
@@ -440,7 +445,7 @@ public abstract class PlaybackController {
updatePlayButtonAppearance(pauseResource, pauseText);
break;
case PREPARING:
- postStatusMsg(R.string.player_preparing_msg);
+ postStatusMsg(R.string.player_preparing_msg, false);
checkMediaInfoLoaded();
if (playbackService != null) {
if (playbackService.isStartWhenPrepared()) {
@@ -451,16 +456,16 @@ public abstract class PlaybackController {
}
break;
case STOPPED:
- postStatusMsg(R.string.player_stopped_msg);
+ postStatusMsg(R.string.player_stopped_msg, false);
break;
case PREPARED:
checkMediaInfoLoaded();
- postStatusMsg(R.string.player_ready_msg);
+ postStatusMsg(R.string.player_ready_msg, false);
updatePlayButtonAppearance(playResource, playText);
break;
case SEEKING:
onPositionObserverUpdate();
- postStatusMsg(R.string.player_seeking_msg);
+ postStatusMsg(R.string.player_seeking_msg, false);
break;
case INITIALIZED:
checkMediaInfoLoaded();
@@ -484,27 +489,28 @@ public abstract class PlaybackController {
public ImageButton getPlayButton() {
return null;
- };
+ }
- public void postStatusMsg(int msg) {};
+ public void postStatusMsg(int msg, boolean showToast) {}
- public void clearStatusMsg() {};
+ public void clearStatusMsg() {}
public boolean loadMediaInfo() {
return false;
- };
+ }
- public void onAwaitingVideoSurface() {};
+ public void onAwaitingVideoSurface() {}
/**
* Called when connection to playback service has been established or
* information has to be refreshed
*/
- void queryService() {
+ private void queryService() {
Log.d(TAG, "Querying service info");
if (playbackService != null) {
- status = playbackService.getStatus();
- media = playbackService.getPlayable();
+ PlaybackServiceMediaPlayer.PSMPInfo info = playbackService.getPSMPInfo();
+ status = info.playerStatus;
+ media = info.playable;
/*
if (media == null) {
Log.w(TAG,
@@ -528,7 +534,7 @@ public abstract class PlaybackController {
}
}
- public void onServiceQueried() {};
+ public void onServiceQueried() {}
/**
* Should be used by classes which implement the OnSeekBarChanged interface.
@@ -574,34 +580,32 @@ public abstract class PlaybackController {
}
- public OnClickListener newOnPlayButtonClickListener() {
- 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();
- }
- break;
- case INITIALIZED:
- playbackService.setStartWhenPrepared(true);
- playbackService.prepare();
- break;
- }
- };
+ public void playPause() {
+ 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()) {
+ playbackService.reinit();
+ }
+ break;
+ case INITIALIZED:
+ playbackService.setStartWhenPrepared(true);
+ playbackService.prepare();
+ break;
+ }
}
public boolean serviceAvailable() {
@@ -679,12 +683,10 @@ public abstract class PlaybackController {
}
public boolean canSetPlaybackSpeed() {
- if (org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(activity.getApplicationContext())
+ return org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(activity.getApplicationContext())
|| UserPreferences.useSonic()
- || Build.VERSION.SDK_INT >= 23) {
- return true;
- }
- return playbackService != null && playbackService.canSetSpeed();
+ || Build.VERSION.SDK_INT >= 23
+ || playbackService != null && playbackService.canSetSpeed();
}
public void setPlaybackSpeed(float speed) {
@@ -717,11 +719,9 @@ public abstract class PlaybackController {
}
}
- public boolean isPlayingVideo() {
- if (playbackService != null) {
- return PlaybackService.getCurrentMediaType() == MediaType.VIDEO;
- }
- return false;
+ public boolean isPlayingVideoLocally() {
+ return playbackService != null && PlaybackService.getCurrentMediaType() == MediaType.VIDEO
+ && !PlaybackService.isCasting();
}
public Pair<Integer, Integer> getVideoSize() {
@@ -737,7 +737,7 @@ public abstract class PlaybackController {
* Returns true if PlaybackController can communicate with the playback
* service.
*/
- public boolean isConnectedToPlaybackService() {
+ private boolean isConnectedToPlaybackService() {
return playbackService != null;
}
@@ -753,9 +753,10 @@ public abstract class PlaybackController {
public void reinitServiceIfPaused() {
if (playbackService != null
&& playbackService.isStreaming()
- && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService
- .getStatus() == PlayerStatus.PREPARING && playbackService
- .isStartWhenPrepared() == false))) {
+ && !PlaybackService.isCasting()
+ && (playbackService.getStatus() == PlayerStatus.PAUSED ||
+ (playbackService.getStatus() == PlayerStatus.PREPARING &&
+ !playbackService.isStartWhenPrepared()))) {
playbackService.reinit();
}
}
@@ -770,13 +771,7 @@ public abstract class PlaybackController {
@Override
public void run() {
if (playbackService != null && playbackService.getStatus() == PlayerStatus.PLAYING) {
- activity.runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- onPositionObserverUpdate();
- }
- });
+ activity.runOnUiThread(PlaybackController.this::onPositionObserverUpdate);
}
}
}
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 2eee1ac87..efdf46a97 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,7 +2,10 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
@@ -14,6 +17,7 @@ import org.jsoup.select.Elements;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@@ -32,33 +36,39 @@ public class Timeline {
private ShownotesProvider shownotesProvider;
-
- private final String colorString;
+ private final String noShownotesLabel;
+ private final String colorPrimaryString;
+ private final String colorSecondaryString;
private final int pageMargin;
public Timeline(Context context, ShownotesProvider shownotesProvider) {
if (shownotesProvider == null) throw new IllegalArgumentException("shownotesProvider = null");
this.shownotesProvider = shownotesProvider;
- TypedArray res = context
- .getTheme()
- .obtainStyledAttributes(
- new int[]{android.R.attr.textColorPrimary});
- int colorResource = res.getColor(0, 0);
- colorString = String.format("#%06X",
- 0xFFFFFF & colorResource);
+ noShownotesLabel = context.getString(R.string.no_shownotes_label);
+
+ TypedArray res = context.getTheme().obtainStyledAttributes(
+ new int[]{ android.R.attr.textColorPrimary});
+ @ColorInt int col = res.getColor(0, 0);
+ colorPrimaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," +
+ Color.blue(col) + "," + (Color.alpha(col)/256.0) + ")";
+ res.recycle();
+ res = context.getTheme().obtainStyledAttributes(
+ new int[]{android.R.attr.textColorSecondary});
+ col = res.getColor(0, 0);
+ colorSecondaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," +
+ Color.blue(col) + "," + (Color.alpha(col)/256.0) + ")";
res.recycle();
- pageMargin = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, 8, context.getResources()
- .getDisplayMetrics()
+ pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
+ context.getResources().getDisplayMetrics()
);
}
private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))");
private static final String TIMECODE_LINK = "<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 *\\/?>");
+ private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br */?>");
/**
@@ -82,9 +92,24 @@ public class Timeline {
e.printStackTrace();
return null;
}
- if (shownotes == null) {
- Log.d(TAG, "shownotesProvider contained no shownotes. Returning empty string");
- return "";
+
+ if(TextUtils.isEmpty(shownotes)) {
+ Log.d(TAG, "shownotesProvider contained no shownotes. Returning 'no shownotes' message");
+ shownotes ="<html>" +
+ "<head>" +
+ "<style type='text/css'>" +
+ "html, body { margin: 0; padding: 0; width: 100%; height: 100%; } " +
+ "html { display: table; }" +
+ "body { display: table-cell; vertical-align: middle; text-align:center;" +
+ "-webkit-text-size-adjust: none; font-size: 87%; color: " + colorSecondaryString + ";} " +
+ "</style>" +
+ "</head>" +
+ "<body>" +
+ "<p>" + noShownotesLabel + "</p>" +
+ "</body>" +
+ "</html>";
+ Log.d(TAG, "shownotes: " + shownotes);
+ return shownotes;
}
// replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already
@@ -95,7 +120,7 @@ public class Timeline {
Document document = Jsoup.parse(shownotes);
// apply style
- String styleStr = String.format(WEBVIEW_STYLE, colorString, "100%", pageMargin,
+ String styleStr = String.format(WEBVIEW_STYLE, colorPrimaryString, "100%", pageMargin,
pageMargin, pageMargin, pageMargin);
document.head().appendElement("style").attr("type", "text/css").text(styleStr);
@@ -125,8 +150,7 @@ public class Timeline {
element.html(buffer.toString());
}
}
-
- Log.i(TAG, "Out: " + document.toString());
+
return document.toString();
}
@@ -148,7 +172,7 @@ public class Timeline {
try {
if (m.find()) {
- return Integer.valueOf(m.group(1));
+ return Integer.parseInt(m.group(1));
}
} catch (NumberFormatException e) {
e.printStackTrace();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java
index c4961a3ab..6243da5bc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java
@@ -9,7 +9,7 @@ import java.util.ArrayList;
import java.util.List;
public class VorbisCommentChapterReader extends VorbisCommentReader {
- private static final String TAG = "VorbisCommentChapterReader";
+ private static final String TAG = "VorbisCommentChptrReadr";
private static final String CHAPTER_KEY = "chapter\\d\\d\\d.*";
private static final String CHAPTER_ATTRIBUTE_TITLE = "name";
@@ -27,7 +27,7 @@ public class VorbisCommentChapterReader extends VorbisCommentReader {
@Override
public void onVorbisCommentHeaderFound(VorbisCommentHeader header) {
- chapters = new ArrayList<Chapter>();
+ chapters = new ArrayList<>();
System.out.println(header.toString());
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java
index 9639b9c42..49ea18721 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java
@@ -180,13 +180,13 @@ public abstract class VorbisCommentReader {
private String readContentVectorKey(InputStream input, long vectorLength)
throws IOException {
- StringBuffer buffer = new StringBuffer();
+ StringBuilder builder = new StringBuilder();
for (int i = 0; i < vectorLength; i++) {
char c = (char) input.read();
if (c == '=') {
- return buffer.toString();
+ return builder.toString();
} else {
- buffer.append(c);
+ builder.append(c);
}
}
return null; // no key found
diff --git a/core/src/main/res/drawable-hdpi-v11/ic_stat_antenna_default.png b/core/src/main/res/drawable-hdpi-v11/ic_stat_antenna_default.png
index 37d73c734..af99f4b3b 100644
--- a/core/src/main/res/drawable-hdpi-v11/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-hdpi-v11/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi-v11/ic_stat_authentication.png b/core/src/main/res/drawable-hdpi-v11/ic_stat_authentication.png
index ad148cc6b..398dffa4b 100755
--- a/core/src/main/res/drawable-hdpi-v11/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-hdpi-v11/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_add_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_add_grey600_24dp.png
index 492c8f880..69efaccd1 100644
--- a/core/src/main/res/drawable-hdpi/ic_add_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_add_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_add_white_24dp.png
index 481643ecd..023e5d21a 100644
--- a/core/src/main/res/drawable-hdpi/ic_add_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_audiotrack_light.png b/core/src/main/res/drawable-hdpi/ic_audiotrack_light.png
new file mode 100644
index 000000000..04e23e1f6
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_audiotrack_light.png
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
index a32968a19..43783fbce 100755
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_av_pause_circle_outline_80dp.png b/core/src/main/res/drawable-hdpi/ic_av_pause_circle_outline_80dp.png
index 6f9df0934..115f7ebef 100644
--- a/core/src/main/res/drawable-hdpi/ic_av_pause_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_av_pause_circle_outline_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_av_play_circle_outline_80dp.png b/core/src/main/res/drawable-hdpi/ic_av_play_circle_outline_80dp.png
index bddb0b024..392c87bd2 100644
--- a/core/src/main/res/drawable-hdpi/ic_av_play_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_av_play_circle_outline_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
index e39de4dcf..41052af65 100755
--- a/core/src/main/res/drawable-hdpi/ic_av_rewind_80dp.png
+++ 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_cancel_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_cancel_grey600_24dp.png
index 1f4fd985f..6a2ca5023 100644
--- a/core/src/main/res/drawable-hdpi/ic_cancel_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_cancel_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cancel_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_cancel_white_24dp.png
index 794b7b780..9698d4b7d 100644
--- a/core/src/main/res/drawable-hdpi/ic_cancel_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-hdpi/ic_cast_disabled_light.png
new file mode 100644
index 000000000..c0a55d555
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_disconnect_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_cast_disconnect_grey600_36dp.png
new file mode 100644
index 000000000..07da25ec0
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_disconnect_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_disconnect_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_cast_disconnect_white_36dp.png
new file mode 100644
index 000000000..ff9d97fd1
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_disconnect_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_light.png b/core/src/main/res/drawable-hdpi/ic_cast_light.png
new file mode 100644
index 000000000..b0c581a0e
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_off_light.png b/core/src/main/res/drawable-hdpi/ic_cast_off_light.png
new file mode 100644
index 000000000..5f3c0179c
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_off_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_0_light.png
new file mode 100644
index 000000000..e872693a4
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_1_light.png
new file mode 100644
index 000000000..d8be1ebc6
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_2_light.png
new file mode 100644
index 000000000..27cda9e9d
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_cast_on_light.png b/core/src/main/res/drawable-hdpi/ic_cast_on_light.png
new file mode 100644
index 000000000..4ee525875
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_cast_on_light.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
index e4e833242..187a426a8 100644
--- 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
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
index 4f1a804b3..076773bca 100644
--- 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
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
index 6dbf83c58..ecaf0d5be 100644
--- 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
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
index d3f636f13..5ce64cc5f 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_close_light.png b/core/src/main/res/drawable-hdpi/ic_close_light.png
new file mode 100644
index 000000000..93187e450
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_close_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_create_new_folder_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_create_new_folder_grey600_24dp.png
index bfe98fd07..75c703ae4 100644
--- a/core/src/main/res/drawable-hdpi/ic_create_new_folder_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_create_new_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_create_new_folder_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_create_new_folder_white_24dp.png
index a8b0ada87..e6ae7f36b 100644
--- a/core/src/main/res/drawable-hdpi/ic_create_new_folder_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_create_new_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_delete_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_delete_grey600_24dp.png
index b72a9f3c6..501e60fa0 100644
--- a/core/src/main/res/drawable-hdpi/ic_delete_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_delete_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_delete_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_delete_white_24dp.png
index a9eac0ca7..4d8ce49e3 100644
--- a/core/src/main/res/drawable-hdpi/ic_delete_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_delete_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_description_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_description_grey600_36dp.png
deleted file mode 100644
index dd7d0734d..000000000
--- a/core/src/main/res/drawable-hdpi/ic_description_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_description_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_description_white_36dp.png
deleted file mode 100644
index 93aa3032a..000000000
--- a/core/src/main/res/drawable-hdpi/ic_description_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_done_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_done_grey600_24dp.png
index 6e42e1359..72eea4a8d 100644
--- a/core/src/main/res/drawable-hdpi/ic_done_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_done_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_done_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_done_white_24dp.png
index f42a0e2d2..9b4047a41 100644
--- a/core/src/main/res/drawable-hdpi/ic_done_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_done_white_24dp.png
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
index f2d9906c6..a0b47594b 100644
--- 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
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
index a2a91938d..a4ce8fa7e 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_expand_more_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_expand_more_grey600_36dp.png
index 0f0ddf717..96ed70a40 100644
--- a/core/src/main/res/drawable-hdpi/ic_expand_more_grey600_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_expand_more_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_expand_more_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_expand_more_white_36dp.png
index 724eb1f7a..f3c628e77 100644
--- a/core/src/main/res/drawable-hdpi/ic_expand_more_white_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_expand_more_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_24dp.png
index 4d1c1d4ae..df9e662c1 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_36dp.png
index deeff701f..59ae40266 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_forward_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_forward_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_fast_forward_white_24dp.png
index 03a54ee75..2d61b31f1 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_forward_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_forward_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_forward_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_fast_forward_white_36dp.png
index 60e3decf6..e5ae251d3 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_forward_white_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_forward_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_24dp.png
index 9866ee629..f661ca723 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_36dp.png
index 8b9bfd34b..d36891a8d 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_rewind_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_24dp.png
index b4f526e62..ab8b48ec3 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_36dp.png
index 5d9d35f70..75796d7a9 100644
--- a/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_fast_rewind_white_36dp.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 d1ad6629d..c09c73441 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 9ff662fca..43183d393 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_file_download_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_file_download_grey600_24dp.png
index c2222fb8e..62caf8392 100644
--- a/core/src/main/res/drawable-hdpi/ic_file_download_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_file_download_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png
index 46aeab458..8300268dc 100644
--- a/core/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png
index 83c564377..0d2f6ccb4 100644
--- a/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png
index e3517a57d..ea6f9078e 100644
--- a/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_folder_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_folder_grey600_24dp.png
new file mode 100755
index 000000000..e3dccd298
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_folder_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_folder_white_24dp.png
new file mode 100755
index 000000000..9f5c75609
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_hearing_grey600_18dp.png b/core/src/main/res/drawable-hdpi/ic_hearing_grey600_18dp.png
index 294523814..2452cfa92 100644
--- a/core/src/main/res/drawable-hdpi/ic_hearing_grey600_18dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_hearing_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_hearing_white_18dp.png b/core/src/main/res/drawable-hdpi/ic_hearing_white_18dp.png
index 11114b568..96a06141a 100644
--- a/core/src/main/res/drawable-hdpi/ic_hearing_white_18dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_hearing_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_history_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_history_grey600_24dp.png
index f87403fa5..f02be50e2 100644
--- a/core/src/main/res/drawable-hdpi/ic_history_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_history_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_history_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_history_white_24dp.png
index b2b1332e1..a0756f264 100644
--- a/core/src/main/res/drawable-hdpi/ic_history_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_history_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
index 62dd8ef48..e56fbf224 100644
--- 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
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
index 5bb43b464..dccf44930 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_info_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_info_grey600_24dp.png
index 713a9ba9a..754299c4a 100644
--- a/core/src/main/res/drawable-hdpi/ic_info_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_info_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_info_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_info_white_24dp.png
index f4b88742d..61ca3fee4 100644
--- a/core/src/main/res/drawable-hdpi/ic_info_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_info_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_launcher.png b/core/src/main/res/drawable-hdpi/ic_launcher.png
index 994b763cc..8bd22b54a 100644
--- a/core/src/main/res/drawable-hdpi/ic_launcher.png
+++ b/core/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_list_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_list_grey600_24dp.png
index 2aee6f902..11c973d3d 100644
--- a/core/src/main/res/drawable-hdpi/ic_list_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_list_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_list_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_list_white_24dp.png
index 436113d27..4f1e57ec1 100644
--- a/core/src/main/res/drawable-hdpi/ic_list_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_list_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png
index b6dba1002..3e52fd88d 100644
--- a/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png
index 5c60ab08a..2260411cb 100644
--- a/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png
index a99e9f2b6..a42417431 100644
--- a/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
index 61c623ce2..7bde52e12 100644
--- a/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.png
new file mode 100644
index 000000000..700c116e5
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_media_cast_disconnect.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_more_vert_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_more_vert_grey600_24dp.png
deleted file mode 100644
index e141502a6..000000000
--- a/core/src/main/res/drawable-hdpi/ic_more_vert_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png
deleted file mode 100644
index fdc4a5ad2..000000000
--- a/core/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_new.png b/core/src/main/res/drawable-hdpi/ic_new.png
deleted file mode 100755
index 8ff519052..000000000
--- a/core/src/main/res/drawable-hdpi/ic_new.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_new_dark.png b/core/src/main/res/drawable-hdpi/ic_new_dark.png
deleted file mode 100755
index c8581e01c..000000000
--- a/core/src/main/res/drawable-hdpi/ic_new_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_new_releases_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_new_releases_grey600_24dp.png
index 7a08c4138..2aa5de2ad 100644
--- a/core/src/main/res/drawable-hdpi/ic_new_releases_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_new_releases_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_new_releases_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_new_releases_white_24dp.png
index 0d08f329c..8f109572b 100644
--- a/core/src/main/res/drawable-hdpi/ic_new_releases_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_new_releases_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png
index 711e0cf94..7281f37e1 100644
--- a/core/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png
index 5e884dfe8..dde9bb25c 100644
--- a/core/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_pause_light.png b/core/src/main/res/drawable-hdpi/ic_pause_light.png
new file mode 100644
index 000000000..0c505d1c8
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_pause_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
index b4bdbb558..1701f34b0 100644
--- a/core/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_pause_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_pause_white_36dp.png
index da17d12ba..1d024393a 100644
--- a/core/src/main/res/drawable-hdpi/ic_pause_white_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png
index 264416933..b540e4a63 100644
--- a/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png
index 8af4c6cd6..a12b921e4 100644
--- a/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
index 164385d04..f77ad6b57 100644
--- a/core/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png
index 0cd5cb61a..2b8e3513f 100644
--- a/core/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_play_light.png b/core/src/main/res/drawable-hdpi/ic_play_light.png
new file mode 100644
index 000000000..7957dff5b
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_play_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png
index 51cc4dbd1..ec8ac3041 100644
--- a/core/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png
index cd16fdd50..db069b266 100644
--- a/core/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_remove_red_eye_grey600_18dp.png b/core/src/main/res/drawable-hdpi/ic_remove_red_eye_grey600_18dp.png
index c4df13afa..4f1af39ab 100644
--- a/core/src/main/res/drawable-hdpi/ic_remove_red_eye_grey600_18dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_remove_red_eye_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_remove_red_eye_white_18dp.png b/core/src/main/res/drawable-hdpi/ic_remove_red_eye_white_18dp.png
index fdf524010..abc338d51 100644
--- a/core/src/main/res/drawable-hdpi/ic_remove_red_eye_white_18dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_remove_red_eye_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sd_storage_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_sd_storage_grey600_36dp.png
index 0890b2e5c..26a39bec9 100644
--- a/core/src/main/res/drawable-hdpi/ic_sd_storage_grey600_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_sd_storage_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_sd_storage_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_sd_storage_white_36dp.png
index d9bfcfbfb..751e0e360 100644
--- a/core/src/main/res/drawable-hdpi/ic_sd_storage_white_36dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_sd_storage_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_search_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_search_grey600_24dp.png
index ba65a135b..022f081e0 100644
--- a/core/src/main/res/drawable-hdpi/ic_search_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_search_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_search_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_search_white_24dp.png
index a2fc5b2e7..cc6d5da86 100644
--- a/core/src/main/res/drawable-hdpi/ic_search_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_search_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png
index 20d2b66e0..a7c216d3d 100644
--- a/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_grey600_24dp.png
index 9c0172ad0..f8f215236 100644
--- a/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_white_24dp.png
index 9b5a9fb47..eb2611f95 100644
--- a/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_settings_input_antenna_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png
index f9a8915fd..14c7da0c2 100644
--- a/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_share_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_share_grey600_24dp.png
index 987e9b86c..9457cac52 100644
--- a/core/src/main/res/drawable-hdpi/ic_share_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_share_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_share_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_share_white_24dp.png
index 93b3c219c..80c24d993 100644
--- a/core/src/main/res/drawable-hdpi/ic_share_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_share_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
index edbc95b05..6e1dffc93 100644
--- a/core/src/main/res/drawable-hdpi/ic_skip_grey600_36dp.png
+++ 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
index cbfb262d8..760ec9987 100644
--- a/core/src/main/res/drawable-hdpi/ic_skip_white_36dp.png
+++ 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
index 809066499..f25063071 100644
--- a/core/src/main/res/drawable-hdpi/ic_sleep_grey600_24dp.png
+++ 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
index 4496a320d..0db6381ea 100644
--- 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
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
index 79684ab65..c614c2b3d 100644
--- 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
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
index f0df6032c..5d8072149 100644
--- a/core/src/main/res/drawable-hdpi/ic_sleep_white_24dp.png
+++ 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
index 0a52de9fe..715dea65f 100644
--- a/core/src/main/res/drawable-hdpi/ic_sort_grey600_24dp.png
+++ 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
index 26014a542..9cdac3582 100644
--- a/core/src/main/res/drawable-hdpi/ic_sort_white_24dp.png
+++ 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
index 006410bc3..6b5bc858a 100644
--- 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
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
index 27831192f..d59bc4792 100644
--- 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
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
index 93f70a024..e4fe7f43a 100644
--- a/core/src/main/res/drawable-hdpi/ic_star_grey600_24dp.png
+++ 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
index e8619b780..394c5a60c 100644
--- a/core/src/main/res/drawable-hdpi/ic_star_white_24dp.png
+++ 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_stat_antenna_default.png b/core/src/main/res/drawable-hdpi/ic_stat_antenna_default.png
index 36d502492..fb15f7ee1 100644
--- a/core/src/main/res/drawable-hdpi/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-hdpi/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_stat_authentication.png b/core/src/main/res/drawable-hdpi/ic_stat_authentication.png
index c6b5efd33..f40820ef7 100755
--- a/core/src/main/res/drawable-hdpi/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-hdpi/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_timer_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_timer_grey600_24dp.png
deleted file mode 100644
index a5fdd2c40..000000000
--- a/core/src/main/res/drawable-hdpi/ic_timer_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_timer_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_timer_white_24dp.png
deleted file mode 100644
index 864291fa7..000000000
--- a/core/src/main/res/drawable-hdpi/ic_timer_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_toc_grey600_36dp.png b/core/src/main/res/drawable-hdpi/ic_toc_grey600_36dp.png
deleted file mode 100644
index e299eb41b..000000000
--- a/core/src/main/res/drawable-hdpi/ic_toc_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_toc_white_36dp.png b/core/src/main/res/drawable-hdpi/ic_toc_white_36dp.png
deleted file mode 100644
index bfdc05d9e..000000000
--- a/core/src/main/res/drawable-hdpi/ic_toc_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_web_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_web_grey600_24dp.png
index 1942f3f12..57c236892 100644
--- a/core/src/main/res/drawable-hdpi/ic_web_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_web_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_web_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_web_white_24dp.png
index 952404cbc..410ef41e9 100644
--- a/core/src/main/res/drawable-hdpi/ic_web_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_web_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
index 8b583b285..85a537154 100644
--- a/core/src/main/res/drawable-hdpi/ic_widget_preview.png
+++ b/core/src/main/res/drawable-hdpi/ic_widget_preview.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/navigation_up.png b/core/src/main/res/drawable-hdpi/navigation_up.png
index a2cf2ba52..370092e7e 100755
--- a/core/src/main/res/drawable-hdpi/navigation_up.png
+++ b/core/src/main/res/drawable-hdpi/navigation_up.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/navigation_up_dark.png b/core/src/main/res/drawable-hdpi/navigation_up_dark.png
index f2374a323..2b520c553 100755
--- a/core/src/main/res/drawable-hdpi/navigation_up_dark.png
+++ b/core/src/main/res/drawable-hdpi/navigation_up_dark.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 ca6c68e4e..f70373d50 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 84f19cd4e..222e9e8b1 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-ldpi-v11/ic_stat_antenna_default.png b/core/src/main/res/drawable-ldpi-v11/ic_stat_antenna_default.png
index e44f42510..ddf545c0b 100644
--- a/core/src/main/res/drawable-ldpi-v11/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-ldpi-v11/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-ldpi/ic_launcher.png b/core/src/main/res/drawable-ldpi/ic_launcher.png
index 546090dd2..494468020 100644
--- a/core/src/main/res/drawable-ldpi/ic_launcher.png
+++ b/core/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png
index 63d72970d..501dfa848 100644
--- a/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-ldpi/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi-v11/ic_stat_antenna_default.png b/core/src/main/res/drawable-mdpi-v11/ic_stat_antenna_default.png
index 8808dedc7..41fd20655 100644
--- a/core/src/main/res/drawable-mdpi-v11/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-mdpi-v11/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi-v11/ic_stat_authentication.png b/core/src/main/res/drawable-mdpi-v11/ic_stat_authentication.png
index de69b17c0..550b56b33 100755
--- a/core/src/main/res/drawable-mdpi-v11/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-mdpi-v11/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_add_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_add_grey600_24dp.png
index 07858bc09..27e311f39 100644
--- a/core/src/main/res/drawable-mdpi/ic_add_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_add_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_add_white_24dp.png
index 977dd3427..3856041d7 100644
--- a/core/src/main/res/drawable-mdpi/ic_add_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_audiotrack_light.png b/core/src/main/res/drawable-mdpi/ic_audiotrack_light.png
new file mode 100644
index 000000000..8ce237e38
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_audiotrack_light.png
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
index 69b81c10d..0bf060d89 100755
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_av_pause_circle_outline_80dp.png b/core/src/main/res/drawable-mdpi/ic_av_pause_circle_outline_80dp.png
index 08c523d48..1e71c271f 100644
--- a/core/src/main/res/drawable-mdpi/ic_av_pause_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_av_pause_circle_outline_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_av_play_circle_outline_80dp.png b/core/src/main/res/drawable-mdpi/ic_av_play_circle_outline_80dp.png
index 27738aacb..d305f0c69 100644
--- a/core/src/main/res/drawable-mdpi/ic_av_play_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_av_play_circle_outline_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
index 5355abfd6..99ac9f3c9 100755
--- a/core/src/main/res/drawable-mdpi/ic_av_rewind_80dp.png
+++ 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_cancel_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_cancel_grey600_24dp.png
index a7a30cc24..e91e2cbf6 100644
--- a/core/src/main/res/drawable-mdpi/ic_cancel_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_cancel_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cancel_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_cancel_white_24dp.png
index df332ef3c..c433d9ba0 100644
--- a/core/src/main/res/drawable-mdpi/ic_cancel_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-mdpi/ic_cast_disabled_light.png
new file mode 100644
index 000000000..7940a0332
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_disconnect_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_cast_disconnect_grey600_36dp.png
new file mode 100644
index 000000000..b66b17e44
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_disconnect_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_disconnect_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_cast_disconnect_white_36dp.png
new file mode 100644
index 000000000..4f3c9a9ef
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_disconnect_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_light.png b/core/src/main/res/drawable-mdpi/ic_cast_light.png
new file mode 100644
index 000000000..1f5bec20b
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_off_light.png b/core/src/main/res/drawable-mdpi/ic_cast_off_light.png
new file mode 100644
index 000000000..963db27d4
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_off_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_0_light.png
new file mode 100644
index 000000000..a90d9e305
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_1_light.png
new file mode 100644
index 000000000..bb2cf30bf
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_2_light.png
new file mode 100644
index 000000000..3ed59e55b
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_cast_on_light.png b/core/src/main/res/drawable-mdpi/ic_cast_on_light.png
new file mode 100644
index 000000000..713427b97
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_cast_on_light.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
index a6bdce736..d5bdfa433 100644
--- 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
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
index cd90223d1..aefe5b6c1 100644
--- 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
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
index 8a014bda1..a3a588c64 100644
--- 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
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
index 0811a3493..dc94cdbf4 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_close_light.png b/core/src/main/res/drawable-mdpi/ic_close_light.png
new file mode 100644
index 000000000..2c52c9b0f
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_close_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_create_new_folder_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_create_new_folder_grey600_24dp.png
index eeed34653..45c0c29bf 100644
--- a/core/src/main/res/drawable-mdpi/ic_create_new_folder_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_create_new_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_create_new_folder_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_create_new_folder_white_24dp.png
index 3b9eaa827..02167a9f0 100644
--- a/core/src/main/res/drawable-mdpi/ic_create_new_folder_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_create_new_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_delete_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_delete_grey600_24dp.png
index e757fdb07..3bf363a2b 100644
--- a/core/src/main/res/drawable-mdpi/ic_delete_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_delete_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_delete_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_delete_white_24dp.png
index e4ea52ef2..6b44a07da 100644
--- a/core/src/main/res/drawable-mdpi/ic_delete_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_delete_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_description_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_description_grey600_36dp.png
deleted file mode 100644
index ac18b5750..000000000
--- a/core/src/main/res/drawable-mdpi/ic_description_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_description_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_description_white_36dp.png
deleted file mode 100644
index f5612717f..000000000
--- a/core/src/main/res/drawable-mdpi/ic_description_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_done_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_done_grey600_24dp.png
index 9f860915d..73f72da71 100644
--- a/core/src/main/res/drawable-mdpi/ic_done_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_done_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_done_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_done_white_24dp.png
index e91f9048b..e094dee36 100644
--- a/core/src/main/res/drawable-mdpi/ic_done_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_done_white_24dp.png
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
index 0d8277204..7cd66845b 100644
--- 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
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
index 15d016e89..3d1fd0fe3 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_expand_more_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_expand_more_grey600_36dp.png
index 00b9f645c..4d8cd8e99 100644
--- a/core/src/main/res/drawable-mdpi/ic_expand_more_grey600_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_expand_more_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_expand_more_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_expand_more_white_36dp.png
index 6c2476660..682bcf79f 100644
--- a/core/src/main/res/drawable-mdpi/ic_expand_more_white_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_expand_more_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_24dp.png
index c2864a726..c67fc25f1 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_36dp.png
index 4d1c1d4ae..df9e662c1 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_forward_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_forward_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_fast_forward_white_24dp.png
index 215099f9e..fceffcb7b 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_forward_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_forward_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_forward_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_fast_forward_white_36dp.png
index 03a54ee75..2d61b31f1 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_forward_white_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_forward_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_24dp.png
index 75adfc97e..de04575da 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_36dp.png
index 9866ee629..f661ca723 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_rewind_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_24dp.png
index a050e1bb8..bfb476b4a 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_36dp.png
index b4f526e62..ab8b48ec3 100644
--- a/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_fast_rewind_white_36dp.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 3932b6eef..8037c315e 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 9c2a1eb6e..026cac8f2 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_file_download_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_file_download_grey600_24dp.png
index b3ef8ce73..74d554774 100644
--- a/core/src/main/res/drawable-mdpi/ic_file_download_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_file_download_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png
index e089466de..b09c53c86 100644
--- a/core/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png
index e1e55d72b..b78e7d4d2 100644
--- a/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png
index 7d72e7562..59a2ec755 100644
--- a/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_folder_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_folder_grey600_24dp.png
new file mode 100755
index 000000000..1028bfaf3
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_folder_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_folder_white_24dp.png
new file mode 100755
index 000000000..1c5797c9e
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_hearing_grey600_18dp.png b/core/src/main/res/drawable-mdpi/ic_hearing_grey600_18dp.png
index df26536f9..ea44e3f07 100644
--- a/core/src/main/res/drawable-mdpi/ic_hearing_grey600_18dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_hearing_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_hearing_white_18dp.png b/core/src/main/res/drawable-mdpi/ic_hearing_white_18dp.png
index 0771a106d..78e98fe8d 100644
--- a/core/src/main/res/drawable-mdpi/ic_hearing_white_18dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_hearing_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_history_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_history_grey600_24dp.png
index 0a62d358f..397972f67 100644
--- a/core/src/main/res/drawable-mdpi/ic_history_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_history_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_history_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_history_white_24dp.png
index b93da1006..bcc729de0 100644
--- a/core/src/main/res/drawable-mdpi/ic_history_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_history_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
index 52812c42f..0e6ce58e3 100644
--- 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
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
index 20a9fe5d7..c496b4648 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_info_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_info_grey600_24dp.png
index 2537c5f42..b5c14a369 100644
--- a/core/src/main/res/drawable-mdpi/ic_info_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_info_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_info_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_info_white_24dp.png
index 34f980085..6dbeeb0b7 100644
--- a/core/src/main/res/drawable-mdpi/ic_info_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_info_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_launcher.png b/core/src/main/res/drawable-mdpi/ic_launcher.png
index 403dfabc4..219e8c5ab 100644
--- a/core/src/main/res/drawable-mdpi/ic_launcher.png
+++ b/core/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_list_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_list_grey600_24dp.png
index c5c2fc195..ac5b3bed4 100644
--- a/core/src/main/res/drawable-mdpi/ic_list_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_list_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_list_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_list_white_24dp.png
index 5e51ed478..15d8fc2bb 100644
--- a/core/src/main/res/drawable-mdpi/ic_list_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_list_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png
index f1627ce34..f9415627e 100644
--- a/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png
index 8f18d11e6..1203a4406 100644
--- a/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png
index ada8d3be4..192e27ccd 100644
--- a/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
index 72d01c406..232b4bfb4 100644
--- a/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.png
new file mode 100644
index 000000000..767f420df
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_media_cast_disconnect.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_more_vert_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_more_vert_grey600_24dp.png
deleted file mode 100644
index 4ed34354b..000000000
--- a/core/src/main/res/drawable-mdpi/ic_more_vert_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png
deleted file mode 100644
index 1d8ad18a0..000000000
--- a/core/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_new.png b/core/src/main/res/drawable-mdpi/ic_new.png
deleted file mode 100755
index 84994bd10..000000000
--- a/core/src/main/res/drawable-mdpi/ic_new.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_new_dark.png b/core/src/main/res/drawable-mdpi/ic_new_dark.png
deleted file mode 100755
index b723618b4..000000000
--- a/core/src/main/res/drawable-mdpi/ic_new_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_new_releases_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_new_releases_grey600_24dp.png
index 494781a20..4b3ffdbdc 100644
--- a/core/src/main/res/drawable-mdpi/ic_new_releases_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_new_releases_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_new_releases_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_new_releases_white_24dp.png
index 36e3262a4..c5bb20f49 100644
--- a/core/src/main/res/drawable-mdpi/ic_new_releases_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_new_releases_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_pause_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_pause_grey600_24dp.png
index 06abcb50b..126ee03ef 100644
--- a/core/src/main/res/drawable-mdpi/ic_pause_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_pause_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_pause_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_pause_grey600_36dp.png
index 711e0cf94..7281f37e1 100644
--- a/core/src/main/res/drawable-mdpi/ic_pause_grey600_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_pause_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_pause_light.png b/core/src/main/res/drawable-mdpi/ic_pause_light.png
new file mode 100644
index 000000000..6218a774f
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_pause_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_pause_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
index 026f38854..2272d478c 100644
--- a/core/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_pause_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_pause_white_36dp.png
index b4bdbb558..1701f34b0 100644
--- a/core/src/main/res/drawable-mdpi/ic_pause_white_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_24dp.png
index 4ff5decb0..9c8f2c555 100644
--- a/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_36dp.png
index 264416933..b540e4a63 100644
--- a/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_play_arrow_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
index 8d1e433a5..172c211ab 100644
--- a/core/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png
index 164385d04..f77ad6b57 100644
--- a/core/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_play_light.png b/core/src/main/res/drawable-mdpi/ic_play_light.png
new file mode 100644
index 000000000..1e0ccaf80
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_play_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png
index c136c59fc..0e927f106 100644
--- a/core/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png
index 235c84f1e..79b5ec871 100644
--- a/core/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_remove_red_eye_grey600_18dp.png b/core/src/main/res/drawable-mdpi/ic_remove_red_eye_grey600_18dp.png
index 9e840f03e..daa8e568e 100644
--- a/core/src/main/res/drawable-mdpi/ic_remove_red_eye_grey600_18dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_remove_red_eye_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_remove_red_eye_white_18dp.png b/core/src/main/res/drawable-mdpi/ic_remove_red_eye_white_18dp.png
index 0bcba825f..a6b4ff0da 100644
--- a/core/src/main/res/drawable-mdpi/ic_remove_red_eye_white_18dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_remove_red_eye_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sd_storage_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_sd_storage_grey600_36dp.png
index f0c5ed4c3..26977a920 100644
--- a/core/src/main/res/drawable-mdpi/ic_sd_storage_grey600_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_sd_storage_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_sd_storage_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_sd_storage_white_36dp.png
index 6595d2e1e..4aeb2e668 100644
--- a/core/src/main/res/drawable-mdpi/ic_sd_storage_white_36dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_sd_storage_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_search_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_search_grey600_24dp.png
index 29954062c..c386dbb38 100644
--- a/core/src/main/res/drawable-mdpi/ic_search_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_search_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_search_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_search_white_24dp.png
index dff1e3a8a..faefc59c8 100644
--- a/core/src/main/res/drawable-mdpi/ic_search_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_search_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_grey600_24dp.png
index 8995d0f43..dafbeca33 100644
--- a/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_white_24dp.png
index c31fa8309..ccf89acea 100644
--- a/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_settings_input_antenna_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_share_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_share_grey600_24dp.png
index ee0248838..42eb539de 100644
--- a/core/src/main/res/drawable-mdpi/ic_share_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_share_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_share_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_share_white_24dp.png
index 4d0197223..9d2ef6655 100644
--- a/core/src/main/res/drawable-mdpi/ic_share_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_share_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
index be0fcc765..229eeca47 100644
--- a/core/src/main/res/drawable-mdpi/ic_skip_grey600_36dp.png
+++ 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
index 893cf2c64..9032328d4 100644
--- a/core/src/main/res/drawable-mdpi/ic_skip_white_36dp.png
+++ 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
index ea511bf2a..df84861b9 100644
--- a/core/src/main/res/drawable-mdpi/ic_sleep_grey600_24dp.png
+++ 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
index 7f631ad86..463b87d2f 100644
--- 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
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
index 795e318e3..942e7f380 100644
--- 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
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
index 4304a6bca..9f37b124d 100644
--- a/core/src/main/res/drawable-mdpi/ic_sleep_white_24dp.png
+++ 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
index f527d0094..dc393caf7 100644
--- a/core/src/main/res/drawable-mdpi/ic_sort_grey600_24dp.png
+++ 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
index e28dd4592..3d84a4442 100644
--- a/core/src/main/res/drawable-mdpi/ic_sort_white_24dp.png
+++ 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
index dd9d11ba0..8b5275b11 100644
--- 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
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
index 104fb3c9d..edfd7d71d 100644
--- 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
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
index af84b71f2..246b43279 100644
--- a/core/src/main/res/drawable-mdpi/ic_star_grey600_24dp.png
+++ 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
index 0ccebc7c8..c79334c61 100644
--- a/core/src/main/res/drawable-mdpi/ic_star_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png
index 8b1206b51..152239888 100644
--- a/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-mdpi/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_stat_authentication.png b/core/src/main/res/drawable-mdpi/ic_stat_authentication.png
index cadfb9643..7fab11a83 100755
--- a/core/src/main/res/drawable-mdpi/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-mdpi/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_timer_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_timer_grey600_24dp.png
deleted file mode 100644
index 186887c30..000000000
--- a/core/src/main/res/drawable-mdpi/ic_timer_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_timer_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_timer_white_24dp.png
deleted file mode 100644
index 735ed7a00..000000000
--- a/core/src/main/res/drawable-mdpi/ic_timer_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_toc_grey600_36dp.png b/core/src/main/res/drawable-mdpi/ic_toc_grey600_36dp.png
deleted file mode 100644
index 23bec7945..000000000
--- a/core/src/main/res/drawable-mdpi/ic_toc_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_toc_white_36dp.png b/core/src/main/res/drawable-mdpi/ic_toc_white_36dp.png
deleted file mode 100644
index dc03b34b9..000000000
--- a/core/src/main/res/drawable-mdpi/ic_toc_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_web_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_web_grey600_24dp.png
index cdbce22a7..c620bc4dd 100644
--- a/core/src/main/res/drawable-mdpi/ic_web_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_web_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_web_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_web_white_24dp.png
index 3602c4c48..f59e16fa9 100644
--- a/core/src/main/res/drawable-mdpi/ic_web_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_web_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/navigation_up.png b/core/src/main/res/drawable-mdpi/navigation_up.png
index 1ee248a79..520e11da6 100755
--- a/core/src/main/res/drawable-mdpi/navigation_up.png
+++ b/core/src/main/res/drawable-mdpi/navigation_up.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/navigation_up_dark.png b/core/src/main/res/drawable-mdpi/navigation_up_dark.png
index 8ef44cbac..e53627915 100755
--- a/core/src/main/res/drawable-mdpi/navigation_up_dark.png
+++ b/core/src/main/res/drawable-mdpi/navigation_up_dark.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 516b65bd9..02066f847 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 c3ed306cf..d797f59c3 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/ic_stat_antenna_default.png b/core/src/main/res/drawable-xhdpi-v11/ic_stat_antenna_default.png
index 59de64c87..30431ed6a 100644
--- a/core/src/main/res/drawable-xhdpi-v11/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-xhdpi-v11/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi-v11/ic_stat_authentication.png b/core/src/main/res/drawable-xhdpi-v11/ic_stat_authentication.png
index f58fb21df..e83cbc333 100755
--- a/core/src/main/res/drawable-xhdpi-v11/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-xhdpi-v11/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_add_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_add_grey600_24dp.png
index 0d8af34b9..19ed567e9 100644
--- a/core/src/main/res/drawable-xhdpi/ic_add_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_add_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
index 67042105d..67bb598e5 100644
--- a/core/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_audiotrack_light.png b/core/src/main/res/drawable-xhdpi/ic_audiotrack_light.png
new file mode 100644
index 000000000..247df6f39
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_audiotrack_light.png
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
index ed34e22a1..270dc9bf8 100755
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_av_pause_circle_outline_80dp.png b/core/src/main/res/drawable-xhdpi/ic_av_pause_circle_outline_80dp.png
index 3288241e7..2e0b8ff6f 100644
--- a/core/src/main/res/drawable-xhdpi/ic_av_pause_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_av_pause_circle_outline_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_av_play_circle_outline_80dp.png b/core/src/main/res/drawable-xhdpi/ic_av_play_circle_outline_80dp.png
index db7b695e9..990ae524a 100644
--- a/core/src/main/res/drawable-xhdpi/ic_av_play_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_av_play_circle_outline_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
index 3dc7bf5cb..d94a40811 100755
--- a/core/src/main/res/drawable-xhdpi/ic_av_rewind_80dp.png
+++ 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_cancel_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_cancel_grey600_24dp.png
index ce0da2222..2ad1ac3f5 100644
--- a/core/src/main/res/drawable-xhdpi/ic_cancel_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_cancel_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cancel_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_cancel_white_24dp.png
index e9e9c7a53..e8836a1b0 100644
--- a/core/src/main/res/drawable-xhdpi/ic_cancel_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.png
new file mode 100644
index 000000000..fbb3e062c
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_disconnect_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_cast_disconnect_grey600_36dp.png
new file mode 100644
index 000000000..d81267f09
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_disconnect_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_disconnect_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_cast_disconnect_white_36dp.png
new file mode 100644
index 000000000..9aab16855
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_disconnect_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_light.png
new file mode 100644
index 000000000..f2713e20e
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_off_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_off_light.png
new file mode 100644
index 000000000..f4f8aaea8
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.png
new file mode 100644
index 000000000..247fc95ba
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.png
new file mode 100644
index 000000000..ecf4b4723
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.png
new file mode 100644
index 000000000..60e3afa5d
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_cast_on_light.png b/core/src/main/res/drawable-xhdpi/ic_cast_on_light.png
new file mode 100644
index 000000000..40ce9d4f2
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_cast_on_light.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
index f7c205dd2..e46ab71b4 100644
--- 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
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
index ea2ff8671..bb15a903a 100644
--- 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
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
index a615ee436..336682497 100644
--- 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
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
index 946cfea57..cdb4a3181 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_close_light.png b/core/src/main/res/drawable-xhdpi/ic_close_light.png
new file mode 100644
index 000000000..49faa429a
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_close_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_create_new_folder_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_create_new_folder_grey600_24dp.png
index 82f0ad458..bb16eabce 100644
--- a/core/src/main/res/drawable-xhdpi/ic_create_new_folder_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_create_new_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_create_new_folder_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_create_new_folder_white_24dp.png
index aa54623c8..ae5448d24 100644
--- a/core/src/main/res/drawable-xhdpi/ic_create_new_folder_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_create_new_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png
index c6bb43e8b..5ad22d0f1 100644
--- a/core/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_delete_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png
index cdb230c2f..63f990408 100644
--- a/core/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_description_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_description_grey600_36dp.png
deleted file mode 100644
index 50f854ea5..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_description_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_description_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_description_white_36dp.png
deleted file mode 100644
index 60d988dd4..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_description_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_done_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_done_grey600_24dp.png
index 36ed24eec..3f78a47a3 100644
--- a/core/src/main/res/drawable-xhdpi/ic_done_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_done_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_done_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_done_white_24dp.png
index e5024472a..5567661ac 100644
--- a/core/src/main/res/drawable-xhdpi/ic_done_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_done_white_24dp.png
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
index 8f335e274..83cb91561 100644
--- 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
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
index ae9972926..4b030e169 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_expand_more_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_expand_more_grey600_36dp.png
index 921249fb2..8e697365f 100644
--- a/core/src/main/res/drawable-xhdpi/ic_expand_more_grey600_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_expand_more_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_expand_more_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_expand_more_white_36dp.png
index f00aa8bd4..16b7c081e 100644
--- a/core/src/main/res/drawable-xhdpi/ic_expand_more_white_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_expand_more_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_24dp.png
index 6c26d6ea2..6f8b42221 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_36dp.png
index 169a3b386..521f7490b 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_forward_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_24dp.png
index b7c86059b..2b34fb82d 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_36dp.png
index 8aec1abb0..2d9a7e3c9 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_forward_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_24dp.png
index d1db46a23..9468020b7 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_36dp.png
index ccac2c273..ea5493251 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_24dp.png
index 379435f54..f4182f174 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png
index 857a1aaa2..de9ec1d90 100644
--- a/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_fast_rewind_white_36dp.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 995aafb5c..84554ce5b 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 1495c4fa6..339113c20 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_file_download_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_file_download_grey600_24dp.png
index aa89d4977..0d42f1749 100644
--- a/core/src/main/res/drawable-xhdpi/ic_file_download_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_file_download_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png
index 990dfb82b..219f47167 100644
--- a/core/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png
index fdbb8eb4e..4c6a96eba 100644
--- a/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png
index 7e14f7fbf..9416c70ec 100644
--- a/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_folder_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_folder_grey600_24dp.png
new file mode 100755
index 000000000..190df8236
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png
new file mode 100755
index 000000000..e5f54cef0
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_hearing_grey600_18dp.png b/core/src/main/res/drawable-xhdpi/ic_hearing_grey600_18dp.png
index a0c760e6f..d014684e1 100644
--- a/core/src/main/res/drawable-xhdpi/ic_hearing_grey600_18dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_hearing_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_hearing_white_18dp.png b/core/src/main/res/drawable-xhdpi/ic_hearing_white_18dp.png
index 1b8722a7f..91adb4437 100644
--- a/core/src/main/res/drawable-xhdpi/ic_hearing_white_18dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_hearing_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_history_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_history_grey600_24dp.png
index a098d8d0f..c0ffea719 100644
--- a/core/src/main/res/drawable-xhdpi/ic_history_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_history_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_history_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_history_white_24dp.png
index 7681ac8b8..3aa40c305 100644
--- a/core/src/main/res/drawable-xhdpi/ic_history_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_history_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
index e67d41cc1..3b0d3aa1a 100644
--- 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
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
index 48e52d596..57e478e9f 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_info_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_info_grey600_24dp.png
index 9dcb234fe..57f3ec5e3 100644
--- a/core/src/main/res/drawable-xhdpi/ic_info_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_info_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_info_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_info_white_24dp.png
index bee33abb7..e18df93a1 100644
--- a/core/src/main/res/drawable-xhdpi/ic_info_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_info_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_launcher.png b/core/src/main/res/drawable-xhdpi/ic_launcher.png
index 857a1b12e..2dbfd8874 100644
--- a/core/src/main/res/drawable-xhdpi/ic_launcher.png
+++ b/core/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_list_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_list_grey600_24dp.png
index c51f8a258..030263ce3 100644
--- a/core/src/main/res/drawable-xhdpi/ic_list_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_list_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_list_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_list_white_24dp.png
index abefc200a..2b7253975 100644
--- a/core/src/main/res/drawable-xhdpi/ic_list_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_list_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png
index ca35f6d0a..c6f6fa06c 100644
--- a/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png
index 01fb55ca1..873687188 100644
--- a/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png
index 11d9a4b8b..d82d4bd8b 100644
--- a/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
index 01ca4b56c..a5ed62d8a 100644
--- a/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.png
new file mode 100644
index 000000000..740867129
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_media_cast_disconnect.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_more_vert_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_more_vert_grey600_24dp.png
deleted file mode 100644
index 7bc63a511..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_more_vert_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png
deleted file mode 100644
index 1b04eda04..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_new.png b/core/src/main/res/drawable-xhdpi/ic_new.png
deleted file mode 100755
index 447a9398b..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_new.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_new_dark.png b/core/src/main/res/drawable-xhdpi/ic_new_dark.png
deleted file mode 100755
index 4a23d309c..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_new_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_new_releases_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_new_releases_grey600_24dp.png
index 655658557..a2bb4d1b8 100644
--- a/core/src/main/res/drawable-xhdpi/ic_new_releases_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_new_releases_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_new_releases_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_new_releases_white_24dp.png
index 00a4daa62..e78b002f2 100644
--- a/core/src/main/res/drawable-xhdpi/ic_new_releases_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_new_releases_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png
index 3ec598cfd..6708b4161 100644
--- a/core/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png
index 7efc88511..aeb13ebc4 100644
--- a/core/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_pause_light.png b/core/src/main/res/drawable-xhdpi/ic_pause_light.png
new file mode 100644
index 000000000..40cd79f14
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_pause_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
index 14b6d17d4..f49aed757 100644
--- a/core/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png
index 72dfa9fa6..7192ad487 100644
--- a/core/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png
index 689fda259..6874b8236 100644
--- a/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png
index ba62a8102..dabd252ee 100644
--- a/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
index a55d19922..5b0110454 100644
--- a/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png
index 043acd808..fff3e1f56 100644
--- a/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_play_light.png b/core/src/main/res/drawable-xhdpi/ic_play_light.png
new file mode 100644
index 000000000..33f6a5919
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_play_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png
index 7891efffa..b2f5463a8 100644
--- a/core/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png
index 5f89fc257..3dffcc846 100644
--- a/core/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_grey600_18dp.png b/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_grey600_18dp.png
index 7047ed7ea..2039d9ce8 100644
--- a/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_grey600_18dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_white_18dp.png b/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_white_18dp.png
index ef2023b52..6dd240bca 100644
--- a/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_white_18dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_remove_red_eye_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sd_storage_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_sd_storage_grey600_36dp.png
index 48cc94622..57162f5ca 100644
--- a/core/src/main/res/drawable-xhdpi/ic_sd_storage_grey600_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_sd_storage_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_sd_storage_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_sd_storage_white_36dp.png
index b440536f6..ced6247ca 100644
--- a/core/src/main/res/drawable-xhdpi/ic_sd_storage_white_36dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_sd_storage_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_search_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_search_grey600_24dp.png
index f9c0b2ec3..b0999b31d 100644
--- a/core/src/main/res/drawable-xhdpi/ic_search_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_search_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_search_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
index 043759acd..ee844ccdd 100644
--- a/core/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png
index 2251d2bbb..5a70fdb99 100644
--- a/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_grey600_24dp.png
index c30f31017..49813c945 100644
--- a/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_white_24dp.png
index ea2313395..bac1560a2 100644
--- a/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_settings_input_antenna_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png
index 12e5d100d..d0cf80051 100644
--- a/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png
index 88a0edd6c..e856dca31 100644
--- a/core/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_share_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
index dd536bca2..82692971c 100644
--- a/core/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_share_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
index 2e291dd19..31aa09ca2 100644
--- a/core/src/main/res/drawable-xhdpi/ic_skip_grey600_36dp.png
+++ 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
index fa85f1899..e664f607c 100644
--- a/core/src/main/res/drawable-xhdpi/ic_skip_white_36dp.png
+++ 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
index ae0787a26..2d8cb9a75 100644
--- a/core/src/main/res/drawable-xhdpi/ic_sleep_grey600_24dp.png
+++ 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
index 026224c5e..6ba86245c 100644
--- 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
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
index ef39d7279..b03fb39b4 100644
--- 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
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
index 07e156172..4d03cc0dc 100644
--- a/core/src/main/res/drawable-xhdpi/ic_sleep_white_24dp.png
+++ 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
index f2ef499ef..91447dc4c 100644
--- a/core/src/main/res/drawable-xhdpi/ic_sort_grey600_24dp.png
+++ 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
index 68b0b7ad3..6d4af1bcb 100644
--- a/core/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png
+++ 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
index 5160319b5..2bd46b7e7 100644
--- 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
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
index 33f9727d3..4eaf8b891 100644
--- 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
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
index 7a09ebc33..30f1e6ae6 100644
--- a/core/src/main/res/drawable-xhdpi/ic_star_grey600_24dp.png
+++ 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
index 288799f93..93d3b5d0c 100644
--- a/core/src/main/res/drawable-xhdpi/ic_star_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png b/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png
index 50d73271d..3d8a046a8 100644
--- a/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png
+++ b/core/src/main/res/drawable-xhdpi/ic_stat_antenna_default.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png b/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png
index 4adfb636c..200b60c96 100755
--- a/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-xhdpi/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_timer_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_timer_grey600_24dp.png
deleted file mode 100644
index 23149552f..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_timer_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_timer_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_timer_white_24dp.png
deleted file mode 100644
index 896d9dcf2..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_timer_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_toc_grey600_36dp.png b/core/src/main/res/drawable-xhdpi/ic_toc_grey600_36dp.png
deleted file mode 100644
index 57a60ed9f..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_toc_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_toc_white_36dp.png b/core/src/main/res/drawable-xhdpi/ic_toc_white_36dp.png
deleted file mode 100644
index aa7ee9c02..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_toc_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_undobar_undo.png b/core/src/main/res/drawable-xhdpi/ic_undobar_undo.png
deleted file mode 100644
index 91c8429ad..000000000
--- a/core/src/main/res/drawable-xhdpi/ic_undobar_undo.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_web_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_web_grey600_24dp.png
index a9f5ebc33..38150f680 100644
--- a/core/src/main/res/drawable-xhdpi/ic_web_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_web_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_web_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_web_white_24dp.png
index edc590a0b..cce57ef59 100644
--- a/core/src/main/res/drawable-xhdpi/ic_web_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_web_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/navigation_up.png b/core/src/main/res/drawable-xhdpi/navigation_up.png
index f8c3e6f75..0f954094e 100755
--- a/core/src/main/res/drawable-xhdpi/navigation_up.png
+++ b/core/src/main/res/drawable-xhdpi/navigation_up.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/navigation_up_dark.png b/core/src/main/res/drawable-xhdpi/navigation_up_dark.png
index 6964e069b..539222b2f 100755
--- a/core/src/main/res/drawable-xhdpi/navigation_up_dark.png
+++ b/core/src/main/res/drawable-xhdpi/navigation_up_dark.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
index ab02927fb..d7348168d 100644
--- a/core/src/main/res/drawable-xhdpi/stat_notify_sync.png
+++ 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
index c1f483f71..78c5df4d9 100644
--- a/core/src/main/res/drawable-xhdpi/stat_notify_sync_error.png
+++ b/core/src/main/res/drawable-xhdpi/stat_notify_sync_error.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/undobar.9.png b/core/src/main/res/drawable-xhdpi/undobar.9.png
deleted file mode 100644
index 22fa2205b..000000000
--- a/core/src/main/res/drawable-xhdpi/undobar.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/undobar_button_focused.9.png b/core/src/main/res/drawable-xhdpi/undobar_button_focused.9.png
deleted file mode 100644
index d284ca7cb..000000000
--- a/core/src/main/res/drawable-xhdpi/undobar_button_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/undobar_button_pressed.9.png b/core/src/main/res/drawable-xhdpi/undobar_button_pressed.9.png
deleted file mode 100644
index e990659f0..000000000
--- a/core/src/main/res/drawable-xhdpi/undobar_button_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/undobar_divider.9.png b/core/src/main/res/drawable-xhdpi/undobar_divider.9.png
deleted file mode 100644
index 1b067d4e7..000000000
--- a/core/src/main/res/drawable-xhdpi/undobar_divider.9.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_add_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_add_grey600_24dp.png
index 70e4e86e7..396d18bca 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_add_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_add_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
index 72cedcad4..0fdced8fc 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_audiotrack_light.png b/core/src/main/res/drawable-xxhdpi/ic_audiotrack_light.png
new file mode 100644
index 000000000..8c83b169d
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_audiotrack_light.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
index f90617f45..3b55c5d55 100755
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_av_pause_circle_outline_80dp.png b/core/src/main/res/drawable-xxhdpi/ic_av_pause_circle_outline_80dp.png
index 48e28102a..76cc4db32 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_av_pause_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_av_pause_circle_outline_80dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_av_play_circle_outline_80dp.png b/core/src/main/res/drawable-xxhdpi/ic_av_play_circle_outline_80dp.png
index b5918c0f1..eebf0110c 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_av_play_circle_outline_80dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_av_play_circle_outline_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
index 81709e0ae..38a5fc264 100755
--- a/core/src/main/res/drawable-xxhdpi/ic_av_rewind_80dp.png
+++ 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_cancel_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_cancel_grey600_24dp.png
index f6f7844de..3d2aba75b 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_cancel_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_cancel_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cancel_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_cancel_white_24dp.png
index faa409f80..5bb0c3cc4 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_cancel_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_cancel_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.png
new file mode 100644
index 000000000..e94df3889
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_grey600_36dp.png
new file mode 100644
index 000000000..68f7650a1
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_white_36dp.png
new file mode 100644
index 000000000..828755407
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_disconnect_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_light.png
new file mode 100644
index 000000000..c5722a6eb
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_off_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_off_light.png
new file mode 100644
index 000000000..92ac67b34
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.png
new file mode 100644
index 000000000..2742fcb4a
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.png
new file mode 100644
index 000000000..405178e64
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.png
new file mode 100644
index 000000000..dfe52428d
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_cast_on_light.png b/core/src/main/res/drawable-xxhdpi/ic_cast_on_light.png
new file mode 100644
index 000000000..7e69a0864
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_cast_on_light.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
index 5e52fa65e..7093f28d5 100644
--- 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
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
index 9c8615618..050e6cd6c 100644
--- 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
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
index 565a755f3..56d380575 100644
--- 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
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
index 7b1d9ea34..ba9af5265 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_close_light.png b/core/src/main/res/drawable-xxhdpi/ic_close_light.png
new file mode 100644
index 000000000..be519bfcb
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_close_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_grey600_24dp.png
index 802fc6fa0..23fa54d28 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_white_24dp.png
index 91cbc73d1..d8391d37c 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_create_new_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png
index 4886ab1e9..1a3a6192b 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_delete_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png
index 0e95e9b1d..e4a5d0261 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_description_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_description_grey600_36dp.png
deleted file mode 100644
index 33df5d9d2..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_description_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_description_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_description_white_36dp.png
deleted file mode 100644
index aa5e73299..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_description_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_done_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_done_grey600_24dp.png
index c836c5490..7feeca3a1 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_done_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_done_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png
index f801e7ab3..edb552b49 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png
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
index 88a800f24..dc8682716 100644
--- 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
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
index 148891e6c..808186aa9 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_expand_more_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_expand_more_grey600_36dp.png
index 503678f1b..07bb07a38 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_expand_more_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_expand_more_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_expand_more_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_expand_more_white_36dp.png
index 16094f9bc..fe5c3d280 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_expand_more_white_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_expand_more_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_24dp.png
index 169a3b386..521f7490b 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_36dp.png
index 7b83df521..644645c8b 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_24dp.png
index 8aec1abb0..2d9a7e3c9 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png
index b4cf934f7..76c94c3ba 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_24dp.png
index ccac2c273..ea5493251 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_36dp.png
index 940b5413d..831fda2ab 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_24dp.png
index 857a1aaa2..de9ec1d90 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png
index 751e5d1ba..8e11ac89e 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.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 ddfe32b53..62decef68 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 4929a6a11..7838ab87c 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_file_download_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_file_download_grey600_24dp.png
index e61a48a4d..82b0d1986 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_file_download_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_file_download_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png
index 95502de3f..0b479dba2 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png
index 43ec90ea5..5846ad140 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png
index d3923efee..1263ae82e 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_folder_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_folder_grey600_24dp.png
new file mode 100755
index 000000000..6fbc40459
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png
new file mode 100755
index 000000000..0d1ac4876
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_hearing_grey600_18dp.png b/core/src/main/res/drawable-xxhdpi/ic_hearing_grey600_18dp.png
index 111ed802c..19456de04 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_hearing_grey600_18dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_hearing_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_hearing_white_18dp.png b/core/src/main/res/drawable-xxhdpi/ic_hearing_white_18dp.png
index 417b39b81..82de8bb65 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_hearing_white_18dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_hearing_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_history_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_history_grey600_24dp.png
index 632a73e42..f083e0f2c 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_history_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_history_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png
index 3e438ec4e..17d6d29b8 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_history_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
index c2300b53b..2e7d39a5a 100644
--- 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
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
index 66a710d8c..ec4981f5c 100644
--- 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
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_info_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_info_grey600_24dp.png
index 64445284b..874226124 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_info_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_info_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_info_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_info_white_24dp.png
index 185d18d1a..8a1b185ff 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_info_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_info_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_launcher.png b/core/src/main/res/drawable-xxhdpi/ic_launcher.png
index 2bef52ec7..41b261b4f 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_launcher.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_list_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_list_grey600_24dp.png
index ceaf36811..c92e250b8 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_list_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_list_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_list_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_list_white_24dp.png
index 6b7e78f83..4d2807e4e 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_list_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_list_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png
index 311a7fa13..386595f99 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png
index 39a163843..7bc7c690d 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png
index c0552d564..a424f4c5d 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
index 46852d54f..d37d40dcb 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.png b/core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.png
new file mode 100644
index 000000000..2d2ec9035
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_media_cast_disconnect.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_more_vert_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_more_vert_grey600_24dp.png
deleted file mode 100644
index 44012b87d..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_more_vert_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png
deleted file mode 100644
index 2955c02ec..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_new.png b/core/src/main/res/drawable-xxhdpi/ic_new.png
deleted file mode 100755
index 5e836eae4..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_new.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_new_dark.png b/core/src/main/res/drawable-xxhdpi/ic_new_dark.png
deleted file mode 100755
index bca96b751..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_new_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_new_releases_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_new_releases_grey600_24dp.png
index 3eccc24f0..79507d800 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_new_releases_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_new_releases_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_new_releases_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_new_releases_white_24dp.png
index 4a7083c57..a4dbba8ae 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_new_releases_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_new_releases_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png
index 7efc88511..aeb13ebc4 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png
index c8ed1d00a..8753d9c50 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_pause_light.png b/core/src/main/res/drawable-xxhdpi/ic_pause_light.png
new file mode 100644
index 000000000..a36d4d11e
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_pause_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
index 72dfa9fa6..7192ad487 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png
index 76833ff7d..fb63ddc5a 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png
index ba62a8102..dabd252ee 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png
index 1d44f8f2f..9fcf99558 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
index 043acd808..fff3e1f56 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png
index fe7ea25cc..9b31e2d19 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_play_light.png b/core/src/main/res/drawable-xxhdpi/ic_play_light.png
new file mode 100644
index 000000000..b1424874a
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_play_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png
index 9c1e27d74..e5eef5d8a 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png
index 72128fe69..13157817d 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_grey600_18dp.png b/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_grey600_18dp.png
index 259ddcd14..16cdc31c1 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_grey600_18dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_grey600_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_white_18dp.png b/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_white_18dp.png
index ece7aeb74..695eb950e 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_white_18dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_remove_red_eye_white_18dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sd_storage_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_sd_storage_grey600_36dp.png
index 4352dbc06..597f417d5 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_sd_storage_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_sd_storage_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_sd_storage_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_sd_storage_white_36dp.png
index 5e18ed274..2ef908e26 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_sd_storage_white_36dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_sd_storage_white_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_search_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_search_grey600_24dp.png
index 9424ae98e..2cac5b19c 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_search_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_search_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
index 0bbeab150..0ada3a39f 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png
index 6a70402b4..fe7d1a24d 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_grey600_24dp.png
index 5404820db..37ce4c61a 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_white_24dp.png
index 19752dd44..0609c3154 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_settings_input_antenna_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png
index 6bb8f6e08..e5de22411 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png
index 89136d7c3..97a334744 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_share_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
index 9963c6a05..406bc9ab5 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_share_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
index 00a55a0f8..75a4a9545 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_skip_grey600_36dp.png
+++ 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
index ac38e6d42..a31299c81 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_skip_white_36dp.png
+++ 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
index f4bd9e94d..8346240f1 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_sleep_grey600_24dp.png
+++ 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
index 6c42d6051..07748be06 100644
--- 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
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
index b8e06f9b3..8e2e80e71 100644
--- 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
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
index e2249c357..33770dbb1 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_sleep_white_24dp.png
+++ 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
index 6cdc649ea..fa8c4c593 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_sort_grey600_24dp.png
+++ 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
index 56ea13fe2..b8ef1050e 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png
+++ 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
index 6348e1997..06f24ef24 100644
--- 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
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
index aab4831ff..70785a1e3 100644
--- 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
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
index ef0294931..29fff7d5f 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_star_grey600_24dp.png
+++ 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
index de4b7b29d..d5d478096 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png b/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png
index b274bb60f..965fabc57 100755
--- a/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_stat_authentication.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_timer_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_timer_grey600_24dp.png
deleted file mode 100644
index a9ae43327..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_timer_grey600_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_timer_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_timer_white_24dp.png
deleted file mode 100644
index 276f4e1b9..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_timer_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_toc_grey600_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_toc_grey600_36dp.png
deleted file mode 100644
index bb35c5657..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_toc_grey600_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_toc_white_36dp.png b/core/src/main/res/drawable-xxhdpi/ic_toc_white_36dp.png
deleted file mode 100644
index fdbb3ac3a..000000000
--- a/core/src/main/res/drawable-xxhdpi/ic_toc_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_web_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_web_grey600_24dp.png
index 9e70743e8..abb58b77b 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_web_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_web_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_web_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_web_white_24dp.png
index fcc2d1aca..abf5662b3 100644
--- a/core/src/main/res/drawable-xxhdpi/ic_web_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_web_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
index 9cd2a53b4..dabb1ea27 100644
--- a/core/src/main/res/drawable-xxhdpi/stat_notify_sync.png
+++ 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
index bb76c2756..6810a8249 100644
--- a/core/src/main/res/drawable-xxhdpi/stat_notify_sync_error.png
+++ 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
index c9b68abf0..1c915d9dd 100755
--- 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
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
index 87b0756eb..190bb0f4c 100755
--- a/core/src/main/res/drawable-xxxhdpi/ic_av_rewind_80dp.png
+++ 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_close_light.png b/core/src/main/res/drawable-xxxhdpi/ic_close_light.png
new file mode 100644
index 000000000..679c2a4d5
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_close_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_grey600_24dp.png
index baf7b6ef7..448ca4898 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_white_24dp.png
index aa103bfd0..56a47309c 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_white_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_create_new_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png
index 5d14b5b25..3fe8eb4dd 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png
index e8b865e4a..cb2207f11 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_folder_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_folder_grey600_24dp.png
new file mode 100755
index 000000000..0107ea21c
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_folder_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png
new file mode 100755
index 000000000..7a3c198ee
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png
index e41d5b9ee..03ccfd0d6 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png
index 2376b7334..2586be5ab 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png
index c281784dd..e46b956e0 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
index 25ea3ab99..b2dd88c07 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_pause_light.png b/core/src/main/res/drawable-xxxhdpi/ic_pause_light.png
new file mode 100644
index 000000000..7de2ef4ed
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_pause_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_play_light.png b/core/src/main/res/drawable-xxxhdpi/ic_play_light.png
new file mode 100644
index 000000000..4428c8477
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_play_light.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_grey600_36dp.png b/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_grey600_36dp.png
index cf03bc3f1..25d26d523 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_grey600_36dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_grey600_36dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_white_36dp.png b/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_white_36dp.png
index 2996619c9..505fb28dc 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_white_36dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_sd_storage_white_36dp.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
index 94836e0c8..b599c2207 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_skip_grey600_36dp.png
+++ 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
index a84f34228..a0dd670c3 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_skip_white_36dp.png
+++ 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
index 9c0116c60..e8348b7e4 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_sleep_grey600_24dp.png
+++ 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
index e8141d0df..c06456421 100644
--- 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
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
index ca41ad5e6..5cf1d4f3b 100644
--- 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
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
index 29782e155..3e9984bf0 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_sleep_white_24dp.png
+++ 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
index 1109e95c8..d79dfa577 100644
--- 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
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
index 086cb677c..c2d2ee285 100644
--- 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
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
index dda3262ed..2866aff29 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_star_grey600_24dp.png
+++ 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
index ba29292b0..a74fee378 100644
--- a/core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png
+++ b/core/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable/undobar_button.xml b/core/src/main/res/drawable/undobar_button.xml
deleted file mode 100644
index a4de91b49..000000000
--- a/core/src/main/res/drawable/undobar_button.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-
- Copyright 2012 Roman Nurik
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@drawable/undobar_button_pressed" android:state_pressed="true"/>
- <item android:drawable="@drawable/undobar_button_focused" android:state_focused="true"/>
- <item android:drawable="@android:color/transparent"/>
-</selector>
diff --git a/core/src/main/res/values-ar/strings.xml b/core/src/main/res/values-ar/strings.xml
new file mode 100644
index 000000000..7517e8dae
--- /dev/null
+++ b/core/src/main/res/values-ar/strings.xml
@@ -0,0 +1,127 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="all_episodes_short_label">الكل</string>
+ <string name="favorite_episodes_label">المفضلات</string>
+ <string name="new_label">جديد</string>
+ <string name="settings_label">اعدادات</string>
+ <string name="downloads_label">تنزيل</string>
+ <string name="downloads_running_label">جارى التشغيل</string>
+ <string name="downloads_completed_label">اكتمل</string>
+ <string name="downloads_log_label">سجل</string>
+ <string name="cancel_download_label">الغاء التنزيل</string>
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <string name="drawer_open">قائمة الفتح</string>
+ <string name="drawer_close">قائمة الاغلاف</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">الاعداد الجديدة</string>
+ <string name="drawer_feed_counter_unplayed">الاعداد غير المقرؤة</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">افتح فى المتصفح</string>
+ <string name="copy_url_label">انسخ رابط الموقع</string>
+ <string name="share_url_label">انشر رابط الموقع</string>
+ <string name="copied_url_msg">تم نسخ الرابط للحافظة</string>
+ <string name="go_to_position_label">اذهب لهذا الموقع</string>
+ <!--Playback history-->
+ <!--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>
+ <string name="error_msg_prefix">حدث خطاء</string>
+ <string name="refresh_label">تحديث</string>
+ <string name="external_storage_error_msg">لا توجد ذاكرة خارجية متاحة. فضلا تاكد من اتاحة الذاكرة الخارجية للتطبيق حتى يعمل بصورة جيدة</string>
+ <string name="chapters_label">فصول</string>
+ <string name="description_label">وصف</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="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_after_listening">بعد الانتهاء</string>
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">تعليمه ك تم تشغيله</string>
+ <string name="mark_all_seen_label">تعليمه ك تم مشاهدته</string>
+ <string name="show_info_label">اظهار المعلومات</string>
+ <string name="share_label">مشاركة</string>
+ <string name="share_link_label">مشاركة الرابط</string>
+ <string name="episode_actions">تطبيق الاجراء</string>
+ <string name="hide_unplayed_episodes_label">لم يتم تشغيله</string>
+ <string name="hide_paused_episodes_label">ايقاف مؤقت</string>
+ <string name="hide_downloaded_episodes_label">تم التنزيل</string>
+ <string name="hide_not_downloaded_episodes_label">لم يتم التنزيل</string>
+ <!--actions on feeditems-->
+ <string name="play_label">تشغيل</string>
+ <string name="pause_label">ايقاف مؤقت</string>
+ <string name="stop_label">ايقاف</string>
+ <string name="remove_label">مسح</string>
+ <string name="marked_as_read_label">تم تعليمه ك مشغل</string>
+ <string name="mark_unread_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="activate_auto_download">تفعيل التنزيل التلقائي</string>
+ <string name="deactivate_auto_download">ايقاف التنزيل التلقائي</string>
+ <string name="removed_item">تم حزف العنصر</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">نجحت العملية</string>
+ <string name="download_failed">فشلت العملية</string>
+ <string name="download_pending">التنزيل فى الانتظار</string>
+ <string name="download_running">جارى التنزيل</string>
+ <string name="download_error_device_not_found">حهاز التخزين غير موجود</string>
+ <string name="download_error_insufficient_space">مساحة غير كافية</string>
+ <string name="download_error_file_error">خطاء فى الملف</string>
+ <string name="download_error_http_data_error">خطاء فى بيانات HTTP</string>
+ <string name="download_error_error_unknown">خطاء غير معروف</string>
+ <string name="download_error_connection_error">خطاء فى الاتصال</string>
+ <string name="download_error_unknown_host">المضيف غير معروف</string>
+ <string name="download_error_unauthorized">خطاء فى التحقق</string>
+ <!--Mediaplayer messages-->
+ <string name="playback_error_unknown">خطاء غير معروف</string>
+ <!--Queue operations-->
+ <string name="removed_from_queue">تم حزف العنصر</string>
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <string name="all_label">الكل</string>
+ <string name="unplayed_label">لم يتم تشغيله</string>
+ <string name="downloaded_label">تم التنزيل</string>
+ <string name="not_downloaded_label">لم يتم التنزيل</string>
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-az/strings.xml b/core/src/main/res/values-az/strings.xml
index d305e8127..10771d8f8 100644
--- a/core/src/main/res/values-az/strings.xml
+++ b/core/src/main/res/values-az/strings.xml
@@ -12,6 +12,7 @@
<string name="playback_history_label">Oynatma tarixiçəsi</string>
<string name="gpodnet_main_label">gpodder.net</string>
<!--New episodes fragment-->
+ <!--Statistics fragment-->
<!--Main activity-->
<!--Webview actions-->
<string name="open_in_browser_label">Brauzerdə aç</string>
@@ -170,8 +171,6 @@
<string name="opml_import_label">OPML idxalı</string>
<string name="opml_directory_error">XƏTA!</string>
<string name="reading_opml_label">OPML faylın oxunması</string>
- <string name="opml_reader_error">OPML faylını oxuyanda xəta baş verdi:</string>
- <string name="opml_import_error_dir_empty">İdxal qovliqu boşdur.</string>
<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>
@@ -205,4 +204,5 @@
<!--AntennaPodSP-->
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-ca-rES/strings.xml b/core/src/main/res/values-ca-rES/strings.xml
new file mode 100644
index 000000000..28dfeb6e8
--- /dev/null
+++ b/core/src/main/res/values-ca-rES/strings.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <!--actions on feeditems-->
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-ca/strings.xml b/core/src/main/res/values-ca/strings.xml
index 1821eeab7..b8516001f 100644
--- a/core/src/main/res/values-ca/strings.xml
+++ b/core/src/main/res/values-ca/strings.xml
@@ -1,17 +1,12 @@
<?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">Canals</string>
<string name="add_feed_label">Afegeix podcast</string>
- <string name="podcasts_label">PODCASTS</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>
<string name="add_new_feed_label">Afegeix podcast</string>
<string name="downloads_label">Baixades</string>
@@ -25,9 +20,7 @@
<string name="free_space_label">%1$s lliures</string>
<string name="episode_cache_full_title">Caché d\'episodi completa</string>
<string name="episode_cache_full_message">S\'ha arribat al límit de la caché. Pots incrementar la capacitat de la caché a les Opcions</string>
- <!--New episodes fragment-->
- <string name="recently_published_episodes_label">Publicats recentment</string>
- <string name="episode_filter_label">Mostra només els episodis nous</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Obre menú</string>
<string name="drawer_close">Tanca menú</string>
@@ -76,9 +69,7 @@
<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="send_label">Enviar...</string>
@@ -108,8 +99,6 @@
<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>
@@ -144,8 +133,6 @@
<string name="removed_from_favorites">Tret 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>
@@ -203,7 +190,6 @@
<string name="playback_error_server_died">El servidor no està operatiu</string>
<string name="playback_error_unknown">Error desconegut</string>
<string name="no_media_playing_label">No s\'està reproduint res</string>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">S\'està carregant</string>
<string name="playbackservice_notification_title">Podcast en reproducció</string>
<string name="unknown_media_key">AntennaPod - Control desconegut: %1$d</string>
@@ -388,8 +374,6 @@
<string name="opml_import_label">Importació OPML</string>
<string name="opml_directory_error">Error!</string>
<string name="reading_opml_label">S\'està llegint el fitxer OPML</string>
- <string name="opml_reader_error">S\'ha produït un error en llegir el document OPML:</string>
- <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="select_options_label">Selecciona...</string>
@@ -459,6 +443,7 @@
<string name="create_folder_label">Crea una carpeta</string>
<string name="choose_data_directory">Selecció de la carpeta de dades</string>
<string name="choose_data_directory_message">Selecciona l\'arrel del teu directori d\'informació. AntennaPod crearà els subdirectoris pertinents</string>
+ <string name="choose_data_directory_permission_rationale">Es requereix accés a l\'emmagatzematge extern per canviar el directori d\'informació</string>
<string name="create_folder_msg">Voleu crear una nova carpeta amb el nom \"%1$s\"?</string>
<string name="create_folder_success">S\'ha creat la nova carpeta</string>
<string name="create_folder_error_no_write_access">No es pot escriure dins d\'aquesta carpeta</string>
@@ -480,22 +465,13 @@
<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>
- <string name="show_cover_label">Mosta la imatge</string>
<string name="rewind_label">Rebobina</string>
<string name="fast_forward_label">Avança ràpidament</string>
<string name="media_type_audio_label">Àudio</string>
<string name="media_type_video_label">Vídeo</string>
<string name="navigate_upwards_label">Navega cap amunt</string>
- <string name="butAction_label">Més accions</string>
- <string name="status_playing_label">S\'està reproduïnt l\'episodi</string>
<string name="status_downloading_label">S\'està baixant l\'episodi</string>
- <string name="status_downloaded_label">S\'ha baixat l\'episodi</string>
- <string name="status_unread_label">L\'element és nou</string>
<string name="in_queue_label">S\'ha afegit l\'episodi a la cua</string>
- <string name="new_episodes_count_label">Nombre d\'episodis nous</string>
- <string name="in_progress_episodes_count_label">Nombre d\'episodis que heu començat a escoltar</string>
<string name="drag_handle_content_description">Arrossegueu l\'element per canviar-ne la posició</string>
<string name="load_next_page_label">Carrega la següent pàgina</string>
<!--Feed information screen-->
@@ -513,8 +489,8 @@
<!--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="select_label"><b>Selecciona...</b></string>
<string name="filter">Filtrar</string>
+ <!--Episodes apply actions-->
<string name="all_label">Tot</string>
<string name="selected_all_label">Selecciona tots els episodis</string>
<string name="none_label">Cap</string>
@@ -531,7 +507,7 @@
<string name="selected_queued_label">Episodis en cua seleccionats</string>
<string name="not_queued_label">No a la cua</string>
<string name="selected_not_queued_label">Episodis seleccionats i no a la cua</string>
- <string name="sort_title"><b>Ordena per...</b></string>
+ <!--Sort-->
<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>
@@ -553,4 +529,7 @@
<string name="audio_effects">Efectes de so</string>
<string name="stereo_to_mono">Downmix: D\'estereo a mono</string>
<string name="sonic_only">Només Sonic</string>
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
</resources>
diff --git a/core/src/main/res/values-cs-rCZ/strings.xml b/core/src/main/res/values-cs-rCZ/strings.xml
index 390fff60b..127ba6675 100644
--- a/core/src/main/res/values-cs-rCZ/strings.xml
+++ b/core/src/main/res/values-cs-rCZ/strings.xml
@@ -3,6 +3,7 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Kanály</string>
+ <string name="statistics_label">Statistiky</string>
<string name="add_feed_label">Přidat podcast</string>
<string name="podcasts_label">PODCASTY</string>
<string name="episodes_label">Epizody</string>
@@ -28,6 +29,9 @@
<!--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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Celkový čas poslechnutých podcastů:</string>
+ <string name="statistics_details_dialog">%1$d z %2$d započatých epizod.\n\nPřehraných %3$s z %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Otevřít menu</string>
<string name="drawer_close">Zavřít menu</string>
@@ -126,6 +130,7 @@
<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>
+ <string name="open_podcast">Otevřít podcast</string>
<!--actions on feeditems-->
<string name="download_label">Stáhnout</string>
<string name="play_label">Přehrát</string>
@@ -168,6 +173,7 @@
<string name="download_error_connection_error">Chyba spojení</string>
<string name="download_error_unknown_host">Neznámý host</string>
<string name="download_error_unauthorized">Chyba přihlášení</string>
+ <string name="download_error_file_type_type">Chyba typu souboru</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_canceled_autodownload_enabled_msg">Stahování zrušeno\nVypnuto <i>automatické stahování</i> této položky</string>
@@ -263,6 +269,7 @@
<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>
+ <string name="no_shownotes_label">Tato epizoda neobsahuje žádné poznámky.</string>
<!--Preferences-->
<string name="other_pref">Ostatní</string>
<string name="about_pref">O aplikaci</string>
@@ -342,6 +349,10 @@
<string name="pref_gpodnet_logout_toast">Úspěšně odhlášeno</string>
<string name="pref_gpodnet_setlogin_information_title">Změna přihlašovacích údajů</string>
<string name="pref_gpodnet_setlogin_information_sum">Změní přihlašovací údaje k vašemu gpodder.net účtu.</string>
+ <string name="pref_gpodnet_sync_title">Synchronizovat nyní</string>
+ <string name="pref_gpodnet_sync_sum">Synchronizovat odběry a stav epizod s gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Synchronizace spuštěna</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Přihlášen jako <i>%1$s</i> z přístroje <i>%2$s</i>]]></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_fast_forward">Čas rychlého posunu</string>
@@ -369,6 +380,10 @@
<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>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Nastavit síťovou proxy</string>
+ <string name="pref_known_issues">Známé chyby</string>
+ <string name="pref_no_browser_found">Nebyl nalezen webový prohlížeč.\"</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>
@@ -391,8 +406,8 @@
<string name="opml_import_label">OPML import</string>
<string name="opml_directory_error">CHYBA!</string>
<string name="reading_opml_label">Načítání OPML souboru</string>
- <string name="opml_reader_error">Nastala chyba při čtení OPML souboru:</string>
- <string name="opml_import_error_dir_empty">Adresář importu je prázdný.</string>
+ <string name="opml_reader_error">Došlo k chybě při čtení OPML dokumentu:</string>
+ <string name="opml_import_error_no_file">Nebyl vybrán žádný soubor!</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>
@@ -560,4 +575,16 @@
<string name="audio_effects">Audio efekty</string>
<string name="stereo_to_mono">Downmix: Stereo na mono</string>
<string name="sonic_only">Pouze Sonic</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Typ</string>
+ <string name="host_label">Host</string>
+ <string name="port_label">Port</string>
+ <string name="optional_hint">(Volitelný)</string>
+ <string name="proxy_test_label">Test</string>
+ <string name="proxy_checking">Kontrola...</string>
+ <string name="proxy_test_successful">Test úspěšný</string>
+ <string name="proxy_test_failed">Test selhal</string>
+ <string name="proxy_host_empty_error">Host nesmí být nevyplněný</string>
+ <string name="proxy_host_invalid_error">Host není platná IP nebo doména</string>
+ <string name="proxy_port_invalid_error">Neplatný port</string>
</resources>
diff --git a/core/src/main/res/values-da/strings.xml b/core/src/main/res/values-da/strings.xml
index 13f576bfe..ca144c17f 100644
--- a/core/src/main/res/values-da/strings.xml
+++ b/core/src/main/res/values-da/strings.xml
@@ -22,6 +22,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Nyligt udgivet</string>
<string name="episode_filter_label">Hvis kun nye episoder</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Åben menu</string>
<string name="drawer_close">Luk menu</string>
@@ -257,8 +258,6 @@
<string name="opml_import_label">OPML import</string>
<string name="opml_directory_error">FEJL!</string>
<string name="reading_opml_label">Indlæser OPML fil</string>
- <string name="opml_reader_error">En fejl opstod under indlæsning af opml documentet:</string>
- <string name="opml_import_error_dir_empty">Import mappen er tom.</string>
<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>
@@ -344,4 +343,5 @@
<string name="sp_apps_importing_feeds_msg">Importerer abonnementer fra single-purpose apps…</string>
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml
index bf7f1a435..3f3daff52 100644
--- a/core/src/main/res/values-de/strings.xml
+++ b/core/src/main/res/values-de/strings.xml
@@ -1,23 +1,21 @@
<?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">Feeds</string>
+ <string name="statistics_label">Statistiken</string>
<string name="add_feed_label">Podcast hinzufügen</string>
- <string name="podcasts_label">PODCASTS</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">Beendet</string>
<string name="downloads_log_label">Log</string>
+ <string name="subscriptions_label">Abonnements</string>
+ <string name="subscriptions_list_label">Abonnement-Übersicht</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>
@@ -25,9 +23,9 @@
<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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Gesamtzeit gespielter Podcasts:</string>
+ <string name="statistics_details_dialog">%1$d von %2$d Episoden gestartet.\n\n%3$s von %4$s Episoden gespielt.</string>
<!--Main activity-->
<string name="drawer_open">Menü öffnen</string>
<string name="drawer_close">Menü schließen</string>
@@ -76,9 +74,9 @@
<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="auto_delete_label">Episoden automatisch löschen</string>
<string name="parallel_downloads_suffix">\u0020gleichzeitige Downloads</string>
- <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_global">Standardwert</string>
<string name="feed_auto_download_always">Immer</string>
<string name="feed_auto_download_never">Nie</string>
<string name="send_label">Senden…</string>
@@ -108,8 +106,8 @@
<string name="share_link_label">Webseiten-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="share_item_url_label">URL zur Datei teilen</string>
+ <string name="share_item_url_with_position_label">URL zur Datei und Zeitmarke teilen</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>
@@ -124,6 +122,7 @@
<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>
+ <string name="open_podcast">Öffne Podcast</string>
<!--actions on feeditems-->
<string name="download_label">Herunterladen</string>
<string name="play_label">Abspielen</string>
@@ -144,8 +143,6 @@
<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>
@@ -166,6 +163,8 @@
<string name="download_error_connection_error">Verbindungsfehler</string>
<string name="download_error_unknown_host">Unbekannter Host</string>
<string name="download_error_unauthorized">Authentifizierungsfehler</string>
+ <string name="download_error_file_type_type">Dateityp-Fehler</string>
+ <string name="download_error_forbidden">Verboten</string>
<string name="cancel_all_downloads_label">Alle Downloads abbrechen</string>
<string name="download_canceled_msg">Download abgebrochen</string>
<string name="download_canceled_autodownload_enabled_msg">Download abgebrochen\n<i>Automatischen Download</i> für diese Episode deaktiviert</string>
@@ -203,7 +202,6 @@
<string name="playback_error_server_died">Server ist offline</string>
<string name="playback_error_unknown">Unbekannter Fehler</string>
<string name="no_media_playing_label">Keine Medienwiedergabe</string>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Puffert</string>
<string name="playbackservice_notification_title">Spiele Podcast ab</string>
<string name="unknown_media_key">AntennaPod - Unbekannte Medientaste: %1$d</string>
@@ -260,7 +258,10 @@
<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>
+ <string name="no_shownotes_label">Episode hat keine Shownotes.</string>
<!--Preferences-->
+ <string name="storage_pref">Speicher</string>
+ <string name="project_pref">Projekt</string>
<string name="other_pref">Anderes</string>
<string name="about_pref">Über</string>
<string name="queue_label">Abspielliste</string>
@@ -339,6 +340,10 @@
<string name="pref_gpodnet_logout_toast">Abmeldung war erfolgreich</string>
<string name="pref_gpodnet_setlogin_information_title">Anmeldeinformationen ändern</string>
<string name="pref_gpodnet_setlogin_information_sum">Ändere die Anmeldeinformationen deines gpodder.net Profils</string>
+ <string name="pref_gpodnet_sync_title">Jetzt synchronisieren</string>
+ <string name="pref_gpodnet_sync_sum">Synchronisiere Abonnements und Episodenstatus mit gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Synchronisation starten</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Eingeloggt als <i>%1$s</i> mit dem Gerät <i>%2$s</i>]]></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_fast_forward">Vorspulzeit</string>
@@ -349,6 +354,12 @@
<string name="pref_expandNotify_sum">Erweiterte Wiedergabebenachrichtigung mit Abspiel-, Pause- und Stop-Knöpfen anzeigen.</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_compact_notification_buttons_title">Lockscreen-Wiedergabetasten festlegen</string>
+ <string name="pref_compact_notification_buttons_sum">Verändere die Wiedergabetasten auf dem Lockscreen. Die Abspielen-/Pause-Taste wird immer angezeigt.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Wähle bis zu %1$d Elemente aus</string>
+ <string name="pref_compact_notification_buttons_dialog_error">Du kannst maximal %1$d Elemente auswählen.</string>
+ <string name="pref_show_subscriptions_in_drawer_title">Zeige Abonnements</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">Liste von Abonnements in Seitenleiste anzeigen</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>
@@ -366,6 +377,13 @@
<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>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Richte einen Netzwerk-Proxy ein</string>
+ <string name="pref_faq">Häufig gestellte Fragen</string>
+ <string name="pref_known_issues">Bekannte Probleme</string>
+ <string name="pref_no_browser_found">Kein Browser gefunden.</string>
+ <string name="pref_cast_title">Chromecast-Unterstützung</string>
+ <string name="pref_cast_message">Aktiviere Wiedergabe auf Chromecast-Geräten (unter anderem Lautsprecher, Android TV)</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>
@@ -388,8 +406,8 @@
<string name="opml_import_label">OPML Import</string>
<string name="opml_directory_error">FEHLER!</string>
<string name="reading_opml_label">Lese OPML Datei</string>
- <string name="opml_reader_error">Ein Fehler is beim Lesen des OPML Dokuments aufgetreten:</string>
- <string name="opml_import_error_dir_empty">Der Import-Ordner ist leer.</string>
+ <string name="opml_reader_error">Beim Einlesen der OPML-Datei ist ein Fehler aufgetreten:</string>
+ <string name="opml_import_error_no_file">Keine Datei ausgewählt!</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>
@@ -445,6 +463,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Vorhandenes Gerät auswählen</string>
<string name="gpodnetauth_device_errorEmpty">Geräte-ID darf nicht leer sein</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Geräte-ID wird bereits verwendet</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">Beschreibung darf nicht leer sein</string>
<string name="gpodnetauth_device_butChoose">Auswählen</string>
<string name="gpodnetauth_finish_title">Anmeldung erfolgreich!</string>
<string name="gpodnetauth_finish_descr">Glückwunsch! Dein gpodder.net Profil ist jetzt mit deinem Gerät verbunden. Von nun an wird AntennaPod automatisch deine Abonnements mit deinem gpodder.net Profil synchronisieren.</string>
@@ -481,22 +500,13 @@
<string name="subscribed_label">Abonniert</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>
- <string name="show_cover_label">Bild anzeigen</string>
<string name="rewind_label">Zurückspulen</string>
<string name="fast_forward_label">Vorspulen</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Nach oben navigieren</string>
- <string name="butAction_label">Mehr Aktionen</string>
- <string name="status_playing_label">Episode wird gerade abgespielt</string>
<string name="status_downloading_label">Episode wird gerade heruntergeladen</string>
- <string name="status_downloaded_label">Episode ist heruntergeladen</string>
- <string name="status_unread_label">Eintrag ist neu</string>
<string name="in_queue_label">Episode befindet sich in der Abspielliste</string>
- <string name="new_episodes_count_label">Anzahl neuer Episoden</string>
- <string name="in_progress_episodes_count_label">Anzahl der Episoden, die du angefangen hast zu hören</string>
<string name="drag_handle_content_description">Ziehe, um die Position dieses Objekts zu verändern</string>
<string name="load_next_page_label">Nächste Seite laden</string>
<!--Feed information screen-->
@@ -514,8 +524,8 @@
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importiere Abonnements aus Single-Purpose Apps</string>
<string name="search_itunes_label">iTunes durchsuchen</string>
- <string name="select_label"><b>Wähle aus…</b></string>
<string name="filter">Filtern</string>
+ <!--Episodes apply actions-->
<string name="all_label">Alle</string>
<string name="selected_all_label">Alle Episoden ausgewählt</string>
<string name="none_label">Keine</string>
@@ -532,7 +542,7 @@
<string name="selected_queued_label">Episoden in Abspielliste ausgewählt</string>
<string name="not_queued_label">Nicht in Abspielliste</string>
<string name="selected_not_queued_label">Episoden nicht in Abspielliste ausgewählt</string>
- <string name="sort_title"><b>Sortiere nach…</b></string>
+ <!--Sort-->
<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>
@@ -554,4 +564,31 @@
<string name="audio_effects">Audioeffekte</string>
<string name="stereo_to_mono">Heruntermischen: Stereo zu Mono</string>
<string name="sonic_only">nur Sonic</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Typ</string>
+ <string name="host_label">Host</string>
+ <string name="port_label">Port</string>
+ <string name="optional_hint">(Optional)</string>
+ <string name="proxy_test_label">Testen</string>
+ <string name="proxy_checking">Prüfe…</string>
+ <string name="proxy_test_successful">Test erfolgreich</string>
+ <string name="proxy_test_failed">Test fehlgeschlagen</string>
+ <string name="proxy_host_empty_error">Host darf nicht leer sein</string>
+ <string name="proxy_host_invalid_error">Host ist keine gültige IP oder Domain</string>
+ <string name="proxy_port_invalid_error">Port ungültig</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">Abspielen auf...</string>
+ <string name="cast_disconnect_label">Chromecast-Sitzung trennen</string>
+ <string name="cast_not_castable">Ausgewähltes Medium ist nicht kompatibel mit Cast-Gerät</string>
+ <string name="cast_failed_to_play">Fehler beim Starten der Wiedergabe</string>
+ <string name="cast_failed_to_stop">Fehler beim Stoppen der Wiedergabe</string>
+ <string name="cast_failed_to_pause">Fehler beim Pausieren der Wiedergabe</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Fehler beim Einstellen der Lautstärke</string>
+ <string name="cast_failed_no_connection">Keine Verbindung zum Cast-Gerät</string>
+ <string name="cast_failed_no_connection_trans">Verbindung zum Cast-Device unterbrochen. Wenn möglich, wird die App die Verbindung wiederherstellen. Bitte warte ein paar Sekunden und versuche es erneut.</string>
+ <string name="cast_failed_perform_action">Aktion konnte nicht ausgeführt werden</string>
+ <string name="cast_failed_status_request">Synchronization mit Cast-Gerät fehlgeschlagen</string>
+ <string name="cast_failed_seek">Spulen zur neuen Position fehlgeschlagen</string>
+ <string name="cast_failed_media_error_skipping">Fehler bei Wiedergabe. Überspringe...</string>
</resources>
diff --git a/core/src/main/res/values-el/strings.xml b/core/src/main/res/values-el/strings.xml
new file mode 100644
index 000000000..56c6d98ec
--- /dev/null
+++ b/core/src/main/res/values-el/strings.xml
@@ -0,0 +1,343 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="feeds_label">Ροές</string>
+ <string name="episodes_label">Επεισόδια</string>
+ <string name="all_episodes_short_label">Όλα</string>
+ <string name="favorite_episodes_label">Αγαπημένα</string>
+ <string name="new_label">Νέα</string>
+ <string name="settings_label">Ρυθμίσεις</string>
+ <string name="downloads_label">Λήψεις</string>
+ <string name="downloads_running_label">Εκτέλεση</string>
+ <string name="downloads_completed_label">Ολοκληρώθηκε</string>
+ <string name="downloads_log_label">Είσοδος</string>
+ <string name="playback_history_label">Ιστορικό Αναπαραγωγής</string>
+ <string name="gpodnet_main_label">gpodder.net</string>
+ <string name="gpodnet_auth_label">gpodder.net Σύνδεση</string>
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <string name="drawer_open">Άνοιγμα μενού</string>
+ <string name="drawer_close">Κλείσιμο μενού</string>
+ <string name="drawer_feed_order_alphabetical">Ταξινόμηση αλφαβητικά</string>
+ <string name="drawer_feed_order_last_update">Ταξινόμηση κατά ημερομηνία δημοσίευσης</string>
+ <string name="drawer_feed_counter_new">Αριθμός νέων επεισοδίων</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>
+ <!--Playback history-->
+ <string name="clear_history_label">Εκκαθάριση Ιστορικού</string>
+ <!--Other-->
+ <string name="confirm_label">Επιβεβαίωση</string>
+ <string name="cancel_label">Ακύρωση</string>
+ <string name="author_label">Δημιουργος</string>
+ <string name="language_label">Γλωσσα</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="chapters_label">Κεφάλαια</string>
+ <string name="shownotes_label">Εμφάνιση Σημειώσεων</string>
+ <string name="description_label">Περιγραφή</string>
+ <string name="episodes_suffix">\u0020επεισοδια</string>
+ <string name="length_prefix">Μήκος:\u0020</string>
+ <string name="size_prefix">Μέγεθος:\u0020</string>
+ <string name="processing_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="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">URL της Ροής</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
+ <string name="txtvfeedurl_label">Προσθήκη Podcast με τη διεύθυνση URL</string>
+ <string name="podcastdirectories_descr">Μπορείτε να ψάξετε για νέα podcast από το όνομα, κατηγορία ή δημοτικότητα στον κατάλογο του iTunes.</string>
+ <string name="browse_gpoddernet_label">Περιήγηση στο gpodder.net</string>
+ <!--Actions on feeds-->
+ <string name="show_info_label">Εμφάνιση πληροφοριών</string>
+ <string name="remove_feed_label">Κατάργηση Podcast</string>
+ <string name="feed_delete_confirmation_msg">Παρακαλούμε επιβεβαιώστε ότι θέλετε να διαγράφθουν αυτες οι τροφοδοσιες και όλα τα επεισόδια του υλικού αυτού που έχετε κανει λήψη.</string>
+ <string name="hide_episodes_title">Απόκρυψη Επεισοδίων</string>
+ <string name="hide_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>
+ <string name="pause_label">Παύση</string>
+ <string name="stop_label">Στοπ</string>
+ <string name="stream_label">Ροή</string>
+ <string name="remove_label">Κατάργηση</string>
+ <string name="add_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="skip_episode_label">Μετάβαση επεισοδίων</string>
+ <string name="activate_auto_download">Ενεργοποίηση Αυτόματης Λήψης</string>
+ <string name="deactivate_auto_download">Απενεργοποίηση Αυτόματης Λήψης</string>
+ <string name="reset_position">Επαναφορά Θέσης Αναπαραγωγής</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">επιτυχής</string>
+ <string name="download_failed">απέτυχε</string>
+ <string name="download_pending">Λήψη σε εκκρεμότητα</string>
+ <string name="download_running">Λήψη εν λειτουργία</string>
+ <string name="download_error_device_not_found">Η Συσκευή Αποθήκευσης δεν υπάρχει</string>
+ <string name="download_error_file_error">Σφάλμα Αρχείου</string>
+ <string name="download_error_http_data_error">Σφάλμα HTTP Δεδομένων</string>
+ <string name="download_error_error_unknown">Άγνωστο σφάλμα</string>
+ <string name="download_error_parser_exception">Αναλυτής εξαίρεσης</string>
+ <string name="download_error_connection_error">Σφάλμα Σύνδεσης</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_report_content_title">Αναφορά Λήψεων</string>
+ <string name="download_error_malformed_url">Εσφαλμένη διεύθυνση URL</string>
+ <string name="download_error_io_error">IO Σφαλμα</string>
+ <string name="download_error_db_access">Σφάλμα Πρόσβασης Βάσης Δεδομένων</string>
+ <string name="downloads_processing">Λήψη Επεξεργασίας</string>
+ <string name="download_notification_title">Λήψη podcast δεδομένων </string>
+ <string name="download_report_content">%1$d λήψη πέτυχε, %2$d απέτυχε</string>
+ <string name="download_log_title_unknown">Άγνωστος Τίτλος</string>
+ <string name="download_type_feed">Τροφοδοσία</string>
+ <string name="download_type_media">Αρχείο πολυμέσων</string>
+ <string name="download_type_image">Εικόνα</string>
+ <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_only_add_to_queue">Τοποθέτηση στην ουρά αναμονής</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Σφάλμα!</string>
+ <string name="player_stopped_msg">Δεν απαραγωνται πολυμεσα</string>
+ <string name="player_preparing_msg">Προετοιμασία</string>
+ <string name="player_ready_msg">Έτοιμοι</string>
+ <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="player_buffering_msg">Προσωρινή μνήμη</string>
+ <string name="playbackservice_notification_title">Αναπαραγωγή του podcast</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 στον browser σας και να σας ζητηθεί να δώσει AntennaPod την άδεια να Flattr πράγματα. Αφού έχετε δώσει την άδεια, θα επιστρέψετε σε αυτήν την οθόνη αυτόματα.</string>
+ <string name="authenticate_label">Έλεγχος</string>
+ <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 πράγματα μέσα από την εφαρμογή ή μπορείτε να επισκεφθείτε την ιστοσελίδα του πράγματος για 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 ιστοσελίδα.</string>
+ <!--Flattr-->
+ <string name="flattr_click_success">Flattr\'ed ένα πράγμα!</string>
+ <string name="flattr_click_success_count">Flattr\'ed%d πράγματα!</string>
+ <string name="flattr_click_success_queue">Flattr\'ed: %s.</string>
+ <string name="flattr_click_failure_count">Αποτυχία Flattr%d πράγματα!</string>
+ <string name="flattr_click_failure">Οχι flattr\'ed: %s.</string>
+ <string name="flattr_click_enqueued">Πράγμα θα flattr\'ed αργότερα</string>
+ <string name="flattring_thing">Flattring %s</string>
+ <string name="flattring_label">Το AntennaPod είναι flattring</string>
+ <string name="flattrd_label">Το AntennaPod έχει flattr\'ed</string>
+ <string name="flattrd_failed_label">AntennaPod Flattr απέτυχε</string>
+ <string name="flattr_retrieving_status">Ανάκτηση flattr\'ed πραγμάτων</string>
+ <!--Variable Speed-->
+ <string name="download_plugin_label">Λήψη Πρόσθετου</string>
+ <string name="no_playback_plugin_title">Το Πρόσθετο δεν είναι εγκατεστημένο</string>
+ <string name="set_playback_speed_label">Ταχύτητες αναπαραγωγής</string>
+ <!--Empty list labels-->
+ <string name="no_items_label">Δεν υπάρχουν στοιχεία σε αυτή τη λίστα.</string>
+ <string name="no_feeds_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_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_downloadMediaOnWifiOnly_sum">Λήψη αρχείων πολυμέσων μόνο μέσω WiFi</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">WiFi λυψη πολυμεσων</string>
+ <string name="pref_mobileUpdate_sum">Να επιτρέπονται ενημερώσεις μέσω της σύνδεσης δεδομένων κινητής τηλεφωνίας</string>
+ <string name="refreshing_label">Ανανεωση</string>
+ <string name="flattr_settings_label">Flattr Ρυθμισεις</string>
+ <string name="pref_flattr_auth_title">Flattr Συνδεση</string>
+ <string name="pref_flattr_auth_sum">Συνδεθείτε στον Flattr λογαριασμό σας για να Flattr πράγματα απευθείας από την εφαρμογή.</string>
+ <string name="pref_flattr_this_app_sum">Υποστήριξη της ανάπτυξης του AntennaPod flattring αυτό. Ευχαριστούμε!</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_sum">Αλλαγή της εμφάνισης του AntennaPod.</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_theme_title_light">Φως</string>
+ <string name="pref_theme_title_dark">Σκοτάδι</string>
+ <string name="pref_episode_cache_unlimited">Απεριόριστα</string>
+ <string name="pref_update_interval_hours_plural">ώρες</string>
+ <string name="pref_update_interval_hours_singular">ώρα</string>
+ <string name="pref_update_interval_hours_manual">Εγχειρίδιο</string>
+ <string name="pref_gpodnet_authenticate_title">Σύνδεση</string>
+ <string name="pref_gpodnet_authenticate_sum">Σύνδεση με τον λογαριασμό σας στο gpodder.net σας για να συγχρονίσετε τις συνδρομές σας.</string>
+ <string name="pref_gpodnet_logout_title">Αποσύνδεση</string>
+ <string name="pref_gpodnet_logout_toast">Η Αποσύνδεση ήταν επιτυχής</string>
+ <string name="pref_gpodnet_setlogin_information_title">Αλλαγή στοιχείων σύνδεσης</string>
+ <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_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_sum">Διατηρηση των ελέγχων κοινοποίησης και lockscreen όταν γίνεται παύση της αναπαραγωγής.</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="experimental_pref">Πειραματικό</string>
+ <!--Auto-Flattr dialog-->
+ <string name="auto_flattr_enable">Ενεργοποίηση αυτόματου flattring</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>
+ <!--Search-->
+ <string name="search_hint">Αναζήτηση για Τροφοδοσίες ή Επεισόδια</string>
+ <string name="found_in_shownotes_label">Βρέθηκε στις σημειώσεις</string>
+ <string name="found_in_chapters_label">Βρέθηκε σε κεφάλαια</string>
+ <string name="search_status_no_results">Δεν βρέθηκαν αποτελέσματα</string>
+ <string name="search_label">Αναζήτηση</string>
+ <string name="found_in_title_label">Βρέθηκε στον τίτλο</string>
+ <!--OPML import and export-->
+ <string name="opml_import_txtv_button_lable">Τα OPML αρχεία σας επιτρέπουν να μετακινήσετε τα podcast από το ένα στο άλλο podcatcher.</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">Πολλές από τις εφαρμογές όπως το Google Mail, Dropbox, το Google Drive και οι περισσότεροι διαχειριστές αρχείων μπορούν να &lt;i&gt; ανοικτό &lt;/ i&gt; Τα αρχεία OPML &lt;i&gt; με &lt;/ i&gt; AntennaPod.</string>
+ <string name="start_import_label">Εναρξη εισαγωγής</string>
+ <string name="opml_import_label">Εισαγωγή OPML</string>
+ <string name="opml_directory_error">ΣΦΑΛΜΑ!</string>
+ <string name="reading_opml_label">Ανάγνωση αρχείου OPML</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="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>
+ <string name="disable_sleeptimer_label">Απενεργοποίηση χρονοδιακόπτη ύπνου</string>
+ <string name="enter_time_here_label">Εισαγωγη χρονου</string>
+ <string name="sleep_timer_label">Χρονοδιακόπτης ύπνου</string>
+ <string name="time_left_label">Χρόνος που απομένει:\u0020</string>
+ <string name="time_dialog_invalid_input">Μη έγκυρη εισαγωγή, ο χρόνος πρέπει να είναι ακέραιος</string>
+ <string name="time_seconds">δευτερόλεπτα</string>
+ <string name="time_minutes">λεπτά</string>
+ <string name="time_hours">ώρες</string>
+ <!--gpodder.net-->
+ <string name="gpodnet_taglist_header">ΚΑΤΗΓΟΡΙΕΣ</string>
+ <string name="gpodnet_toplist_header">ΚΟΡΥΦΑΙΑ PODCAST</string>
+ <string name="gpodnet_suggestions_header">ΠΡΟΤΑΣΕΙΣ</string>
+ <string name="gpodnet_search_hint">Αναζήτηση στο gpodder.net</string>
+ <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>
+ <string name="gpodnetauth_device_descr">Δημιουργήστε μια νέα συσκευή που θα χρησιμοποιηθεί για λογαριασμό του gpodder.net ή επιλέξτε μια ήδη υπάρχουσα:</string>
+ <string name="gpodnetauth_device_deviceID">Αναγνωριστικό συσκευής:\u0020</string>
+ <string name="gpodnetauth_device_caption">Επικεφαλίδα</string>
+ <string name="gpodnetauth_device_butCreateNewDevice">Δημιουργία νέας συσκευής</string>
+ <string name="gpodnetauth_device_chooseExistingDevice">Επιλογή υπάρχουσας συσκευής:</string>
+ <string name="gpodnetauth_device_errorEmpty">Το Αναγνωριστικό συσκευής δεν πρέπει να είναι κενό</string>
+ <string name="gpodnetauth_device_errorAlreadyUsed">Το Αναγνωριστικό συσκευής ειναι ήδη σε χρήση</string>
+ <string name="gpodnetauth_device_butChoose">Επιλογή</string>
+ <string name="gpodnetauth_finish_title">Είσοδος επιτυχής!</string>
+ <string name="gpodnetauth_finish_descr">Συγχαρητήρια! Ο Λογαριασμός σας στο gpodder.net συνδέεται πλέον με τη συσκευή σας. Το AntennaPod θα συγχρονίζει αυτόματα τις συνδρομές στην συσκευή σας με το λογαριασμό σας στο gpodder.net.</string>
+ <string name="gpodnetauth_finish_butsyncnow">Έναρξη συγχρονισμού τώρα</string>
+ <string name="gpodnetauth_finish_butgomainscreen">Μετάβαση στην κύρια οθόνη</string>
+ <string name="gpodnetsync_auth_error_title">gpodder.net σφάλμα ελέγχου ταυτότητας</string>
+ <string name="gpodnetsync_auth_error_descr">Λάθος όνομα χρήστη ή κωδικός πρόσβασης</string>
+ <string name="gpodnetsync_error_title">gpodder.net σφάλμα συγχρονισμού</string>
+ <string name="gpodnetsync_error_descr">Παρουσιάστηκε σφάλμα κατά το συγχρονισμό:\u0020</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Επιλεγμένος φάκελος:</string>
+ <string name="create_folder_label">Δημιουργία φακέλου</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_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>
+ <!--Content descriptions for image buttons-->
+ <string name="rewind_label">Κίνηση εμπρός</string>
+ <string name="fast_forward_label">Γρήγορη κίνηση εμπρός</string>
+ <string name="media_type_audio_label">Ήχος</string>
+ <string name="media_type_video_label">Βίντεο</string>
+ <string name="navigate_upwards_label">Πλοήγηση προς τα πάνω</string>
+ <string name="status_downloading_label">Το επεισόδιο γίνεται λήφθετε</string>
+ <string name="in_queue_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">Αλλαγή του όνοματος χρήστη και του κωδικόυ πρόσβασής για αυτό το podcast και τα επεισόδια του.</string>
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <string name="sp_apps_importing_feeds_msg">Εισαγωγή εγγραφών από εφαρμογές και μονο...</string>
+ <string name="search_itunes_label">iTunes Αναζήτηση</string>
+ <!--Episodes apply actions-->
+ <string name="all_label">Όλα</string>
+ <string name="downloaded_label">Ειλημμένα</string>
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</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 7c851db62..cd6eb01cf 100644
--- a/core/src/main/res/values-es-rES/strings.xml
+++ b/core/src/main/res/values-es-rES/strings.xml
@@ -23,6 +23,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Publicado recientemente</string>
<string name="episode_filter_label">Mostrar solo episodios nuevos</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Abrir menu</string>
<string name="drawer_close">Cerrar menu</string>
@@ -203,8 +204,6 @@
<string name="opml_import_label">Importación de OPML</string>
<string name="opml_directory_error">¡ERROR!</string>
<string name="reading_opml_label">Leyendo el archivo OPML</string>
- <string name="opml_reader_error">Ha ocurrido un error al leer el archivo OPML</string>
- <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="opml_export_label">Exportar a OPML</string>
@@ -240,4 +239,5 @@
<string name="none_label">Ningun</string>
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-es/strings.xml b/core/src/main/res/values-es/strings.xml
index bee1d5903..0eda43051 100644
--- a/core/src/main/res/values-es/strings.xml
+++ b/core/src/main/res/values-es/strings.xml
@@ -1,23 +1,21 @@
<?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">Canales</string>
+ <string name="statistics_label">Estadísticas</string>
<string name="add_feed_label">Añadir podcast</string>
- <string name="podcasts_label">PODCASTS</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>
<string name="add_new_feed_label">Añadir podcast</string>
<string name="downloads_label">Descargas</string>
<string name="downloads_running_label">En curso</string>
<string name="downloads_completed_label">Completadas</string>
<string name="downloads_log_label">Registro</string>
+ <string name="subscriptions_label">Subscripciones</string>
+ <string name="subscriptions_list_label">Lista de subscripciones</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>
@@ -25,9 +23,9 @@
<string name="free_space_label">%1$s libre</string>
<string name="episode_cache_full_title">Caché de episodios completa</string>
<string name="episode_cache_full_message">Se ha alcanzado el límite de caché de episodios. Puedes aumentar el tamaño de la caché en las Opciones.</string>
- <!--New episodes fragment-->
- <string name="recently_published_episodes_label">Publicados recientemente</string>
- <string name="episode_filter_label">Mostrar solo episodios nuevos</string>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Tiempo total reproducido:</string>
+ <string name="statistics_details_dialog">Empezados %1$d episodios de %2$d.\n\nReproducidos %3$s de %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Abrir menú</string>
<string name="drawer_close">Cerrar menú</string>
@@ -76,9 +74,9 @@
<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="auto_delete_label">Borrar Episodio Automáticamente</string>
<string name="parallel_downloads_suffix">\u0020descargas paralelas</string>
- <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_global">Global por defecto</string>
<string name="feed_auto_download_always">Siempre</string>
<string name="feed_auto_download_never">Nunca</string>
<string name="send_label">Enviar…</string>
@@ -108,7 +106,7 @@
<string name="share_link_label">Compartir el enlace de la web</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_label">Compartir URL del archivo 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>
@@ -124,6 +122,7 @@
<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>
+ <string name="open_podcast">Abrir Podcast</string>
<!--actions on feeditems-->
<string name="download_label">Descargar</string>
<string name="play_label">Reproducir</string>
@@ -144,8 +143,6 @@
<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>
@@ -166,6 +163,8 @@
<string name="download_error_connection_error">Error de conexión</string>
<string name="download_error_unknown_host">Host desconocido</string>
<string name="download_error_unauthorized">Error de autenticación</string>
+ <string name="download_error_file_type_type">Tipo de archivo erróneo</string>
+ <string name="download_error_forbidden">Prohibido</string>
<string name="cancel_all_downloads_label">Cancelar todas las descargas</string>
<string name="download_canceled_msg">Descarga cancelada</string>
<string name="download_canceled_autodownload_enabled_msg">Descarga cancelada\nSe desactivó <i>Descarga automática</i> en este elemento</string>
@@ -203,7 +202,6 @@
<string name="playback_error_server_died">El servidor está inactivo</string>
<string name="playback_error_unknown">Error desconocido</string>
<string name="no_media_playing_label">No hay medios en reproducción</string>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Almacenando</string>
<string name="playbackservice_notification_title">Reproduciendo el podcast</string>
<string name="unknown_media_key">AntennaPod - Tecla multimedia desconocida: %1$d</string>
@@ -260,7 +258,10 @@
<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>
+ <string name="no_shownotes_label">Este episodio no tiene anotaciones.</string>
<!--Preferences-->
+ <string name="storage_pref">Almacenamiento</string>
+ <string name="project_pref">Proyecto</string>
<string name="other_pref">Otros</string>
<string name="about_pref">Acerca de</string>
<string name="queue_label">Cola</string>
@@ -339,6 +340,10 @@
<string name="pref_gpodnet_logout_toast">Ha cerrado la sesión correctamente.</string>
<string name="pref_gpodnet_setlogin_information_title">Cambiar información de acceso</string>
<string name="pref_gpodnet_setlogin_information_sum">Modificar datos de inicio de sesión en gpodder.net.</string>
+ <string name="pref_gpodnet_sync_title">Sincronizar ahora</string>
+ <string name="pref_gpodnet_sync_sum">Sincronizar suscripciones y estados con gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Comenzó sincronización</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Autenticado como <i>%1$s</i> con dispositivo <i>%2$s</i>]]></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_fast_forward">Intervalo de avance</string>
@@ -349,6 +354,12 @@
<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_compact_notification_buttons_title">Configurar botones de la pantalla de bloqueo</string>
+ <string name="pref_compact_notification_buttons_sum">Cambiar los botones de la pantalla de bloqueo. El botón play/pausa siempre está incluido.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Seleccionar un máximo de %1$d elementos</string>
+ <string name="pref_compact_notification_buttons_dialog_error">Sólo puedes seleccionar un máximo de %1$d elementos.</string>
+ <string name="pref_show_subscriptions_in_drawer_title">Mostrar Subscripciones</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">Mostrar la lista de suscripción directamente en el cajón de navegación</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>
@@ -366,6 +377,13 @@
<string name="pref_sonic_title">Sonic media player</string>
<string name="pref_sonic_message">Usar el reproductor Sonic Media incorporado en lugar del reproductor multimedia de Android y Prestissimo</string>
<string name="pref_current_value">Valor actual: %1$s</string>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Configurar proxy de red</string>
+ <string name="pref_faq">Preguntas frecuentes</string>
+ <string name="pref_known_issues">Problemas conocidos</string>
+ <string name="pref_no_browser_found">No se ha encontrado navegador web.</string>
+ <string name="pref_cast_title">Soporte para Chromecast</string>
+ <string name="pref_cast_message">Habilitar soporte para reproducción remota en dispositivos Cast (como Chromecast, altavoces o Android TV)</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>
@@ -388,8 +406,8 @@
<string name="opml_import_label">Importación de OPML</string>
<string name="opml_directory_error">¡ERROR!</string>
<string name="reading_opml_label">Leyendo el archivo OPML</string>
- <string name="opml_reader_error">Ha ocurrido un error al leer el archivo OPML</string>
- <string name="opml_import_error_dir_empty">El directorio de importación está vacío.</string>
+ <string name="opml_reader_error">Error al leer el documento OPML</string>
+ <string name="opml_import_error_no_file">¡Debes seleccionar un archivo!</string>
<string name="select_all_label">Seleccionar todo</string>
<string name="deselect_all_label">Deseleccionar todo</string>
<string name="select_options_label">Seleccionar…</string>
@@ -445,6 +463,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Elegir dispositivo existente:</string>
<string name="gpodnetauth_device_errorEmpty">El ID de dispositivo no puede estar vacío</string>
<string name="gpodnetauth_device_errorAlreadyUsed">El ID de dispositivo ya está en uso</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">El texto no puede estar en blanco</string>
<string name="gpodnetauth_device_butChoose">Elegir</string>
<string name="gpodnetauth_finish_title">¡Inicio de sesión correcto!</string>
<string name="gpodnetauth_finish_descr">¡Enhorabuena! Su cuenta de gpodder.net está ahora asociada con su dispositivo. A partir de ahora AntennaPod sincronizará automáticamente las suscripciones de su dispositivo con su cuenta de gpodder.net.</string>
@@ -481,22 +500,13 @@
<string name="subscribed_label">Suscrito</string>
<string name="downloading_label">Descargando…</string>
<!--Content descriptions for image buttons-->
- <string name="show_chapters_label">Mostrar capítulos</string>
- <string name="show_shownotes_label">Mostrar notas del programa</string>
- <string name="show_cover_label">Mostrar imagen</string>
<string name="rewind_label">Rebobinar</string>
<string name="fast_forward_label">Avance rápido</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Vídeo</string>
<string name="navigate_upwards_label">Navegar hacia arriba</string>
- <string name="butAction_label">Más acciones</string>
- <string name="status_playing_label">El episodio se está reproduciendo</string>
<string name="status_downloading_label">El episodio se está descargando</string>
- <string name="status_downloaded_label">El episodio está descargado</string>
- <string name="status_unread_label">El elemento es nuevo</string>
<string name="in_queue_label">El episodio está en la cola</string>
- <string name="new_episodes_count_label">Cantidad de episodios nuevos</string>
- <string name="in_progress_episodes_count_label">Cantidad de episodios que ha comenzado a escuchar</string>
<string name="drag_handle_content_description">Arrastrar para cambiar la posición de este ítem</string>
<string name="load_next_page_label">Cargar la página siguiente</string>
<!--Feed information screen-->
@@ -514,8 +524,8 @@
<!--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>
+ <!--Episodes apply actions-->
<string name="all_label">Todo</string>
<string name="selected_all_label">Seleccionados todos los episodios</string>
<string name="none_label">Ninguno</string>
@@ -532,7 +542,7 @@
<string name="selected_queued_label">Seleccionados episodios en cola</string>
<string name="not_queued_label">No en cola</string>
<string name="selected_not_queued_label">Seleccionados episodios no en cola</string>
- <string name="sort_title"><b>Ordenar por…</b></string>
+ <!--Sort-->
<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>
@@ -554,4 +564,32 @@
<string name="audio_effects">Efectos de audio</string>
<string name="stereo_to_mono">Downmix: De estereo a mono</string>
<string name="sonic_only">Sólo Sonic</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Tipo</string>
+ <string name="host_label">Host</string>
+ <string name="port_label">Puerto</string>
+ <string name="optional_hint">(Opcional)</string>
+ <string name="proxy_test_label">Probar</string>
+ <string name="proxy_checking">Comprobando...</string>
+ <string name="proxy_test_successful">Test correcto</string>
+ <string name="proxy_test_failed">Test fallido</string>
+ <string name="proxy_host_empty_error">El host no puede estar en blanco</string>
+ <string name="proxy_host_invalid_error">El host no es una IP ni un host válido</string>
+ <string name="proxy_port_invalid_error">Puerto inválido</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">Reproducir en...</string>
+ <string name="cast_disconnect_label">Desconectar la sesión Cast</string>
+ <string name="cast_not_castable">El medio seleccionado no es compatible con el dispositivo Cast</string>
+ <string name="cast_failed_to_play">Fallo al iniciar la reproducción del medio</string>
+ <string name="cast_failed_to_stop">Fallo al detener la reproducción del medio</string>
+ <string name="cast_failed_to_pause">Fallo al pausar la reproducción del medio</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Fallo al ajustar el volumen</string>
+ <string name="cast_failed_no_connection">No hay conexión con el dispositivo Cast</string>
+ <string name="cast_failed_no_connection_trans">Se ha perdido la conexión con el dispositivo Cast. La aplicación está intentando reconectar. Por favor, espera unos segundos e intenta de nuevo.</string>
+ <string name="cast_failed_perform_action">Fallo en la acción</string>
+ <string name="cast_failed_status_request">Fallo al sincronizar con el dispositivo Cast</string>
+ <string name="cast_failed_seek">Fallo al cambiar de posición en el dispositivo Cast</string>
+ <string name="cast_failed_receiver_player_error">El reproductor ha encontrado un error grave</string>
+ <string name="cast_failed_media_error_skipping">Error reproduciendo medio. Saltando...</string>
</resources>
diff --git a/core/src/main/res/values-fi/strings.xml b/core/src/main/res/values-fi/strings.xml
new file mode 100644
index 000000000..28dfeb6e8
--- /dev/null
+++ b/core/src/main/res/values-fi/strings.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <!--actions on feeditems-->
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-fr/strings.xml b/core/src/main/res/values-fr/strings.xml
index 239946097..623476f8f 100644
--- a/core/src/main/res/values-fr/strings.xml
+++ b/core/src/main/res/values-fr/strings.xml
@@ -1,33 +1,31 @@
<?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">Flux</string>
+ <string name="statistics_label">Statistiques</string>
<string name="add_feed_label">Ajouter un podcast</string>
- <string name="podcasts_label">PODCASTS</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>
<string name="add_new_feed_label">Ajouter un podcast</string>
<string name="downloads_label">Téléchargements</string>
<string name="downloads_running_label">En cours</string>
<string name="downloads_completed_label">Terminé</string>
<string name="downloads_log_label">Journal d\'activités</string>
+ <string name="subscriptions_label">Abonnements</string>
+ <string name="subscriptions_list_label">Liste des abonnements</string>
<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="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>
+ <string name="episode_cache_full_message">Le nombre maximal d\'épisodes téléchargés a été atteint. Vous pouvez changer ce nombre dans les paramètres.</string>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Temps d\'écoute total</string>
+ <string name="statistics_details_dialog">%1$d épisodes sur %2$d commencés.\n\nLu %3$s sur %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Ouvrir le menu</string>
<string name="drawer_close">Fermer le menu</string>
@@ -76,9 +74,9 @@
<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="auto_delete_label">Suppression automatique de l\'épisode</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_global">Global défaut</string>
<string name="feed_auto_download_always">Toujours</string>
<string name="feed_auto_download_never">Jamais</string>
<string name="send_label">Envoyer...</string>
@@ -108,8 +106,8 @@
<string name="share_link_label">Partager un lien vers le site</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="share_item_url_label">Partager le lien de l\'épisode</string>
+ <string name="share_item_url_with_position_label">Partager le lien de l\'épisode avec la 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>
@@ -124,6 +122,7 @@
<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>
+ <string name="open_podcast">Ouvrir Podcast</string>
<!--actions on feeditems-->
<string name="download_label">Télécharger</string>
<string name="play_label">Lire</string>
@@ -139,13 +138,11 @@
<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="added_to_favorites">Ajouté 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>
@@ -166,6 +163,8 @@
<string name="download_error_connection_error">Erreur de connexion</string>
<string name="download_error_unknown_host">Hôte inconnu</string>
<string name="download_error_unauthorized">Erreur d\'authentification</string>
+ <string name="download_error_file_type_type">Erreur format de fichier</string>
+ <string name="download_error_forbidden">Interdit</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_canceled_autodownload_enabled_msg">Téléchargement annulé\n <i>Téléchargement Automatique</i> désactivé pour cet élément</string>
@@ -203,7 +202,6 @@
<string name="playback_error_server_died">Le serveur ne répond pas</string>
<string name="playback_error_unknown">Erreur inconnue</string>
<string name="no_media_playing_label">Aucune lecture</string>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Mise en mémoire</string>
<string name="playbackservice_notification_title">Lecture de podcast en cours</string>
<string name="unknown_media_key">AntennaPod - Touche média inconnue : %1$d</string>
@@ -253,14 +251,17 @@
<!--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_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="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\nVous pouvez également 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>
+ <string name="no_shownotes_label">Aucun descriptif pour cet épisode.</string>
<!--Preferences-->
+ <string name="storage_pref">Stockage</string>
+ <string name="project_pref">Projet</string>
<string name="other_pref">Autres</string>
<string name="about_pref">À propos</string>
<string name="queue_label">Liste</string>
@@ -339,6 +340,10 @@
<string name="pref_gpodnet_logout_toast">Vous êtes maintenant déconnecté</string>
<string name="pref_gpodnet_setlogin_information_title">Modifier les informations de connexion</string>
<string name="pref_gpodnet_setlogin_information_sum">Modifier les information de connexion pour votre compte gpodder.net</string>
+ <string name="pref_gpodnet_sync_title">Synchroniser maintenant</string>
+ <string name="pref_gpodnet_sync_sum">Synchroniser les abonnements et l\'état de lecture avec gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Synchronisation démarrée</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Connecté comme <i>%1$s</i> avec l\'appareil <i>%2$s</i>]]></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_fast_forward">Avance rapide</string>
@@ -349,6 +354,12 @@
<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_compact_notification_buttons_title">Définir les boutons de l\'écran de verrouillage</string>
+ <string name="pref_compact_notification_buttons_sum">Change les boutons de lecture sur l\'écran de verrouillage. Le bouton de lecture/pause est toujours affiché.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Choisir un maximum de %1$d éléments</string>
+ <string name="pref_compact_notification_buttons_dialog_error">Vous ne pouvez pas choisir plus de %1$d éléments.</string>
+ <string name="pref_show_subscriptions_in_drawer_title">Montrer Abonnements</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">Montrer la liste des abonnements directement dans le volet de navigation</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>
@@ -364,8 +375,15 @@
<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_sonic_message">Utiliser le lecteur multimédia interne Sonic au lieu du natif d\'Android ou de Prestissimo</string>
<string name="pref_current_value">Valeur actuelle : %1$s</string>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Paramétrer un réseau proxy</string>
+ <string name="pref_faq">FAQ</string>
+ <string name="pref_known_issues">Problèmes connus</string>
+ <string name="pref_no_browser_found">Aucun navigateur trouvé.</string>
+ <string name="pref_cast_title">Chromecast support</string>
+ <string name="pref_cast_message">Activer le support de lecture de médias à distance pour les appareils Cast (comme Chromecast, haut parleurs ou Android Tv) </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>
@@ -388,8 +406,8 @@
<string name="opml_import_label">Importation OPML</string>
<string name="opml_directory_error">ERREUR !</string>
<string name="reading_opml_label">Lecture du fichier OPML en cours</string>
- <string name="opml_reader_error">Une erreur s\'est produite à la lecture du document OPML :</string>
- <string name="opml_import_error_dir_empty">Le répertoire d\'importation est vide.</string>
+ <string name="opml_reader_error">Une erreur s\'est produite pendant la lecture du fichier OPML :</string>
+ <string name="opml_import_error_no_file">Aucun fichier sélectionné !</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>
@@ -400,7 +418,7 @@
<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>
- <string name="opml_import_ask_read_permission">L\'accès aux stockages externes est requis pour lire le fichier OPML</string>
+ <string name="opml_import_ask_read_permission">L\'accès au stockage externe est requis pour lire le fichier OPML</string>
<!--Sleep timer-->
<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>
@@ -445,6 +463,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Choisir un appareil existant :</string>
<string name="gpodnetauth_device_errorEmpty">L\'ID de l\'appareil ne peut pas être vide</string>
<string name="gpodnetauth_device_errorAlreadyUsed">L\'ID de cet appareil est déjà en cours d\'utilisation</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">Le nom ne peut pas être vide</string>
<string name="gpodnetauth_device_butChoose">Choisir</string>
<string name="gpodnetauth_finish_title">Connexion réussie !</string>
<string name="gpodnetauth_finish_descr">Félicitations ! Votre compte gpodder.net est maintenant lié à votre appareil. AntennaPod va désormais automatiquement synchroniser vos podcasts sur votre appareil avec votre compte gpodder.</string>
@@ -459,6 +478,7 @@
<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="choose_data_directory_permission_rationale">L\'accès au stockage externe est requis pour changer le répertoire des données</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>
@@ -480,22 +500,13 @@
<string name="subscribed_label">Abonné</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>
- <string name="show_cover_label">Afficher image</string>
<string name="rewind_label">Retour en arrière</string>
<string name="fast_forward_label">Avance rapide</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Vidéo</string>
<string name="navigate_upwards_label">Naviguer vers le haut</string>
- <string name="butAction_label">Plus d\'actions</string>
- <string name="status_playing_label">L\'épisode est en train d\'être joué</string>
<string name="status_downloading_label">L\'épisode est en train d\'être téléchargé</string>
- <string name="status_downloaded_label">L\'épisode a été téléchargé</string>
- <string name="status_unread_label">L\'élément est nouveau</string>
<string name="in_queue_label">L\'épisode est dans la liste</string>
- <string name="new_episodes_count_label">Nombre de nouveaux épisodes</string>
- <string name="in_progress_episodes_count_label">Nombre d\'épisodes que vous avez commencé à écouter</string>
<string name="drag_handle_content_description">Faire glisser pour changer la position de cet élément</string>
<string name="load_next_page_label">Charger la page suivante</string>
<!--Feed information screen-->
@@ -503,18 +514,18 @@
<string name="authentication_descr">Modifier votre identifiant et mot de passe pour ce podcast et tous ses épisodes</string>
<string name="auto_download_settings_label">Préférence de téléchargement automatique</string>
<string name="episode_filters_label">Filtre d\'épisode</string>
- <string name="episode_filters_description">Liste de mots décidant si un épisode est à inclure ou exclure des téléchargements automatiques</string>
+ <string name="episode_filters_description">Liste de mots permettant de décider si un épisode est à inclure ou exclure des téléchargements automatiques</string>
<string name="episode_filters_include">Inclure</string>
<string name="episode_filters_exclude">Exclure</string>
<string name="episode_filters_hint">Mots uniques \n\"Liste de mots\"</string>
- <string name="keep_updated">Mettre à jour</string>
+ <string name="keep_updated">Maintenir à jour</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>
+ <!--Episodes apply actions-->
<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>
@@ -531,7 +542,7 @@
<string name="selected_queued_label">Episodes présents dans la liste de lecture sélectionnés</string>
<string name="not_queued_label">Hors liste de lecture</string>
<string name="selected_not_queued_label">Episodes absents de la liste de lecture sélectionnés</string>
- <string name="sort_title"><b>Trier par...</b></string>
+ <!--Sort-->
<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>
@@ -553,4 +564,32 @@
<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>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Type</string>
+ <string name="host_label">Hôte</string>
+ <string name="port_label">Port</string>
+ <string name="optional_hint">(Facultatif)</string>
+ <string name="proxy_test_label">Tester</string>
+ <string name="proxy_checking">Vérification...</string>
+ <string name="proxy_test_successful">Test réussi</string>
+ <string name="proxy_test_failed">Échec</string>
+ <string name="proxy_host_empty_error">Hôte ne peut pas être vide</string>
+ <string name="proxy_host_invalid_error">L\'hôte n\'est pas une adresse IP ou un domaine valide</string>
+ <string name="proxy_port_invalid_error">Port non valide</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">Lire sur...</string>
+ <string name="cast_disconnect_label">Déconnecter la session cast</string>
+ <string name="cast_not_castable">Le média choisi n\'est pas compatible avec l\'appareil cast</string>
+ <string name="cast_failed_to_play">Échec de lecture du média</string>
+ <string name="cast_failed_to_stop">Échec de l\'arrêt de lecture du média</string>
+ <string name="cast_failed_to_pause">Échec de mise en pause du média</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Échec de réglage du volume</string>
+ <string name="cast_failed_no_connection">Aucune connexion à l\'appareil Cast existe</string>
+ <string name="cast_failed_no_connection_trans">La connexion à l\'appareil cast a été perdu. L\'application tente de rétablir la connexion. Veuillez patienter quelques secondes et réessayer.</string>
+ <string name="cast_failed_perform_action">Échec de l\'action</string>
+ <string name="cast_failed_status_request">Échec de la synchronisation avec l\'appareil cast</string>
+ <string name="cast_failed_seek">Échec de la recherche de la nouvelle position sur l\'appareil cast</string>
+ <string name="cast_failed_receiver_player_error">Le lecteur de réception à rencontrer une grave erreur</string>
+ <string name="cast_failed_media_error_skipping">Erreur de lecture du média. Passage au suivant...</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 87aa77caf..2d3bfb539 100644
--- a/core/src/main/res/values-hi-rIN/strings.xml
+++ b/core/src/main/res/values-hi-rIN/strings.xml
@@ -14,6 +14,7 @@
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net login</string>
<!--New episodes fragment-->
+ <!--Statistics fragment-->
<!--Main activity-->
<!--Webview actions-->
<string name="open_in_browser_label">ब्राउज़र में खोलें</string>
@@ -205,8 +206,6 @@
<string name="opml_import_label">OPML आयात</string>
<string name="opml_directory_error">त्रुटि!</string>
<string name="reading_opml_label">OPML फ़ाइल पढ़ना</string>
- <string name="opml_reader_error">OPML दस्तावेज़ पढ़ते समय एक त्रुटि हुई है:</string>
- <string name="opml_import_error_dir_empty">आयात निर्देशिका खाली है.</string>
<string name="select_all_label">सभी का चयन करें</string>
<string name="deselect_all_label">सभी का चयन रद्द करें</string>
<string name="opml_export_label">OPML निर्यात</string>
@@ -269,4 +268,5 @@
<!--AntennaPodSP-->
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-hu/strings.xml b/core/src/main/res/values-hu/strings.xml
new file mode 100644
index 000000000..28dfeb6e8
--- /dev/null
+++ b/core/src/main/res/values-hu/strings.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <!--actions on feeditems-->
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-id/strings.xml b/core/src/main/res/values-id/strings.xml
new file mode 100644
index 000000000..d91fd609b
--- /dev/null
+++ b/core/src/main/res/values-id/strings.xml
@@ -0,0 +1,133 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="all_episodes_short_label">Semua</string>
+ <string name="settings_label">Pengaturan</string>
+ <string name="downloads_label">Unduhan</string>
+ <string name="gpodnet_main_label">gpodder.net</string>
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <string name="drawer_open">Buka daftar</string>
+ <string name="drawer_close">Tutup daftar</string>
+ <!--Webview actions-->
+ <string name="copy_url_label">Salin URL</string>
+ <string name="share_url_label">Bagikan URL</string>
+ <!--Playback history-->
+ <!--Other-->
+ <string name="cancel_label">Batal</string>
+ <string name="yes">Ya</string>
+ <string name="no">Tidak</string>
+ <string name="language_label">Bahasa</string>
+ <string name="url_label">URL</string>
+ <string name="podcast_settings_label">Pengaturan</string>
+ <string name="cover_label">Gambar</string>
+ <string name="refresh_label">Segarkan</string>
+ <string name="description_label">Deskripsi</string>
+ <string name="save_username_password_label">Simpan nama pengguna dan kata sandi</string>
+ <string name="close_label">Tutup</string>
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <string name="share_label">Bagikan...</string>
+ <string name="share_link_label">Bagikan Tautan</string>
+ <string name="hide_unplayed_episodes_label">Tidak diputar</string>
+ <string name="hide_played_episodes_label">Diputar</string>
+ <string name="hide_queued_episodes_label">Diantri</string>
+ <string name="hide_not_queued_episodes_label">Tidak diantri</string>
+ <string name="hide_downloaded_episodes_label">Diunduh</string>
+ <string name="hide_not_downloaded_episodes_label">Tidak diunduh</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Unduh</string>
+ <string name="play_label">Putar</string>
+ <string name="stop_label">Henti</string>
+ <string name="remove_label">Hapus</string>
+ <string name="remove_episode_lable">Hapus Episode</string>
+ <string name="add_to_queue_label">Tambah ke Antrian</string>
+ <string name="added_to_queue_label">Ditambah ke Antrian</string>
+ <string name="remove_from_queue_label">Hapus dari Antrian</string>
+ <string name="add_to_favorite_label">Tambah ke Favorit</string>
+ <string name="added_to_favorites">Ditambah ke Favorit</string>
+ <string name="remove_from_favorite_label">Hapus dari Favorit</string>
+ <string name="removed_from_favorites">Dihapus dari Favorit</string>
+ <string name="support_label">Flattr ini</string>
+ <!--Download messages and labels-->
+ <string name="cancel_all_downloads_label">Batalkan semua unduhan</string>
+ <string name="download_canceled_msg">Unduhan dibatalkan</string>
+ <string name="download_type_image">Gambar</string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Tambah ke antrian</string>
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <string name="clear_queue_label">Hapus Antrian</string>
+ <string name="date">Tanggal</string>
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <string name="about_pref">Tentang</string>
+ <string name="queue_label">Antrian</string>
+ <string name="flattr_label">Flattr</string>
+ <string name="refreshing_label">Menyegarkan</string>
+ <string name="flattr_settings_label">Pengaturan Flattr</string>
+ <string name="pref_flattr_this_app_title">Flattr Aplikasi ini</string>
+ <string name="pref_update_interval_hours_plural">jam</string>
+ <string name="pref_update_interval_hours_singular">jam</string>
+ <string name="pref_gpodnet_authenticate_title">Masuk</string>
+ <string name="pref_gpodnet_logout_title">Keluar</string>
+ <string name="pref_gpodnet_sync_title">Sinkronkan sekarang</string>
+ <string name="pref_gpodnet_sync_started">Sinkronkan dimulai</string>
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <string name="search_label">Telusur</string>
+ <!--OPML import and export-->
+ <string name="select_all_label">Pilih semua</string>
+ <string name="select_options_label">Pilih...</string>
+ <!--Sleep timer-->
+ <string name="timer_vibration_label">Getar</string>
+ <string name="time_seconds">detik</string>
+ <string name="time_minutes">menit</string>
+ <string name="time_hours">jam</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="other">%d detik</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="other">%d menit</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="other">%d jam</item>
+ </plurals>
+ <!--gpodder.net-->
+ <string name="gpodnet_search_hint">Telusur gpodder.net</string>
+ <string name="gpodnetauth_login_title">Masuk</string>
+ <string name="gpodnetauth_login_butLabel">Masuk</string>
+ <string name="username_label">Nama pengguna</string>
+ <string name="password_label">Kata sandi</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Direktori dipilih:</string>
+ <string name="create_folder_label">Buat direktori</string>
+ <string name="choose_data_directory">Pilih Direktori Data</string>
+ <string name="create_folder_success">Direktori baru dibuat</string>
+ <!--Online feed view-->
+ <string name="downloading_label">Mengunduh...</string>
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <string name="search_itunes_label">Telusur iTunes</string>
+ <!--Episodes apply actions-->
+ <string name="all_label">Semua</string>
+ <string name="selected_all_label">Semua episode dipilih</string>
+ <string name="played_label">Diputar</string>
+ <string name="unplayed_label">Tidak diputar</string>
+ <string name="downloaded_label">Diunduh</string>
+ <string name="not_downloaded_label">Tidak diunduh</string>
+ <string name="queued_label">Diantri</string>
+ <string name="not_queued_label">Tidak diantri</string>
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <string name="left_short">Kiri</string>
+ <string name="right_short">Kanan</string>
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-it-rIT/strings.xml b/core/src/main/res/values-it-rIT/strings.xml
index d7aff71cf..213a6655a 100644
--- a/core/src/main/res/values-it-rIT/strings.xml
+++ b/core/src/main/res/values-it-rIT/strings.xml
@@ -25,6 +25,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Pubblicati di recente</string>
<string name="episode_filter_label">Mostra solo gli episodi nuovi</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Apri il menù</string>
<string name="drawer_close">Chiudi il menù</string>
@@ -90,6 +91,7 @@
<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_label">Condividi...</string>
<string name="share_link_label">Condividi il link al sito</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>
@@ -123,7 +125,9 @@
<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="added_to_favorites">Aggiunto ai Preferiti</string>
<string name="remove_from_favorite_label">Rimuovi dai preferiti</string>
+ <string name="removed_from_favorites">Rimosso 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>
@@ -185,6 +189,8 @@
<!--Queue operations-->
<string name="lock_queue">Blocca la coda</string>
<string name="unlock_queue">Sblocca la coda</string>
+ <string name="queue_locked">Coda bloccata</string>
+ <string name="queue_unlocked">Coda sbloccata</string>
<string name="clear_queue_label">Svuota la coda</string>
<string name="undo">Undo</string>
<string name="removed_from_queue">Oggetto rimosso</string>
@@ -226,9 +232,11 @@
<string name="download_plugin_label">Scarica plugin</string>
<string name="no_playback_plugin_title">Plugin non installato</string>
<string name="set_playback_speed_label">Velocità di riproduzione</string>
+ <string name="enable_sonic">Abilita Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Non ci sono oggetti in questo elenco.</string>
<string name="no_feeds_label">Non sei ancora abbonato a nessun feed.</string>
+ <string name="no_chapters_label">Questo episodio non ha capitoli.</string>
<!--Preferences-->
<string name="other_pref">Altro</string>
<string name="about_pref">Informazioni</string>
@@ -293,7 +301,12 @@
<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_queueAddToFront_title">Aggiungi in cima alla coda</string>
<string name="pref_smart_mark_as_played_disabled">Disabilitato</string>
+ <string name="pref_image_cache_size_title">Dimensione Cache delle Immagini</string>
+ <string name="send_email">Invia e-mail</string>
+ <string name="experimental_pref">Sperimentale</string>
+ <string name="pref_current_value">Valore corrente: %1$s</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>
@@ -312,8 +325,6 @@
<string name="opml_import_label">Importazione OPML</string>
<string name="opml_directory_error">ERRORE!</string>
<string name="reading_opml_label">Lettura OPML file in corso</string>
- <string name="opml_reader_error">Un errore è stato rilevato mentre era in corso la lettura del documento opml:</string>
- <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>
@@ -329,6 +340,7 @@
<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="timer_vibration_label">Vibra</string>
<string name="time_seconds">secondi</string>
<string name="time_minutes">minuti</string>
<string name="time_hours">ore</string>
@@ -412,6 +424,9 @@
<!--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>
+ <string name="episode_filters_include">Includi</string>
+ <string name="episode_filters_exclude">Escludi</string>
+ <string name="keep_updated">Mantieni Aggiornato</string>
<!--Progress information-->
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importazione di sottoscrizioni da applicazioni monouso in corso...</string>
@@ -422,6 +437,10 @@
<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="downloaded_label">Scaricati</string>
+ <string name="not_downloaded_label">Non scaricati</string>
+ <string name="queued_label">In coda</string>
+ <string name="not_queued_label">Non in coda</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>
@@ -430,4 +449,7 @@
<string name="sort_duration_long_short">Durata (Long \u2192 Short)</string>
<!--Rating dialog-->
<!--Audio controls-->
+ <string name="volume">Volume</string>
+ <string name="audio_effects">Effetti Audio</string>
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml
new file mode 100644
index 000000000..52f84a143
--- /dev/null
+++ b/core/src/main/res/values-it/strings.xml
@@ -0,0 +1,276 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="feeds_label">Feed</string>
+ <string name="add_feed_label">Aggiungi un podcast</string>
+ <string name="episodes_label">Episodi</string>
+ <string name="all_episodes_short_label">Tutto</string>
+ <string name="favorite_episodes_label">Preferiti</string>
+ <string name="new_label">Nuovo</string>
+ <string name="settings_label">Impostazioni</string>
+ <string name="add_new_feed_label">Aggiungi un podcast</string>
+ <string name="downloads_label">Download</string>
+ <string name="downloads_running_label">In corso</string>
+ <string name="downloads_completed_label">Completati</string>
+ <string name="downloads_log_label">Registro</string>
+ <string name="cancel_download_label">Annulla\nil download</string>
+ <string name="playback_history_label">Cronologia delle riproduzioni</string>
+ <string name="gpodnet_main_label">gpodder.net</string>
+ <string name="gpodnet_auth_label">Accesso a gpodder.net</string>
+ <!--Statistics fragment-->
+ <!--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_order_last_update">Ordina secondo la data di pubblicazione</string>
+ <string name="drawer_feed_counter_new">Numero di episodi nuovi</string>
+ <string name="drawer_feed_counter_unplayed">Numero di episodi non riprodotti</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">Apri nel browser</string>
+ <string name="copy_url_label">Copia l\'URL</string>
+ <string name="share_url_label">Condividi l\'URL</string>
+ <!--Playback history-->
+ <!--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>
+ <string name="error_msg_prefix">È avvenuto un errore:</string>
+ <string name="refresh_label">Ricarica</string>
+ <string name="chapters_label">Capitoli</string>
+ <string name="description_label">Descrizione</string>
+ <string name="episodes_suffix">\u0020episodi</string>
+ <string name="length_prefix">Lunghezza:\u0020</string>
+ <string name="size_prefix">Dimensione:\u0020</string>
+ <string name="processing_label">Processando</string>
+ <string name="save_username_password_label">Salva il nome utente e la 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 agli episodi precedenti</string>
+ <string name="parallel_downloads_suffix">\u0020download paralleli</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_after_listening">Dopo il completamento</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>
+ <string name="txtvfeedurl_label">Aggiungi un podcast inserendo un URL</string>
+ <string name="podcastdirectories_descr">Puoi cercare nuovi podcast per nome, categoria o popolarità su gpodder.net, oppure sull\'iTunes store.</string>
+ <string name="browse_gpoddernet_label">Esplora gpodder.net</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Segna tutti come riprodotti</string>
+ <string name="mark_all_read_confirmation_msg">Conferma che desideri segnare tutti gli episodi come riprodotti.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Conferma che desideri segnare tutti gli episodi in questo feed come riprodotti.</string>
+ <string name="show_info_label">Mostra delle informazioni</string>
+ <string name="feed_delete_confirmation_msg">Conferma che desideri cancellare questo feed e TUTTI gli episodi di questo feed che hai scaricato.</string>
+ <string name="hide_episodes_title">Nascondi gli episodi</string>
+ <string name="episode_actions">Applica le azioni</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">Riprodotto</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">Scaricato</string>
+ <string name="hide_not_downloaded_episodes_label">Non scaricato</string>
+ <string name="filtered_label">Filtrato</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Scarica</string>
+ <string name="play_label">Riproduci</string>
+ <string name="pause_label">Metti in pausa</string>
+ <string name="stop_label">Interrompi</string>
+ <string name="stream_label">Stream</string>
+ <string name="remove_label">Rimuovi</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="visit_website_label">Visita il sito web</string>
+ <string name="skip_episode_label">Salta l\'episodio</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">successo</string>
+ <string name="download_failed">fallito</string>
+ <string name="download_pending">Download in attesa</string>
+ <string name="download_running">Download in corso</string>
+ <string name="download_error_http_data_error">Errore dei dati HTTP</string>
+ <string name="download_error_error_unknown">Errore sconosciuto</string>
+ <string name="cancel_all_downloads_label">Annulla tutti i download</string>
+ <string name="download_canceled_msg">Download annullato</string>
+ <string name="download_canceled_autodownload_enabled_msg">Download annullato\nDisabilitato <i>Download Automatico</i> per questo elemento</string>
+ <string name="download_error_malformed_url">URL malformato</string>
+ <string name="download_error_io_error">Errore IO</string>
+ <string name="download_notification_title">Download dei dati del podcast in corso</string>
+ <string name="download_report_content">%1$d download hanno avuto successo, %2$d hanno fallito</string>
+ <string name="download_type_feed">Feed</string>
+ <string name="download_type_media">File multimediale</string>
+ <string name="download_type_image">Immagine</string>
+ <string name="download_request_error_dialog_message_prefix">Si è verificato un errore nello scaricare il 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>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Errore!</string>
+ <string name="player_stopped_msg">Nessun elemento multimediale in riproduzione</string>
+ <string name="player_preparing_msg">Preparazione in corso</string>
+ <string name="player_ready_msg">Pronto</string>
+ <string name="player_seeking_msg">Ricerca in corso</string>
+ <string name="playback_error_server_died">Server morto</string>
+ <string name="playback_error_unknown">Errore sconosciuto</string>
+ <string name="no_media_playing_label">Nessun elemento multimediale in riproduzione</string>
+ <string name="player_buffering_msg">Buffer in corso</string>
+ <string name="playbackservice_notification_title">Riproduzione del podcast in corso</string>
+ <!--Queue operations-->
+ <string name="undo">Annulla</string>
+ <string name="removed_from_queue">Oggetto rimosso</string>
+ <string name="move_to_top_label">Sposta in cima</string>
+ <string name="move_to_bottom_label">Sposta in fondo</string>
+ <string name="sort">Ordina</string>
+ <string name="alpha">In ordine alfabetico</string>
+ <string name="date">Data</string>
+ <string name="duration">Durata</string>
+ <string name="ascending">Ascendente</string>
+ <string name="descending">Discendente</string>
+ <!--Flattr-->
+ <string name="flattr_auth_label">Accesso a Flattr</string>
+ <string name="authenticate_label">Autentica</string>
+ <string name="return_home_label">Ritorna alla pagina iniziale</string>
+ <string name="no_flattr_token_notification_msg">Il tuo account flattr non sembra connesso ad AntennaPod. Clicca qui per accedere.</string>
+ <string name="authenticate_now_label">Autentica</string>
+ <string name="action_forbidden_title">Azione proibita</string>
+ <string name="access_revoked_title">Accesso revocato</string>
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <string name="download_plugin_label">Scarica plugin</string>
+ <string name="no_playback_plugin_title">Plugin non installato</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>
+ <string name="no_feeds_label">Non sei ancora iscritto a nessun feed.</string>
+ <!--Preferences-->
+ <string name="other_pref">Altro</string>
+ <string name="about_pref">Riguardo a</string>
+ <string name="queue_label">Coda</string>
+ <string name="services_label">Servizi</string>
+ <string name="flattr_label">Flattr</string>
+ <string name="pref_unpauseOnHeadsetReconnect_sum">Riprendi la riproduzione quando le cuffie vengono ricollegate</string>
+ <string name="pref_followQueue_sum">Salta all\'elemento successivo della lista al termine della riproduzione</string>
+ <string name="pref_auto_delete_sum">Elimina l\'episodio al termine della riproduzione</string>
+ <string name="pref_auto_delete_title">Eliminazione Automatica</string>
+ <string name="playback_pref">Riproduzione</string>
+ <string name="network_pref">Rete</string>
+ <string name="pref_mobileUpdate_sum">Permetti gli aggiornamenti su rete dati</string>
+ <string name="refreshing_label">Ricaricamento</string>
+ <string name="pref_flattr_auth_title">Accesso a Flattr</string>
+ <string name="pref_revokeAccess_title">Revoca l\'accesso</string>
+ <string name="user_interface_label">Interfaccia utente</string>
+ <string name="pref_set_theme_sum">Cambia l\'aspetto di AntennaPod</string>
+ <string name="pref_autodl_wifi_filter_title">Abilita il filtro Wi-Fi</string>
+ <string name="pref_automatic_download_on_battery_sum">Scarica automaticamente quando la batteria non è in caricamento</string>
+ <string name="pref_theme_title_light">Chiaro</string>
+ <string name="pref_theme_title_dark">Scuro</string>
+ <string name="pref_episode_cache_unlimited">Illimitato</string>
+ <string name="pref_update_interval_hours_plural">ore</string>
+ <string name="pref_update_interval_hours_singular">ora</string>
+ <string name="pref_update_interval_hours_manual">Manuale</string>
+ <string name="pref_gpodnet_authenticate_title">Accesso</string>
+ <string name="pref_gpodnet_logout_title">Esci</string>
+ <string name="pref_gpodnet_setlogin_information_title">Cambia le informazioni di accesso</string>
+ <string name="pref_playback_speed_title">Velocità di riproduzione</string>
+ <string name="pref_gpodnet_sethostname_title">Imposta il nome dell\'host</string>
+ <string name="pref_gpodnet_sethostname_use_default_host">Utilizza l\'host predefinito</string>
+ <string name="pref_expandNotify_title">Espandi le notifiche</string>
+ <string name="pref_queueAddToFront_sum">Aggiungi un nuovo episodio in testa alla coda.</string>
+ <string name="pref_smart_mark_as_played_disabled">Disabilitato</string>
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <string name="search_hint">Cerca dei feed o degli episodi</string>
+ <string name="found_in_chapters_label">Trovato nei capitoli</string>
+ <string name="search_status_no_results">Nessun risultato trovato</string>
+ <string name="search_label">Cerca</string>
+ <string name="found_in_title_label">Trovato nel titolo</string>
+ <!--OPML import and export-->
+ <string name="start_import_label">Avvia l\'importazione</string>
+ <string name="opml_directory_error">ERRORE!</string>
+ <string name="select_all_label">Seleziona tutto</string>
+ <string name="deselect_all_label">Deseleziona tutto</string>
+ <string name="choose_file_from_external_application">Usa un\'applicazione esterna</string>
+ <string name="opml_export_label">Esportazione OPML</string>
+ <string name="export_error_label">Errore di esportazione</string>
+ <!--Sleep timer-->
+ <string name="set_sleeptimer_label">Imposta timer per lo spegnimento</string>
+ <string name="disable_sleeptimer_label">Disabilta timer per lo spegnimento</string>
+ <string name="sleep_timer_label">Timer per lo spegnimento</string>
+ <string name="time_left_label">Tempo rimasto:\u0020</string>
+ <string name="time_dialog_invalid_input">Input non valido, il tempo deve essere un numero intero</string>
+ <!--gpodder.net-->
+ <string name="gpodnet_taglist_header">CATEGORIE</string>
+ <string name="gpodnet_toplist_header">PODCAST PIÙ POPOLARI</string>
+ <string name="gpodnet_suggestions_header">SUGGERIMENTI</string>
+ <string name="gpodnet_search_hint">Cerca su gpodder.net</string>
+ <string name="gpodnetauth_login_title">Accesso</string>
+ <string name="gpodnetauth_login_descr">Benvenuto nella procedura di accesso a gpodder.net. Per prima cosa, inserisci i tuoi dati di accesso:</string>
+ <string name="gpodnetauth_login_butLabel">Accesso</string>
+ <string name="gpodnetauth_login_register">Se non hai ancora un account , puoi crearne uno su:\nhttps://gpodder.net/register/</string>
+ <string name="username_label">Nome utente</string>
+ <string name="password_label">Password</string>
+ <string name="gpodnetauth_device_title">Selezione del dispositivo</string>
+ <string name="gpodnetauth_device_deviceID">ID del dispositivo:\u0020</string>
+ <string name="gpodnetauth_device_butCreateNewDevice">Crea un nuovo dispositivo</string>
+ <string name="gpodnetauth_device_chooseExistingDevice">Scegli un dispositivo esistente:</string>
+ <string name="gpodnetauth_device_errorEmpty">L\'ID del dispositivo non può essere vuoto</string>
+ <string name="gpodnetauth_device_errorAlreadyUsed">ID del dispositivo già in uso</string>
+ <string name="gpodnetauth_device_butChoose">Scegli</string>
+ <string name="gpodnetauth_finish_title">Accesso avvenuto con successo!</string>
+ <string name="gpodnetauth_finish_butsyncnow">Avvia ora la sincronizzazione</string>
+ <string name="gpodnetauth_finish_butgomainscreen">Vai alla schermata principale</string>
+ <string name="gpodnetsync_auth_error_title">errore di autenticazione di gpodder.net</string>
+ <string name="gpodnetsync_auth_error_descr">Nome utente o password errati</string>
+ <string name="gpodnetsync_error_title">errore di sincronizzazione di gpodder.net</string>
+ <string name="gpodnetsync_error_descr">È avvenuto un errore durante la sincronizzazione:\u0020</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Cartella selezionata:</string>
+ <string name="create_folder_label">Crea una cartella</string>
+ <string name="create_folder_msg">Creare una nuova cartella dal nome \"%1$s\"?</string>
+ <string name="create_folder_success">Creata una nuova cartella</string>
+ <string name="create_folder_error_no_write_access">Non è possibile scrivere in questa cartella</string>
+ <string name="create_folder_error_already_exists">La cartella esiste già</string>
+ <string name="create_folder_error">Non è stato possibile creare la cartella</string>
+ <string name="folder_not_empty_dialog_title">La cartella non è vuota</string>
+ <string name="set_to_default_folder">Scegli la cartella predefinita</string>
+ <!--Online feed view-->
+ <string name="subscribe_label">Abbonati</string>
+ <string name="subscribed_label">Abbonato</string>
+ <!--Content descriptions for image buttons-->
+ <string name="rewind_label">Riavvolgi</string>
+ <string name="fast_forward_label">Avanti veloce</string>
+ <string name="media_type_audio_label">Audio</string>
+ <string name="media_type_video_label">Video</string>
+ <string name="status_downloading_label">L\'episodio sta venendo scaricato</string>
+ <string name="in_queue_label">L\'episodio è in coda</string>
+ <string name="load_next_page_label">Carica la pagina successiva</string>
+ <!--Feed information screen-->
+ <string name="authentication_label">Autenticazione</string>
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <string name="search_itunes_label">Cerca su iTunes</string>
+ <!--Episodes apply actions-->
+ <string name="all_label">Tutto</string>
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-iw-rIL/strings.xml b/core/src/main/res/values-iw-rIL/strings.xml
index 6adc16fd6..a8d1ddfc5 100644
--- a/core/src/main/res/values-iw-rIL/strings.xml
+++ b/core/src/main/res/values-iw-rIL/strings.xml
@@ -24,6 +24,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">פורסם לאחרונה</string>
<string name="episode_filter_label">הצג רק פרקים חדשים</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">פתח תפריט</string>
<string name="drawer_close">סגור תפריט</string>
@@ -279,8 +280,6 @@
<string name="opml_import_label">יבוא OPML</string>
<string name="opml_directory_error">שגיאה!</string>
<string name="reading_opml_label">קורא קובץ OPML</string>
- <string name="opml_reader_error">אירעה שגיאה בזמן קריאת קובץ OPML:</string>
- <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>
@@ -369,4 +368,5 @@
<string name="search_itunes_label">חפש בiTunes</string>
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-ja/strings.xml b/core/src/main/res/values-ja/strings.xml
index 836af9fc0..a892a957d 100644
--- a/core/src/main/res/values-ja/strings.xml
+++ b/core/src/main/res/values-ja/strings.xml
@@ -1,23 +1,21 @@
<?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">フィード</string>
+ <string name="statistics_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>
<string name="add_new_feed_label">フィードを追加</string>
<string name="downloads_label">ダウンロード</string>
<string name="downloads_running_label">実行中</string>
<string name="downloads_completed_label">完了</string>
<string name="downloads_log_label">ログ</string>
+ <string name="subscriptions_label">購読</string>
+ <string name="subscriptions_list_label">購読リスト</string>
<string name="cancel_download_label">ダウンロードをキャンセル</string>
<string name="playback_history_label">再生履歴</string>
<string name="gpodnet_main_label">gpodder.net</string>
@@ -25,9 +23,9 @@
<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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">ポッドキャストを再生した合計時間:</string>
+ <string name="statistics_details_dialog">%2$d から %1$d のエピソードが開始しました。\n\n%4$s から%3$s を再生しました。</string>
<!--Main activity-->
<string name="drawer_open">メニューを開く</string>
<string name="drawer_close">メニューを閉じる</string>
@@ -76,9 +74,9 @@
<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="auto_delete_label">エピソードを自動削除</string>
<string name="parallel_downloads_suffix">\u0020パラレル ダウンロード</string>
- <string name="feed_auto_download_global">全般</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>
@@ -107,8 +105,8 @@
<string name="share_link_label">Webサイトのリンクを共有</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="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>
@@ -123,6 +121,7 @@
<string name="hide_not_downloaded_episodes_label">ダウンロードしていません</string>
<string name="filtered_label">フィルターしました</string>
<string name="refresh_failed_msg">{fa-exclamation-circle} 前回更新に失敗しました</string>
+ <string name="open_podcast">ポッドキャストを開く</string>
<!--actions on feeditems-->
<string name="download_label">ダウンロード</string>
<string name="play_label">再生</string>
@@ -143,8 +142,6 @@
<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>
@@ -165,6 +162,8 @@
<string name="download_error_connection_error">接続エラー</string>
<string name="download_error_unknown_host">ホスト不明</string>
<string name="download_error_unauthorized">認証エラー</string>
+ <string name="download_error_file_type_type">ファイルタイプ エラー</string>
+ <string name="download_error_forbidden">禁止</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>
@@ -201,7 +200,6 @@
<string name="playback_error_server_died">サーバーがダウンしています</string>
<string name="playback_error_unknown">不明なエラー</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>
@@ -258,7 +256,10 @@
<string name="no_items_label">このリストにはアイテムがありません。</string>
<string name="no_feeds_label">まだフィードを何も購読していません。</string>
<string name="no_chapters_label">このエピソードにチャプターはありません。</string>
+ <string name="no_shownotes_label">このエピソードにショーノートはありません。</string>
<!--Preferences-->
+ <string name="storage_pref">ストレージ</string>
+ <string name="project_pref">プロジェクト</string>
<string name="other_pref">その他</string>
<string name="about_pref">について</string>
<string name="queue_label">キュー</string>
@@ -337,6 +338,10 @@
<string name="pref_gpodnet_logout_toast">ログアウトしました</string>
<string name="pref_gpodnet_setlogin_information_title">ログイン情報を変更</string>
<string name="pref_gpodnet_setlogin_information_sum">gpodder.netアカウントのログイン情報を変更します。</string>
+ <string name="pref_gpodnet_sync_title">今すぐ同期</string>
+ <string name="pref_gpodnet_sync_sum">購読とエピソードの状態を gpodder.net で同期</string>
+ <string name="pref_gpodnet_sync_started">同期を開始しました</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[<i>%1$s</i> としてデバイス <i>%2$s</i> でログインしました]]></string>
<string name="pref_playback_speed_title">再生速度</string>
<string name="pref_playback_speed_sum">可変速度音声再生に使用可能な速度をカスタマイズします</string>
<string name="pref_fast_forward">早送り時間</string>
@@ -347,6 +352,12 @@
<string name="pref_expandNotify_sum">常に再生ボタンを表示するように通知を展開します。</string>
<string name="pref_persistNotify_title">永続再生コントロール</string>
<string name="pref_persistNotify_sum">再生が一時停止された時に、通知およびロック画面のコントロールを保持します。</string>
+ <string name="pref_compact_notification_buttons_title">ロック画面ボタンを設定</string>
+ <string name="pref_compact_notification_buttons_sum">ロック画面の再生ボタンを変更します。再生/一時停止ボタンを常に含みます。</string>
+ <string name="pref_compact_notification_buttons_dialog_title">最大 %1$d のアイテムを選択</string>
+ <string name="pref_compact_notification_buttons_dialog_error">最大 %1$d のアイテムのみを選択できます。</string>
+ <string name="pref_show_subscriptions_in_drawer_title">購読を表示</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">ナビゲーションドロワーに直接購読リストを表示します</string>
<string name="pref_lockscreen_background_title">ロック画面の背景を設定</string>
<string name="pref_lockscreen_background_sum">ロック画面の背景を、現在のエピソードの画像に設定します。副作用として、これはサードパーティのアプリケーションでも画像を表示します。</string>
<string name="pref_showDownloadReport_title">ダウンロード レポートを表示</string>
@@ -364,6 +375,13 @@
<string name="pref_sonic_title">Sonic メディアプレーヤー</string>
<string name="pref_sonic_message">Android 標準のメディアプレーヤーと Prestissimo の代わりに、内蔵のソニックメディアプレーヤーを使用します</string>
<string name="pref_current_value">現在の値: %1$s</string>
+ <string name="pref_proxy_title">プロキシ</string>
+ <string name="pref_proxy_sum">ネットワーク プロキシの設定</string>
+ <string name="pref_faq">FAQ</string>
+ <string name="pref_known_issues">既知の問題</string>
+ <string name="pref_no_browser_found">Webブラウザーが見つかりません。</string>
+ <string name="pref_cast_title">Chromecast サポート</string>
+ <string name="pref_cast_message">(Chromecast、オーディオスピーカー、Android TV など) キャストデバイス上でリモートメディア再生のサポートを有効にします</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">自動Flattrを有効にする</string>
<string name="auto_flattr_after_percent">%d %再生したらエピソードをFlattr </string>
@@ -386,8 +404,8 @@
<string name="opml_import_label">OPMLインポート</string>
<string name="opml_directory_error">エラー!</string>
<string name="reading_opml_label">OPMLファイルを読み込み中</string>
- <string name="opml_reader_error">OPMLドキュメントの読み込み中にエラーが発生しました。</string>
- <string name="opml_import_error_dir_empty">インポートディレクトリが空です。</string>
+ <string name="opml_reader_error">OPMLドキュメントの読み込み中にエラーが発生しました:</string>
+ <string name="opml_import_error_no_file">ファイルが選択されていません!</string>
<string name="select_all_label">すべてを選択</string>
<string name="deselect_all_label">選択解除</string>
<string name="select_options_label">選択…</string>
@@ -440,6 +458,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">既存の端末を選択:</string>
<string name="gpodnetauth_device_errorEmpty">端末IDは空にできません</string>
<string name="gpodnetauth_device_errorAlreadyUsed">端末IDは既に使用しています</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">キャプションは空にできません</string>
<string name="gpodnetauth_device_butChoose">選択</string>
<string name="gpodnetauth_finish_title">ログインされました!</string>
<string name="gpodnetauth_finish_descr">おめでとうございます! あなたのgpodder.netアカウントが今お使いの端末とリンクされました。 AntennaPodは今から自動的にgpodder.netアカウントを使用して端末の購読を同期します。</string>
@@ -476,22 +495,13 @@
<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>
- <string name="show_cover_label">映像を表示</string>
<string name="rewind_label">巻き戻し</string>
<string name="fast_forward_label">早送り</string>
<string name="media_type_audio_label">音声</string>
<string name="media_type_video_label">動画</string>
<string name="navigate_upwards_label">上に移動</string>
- <string name="butAction_label">その他の操作</string>
- <string name="status_playing_label">エピソードを再生中</string>
<string name="status_downloading_label">エピソードをダウンロード中</string>
- <string name="status_downloaded_label">エピソードをダウンロードしました</string>
- <string name="status_unread_label">新しいアイテムです</string>
<string name="in_queue_label">エピソードはキューの中にあります</string>
- <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-->
@@ -509,8 +519,8 @@
<!--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>
+ <!--Episodes apply actions-->
<string name="all_label">すべて</string>
<string name="selected_all_label">すべてのエピソードを選択しました</string>
<string name="none_label">なし</string>
@@ -527,7 +537,7 @@
<string name="selected_queued_label">キューに入ったエピソードを選択しました</string>
<string name="not_queued_label">キューに入っていません</string>
<string name="selected_not_queued_label">キューに入っていないエピソードを選択しました</string>
- <string name="sort_title"><b>並び替え…</b></string>
+ <!--Sort-->
<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>
@@ -549,4 +559,32 @@
<string name="audio_effects">オーディオエフェクト</string>
<string name="stereo_to_mono">ダウンミックス: ステレオからモノラル</string>
<string name="sonic_only">Sonic のみ</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">タイプ</string>
+ <string name="host_label">ホスト</string>
+ <string name="port_label">ポート</string>
+ <string name="optional_hint">(オプション)</string>
+ <string name="proxy_test_label">テスト</string>
+ <string name="proxy_checking">確認中…</string>
+ <string name="proxy_test_successful">テストに成功しました</string>
+ <string name="proxy_test_failed">テストに失敗しました</string>
+ <string name="proxy_host_empty_error">ホストは空にできません</string>
+ <string name="proxy_host_invalid_error">ホストが有効なIPアドレスやドメインではありません</string>
+ <string name="proxy_port_invalid_error">ポートが正しくありません</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">…で再生</string>
+ <string name="cast_disconnect_label">キャストセッションを切断</string>
+ <string name="cast_not_castable">選択したメディアはキャストデバイスと互換性がありません</string>
+ <string name="cast_failed_to_play">メディア再生の開始に失敗しました</string>
+ <string name="cast_failed_to_stop">メディア再生の停止に失敗しました</string>
+ <string name="cast_failed_to_pause">メディア再生の一時停止に失敗しました</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">音量の設定に失敗しました</string>
+ <string name="cast_failed_no_connection">キャストデバイスへの接続が存在しません</string>
+ <string name="cast_failed_no_connection_trans">キャストデバイスへの接続が失われました。可能な場合、アプリケーションは接続を再確立しようとします。数秒待ってから、もう一度お試しください。</string>
+ <string name="cast_failed_perform_action">操作の実行に失敗しました</string>
+ <string name="cast_failed_status_request">キャストデバイスとの同期に失敗しました</string>
+ <string name="cast_failed_seek">キャストデバイスの新しい位置への移動に失敗しました</string>
+ <string name="cast_failed_receiver_player_error">レシーバープレーヤーで深刻なエラーが発生しました</string>
+ <string name="cast_failed_media_error_skipping">メディアの再生時にエラーが発生しました。スキップしています…</string>
</resources>
diff --git a/core/src/main/res/values-kn-rIN/strings.xml b/core/src/main/res/values-kn-rIN/strings.xml
new file mode 100644
index 000000000..26de5a2fb
--- /dev/null
+++ b/core/src/main/res/values-kn-rIN/strings.xml
@@ -0,0 +1,106 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="feeds_label">ಫೀಡ್ಗಳು</string>
+ <string name="new_label">ಹೊಸ</string>
+ <string name="settings_label">ಸೆಟ್ಟಿಂಗ್ಗಳು</string>
+ <string name="downloads_label">ಇಳಿಕೆಗಳು</string>
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <string name="confirm_label">ದೃಢೀಕರಿಸಿ</string>
+ <string name="cancel_label">ರದ್ದುಗೊಳಿಸು</string>
+ <string name="author_label">ಲೇಖಕ</string>
+ <string name="language_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="chapters_label">ಅಧ್ಯಾಯಗಳು</string>
+ <string name="shownotes_label">ಟಿಪ್ಪಣಿಗಳು </string>
+ <string name="episodes_suffix">\u0020ಕಂತುಗಳು</string>
+ <string name="length_prefix">ಉದ್ದ:\u0020</string>
+ <string name="size_prefix">ಗಾತ್ರ:\u0020</string>
+ <string name="processing_label">ಸಂಸ್ಕರಣೆ</string>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">ಫೀಡ್ ಯು.ಆರ್.ಎಲ್</string>
+ <!--Actions on feeds-->
+ <string name="show_info_label">ಮಾಹಿತಿ ತೋರಿಸು</string>
+ <string name="feed_delete_confirmation_msg">ನೀವು ಈ ಫೀಡ್ ಮತ್ತು ನೀವು ಡೌನ್ಲೋಡ್ ಮಾಡಿದ ಈ ಫೀಡ್ನ ಎಲ್ಲಾ ಕಂತುಗಳು ಅಳಿಸಲು ಬಯಸುತ್ತೀರಿ ಎಂಬುದನ್ನು ದಯವಿಟ್ಟು ದೃಢಪಡಿಸಿ.</string>
+ <!--actions on feeditems-->
+ <string name="download_label">ಡೌನ್ಲೋಡ್</string>
+ <string name="play_label">ಚಾಲನೆಗೊಳಿಸು</string>
+ <string name="stream_label">ಹೊಳೆ</string>
+ <string name="remove_label">ತೆಗೆದುಹಾಕಿ</string>
+ <string name="add_to_queue_label">ಸರದಿಗೆ ಸೇರಿಸಿ</string>
+ <string name="remove_from_queue_label">ಸರದಿಯಿಂದ ತೆಗೆದುಹಾಕಿ</string>
+ <string name="visit_website_label">ಜಾಲತಾಣವನ್ನು ಭೇಟಿಮಾಡಿ</string>
+ <string name="support_label">ಫ್ಲಾಟರ್ ಮಾಡಿ</string>
+ <!--Download messages and labels-->
+ <string name="download_pending">ಡೌನ್ಲೋಡ್ ಬಾಕಿ ಉಳಿದಿದೆ</string>
+ <string name="download_running">ಚಾಲನೆಯಲ್ಲಿರುವ ಡೌನ್ಲೋಡ್</string>
+ <string name="download_error_http_data_error">ಎಚ್ಟಿಟಿಪಿ ಮಾಹಿತಿ ದೋಷ</string>
+ <string name="download_error_error_unknown">ತಿಳಿಯದ ದೋಷ</string>
+ <string name="download_error_parser_exception">ಪಾರ್ಸರ್ ಎಕ್ಸೆಪ್ಶನ್</string>
+ <string name="cancel_all_downloads_label">ಎಲ್ಲಾ ಡೌನ್ಲೋಡ್ಗಳನ್ನು ರದ್ದು ಮಾಡು</string>
+ <string name="download_error_malformed_url">ದೋಷಪೂರಿತ ಯು.ಆರ್.ಎಲ್</string>
+ <string name="download_error_io_error">ಐಓ ದೋಷ</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">ದೋಷ!</string>
+ <string name="player_stopped_msg">ಯಾವುದೇ ಮಾಧ್ಯಮ ಓಡುತ್ತಿಲ್ಲ</string>
+ <string name="player_preparing_msg">ಸಿದ್ಧತೆ</string>
+ <string name="player_ready_msg">ಸಿದ್ಧ</string>
+ <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="player_buffering_msg">ತುಣಿಗಲೆಯಾಗುತ್ತಿದೆ</string>
+ <!--Queue operations-->
+ <!--Flattr-->
+ <string name="flattr_auth_label">ಫ್ಳಟರ್ ಪ್ರವೇಶ</string>
+ <string name="flattr_auth_explanation">ದೃಢೀಕರಣ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಆರಂಭಿಸಲು ಕೆಳಗಿನ ಬಟನ್ ಒತ್ತಿರಿ. ನಿಮ್ಮ ಬ್ರೌಸರ್ನಲ್ಲಿ ನೀವು ಫ್ಳಟರ್ ಪ್ರವೇಶ ತೆರೆಗೆ ರವಾನಿಸಲ್ಪಡುತ್ತೀರಿ ಮತ್ತು ಆಂಟೆನಾ-ಪಾಡ್ ವಿಷಯಗಳನ್ನು ಫ್ಳಟರ್ ಮಾಡಲು ಅನುಮತಿ ನೀಡಲು ಕೇಳಲಾಗುತ್ತದೆ. ನೀವು ಅನುಮತಿ ನೀಡಿದ ನಂತರ, ನೀವು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಈ ಪರದೆಗೆ ಮರಳಬಹುದು.</string>
+ <string name="authenticate_label">ದೃಢೀಕರಿಸು</string>
+ <string name="return_home_label">ಮುಂಪುಟಕ್ಕೆ ಹಿಂತಿರುಗಿ</string>
+ <string name="flattr_auth_success">ದೃಢೀಕರಣ ಯಶಸ್ವಿಯಾಯಿತು! ನೀವು ಈಗ ಅಪ್ಲಿಕೇಶನ್ ಒಳಗೆ ವಿಷಯಗಳನ್ನು ಫ್ಳಟರ್ ಮಾಡಬಹುದು.</string>
+ <string name="no_flattr_token_title">ಫ್ಳಟರ್ ಟೋಕನ್ ಕಂಡುಬಂದಿಲ್ಲ</string>
+ <string name="authenticate_now_label">ದೃಢೀಕರಿಸು</string>
+ <string name="action_forbidden_title">ಕ್ರಿಯೆಯನ್ನು ನಿಷೇಧಿಸಲಾಗಿದೆ</string>
+ <string name="access_revoked_title">ಪ್ರವೇಶವನ್ನು ಹಿಂಪಡೆಯಲಾಗಿದೆ</string>
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <string name="no_items_label">ಈ ಪಟ್ಟಿಯಲ್ಲಿ ಯಾವುದೇ ಅಂಶಗಳು ಇಲ್ಲ.</string>
+ <string name="no_feeds_label">ನೀವು ಇನ್ನೂ ಯಾವುದೇ ಫೀಡ್ಗಳಿಗೆ ಚಂದಾದಾರರಾಗಿಲ್ಲ.</string>
+ <!--Preferences-->
+ <string name="other_pref">ಇತರೆ</string>
+ <string name="about_pref">ಕುರಿತು</string>
+ <string name="queue_label">ಸರದಿ</string>
+ <string name="playback_pref">ಹಿನ್ನೆಲೆ</string>
+ <string name="network_pref"> ಜಾಲಬಂಧ</string>
+ <string name="user_interface_label">ಬಳಕೆದಾರ ಸಂಪರ್ಕಸಾಧನ</string>
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <string name="search_hint">ಫೀಡ್ಸ್ ಅಥವಾ ಸಂಚಿಕೆಗಳಿಗಾಗಿ ಹುಡುಕಿ</string>
+ <string name="found_in_shownotes_label">ಪ್ರದರ್ಶನ ಟಿಪ್ಪಣಿಗಳಲ್ಲಿ ಕಂಡುಬರುತ್ತವೆ</string>
+ <string name="found_in_chapters_label">ಅಧ್ಯಾಯಗಳಲ್ಲಿ ಕಂಡುಬರುತ್ತವೆ</string>
+ <string name="search_status_no_results">ಯಾವುದೇ ಫಲಿತಾಂಶಗಳು ಕಂಡುಬಂದಿಲ್ಲ</string>
+ <string name="search_label">ಹುಡುಕು</string>
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-ko-rKR/strings.xml b/core/src/main/res/values-ko-rKR/strings.xml
new file mode 100644
index 000000000..28dfeb6e8
--- /dev/null
+++ b/core/src/main/res/values-ko-rKR/strings.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <!--actions on feeditems-->
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-ko/strings.xml b/core/src/main/res/values-ko/strings.xml
index d0b3aae33..561771a2f 100644
--- a/core/src/main/res/values-ko/strings.xml
+++ b/core/src/main/res/values-ko/strings.xml
@@ -1,17 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--Activitiy and fragment titles-->
- <string name="app_name">안테나팟</string>
<string name="feeds_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>
<string name="add_new_feed_label">팟캐스트 추가</string>
<string name="downloads_label">다운로드</string>
@@ -25,9 +20,7 @@
<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>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">메뉴 열기</string>
<string name="drawer_close">메뉴 닫기</string>
@@ -76,9 +69,7 @@
<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="parallel_downloads_suffix">개 동시 다운로드</string>
<string name="feed_auto_download_always">항상</string>
<string name="feed_auto_download_never">안 함</string>
<string name="send_label">보내기…</string>
@@ -107,8 +98,6 @@
<string name="share_link_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>
@@ -143,8 +132,6 @@
<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>
@@ -201,7 +188,6 @@
<string name="playback_error_server_died">서버가 죽었습니다</string>
<string name="playback_error_unknown">알 수 없는 오류</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">안테나팟 - 알 수 없는 미디어 키: %1$d</string>
@@ -266,18 +252,18 @@
<string name="flattr_label">Flattr</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_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_hardwareForwardButtonSkips_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="pref_skip_keeps_episodes_sum">에피소드를 넘겼을 경우에도 유지</string>
- <string name="pref_skip_keeps_episodes_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_autoUpdateIntervallOrTime_title">업데이트 주기 또는 하루 중 시각</string>
@@ -292,10 +278,10 @@
<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_unpauseOnHeadsetReconnect_title">헤드폰 다시 연결</string>
<string name="pref_unpauseOnBluetoothReconnect_title">블루투스 다시 연결</string>
<string name="pref_mobileUpdate_title">휴대전화망 업데이트</string>
- <string name="pref_mobileUpdate_sum">휴대전화 데이터 연결을 통해 업데이트 허용</string>
+ <string name="pref_mobileUpdate_sum">휴대전화 데이터 연결을 통해 업데이트를 허용합니다</string>
<string name="refreshing_label">새로 고치는 중</string>
<string name="flattr_settings_label">Flattr 설정</string>
<string name="pref_flattr_auth_title">Flattr 로그인</string>
@@ -344,9 +330,9 @@
<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_expandNotify_sum">항상 알림에서 재생 버튼이 표시되도록 확장합니다.</string>
<string name="pref_persistNotify_title">재생 조작 고정</string>
- <string name="pref_persistNotify_sum">재생이 일시 중지했을 때에도 알림과 잠금 화면의 조작 기능 유지</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>
@@ -358,8 +344,8 @@
<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="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>
@@ -386,8 +372,6 @@
<string name="opml_import_label">OPML 가져오기</string>
<string name="opml_directory_error">오류!</string>
<string name="reading_opml_label">OPML 파일을 읽는 중</string>
- <string name="opml_reader_error">OPML 문서를 읽는 중 오류가 발생했습니다:</string>
- <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>
@@ -398,6 +382,7 @@
<string name="export_error_label">내보내기 오류</string>
<string name="opml_export_success_title">OPML 내보내기가 성공했습니다.</string>
<string name="opml_export_success_sum">OPML 파일을 다음에 저장했습니다:\u0020</string>
+ <string name="opml_import_ask_read_permission">OPML 파일을 읽으려면 외부 저장소 접근이 필요합니다</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">취침 타이머 설정</string>
<string name="disable_sleeptimer_label">취침 타이머 사용 않음</string>
@@ -453,6 +438,7 @@
<string name="create_folder_label">폴더 만들기</string>
<string name="choose_data_directory">데이터 폴더 선택</string>
<string name="choose_data_directory_message">데이터 폴더를 선택하십시오. 안테나팟은 알아서 그 하위 디렉토리를 생성합니다.</string>
+ <string name="choose_data_directory_permission_rationale">데이터 폴더를 바꾸려면 외부 저장소 접근이 필요합니다</string>
<string name="create_folder_msg">이름이 \"%1$s\"인 폴더를 만드시겠습니까?</string>
<string name="create_folder_success">새 폴더를 만들었습니다</string>
<string name="create_folder_error_no_write_access">이 폴더에 쓸 수 없습니다</string>
@@ -466,30 +452,21 @@
<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_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>
<!--Content descriptions for image buttons-->
- <string name="show_chapters_label">챕터 보이기</string>
- <string name="show_shownotes_label">프로그램 메모 표시</string>
- <string name="show_cover_label">그림 보이기</string>
<string name="rewind_label">뒤로 감기</string>
<string name="fast_forward_label">앞으로 감기</string>
<string name="media_type_audio_label">오디오</string>
<string name="media_type_video_label">비디오</string>
<string name="navigate_upwards_label">위 단계로 이동</string>
- <string name="butAction_label">기타 동작</string>
- <string name="status_playing_label">에피소드를 재생하는 중입니다</string>
<string name="status_downloading_label">에피소드를 다운로드하는 중입니다</string>
- <string name="status_downloaded_label">에피소드를 다운로드했습니다</string>
- <string name="status_unread_label">새로운 항목입니다</string>
<string name="in_queue_label">에피소드가 대기열에 들어 있습니다</string>
- <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-->
@@ -500,15 +477,15 @@
<string name="episode_filters_description">자동 다운로드를 할 때 에피소드가 포함되어야할지 제외되어야할지 결정하는 규칙의 목록</string>
<string name="episode_filters_include">포함</string>
<string name="episode_filters_exclude">제외</string>
- <string name="episode_filters_hint">단수 단어 \n\"복수 단어\"</string>
+ <string name="episode_filters_hint">개별 단어 \n\"여러 단어\"</string>
<string name="keep_updated">최신 업데이트 유지</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>
+ <!--Episodes apply actions-->
<string name="all_label">모두</string>
<string name="selected_all_label">모든 에피소드 선택</string>
<string name="none_label">없음</string>
@@ -521,7 +498,11 @@
<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="queued_label">대기열에 있음</string>
+ <string name="selected_queued_label">선택한 대기열의 에피소드</string>
+ <string name="not_queued_label">대기열에 없음</string>
+ <string name="selected_not_queued_label">선택한 대기열 외의 에피소드</string>
+ <!--Sort-->
<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>
@@ -543,4 +524,7 @@
<string name="audio_effects">오디오 효과</string>
<string name="stereo_to_mono">다운믹스: 스테레오에서 모노로</string>
<string name="sonic_only">소닉 전용</string>
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
</resources>
diff --git a/core/src/main/res/values-nb/strings.xml b/core/src/main/res/values-nb/strings.xml
index e3aa1ca5d..537bd49d6 100644
--- a/core/src/main/res/values-nb/strings.xml
+++ b/core/src/main/res/values-nb/strings.xml
@@ -26,6 +26,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Nylig publisert</string>
<string name="episode_filter_label">Vis kun nye episoder</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Åpne menyen</string>
<string name="drawer_close">Lukk menyen</string>
@@ -369,7 +370,6 @@
<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>
@@ -494,6 +494,8 @@
<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="queued_label">I kø</string>
+ <string name="not_queued_label">Ikke i kø</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>
@@ -507,4 +509,5 @@
<string name="rating_later_label">Minn meg på dette senere</string>
<string name="rating_now_label">Naturligvis, kom igjen!</string>
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-nl/strings.xml b/core/src/main/res/values-nl/strings.xml
index 1973cd51b..d7c5d4a97 100644
--- a/core/src/main/res/values-nl/strings.xml
+++ b/core/src/main/res/values-nl/strings.xml
@@ -1,23 +1,21 @@
<?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">Feeds</string>
+ <string name="statistics_label">Statistieken</string>
<string name="add_feed_label">Podcast toevoegen</string>
- <string name="podcasts_label">PODCASTS</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="subscriptions_label">Feeds</string>
+ <string name="subscriptions_list_label">Lijst met feeds</string>
<string name="cancel_download_label">Annuleer download</string>
<string name="playback_history_label">Afspeelgeschiedenis</string>
<string name="gpodnet_main_label">gpodder.net</string>
@@ -25,9 +23,9 @@
<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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Totale duur van afgespeelde podcasts:</string>
+ <string name="statistics_details_dialog">%1$d van de %2$d afleveringen gestart.\n\n%3$s van de %4$s afgespeeld.</string>
<!--Main activity-->
<string name="drawer_open">Menu openen</string>
<string name="drawer_close">Menu sluiten</string>
@@ -35,7 +33,7 @@
<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_unplayed">Aantal nieuwe plus 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>
@@ -76,7 +74,7 @@
<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="auto_delete_label">Afleveringen automatisch\nverwijderen</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>
@@ -110,7 +108,7 @@
<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_delete_confirmation_msg">Bevestig dat u deze feed en ALLE afleveringen van deze feed die u heeft 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>
@@ -124,6 +122,7 @@
<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>
+ <string name="open_podcast">Open podcast</string>
<!--actions on feeditems-->
<string name="download_label">Download</string>
<string name="play_label">Spelen</string>
@@ -144,8 +143,6 @@
<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 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>
@@ -166,6 +163,8 @@
<string name="download_error_connection_error">Verbindingsfout</string>
<string name="download_error_unknown_host">Onbekende host</string>
<string name="download_error_unauthorized">Authenticatie fout</string>
+ <string name="download_error_file_type_type">Fout met bestandsformaat</string>
+ <string name="download_error_forbidden">Actie niet mogelijk</string>
<string name="cancel_all_downloads_label">Alle downloads annuleren</string>
<string name="download_canceled_msg">Download geannuleerd</string>
<string name="download_canceled_autodownload_enabled_msg">Downloaden geannuleerd\n<i>Automatisch downloaden</i> uitgezet voor deze aflevering</string>
@@ -180,7 +179,7 @@
<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_notification_title">Podcastgegevens 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>
<string name="download_type_feed">Feed</string>
@@ -203,7 +202,6 @@
<string name="playback_error_server_died">Server antwoord niet</string>
<string name="playback_error_unknown">Onbekende fout</string>
<string name="no_media_playing_label">Geen media aan het afspelen</string>
- <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>
@@ -225,8 +223,8 @@
<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>
+ <string name="flattr_auth_label">Flattr login</string>
+ <string name="flattr_auth_explanation">Druk op onderstaande knop om het verificatieproces te starten. U wordt doorgestuurd naar het Flattr inlogscherm in uw browser en u wordt gevraagd om toestemming aan AntennaPod te geven om dingen te Flattr\'en. Nadat u toestemming heeft gegeven, keert u automatisch terug naar dit scherm.</string>
<string name="authenticate_label">Authenticeren</string>
<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>
@@ -255,12 +253,15 @@
<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>
+ <string name="enable_sonic">Sonic inschakelen</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>
+ <string name="no_shownotes_label">Deze aflevering heeft geen shownotes.</string>
<!--Preferences-->
+ <string name="storage_pref">Geheugen</string>
+ <string name="project_pref">Project</string>
<string name="other_pref">Overig</string>
<string name="about_pref">Over AntennaPod</string>
<string name="queue_label">Wachtrij</string>
@@ -300,7 +301,7 @@
<string name="pref_mobileUpdate_sum">Updates toestaan ​​via de mobiele dataverbinding</string>
<string name="refreshing_label">Aan het verversen</string>
<string name="flattr_settings_label">Flattr settings</string>
- <string name="pref_flattr_auth_title">Flattr inlog</string>
+ <string name="pref_flattr_auth_title">Flattr accountgegevens</string>
<string name="pref_flattr_auth_sum">Log in je Flattr account om dingen rechtstreeks vanuit de app te flattr\'en.</string>
<string name="pref_flattr_this_app_title">Flattr deze app</string>
<string name="pref_flattr_this_app_sum">Ondersteun de ontwikkeling van AntennaPod door het te flattr\'en. Bedankt!</string>
@@ -333,12 +334,16 @@
<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>
- <string name="pref_gpodnet_authenticate_sum">Log met je gpodder.net account in om je abonnementen te synchroniseren.</string>
+ <string name="pref_gpodnet_authenticate_title">Inloggen</string>
+ <string name="pref_gpodnet_authenticate_sum">Log in met je gpodder.net account om je abonnementen te synchroniseren.</string>
<string name="pref_gpodnet_logout_title">Log uit</string>
<string name="pref_gpodnet_logout_toast">Uitlog was succesvol</string>
<string name="pref_gpodnet_setlogin_information_title">Aanmeldingsgegevens wijzigen</string>
<string name="pref_gpodnet_setlogin_information_sum">Wijzig de aanmeldingsgegevens van je gpodder.net account.</string>
+ <string name="pref_gpodnet_sync_title">Nu synchroniseren</string>
+ <string name="pref_gpodnet_sync_sum">Synchroniseer abonnementen en aflevering-status met gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Synchroniseren gestart</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Ingelogd als <i>%1$s</i> met apparaat <i>%2$s</i>]]></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>
@@ -349,8 +354,12 @@
<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_compact_notification_buttons_title">Bedieningsknoppen vergrendelscherm</string>
+ <string name="pref_compact_notification_buttons_sum">Bedieningsknopppen voor op het vergrendelscherm kiezen. De play/pauze knop wordt altijd getoond.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Selecteer maximaal %1$d knoppen.</string>
+ <string name="pref_compact_notification_buttons_dialog_error">U kunt slechts maximaal %1$d knoppen selecteren.</string>
+ <string name="pref_lockscreen_background_title">Achtergrondafbeelding vergrendelscherm</string>
+ <string name="pref_lockscreen_background_sum">Laat de afbeelding van de huidige aflevering zien op het vergrendelscherm. Ten gevolge van deze functie is de afbeelding 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>
@@ -366,6 +375,13 @@
<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>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Netwerkproxy instellen</string>
+ <string name="pref_faq">FAQ - veelgestelde vragen</string>
+ <string name="pref_known_issues">Reeds bekende bugs</string>
+ <string name="pref_no_browser_found">Geen browser gevonden</string>
+ <string name="pref_cast_title">Chromecast</string>
+ <string name="pref_cast_message">Ondersteuning voor \'Cast\' apparaten (zoals Chromecast, Audio Speakers en Android TV) inschakelen</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>
@@ -388,8 +404,8 @@
<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="opml_reader_error">Er is iets misgegaan met het lezen van het OPML-bestand:</string>
+ <string name="opml_import_error_no_file">Geen bestand geselecteerd!</string>
<string name="select_all_label">Alles selecteren</string>
<string name="deselect_all_label">Alles deselecteren</string>
<string name="select_options_label">Selecteren…</string>
@@ -432,9 +448,9 @@
<string name="gpodnet_suggestions_header">SUGGESTIES</string>
<string name="gpodnet_search_hint">Zoek gpodder.net</string>
<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_descr">Welkom bij het inlogproces van gpodder.net. Typ eerst je accountgegevens:</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="gpodnetauth_login_register">Als u nog geen account heeft, 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>
@@ -445,6 +461,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Kies een bestaand apparaat:</string>
<string name="gpodnetauth_device_errorEmpty">Apparaat ID mag niet leeg zijn</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Apparaat ID al in gebruik</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">Apparaatomschrijving kan niet leeg zijn</string>
<string name="gpodnetauth_device_butChoose">Kies</string>
<string name="gpodnetauth_finish_title">Login succesvol</string>
<string name="gpodnetauth_finish_descr">Gefeliciteerd! Jou gpodder.net account is nu verbonden met je apparaat. AntennaPod zal voortaan abonnementen op je apparaat automatisch met je gpodder.net account synchroniseren.</string>
@@ -469,7 +486,7 @@
<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="folder_not_empty_dialog_msg">De map die u heeft gekozen is niet leeg. Gedownloade media 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 wil spelen</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pauzeren bij onderbrekingen</string>
@@ -481,22 +498,13 @@
<string name="subscribed_label">Geabonneerd</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>
- <string name="show_cover_label">Beeld tonen</string>
<string name="rewind_label">Terugspoelen</string>
<string name="fast_forward_label">Vooruitspoelen</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Navigeer naar boven</string>
- <string name="butAction_label">Meer acties</string>
- <string name="status_playing_label">Aflevering wordt gespeeld</string>
<string name="status_downloading_label">Aflevering wordt gedownload</string>
- <string name="status_downloaded_label">Aflevering is gedownload</string>
- <string name="status_unread_label">Item is nieuw</string>
<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-->
@@ -507,14 +515,15 @@
<string name="episode_filters_description">Lijst van zoektermen die bepalen of een aflevering meegenomen of uitgesloten wordt bij automatisch downloaden</string>
<string name="episode_filters_include">Meenemen</string>
<string name="episode_filters_exclude">Uitsluiten</string>
+ <string name="episode_filters_hint">Losse woorden \n\"Meerdere tussen aanhalingstekens\"</string>
<string name="keep_updated">Up to date houden</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>
+ <!--Episodes apply actions-->
<string name="all_label">Alle</string>
<string name="selected_all_label">Alle afleveringen selecteren</string>
<string name="none_label">Geen</string>
@@ -531,7 +540,7 @@
<string name="selected_queued_label">Afleveringen in de wachtrij selecteren</string>
<string name="not_queued_label">Niet in de wachtrij</string>
<string name="selected_not_queued_label">Afleveringen geselecteerd die niet in de wachtrij staan</string>
- <string name="sort_title"><b>Sorteren op…</b></string>
+ <!--Sort-->
<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>
@@ -552,5 +561,31 @@
<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>
+ <string name="sonic_only">Sonic vereist</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Type</string>
+ <string name="host_label">Host</string>
+ <string name="port_label">Poort</string>
+ <string name="optional_hint">(Optioneel)</string>
+ <string name="proxy_test_label">Testen</string>
+ <string name="proxy_checking">Controleren…</string>
+ <string name="proxy_test_successful">Test succesvol</string>
+ <string name="proxy_test_failed">Test mislukt</string>
+ <string name="proxy_host_empty_error">Host kan niet leeg zijn</string>
+ <string name="proxy_host_invalid_error">Host is geen geldig IP-adres of domein</string>
+ <string name="proxy_port_invalid_error">Poortnummer ongeldig</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">Afspelen op…</string>
+ <string name="cast_not_castable">Dit bestand kan niet afgespeeld woorden door het Cast apparaat</string>
+ <string name="cast_failed_to_play">Starten van afspelen is mislukt</string>
+ <string name="cast_failed_to_stop">Stoppen van afspelen is mislukt</string>
+ <string name="cast_failed_to_pause">Pauzeren van afspelen is mislukt</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Het aanpassen van het volume is mislukt</string>
+ <string name="cast_failed_no_connection">Er is geen verbinding met het Cast apparaat</string>
+ <string name="cast_failed_no_connection_trans">De verbinding met het Cast apparaat is verloren. Antennapod probeert de verbinding te herstellen. Wacht aub een paar seconden en probeer opnieuw.</string>
+ <string name="cast_failed_perform_action">Het uitvoeren van de actie is mislukt</string>
+ <string name="cast_failed_status_request">Het synchroniseren met het Cast apparaat is mislukt</string>
+ <string name="cast_failed_seek">Het opzoeken van het nieuwe tijdstip op het Cast apparaat is mislukt</string>
+ <string name="cast_failed_media_error_skipping">Er was een fout bij het afspelen; de aflevering wordt overgeslagen…</string>
</resources>
diff --git a/core/src/main/res/values-no-rNB/strings.xml b/core/src/main/res/values-no-rNB/strings.xml
new file mode 100644
index 000000000..58e8d9891
--- /dev/null
+++ b/core/src/main/res/values-no-rNB/strings.xml
@@ -0,0 +1,490 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="feeds_label">Strømmer</string>
+ <string name="add_feed_label">Legg til podcast</string>
+ <string name="episodes_label">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="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>
+ <!--Statistics fragment-->
+ <!--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="parallel_downloads_suffix">\u0020samtidige nedlastinger</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="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="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="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="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="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="status_downloading_label">Episode lastes ned nå</string>
+ <string name="in_queue_label">Episoden er i queue</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>
+ <!--Episodes apply actions-->
+ <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>
+ <!--Sort-->
+ <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-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-no/strings.xml b/core/src/main/res/values-no/strings.xml
new file mode 100644
index 000000000..28dfeb6e8
--- /dev/null
+++ b/core/src/main/res/values-no/strings.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <!--actions on feeditems-->
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</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 81d9ebec4..51def2c0e 100644
--- a/core/src/main/res/values-pl-rPL/strings.xml
+++ b/core/src/main/res/values-pl-rPL/strings.xml
@@ -25,6 +25,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Ostatnio opublikowane</string>
<string name="episode_filter_label">Pokaż tylko nowe odcinki</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Otwórz menu</string>
<string name="drawer_close">Zamknij menu</string>
@@ -347,8 +348,6 @@
<string name="opml_import_label">Import OPML</string>
<string name="opml_directory_error">BŁĄD!</string>
<string name="reading_opml_label">Odczytuję plik OPML</string>
- <string name="opml_reader_error">Wystąpił błąd w czasie odczytu dokumentu OPML:</string>
- <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>
@@ -489,4 +488,5 @@ https://gpodder.net/register/</string>
<string name="rating_later_label">Przypomnij później</string>
<string name="rating_now_label">Pewnie, zróbmy to!</string>
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-pl/strings.xml b/core/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..975aa5263
--- /dev/null
+++ b/core/src/main/res/values-pl/strings.xml
@@ -0,0 +1,99 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="settings_label">Ustawienia</string>
+ <string name="downloads_log_label">Dziennik</string>
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <string name="drawer_open">Otwórz menu</string>
+ <string name="drawer_close">Zamknij menu</string>
+ <!--Webview actions-->
+ <string name="copy_url_label">Kopiuj URL</string>
+ <string name="share_url_label">Udostępnij URL</string>
+ <!--Playback history-->
+ <!--Other-->
+ <string name="confirm_label">Potwierdź</string>
+ <string name="cancel_label">Anuluj</string>
+ <string name="author_label">Autor</string>
+ <string name="language_label">Język</string>
+ <string name="url_label">URL</string>
+ <string name="podcast_settings_label">Ustawienia</string>
+ <string name="cover_label">Obrazek</string>
+ <string name="error_label">Błąd</string>
+ <string name="refresh_label">Odśwież</string>
+ <string name="chapters_label">Rozdział</string>
+ <string name="description_label">Opis</string>
+ <string name="episodes_suffix">\u0020 odcinków</string>
+ <string name="length_prefix">Długość:\u0020</string>
+ <string name="size_prefix">Rozmiar:\u0020</string>
+ <string name="save_username_password_label">Zapis nazwę użytkownika i hasło</string>
+ <string name="close_label">Zamknij</string>
+ <string name="retry_label">Ponów</string>
+ <!--'Add Feed' Activity labels-->
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
+ <string name="browse_gpoddernet_label">Przeglądaj gpodder.net</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Zaznacz wszystkie jako przesłuchane</string>
+ <string name="show_info_label">Pokaż informacje</string>
+ <string name="hide_downloaded_episodes_label">Pobrany</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Pobierz</string>
+ <string name="play_label">Odtwórz</string>
+ <string name="stop_label">Zatrzymaj</string>
+ <string name="remove_label">Usuń</string>
+ <string name="mark_read_label">Zaznacz jako przesłuchany</string>
+ <string name="mark_unread_label">Zaznacz jako nieprzesłuchany</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="visit_website_label">Odwiedź stronę</string>
+ <string name="skip_episode_label">Pomiń odcinek</string>
+ <!--Download messages and labels-->
+ <string name="download_error_error_unknown">Nieznany błąd</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Błąd!</string>
+ <string name="playback_error_server_died">Serwer umarł</string>
+ <string name="playback_error_unknown">Nieznany błąd</string>
+ <string name="player_buffering_msg">Buforowanie</string>
+ <!--Queue operations-->
+ <string name="sort">Sortuj</string>
+ <string name="date">Data</string>
+ <string name="duration">Czas trwania</string>
+ <string name="ascending">Rosnąco</string>
+ <string name="descending">Malejąco</string>
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <string name="queue_label">Kolejka</string>
+ <string name="services_label">Usługi</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Pobieraj pliki tylko przez WiFi</string>
+ <string name="refreshing_label">Odświeżanie</string>
+ <string name="user_interface_label">Interfejs Użytkownika</string>
+ <string name="pref_gpodnet_logout_toast">Wylogowano pomyślnie</string>
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Wybrany folder</string>
+ <string name="create_folder_msg">Utworzyć nowy folder z nazwą \"%1$s\"?</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_not_empty_dialog_title">Folder nie jest pusty</string>
+ <string name="set_to_default_folder">Wybierz domyślny folder</string>
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-pt-rBR/strings.xml b/core/src/main/res/values-pt-rBR/strings.xml
index 95b279022..21a5af0b4 100644
--- a/core/src/main/res/values-pt-rBR/strings.xml
+++ b/core/src/main/res/values-pt-rBR/strings.xml
@@ -1,27 +1,41 @@
<?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">Feeds</string>
+ <string name="statistics_label">Estatísticas</string>
<string name="add_feed_label">Adicionar Podcast</string>
- <string name="podcasts_label">PODCASTS</string>
- <string name="new_episodes_label">Novos Episódios</string>
- <string name="all_episodes_label">Todos Episódios</string>
+ <string name="episodes_label">Episódios</string>
+ <string name="all_episodes_short_label">Todos</string>
+ <string name="favorite_episodes_label">Favoritos</string>
<string name="new_label">Novo</string>
- <string name="waiting_list_label">Lista de espera</string>
<string name="settings_label">Configurações</string>
<string name="add_new_feed_label">Adicionar podcast</string>
<string name="downloads_label">Downloads</string>
+ <string name="downloads_running_label">Rodando</string>
+ <string name="downloads_completed_label">Completado</string>
+ <string name="downloads_log_label">Log</string>
+ <string name="subscriptions_label">Assinaturas</string>
+ <string name="subscriptions_list_label">Lista de Assinaturas</string>
<string name="cancel_download_label">Cancelar Download</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">gpodder.net login</string>
- <!--New episodes fragment-->
- <string name="episode_filter_label">Mostrar apenas novos episódios</string>
+ <string name="free_space_label">%1$s livre</string>
+ <string name="episode_cache_full_title">Cache de episódio completo</string>
+ <string name="episode_cache_full_message">O limite de cache de episódios foi alcançado. Você pode aumentar o tamanho do cache em Configurações.</string>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Tempo total de podcasts escutados:</string>
+ <string name="statistics_details_dialog">%1$d de %2$d episódios iniciados.\n\nEscutados %3$s de %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Abrir menu</string>
<string name="drawer_close">Fechar menu</string>
+ <string name="drawer_preferences">Preferências de Navegações</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 e não escutados ainda</string>
<string name="drawer_feed_counter_new">Numero de novos episódios</string>
+ <string name="drawer_feed_counter_unplayed">Número de episódios não escutados</string>
<string name="drawer_feed_counter_none">Nenhum</string>
<!--Webview actions-->
<string name="open_in_browser_label">Abrir no navegador</string>
@@ -53,33 +67,61 @@
<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="auto_download_apply_to_items_title">Aplicar a episódios anteriores</string>
+ <string name="auto_download_apply_to_items_message">A nova <i>Download Automático</i> configuração irá se aplicar automaticamente a novos episódios.\nVocê quer aplicá-la a aos episódios anteriores?</string>
+ <string name="auto_delete_label">Apagar episódio automaticamente</string>
+ <string name="parallel_downloads_suffix">\u0020 downloads paralelos</string>
+ <string name="feed_auto_download_global">configurações globais</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">Quando não está na fila</string>
+ <string name="episode_cleanup_after_listening">Depois de concluído</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dia depois de concluído</item>
+ <item quantity="other">%d dias depois de concluído</item>
+ </plurals>
<!--'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>
+ <string name="podcastdirectories_label">Procurar Podcast na Pasta</string>
+ <string name="podcastdirectories_descr">Você pode buscar novos episódios por nome, categoria ou popularidade no gpodder.net, ou buscar no iTunes.</string>
+ <string name="browse_gpoddernet_label">Listar no gpodder.net</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Marcar todos como lido</string>
+ <string name="mark_all_read_msg">Marcar todos Episódios como lidos</string>
+ <string name="mark_all_read_confirmation_msg">Por favor, confirme que você quer marcar todos os episódios como já tocados.</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_label">Compartilhar...</string>
<string name="share_link_label">Compartilhar link do site</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="share_feed_url_label">Compartilhar Link do Feed</string>
+ <string name="share_item_url_label">Compartilhar Link do arquivo do episódio</string>
+ <string name="share_item_url_with_position_label">Compartilhar url do arquivo 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="load_complete_feed">Atualizar feed completamente</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 lido</string>
<string name="hide_paused_episodes_label">Pausado</string>
+ <string name="hide_played_episodes_label">Lido</string>
+ <string name="hide_queued_episodes_label">Enfileirado</string>
+ <string name="hide_not_queued_episodes_label">Não enfileirado</string>
<string name="hide_downloaded_episodes_label">Baixado</string>
<string name="hide_not_downloaded_episodes_label">Não baixado</string>
+ <string name="filtered_label">Filtrado</string>
<string name="refresh_failed_msg">{fa-exclamation-circle} Última Atualização falhou</string>
+ <string name="open_podcast">Podcast Aberto</string>
<!--actions on feeditems-->
<string name="download_label">Download</string>
<string name="play_label">Reproduzir</string>
@@ -94,15 +136,20 @@
<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">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>
+ <string name="reset_position">Resetar a Posição da Reprodução</string>
+ <string name="removed_item">Item removido</string>
<!--Download messages and labels-->
<string name="download_successful">com sucesso</string>
+ <string name="download_failed">falhou</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>
@@ -115,13 +162,21 @@
<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="download_error_file_type_type">Erro de Tipo de Arquivo</string>
+ <string name="download_error_forbidden">Proibido</string>
<string name="cancel_all_downloads_label">Cancelar todos os downloads</string>
<string name="download_canceled_msg">Download cancelado</string>
+ <string name="download_canceled_autodownload_enabled_msg">Download cancelado\nDesabilitado <i>Download Automático</i> para este item</string>
<string name="download_report_title">Downloads finalizados</string>
+ <string name="download_report_content_title">Reportar Download</string>
<string name="download_error_malformed_url">URL inválida</string>
<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>
+ <plurals name="downloads_left">
+ <item quantity="one">%d download restante</item>
+ <item quantity="other">%d downloads restantes</item>
+ </plurals>
<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>
@@ -130,6 +185,10 @@
<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="authentication_notification_title">Autenticação requerida</string>
+ <string name="authentication_notification_msg">O recurso que você requisitou requer um usuário e uma senha</string>
+ <string name="confirm_mobile_download_dialog_title">Confirmar Download Mobile</string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Enfileirados</string>
<string name="confirm_mobile_download_dialog_enable_temporarily">Permitir temporariamente</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Erro!</string>
@@ -140,10 +199,14 @@
<string name="playback_error_server_died">Servidor morreu</string>
<string name="playback_error_unknown">Erro desconhecido</string>
<string name="no_media_playing_label">Nenhuma mídia tocando</string>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Armazenando</string>
<string name="playbackservice_notification_title">Reproduzindo podcast</string>
+ <string name="unknown_media_key">AntennaPod - Chave de mí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">Desfazer</string>
<string name="removed_from_queue">Item removido</string>
@@ -155,6 +218,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 você quer limpar a fila de TODOS eposódios</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>
@@ -169,19 +233,38 @@
<string name="access_revoked_title">Acesso revogado</string>
<string name="access_revoked_info">Você revogou o token de acesso do AntennaPod com sucesso. Para finalizar o processo, você deve remover esta app da lista de aplicativos aprovados nas configurações de sua conta no website do Flattr.</string>
<!--Flattr-->
+ <string name="flattr_click_success">Flattr\'ou alguma coisa!</string>
+ <string name="flattr_click_success_count">Flattr\'ou %d coisas!</string>
+ <string name="flattr_click_success_queue">Flattr\'ou: %s</string>
+ <string name="flattr_click_failure_count">Falou em flattr %d coisas!</string>
+ <string name="flattr_click_failure">Não flattr\'ou: %s.</string>
+ <string name="flattr_click_enqueued">Coisas que serão flattr\'adas mais tarde</string>
+ <string name="flattring_thing">Flattrando %s</string>
+ <string name="flattring_label">AntennaPod está flattrando</string>
+ <string name="flattrd_label">AntennaPod foi flattr\'ado</string>
+ <string name="flattrd_failed_label">AntennaPod flattr falhou</string>
+ <string name="flattr_retrieving_status">Recebendo coisas flattr\'adas</string>
<!--Variable Speed-->
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Não Instalado</string>
<string name="set_playback_speed_label">Velocidades de Reprodução</string>
+ <string name="enable_sonic">Habilitar Sonic</string>
<!--Empty list labels-->
<string name="no_items_label">Não existem itens nesta lista.</string>
<string name="no_feeds_label">Você ainda não assinou nenhum feed.</string>
+ <string name="no_chapters_label">Este episódio não possui capítulos</string>
+ <string name="no_shownotes_label">Este episódio não possui notas de apresentação</string>
<!--Preferences-->
+ <string name="storage_pref">Armazenamento</string>
+ <string name="project_pref">Projeto</string>
<string name="other_pref">Outros</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_episode_cleanup_title">Limpar Episódio</string>
+ <string name="pref_episode_cleanup_summary">Episódios que não foram enfileirados e não foram favoritados deveriam ser elegídos para remoção se o Auto Download precisar de espaço para novos episódios</string>
+ <string name="pref_pauseOnDisconnect_sum">Pausar a exibição quando phones de ouvidos ou bluetooth forem disconectados</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>
@@ -237,8 +320,6 @@
<string name="opml_import_label">Importação de OPML</string>
<string name="opml_directory_error">ERRO!</string>
<string name="reading_opml_label">Lendo arquivo OPML</string>
- <string name="opml_reader_error">Ocorreu um erro durante a leitura do documento OPML:</string>
- <string name="opml_import_error_dir_empty">O diretório de importação está vazio.</string>
<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>
@@ -300,16 +381,15 @@
<string name="subscribe_label">Assinar</string>
<string name="subscribed_label">Assinado</string>
<!--Content descriptions for image buttons-->
- <string name="show_cover_label">Mostrar imagem</string>
- <string name="butAction_label">Mais ações</string>
- <string name="status_playing_label">Episódio está sendo reproduzido</string>
- <string name="status_downloaded_label">Episódio foi baixado</string>
- <string name="status_unread_label">Item é novo</string>
<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-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
</resources>
diff --git a/core/src/main/res/values-pt/strings.xml b/core/src/main/res/values-pt/strings.xml
index ebea5f391..fb5bc533e 100644
--- a/core/src/main/res/values-pt/strings.xml
+++ b/core/src/main/res/values-pt/strings.xml
@@ -1,23 +1,21 @@
<?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">Fontes</string>
+ <string name="statistics_label">Estatísticas</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 os episódios</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">Descargas</string>
<string name="downloads_running_label">Em curso</string>
<string name="downloads_completed_label">Terminada</string>
<string name="downloads_log_label">Registo</string>
+ <string name="subscriptions_label">Subscrições</string>
+ <string name="subscriptions_list_label">Lista de subscrições</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>
@@ -25,9 +23,9 @@
<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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Tempo total dos podcasts reproduzidos:</string>
+ <string name="statistics_details_dialog">%1$d de %2$d episódios iniciados.\n\nReproduzidos %3$s de %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Abrir menu</string>
<string name="drawer_close">Fechar menu</string>
@@ -48,7 +46,7 @@
<!--Playback history-->
<string name="clear_history_label">Limpar histórico</string>
<!--Other-->
- <string name="confirm_label">Confirmação</string>
+ <string name="confirm_label">Confirmar</string>
<string name="cancel_label">Cancelar</string>
<string name="yes">Sim</string>
<string name="no">Não</string>
@@ -76,9 +74,9 @@
<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="auto_delete_label">Apagar episódio automaticamente</string>
<string name="parallel_downloads_suffix">\u0020descargas simultâneas</string>
- <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_global">Predefinições</string>
<string name="feed_auto_download_always">Sempre</string>
<string name="feed_auto_download_never">Nunca</string>
<string name="send_label">Enviar...</string>
@@ -94,13 +92,13 @@
<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 por novos podcasts no gPodder.net e também na loja iTunes. Pode procurar por nome, categoria e popularidade.</string>
+ <string name="podcastdirectories_descr">Pode procurar por novos podcasts no gPodder.net e também na loja iTunes. Pode pesquisar 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 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_read_confirmation_msg">Tem a certeza de que deseja marcar todos os episódios como reproduzidos?</string>
+ <string name="mark_all_read_feed_confirmation_msg">Tem a certeza de 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>
@@ -110,7 +108,7 @@
<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_delete_confirmation_msg">Tem a certeza de que deseja remover esta fonte e 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>
@@ -124,6 +122,7 @@
<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>
+ <string name="open_podcast">Abrir podcast</string>
<!--actions on feeditems-->
<string name="download_label">Descarregar</string>
<string name="play_label">Reproduzir</string>
@@ -144,8 +143,6 @@
<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">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>
@@ -166,6 +163,8 @@
<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="download_error_file_type_type">Erro do tipo de ficheiro</string>
+ <string name="download_error_forbidden">Proibido</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>
@@ -203,7 +202,6 @@
<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>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">A processar...</string>
<string name="playbackservice_notification_title">Reproduzir podcast</string>
<string name="unknown_media_key">Tecla multimédia desconhecida: %1$d</string>
@@ -223,7 +221,7 @@
<string name="duration">Duração</string>
<string name="ascending">Crescente</string>
<string name="descending">Decrescente</string>
- <string name="clear_queue_confirmation_msg">Tem a certeza de que deseja remover todos os episódios da lista de reprodução?</string>
+ <string name="clear_queue_confirmation_msg">Tem a certeza de que deseja remover todos os episódios da fila 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>
@@ -260,7 +258,10 @@
<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>
+ <string name="no_shownotes_label">Este episódio não tem notas.</string>
<!--Preferences-->
+ <string name="storage_pref">Armazenamento</string>
+ <string name="project_pref">Projeto</string>
<string name="other_pref">Outras</string>
<string name="about_pref">Sobre</string>
<string name="queue_label">Fila</string>
@@ -320,7 +321,7 @@
<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_automatic_download_sum">Configurar 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 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>
@@ -339,6 +340,10 @@
<string name="pref_gpodnet_logout_toast">Sessão terminada</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_gpodnet_sync_title">Sincronizar agora</string>
+ <string name="pref_gpodnet_sync_sum">Sincronizar subscrições e estado dos episódios com o gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Sincronização iniciada</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Sessão iniciada como <i>%1$s</i> com o dispositivo <i>%2$s</i>]]></string>
<string name="pref_playback_speed_title">Velocidades de reprodução</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>
@@ -349,6 +354,12 @@
<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_compact_notification_buttons_title">Definir botões do ecrã de bloqueio</string>
+ <string name="pref_compact_notification_buttons_sum">Alterar botões de reprodução do ecrã de bloqueio. Os botões Reproduzir/Pausa serão sempre incluídos.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Selecione no máximo %1$d itens</string>
+ <string name="pref_compact_notification_buttons_dialog_error">Apenas pode selecionar um máximo de %1$d itens.</string>
+ <string name="pref_show_subscriptions_in_drawer_title">Mostrar subscrições</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">Mostrar lista de subscrições no menu de navegação</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>
@@ -366,17 +377,24 @@
<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>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Definir um proxy de rede</string>
+ <string name="pref_faq">FAQ</string>
+ <string name="pref_known_issues">Problemas conhecidos</string>
+ <string name="pref_no_browser_found">Navegador web não encontrado</string>
+ <string name="pref_cast_title">Suporte Chromecast</string>
+ <string name="pref_cast_message">Ativar suporte a reprodução multimédia remota em dispositivos Cast (tais como Chromecast, colunas áudio ou Android TV)</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>
<string name="auto_flattr_ater_beginning">Flattr de episodios ao iniciar a reprodução</string>
<string name="auto_flattr_ater_end">Flattr de episódios ao terminar a reprodução</string>
<!--Search-->
- <string name="search_hint">Procurar fontes ou episódios</string>
+ <string name="search_hint">Pesquisar fontes ou episódios</string>
<string name="found_in_shownotes_label">Encontrado nas notas</string>
<string name="found_in_chapters_label">Encontrado nos capítulos</string>
<string name="search_status_no_results">Nenhum resultado</string>
- <string name="search_label">Procura</string>
+ <string name="search_label">Pesquisar</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>
@@ -389,7 +407,7 @@
<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_no_file">Nenhum ficheiro selecionado!</string>
<string name="select_all_label">Marcar tudo</string>
<string name="deselect_all_label">Desmarcar tudo</string>
<string name="select_options_label">Selecionar...</string>
@@ -430,7 +448,7 @@
<string name="gpodnet_taglist_header">Categorias</string>
<string name="gpodnet_toplist_header">Melhores</string>
<string name="gpodnet_suggestions_header">Sugestões</string>
- <string name="gpodnet_search_hint">Procurar no gpodder.net</string>
+ <string name="gpodnet_search_hint">Pesquisar no gpodder.net</string>
<string name="gpodnetauth_login_title">Acesso</string>
<string name="gpodnetauth_login_descr">Bem-vindo ao processo de acesso ao gpodder.net. Introduza os dados de acesso:</string>
<string name="gpodnetauth_login_butLabel">Acesso</string>
@@ -445,6 +463,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Escolher dispositivo:</string>
<string name="gpodnetauth_device_errorEmpty">ID do dispositivo não pode estar vazia</string>
<string name="gpodnetauth_device_errorAlreadyUsed">ID de dispositivo já utilizada</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">A descrição não pode estar vazia</string>
<string name="gpodnetauth_device_butChoose">Escolher</string>
<string name="gpodnetauth_finish_title">Sessão iniciada!</string>
<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>
@@ -481,22 +500,13 @@
<string name="subscribed_label">Subscrito</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">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 descarregado</string>
- <string name="status_downloaded_label">Episódio descarregado</string>
- <string name="status_unread_label">Novo item</string>
+ <string name="status_downloading_label">A descarregar episódio</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 alterar a posição deste item</string>
<string name="load_next_page_label">Carregar próxima página</string>
<!--Feed information screen-->
@@ -513,9 +523,9 @@
<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="search_itunes_label">Pesquisar no iTunes</string>
<string name="filter">Filtro</string>
+ <!--Episodes apply actions-->
<string name="all_label">Todos</string>
<string name="selected_all_label">Marcar todos os episódios</string>
<string name="none_label">Nenhum</string>
@@ -532,7 +542,7 @@
<string name="selected_queued_label">Episódios selecionados na fila</string>
<string name="not_queued_label">Não na fila</string>
<string name="selected_not_queued_label">Episódios não selecionados na fila</string>
- <string name="sort_title"><b>Ordenar por…</b></string>
+ <!--Sort-->
<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>
@@ -554,4 +564,32 @@
<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>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Tipo</string>
+ <string name="host_label">Servidor</string>
+ <string name="port_label">Porta</string>
+ <string name="optional_hint">(Opcional)</string>
+ <string name="proxy_test_label">Testar</string>
+ <string name="proxy_checking">A verificar...</string>
+ <string name="proxy_test_successful">Teste bem sucedido</string>
+ <string name="proxy_test_failed">O teste falhou</string>
+ <string name="proxy_host_empty_error">Servidor não pode estar vazio</string>
+ <string name="proxy_host_invalid_error">O servidor não tem um endereço ou domínio válido</string>
+ <string name="proxy_port_invalid_error">Porta inválido</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">Reproduzir em...</string>
+ <string name="cast_disconnect_label">Desligar da última sessão</string>
+ <string name="cast_not_castable">O ficheiro selecionado não é compatível com este dispositivo</string>
+ <string name="cast_failed_to_play">Falha ao iniciar a reprodução</string>
+ <string name="cast_failed_to_stop">Falha ao parar a reprodução</string>
+ <string name="cast_failed_to_pause">Falha ao pausar a reprodução</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Falha ao definir o volume</string>
+ <string name="cast_failed_no_connection">Não existe ligação ao dispositivo</string>
+ <string name="cast_failed_no_connection_trans">Falha de ligação ao dispositivo. A aplicação está a tentar estabelecer uma nova ligação. Por favor aguarde alguns segundos e tente novamente.</string>
+ <string name="cast_failed_perform_action">Falha ao executar a ação</string>
+ <string name="cast_failed_status_request">Falha de sincronização com o dispositivo</string>
+ <string name="cast_failed_seek">Falha ao procurar a nova posição no dispositivo</string>
+ <string name="cast_failed_receiver_player_error">O reprodutor encontrou um erro crítico</string>
+ <string name="cast_failed_media_error_skipping">Erro de reprodução. A ignorar...</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 a112db8dd..0a8eb83ee 100644
--- a/core/src/main/res/values-ro-rRO/strings.xml
+++ b/core/src/main/res/values-ro-rRO/strings.xml
@@ -13,6 +13,7 @@
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">autentificare gpodder.net</string>
<!--New episodes fragment-->
+ <!--Statistics fragment-->
<!--Main activity-->
<!--Webview actions-->
<string name="open_in_browser_label">Deschide în browser</string>
@@ -179,8 +180,6 @@
<string name="opml_import_label">OPML import</string>
<string name="opml_directory_error">EROARE!</string>
<string name="reading_opml_label">Citește fișierul OPML</string>
- <string name="opml_reader_error">A avut loc o eroare la citirea documentului opml:</string>
- <string name="opml_import_error_dir_empty">Directorul de import este gol.</string>
<string name="select_all_label">Selectează toate</string>
<string name="deselect_all_label">Deselectează toate</string>
<string name="opml_export_label">Exportă OPML</string>
@@ -232,4 +231,5 @@
<!--AntennaPodSP-->
<!--Rating dialog-->
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-ru/strings.xml b/core/src/main/res/values-ru/strings.xml
index 46a9d5feb..991c185e4 100644
--- a/core/src/main/res/values-ru/strings.xml
+++ b/core/src/main/res/values-ru/strings.xml
@@ -25,6 +25,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Свежие</string>
<string name="episode_filter_label">Только новые</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Открыть меню</string>
<string name="drawer_close">Закрыть меню</string>
@@ -359,8 +360,6 @@
<string name="opml_import_label">Импорт OPML</string>
<string name="opml_directory_error">Ошибка</string>
<string name="reading_opml_label">Чтение файла OPML</string>
- <string name="opml_reader_error">Ошибка чтения файла OPML</string>
- <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>
@@ -503,4 +502,5 @@
<string name="rating_later_label">Напомни позже</string>
<string name="rating_now_label">Конечно, давай!</string>
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-sv-rSE/strings.xml b/core/src/main/res/values-sv-rSE/strings.xml
index 7407b3474..8cc3cba6a 100644
--- a/core/src/main/res/values-sv-rSE/strings.xml
+++ b/core/src/main/res/values-sv-rSE/strings.xml
@@ -1,23 +1,21 @@
<?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">Flöden</string>
+ <string name="statistics_label">Statistik</string>
<string name="add_feed_label">Lägg till Podcast</string>
- <string name="podcasts_label">PODCASTS</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="downloads_label">Nedladdningar</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="subscriptions_label">Prenumerationer</string>
+ <string name="subscriptions_list_label">Prenumerationslista</string>
<string name="cancel_download_label">Avbryt\nNedladdning</string>
<string name="playback_history_label">Uppspelningshistorik</string>
<string name="gpodnet_main_label">gpodder.net</string>
@@ -25,9 +23,9 @@
<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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Total uppspelningstid:</string>
+ <string name="statistics_details_dialog">%1$d av %2$d episoder startade.\n\nSpelat %3$s av %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Öppna meny</string>
<string name="drawer_close">Stäng meny</string>
@@ -76,9 +74,9 @@
<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="auto_delete_label">Automatisk Episodborttagning</string>
<string name="parallel_downloads_suffix">\u0020parallella nedladdningar</string>
- <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_global">Globala standardinställningar</string>
<string name="feed_auto_download_always">Alltid</string>
<string name="feed_auto_download_never">Aldrig</string>
<string name="send_label">Skicka…</string>
@@ -108,8 +106,8 @@
<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="share_item_url_label">Dela Episoden Fil-URL</string>
+ <string name="share_item_url_with_position_label">Dela Episodens Fil-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>
@@ -124,6 +122,7 @@
<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>
+ <string name="open_podcast">Öppna Podcast</string>
<!--actions on feeditems-->
<string name="download_label">Ladda ned</string>
<string name="play_label">Spela</string>
@@ -144,8 +143,6 @@
<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 episoden</string>
<string name="activate_auto_download">Aktivera Automatisk Nedladdning</string>
<string name="deactivate_auto_download">Avaktivera Automatisk Nedladdning</string>
@@ -166,6 +163,8 @@
<string name="download_error_connection_error">Anslutningsfel</string>
<string name="download_error_unknown_host">Okänd Värd</string>
<string name="download_error_unauthorized">Autentiseringsfel</string>
+ <string name="download_error_file_type_type">Filtypsfel</string>
+ <string name="download_error_forbidden">Förbjuden</string>
<string name="cancel_all_downloads_label">Avbryt alla nedladdningar</string>
<string name="download_canceled_msg">Nedladdning avbruten</string>
<string name="download_canceled_autodownload_enabled_msg">Nedladdning avbruten\nStängde av <i>Automatisk nedladdning</i> för denna sak</string>
@@ -203,7 +202,6 @@
<string name="playback_error_server_died">Servern dog</string>
<string name="playback_error_unknown">Okänt fel</string>
<string name="no_media_playing_label">Inget media spelar</string>
- <string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Buffrar</string>
<string name="playbackservice_notification_title">Spelar podcast</string>
<string name="unknown_media_key">AntannaPod - Okänd mediaknapp: %1$d</string>
@@ -260,7 +258,10 @@
<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>
+ <string name="no_shownotes_label">Denna episod har inga shownotes.</string>
<!--Preferences-->
+ <string name="storage_pref">Lagring</string>
+ <string name="project_pref">Projekt</string>
<string name="other_pref">Annat</string>
<string name="about_pref">Om</string>
<string name="queue_label">Kö</string>
@@ -339,6 +340,10 @@
<string name="pref_gpodnet_logout_toast">Utloggning lyckades</string>
<string name="pref_gpodnet_setlogin_information_title">Ändra inloggningsinformation</string>
<string name="pref_gpodnet_setlogin_information_sum">Ändra inloggningsinformationen för ditt gpodder.net konto.</string>
+ <string name="pref_gpodnet_sync_title">Synkronisera nu</string>
+ <string name="pref_gpodnet_sync_sum">Synkronisera prenumerationer och episodtillstånd med gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Synkronisering startad</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Inloggad som <i>%1$s</i> med enhet <i>%2$s</i>]]></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_fast_forward">Spola framåt</string>
@@ -349,6 +354,12 @@
<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_compact_notification_buttons_title">Sätt Låsskärmens Knappar</string>
+ <string name="pref_compact_notification_buttons_sum">Ändra uppspelningsknapparna på låsskärmen. Spela/Pausa knappen är alltid inkluderad.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Välj maximalt %1$d st.</string>
+ <string name="pref_compact_notification_buttons_dialog_error">Du kan bara välja maximalt %1$d st.</string>
+ <string name="pref_show_subscriptions_in_drawer_title">Visa Prenumerationer</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">Visa prenumerationslistan direkt i navigeringsmenyn</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>
@@ -366,6 +377,13 @@
<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>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Använd en nätverksproxy</string>
+ <string name="pref_faq">FAQ</string>
+ <string name="pref_known_issues">Kända problem</string>
+ <string name="pref_no_browser_found">Ingen webbläsare hittades.</string>
+ <string name="pref_cast_title">Chromecast-stöd</string>
+ <string name="pref_cast_message">Aktivera stöd för fjärruppspelning av media på Cast-enheter (såsom Chromecast, Ljudanläggningar eller Android TV)</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>
@@ -388,8 +406,8 @@
<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="opml_reader_error">Ett fel uppstod vid läsning av OPML dokumentet:</string>
+ <string name="opml_import_error_no_file">Ingen fil vald!</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>
@@ -445,6 +463,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Välj befintlig enhet:</string>
<string name="gpodnetauth_device_errorEmpty">Enhets ID måste fyllas i</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Enhets ID används redan</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">Rubrik måste fyllas i</string>
<string name="gpodnetauth_device_butChoose">Välj</string>
<string name="gpodnetauth_finish_title">Inloggning lyckades!</string>
<string name="gpodnetauth_finish_descr">Grattis! Ditt gpodder.net konto är nu länkat med din enhet. AntennaPod kommer från och med nu automatiskt synkronisera dina prenumerationer på din enhet med ditt gpodder.net konto.</string>
@@ -481,22 +500,13 @@
<string name="subscribed_label">Prenumererar</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>
- <string name="show_cover_label">Visa bild</string>
<string name="rewind_label">Backa</string>
<string name="fast_forward_label">Snabbspola</string>
<string name="media_type_audio_label">Ljud</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Navigera upp</string>
- <string name="butAction_label">Fler åtgärder</string>
- <string name="status_playing_label">Episoden spelas</string>
<string name="status_downloading_label">Episoden laddas ner</string>
- <string name="status_downloaded_label">Episoden är nedladdad</string>
- <string name="status_unread_label">Föremålet är nytt</string>
<string name="in_queue_label">Episoden är i kön</string>
- <string name="new_episodes_count_label">Antal nya episoder</string>
- <string name="in_progress_episodes_count_label">Antal episoder du har börjat lyssna på</string>
<string name="drag_handle_content_description">Dra för att ändra dess position</string>
<string name="load_next_page_label">Ladda nästa sida</string>
<!--Feed information screen-->
@@ -514,8 +524,8 @@
<!--AntennaPodSP-->
<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>
+ <!--Episodes apply actions-->
<string name="all_label">Alla</string>
<string name="selected_all_label">Välj alla Episoder</string>
<string name="none_label">Inga</string>
@@ -532,7 +542,7 @@
<string name="selected_queued_label">Valde köade Episoder</string>
<string name="not_queued_label">Ej köad</string>
<string name="selected_not_queued_label">Välj ej köade Episoder</string>
- <string name="sort_title"><b>Sortera efter…</b></string>
+ <!--Sort-->
<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>
@@ -554,4 +564,32 @@
<string name="audio_effects">Ljudeffekter</string>
<string name="stereo_to_mono">Nedmixning: Stereo till mono</string>
<string name="sonic_only">Bara Sonic</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Typ</string>
+ <string name="host_label">Värd</string>
+ <string name="port_label">Port</string>
+ <string name="optional_hint">(Valfritt)</string>
+ <string name="proxy_test_label">Test</string>
+ <string name="proxy_checking">Kontrollerar…</string>
+ <string name="proxy_test_successful">Testet lyckades</string>
+ <string name="proxy_test_failed">Testet misslyckades</string>
+ <string name="proxy_host_empty_error">Värd måste fyllas i</string>
+ <string name="proxy_host_invalid_error">Värd är inte en giltig IP adress eller domän</string>
+ <string name="proxy_port_invalid_error">Porten är inte giltig</string>
+ <!--Casting-->
+ <string name="cast_media_route_menu_title">Spela på...</string>
+ <string name="cast_disconnect_label">Koppla loss castningen</string>
+ <string name="cast_not_castable">Vald media är inte kompatibelt med cast-enheten</string>
+ <string name="cast_failed_to_play">Misslyckades att starta uppspelningen av media</string>
+ <string name="cast_failed_to_stop">Misslyckades att stoppa uppspelning av media</string>
+ <string name="cast_failed_to_pause">Misslyckades att pausa uppspelningen av media</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Misslyckades att ändra volymen</string>
+ <string name="cast_failed_no_connection">Det finns ingen koppling till cast-enheten</string>
+ <string name="cast_failed_no_connection_trans">Kopplingen till cast-enheten tappades. Applikationen försöker att återansluta, om det är möjligt. Vänta en stund och försök igen.</string>
+ <string name="cast_failed_perform_action">Misslyckades att utföra åtgärden</string>
+ <string name="cast_failed_status_request">Misslyckades att synkronisera med cast-enheten</string>
+ <string name="cast_failed_seek">Misslyckades att söka till den nya positionen på cast-enheten</string>
+ <string name="cast_failed_receiver_player_error">Mottagande uppspelaren har stött på ett allvarligt fel</string>
+ <string name="cast_failed_media_error_skipping">Fel vid uppspelning av media. Hoppar över...</string>
</resources>
diff --git a/core/src/main/res/values-tr/strings.xml b/core/src/main/res/values-tr/strings.xml
index 6dca19d57..c57eb0663 100644
--- a/core/src/main/res/values-tr/strings.xml
+++ b/core/src/main/res/values-tr/strings.xml
@@ -25,6 +25,7 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Son yayınlananlar</string>
<string name="episode_filter_label">Sadece yeni bölümleri göster</string>
+ <!--Statistics fragment-->
<!--Main activity-->
<string name="drawer_open">Münüyü aç</string>
<string name="drawer_close">Menüyü kapat</string>
@@ -354,8 +355,6 @@
<string name="opml_import_label">OPML içe aktar</string>
<string name="opml_directory_error">HATA!</string>
<string name="reading_opml_label">OPML dosyası okunuyor</string>
- <string name="opml_reader_error">OPML dosyası okunurken bir hata oluştu:</string>
- <string name="opml_import_error_dir_empty">İça aktarma dizini boş</string>
<string name="select_all_label">Hepsini seç</string>
<string name="deselect_all_label">Tüm seçimleri geri al</string>
<string name="choose_file_from_filesystem">Yerel dosya sisteminden</string>
@@ -493,4 +492,5 @@
<string name="rating_later_label">Daha sonra hatırlat</string>
<string name="rating_now_label">Evet, şimdi yapalım!</string>
<!--Audio controls-->
+ <!--proxy settings-->
</resources>
diff --git a/core/src/main/res/values-uk-rUA/strings.xml b/core/src/main/res/values-uk-rUA/strings.xml
index f0970a6cd..168945c28 100644
--- a/core/src/main/res/values-uk-rUA/strings.xml
+++ b/core/src/main/res/values-uk-rUA/strings.xml
@@ -3,6 +3,7 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Канали</string>
+ <string name="statistics_label">Статистика</string>
<string name="add_feed_label">Додати подкаст</string>
<string name="podcasts_label">Подкасти</string>
<string name="episodes_label">Епізоди</string>
@@ -28,6 +29,9 @@
<!--New episodes fragment-->
<string name="recently_published_episodes_label">Щойно опубліковано</string>
<string name="episode_filter_label">Показати тількі нові епізоди</string>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">Загальний час прослуханих подкастів:</string>
+ <string name="statistics_details_dialog">%1$d з %2$d епізодів почато.\n\nПрослухано %3$s з %4$s.</string>
<!--Main activity-->
<string name="drawer_open">Показати меню</string>
<string name="drawer_close">Сховати меню</string>
@@ -125,6 +129,7 @@
<string name="hide_not_downloaded_episodes_label">Не завантажені</string>
<string name="filtered_label">Фільтровані</string>
<string name="refresh_failed_msg">{fa-exclamation-circle} Останнє оновлення було невдалим</string>
+ <string name="open_podcast">Відкрити подкаст</string>
<!--actions on feeditems-->
<string name="download_label">Завантажити</string>
<string name="play_label">Грати</string>
@@ -167,6 +172,7 @@
<string name="download_error_connection_error">Помилка з\'єднання</string>
<string name="download_error_unknown_host">Невідомий host</string>
<string name="download_error_unauthorized">Помилка автентифікації</string>
+ <string name="download_error_file_type_type">Помилка типа файла</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>
@@ -262,6 +268,7 @@
<string name="no_items_label">Нічого в цьому списку</string>
<string name="no_feeds_label">Немає підписаних каналів </string>
<string name="no_chapters_label">В цьому епізоді немає розділів.</string>
+ <string name="no_shownotes_label">До цього епізода немає нотаток.</string>
<!--Preferences-->
<string name="other_pref">Інше</string>
<string name="about_pref">Про програму</string>
@@ -341,6 +348,10 @@
<string name="pref_gpodnet_logout_toast">Успішно закрили доступ</string>
<string name="pref_gpodnet_setlogin_information_title">Змінити інформацію для входу</string>
<string name="pref_gpodnet_setlogin_information_sum">Змінити інформацію щодо облікового запису gpodder.net</string>
+ <string name="pref_gpodnet_sync_title">Cинхронізувати зараз</string>
+ <string name="pref_gpodnet_sync_sum">Cинхронізувати підписки та стан епізодів з сервісом gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Cинхронізація почалась</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Ви увійшли як <i>%1$s</i> з пристроя <i>%2$s</i>]]></string>
<string name="pref_playback_speed_title">Швидкість програвання</string>
<string name="pref_playback_speed_sum">Налаштування швідкості доступно для змінної швидкості програвання</string>
<string name="pref_fast_forward">Час перемотки вперед</string>
@@ -368,6 +379,10 @@
<string name="pref_sonic_title">Програвач Sonic</string>
<string name="pref_sonic_message">Застосувати вбудований програвач sonic замість програвача Android та Prestissimo</string>
<string name="pref_current_value">Поточне значення: %1$s</string>
+ <string name="pref_proxy_title">Проксі</string>
+ <string name="pref_proxy_sum">Застосувати проксі сервер</string>
+ <string name="pref_known_issues">Відомі проблеми</string>
+ <string name="pref_no_browser_found">Веб-браузер не знайдено.\"</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Включити автоматичне заохочення авторів через сервіс flattr</string>
<string name="auto_flattr_after_percent">Заохотити автора через Flattr щойно %d відсотків епізода було відтворено</string>
@@ -390,8 +405,8 @@
<string name="opml_import_label">OPML імпорт</string>
<string name="opml_directory_error">Помилка!</string>
<string name="reading_opml_label">Читаємо OPML файл</string>
- <string name="opml_reader_error">Трапилась помілка коли читали OPML документ:</string>
- <string name="opml_import_error_dir_empty">Директорія імпорту пуста</string>
+ <string name="opml_reader_error">Помилка при читанні документа OPML:</string>
+ <string name="opml_import_error_no_file">Жодного файла не обрано!</string>
<string name="select_all_label">Обрати все</string>
<string name="deselect_all_label">Убрати виділення</string>
<string name="select_options_label">Обрати…</string>
@@ -559,4 +574,16 @@
<string name="audio_effects">Аудіоефекти</string>
<string name="stereo_to_mono">Зробити моно із стерео</string>
<string name="sonic_only">Тільки Sonic</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">Тип</string>
+ <string name="host_label">Хост</string>
+ <string name="port_label">Порт</string>
+ <string name="optional_hint">(Необов\'язково)</string>
+ <string name="proxy_test_label">Тест</string>
+ <string name="proxy_checking">Перевіряється...</string>
+ <string name="proxy_test_successful">Протестовано успішно</string>
+ <string name="proxy_test_failed">Протестовано з помилками</string>
+ <string name="proxy_host_empty_error">Хост не можете бути пустим</string>
+ <string name="proxy_host_invalid_error">Хост не є правильною IP-адресою або доменним ім\'ям</string>
+ <string name="proxy_port_invalid_error">Неправильний порт</string>
</resources>
diff --git a/core/src/main/res/values-vi-rVN/strings.xml b/core/src/main/res/values-vi-rVN/strings.xml
new file mode 100644
index 000000000..28dfeb6e8
--- /dev/null
+++ b/core/src/main/res/values-vi-rVN/strings.xml
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <!--'Add Feed' Activity labels-->
+ <!--Actions on feeds-->
+ <!--actions on feeditems-->
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-vi/strings.xml b/core/src/main/res/values-vi/strings.xml
new file mode 100644
index 000000000..a900b1217
--- /dev/null
+++ b/core/src/main/res/values-vi/strings.xml
@@ -0,0 +1,65 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="feeds_label">Các feed</string>
+ <string name="new_label">Tạo mới</string>
+ <string name="settings_label">Thiết lập</string>
+ <string name="downloads_label">Tải về</string>
+ <!--Statistics fragment-->
+ <!--Main activity-->
+ <!--Webview actions-->
+ <!--Playback history-->
+ <!--Other-->
+ <string name="confirm_label">Xác nhận</string>
+ <string name="cancel_label">Hủy bỏ</string>
+ <string name="author_label">Tác giả</string>
+ <string name="language_label">Ngôn ngữ</string>
+ <string name="error_label">Lỗi</string>
+ <string name="refresh_label">Cập nhật lại</string>
+ <string name="external_storage_error_msg">Hiện không có thiết bị lưu trữ gắn ngoài nào. Hãy chắc rằng bạn đã kết nối máy của mình với thiết bị lưu trữ gắn ngoài để ứng dụng có thể được hoạt động trơn tru.</string>
+ <string name="chapters_label">Chương</string>
+ <string name="shownotes_label">Hiển thị ghi chú</string>
+ <string name="episodes_suffix">\u0020tập</string>
+ <string name="length_prefix">Độ dài:\u0020</string>
+ <string name="size_prefix">Kích thước:\u0020</string>
+ <string name="processing_label">Đang xử lý</string>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">Đường dẫn liên kết feed</string>
+ <!--Actions on feeds-->
+ <string name="show_info_label">Hiển thị thông ti</string>
+ <string name="feed_delete_confirmation_msg">Xin vui lòng xác nhận rằng bạn muốn xóa feed này và toàn bộ các phần khác của feed này mà bạn đã tải về.</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Tải về</string>
+ <string name="play_label">Phát</string>
+ <string name="stream_label">Phân luồng</string>
+ <string name="remove_label">Loại bỏ</string>
+ <string name="add_to_queue_label">Thêm vào hàng đợi</string>
+ <string name="remove_from_queue_label">Loại bỏ khỏi hàng đợi</string>
+ <string name="visit_website_label">Truy cập website</string>
+ <!--Download messages and labels-->
+ <!--Mediaplayer messages-->
+ <!--Queue operations-->
+ <!--Flattr-->
+ <!--Flattr-->
+ <!--Variable Speed-->
+ <!--Empty list labels-->
+ <!--Preferences-->
+ <!--Auto-Flattr dialog-->
+ <!--Search-->
+ <!--OPML import and export-->
+ <!--Sleep timer-->
+ <!--gpodder.net-->
+ <!--Directory chooser-->
+ <!--Online feed view-->
+ <!--Content descriptions for image buttons-->
+ <!--Feed information screen-->
+ <!--Progress information-->
+ <!--AntennaPodSP-->
+ <!--Episodes apply actions-->
+ <!--Sort-->
+ <!--Rating dialog-->
+ <!--Audio controls-->
+ <!--proxy settings-->
+ <!--Casting-->
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+</resources>
diff --git a/core/src/main/res/values-zh-rCN/strings.xml b/core/src/main/res/values-zh-rCN/strings.xml
index 78017b383..7a1603bba 100644
--- a/core/src/main/res/values-zh-rCN/strings.xml
+++ b/core/src/main/res/values-zh-rCN/strings.xml
@@ -22,9 +22,13 @@
<string name="playback_history_label">播放历史</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net 登录</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>
+ <!--Statistics fragment-->
+ <string name="total_time_listened_to_podcasts">总播放时长:</string>
<!--Main activity-->
<string name="drawer_open">打开菜单</string>
<string name="drawer_close">关闭菜单</string>
@@ -66,6 +70,7 @@
<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>
@@ -76,6 +81,7 @@
<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>
@@ -97,6 +103,7 @@
<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_link_with_position_label">分享网站链接与位置</string>
<string name="share_feed_url_label">分享订阅地址</string>
@@ -116,6 +123,7 @@
<string name="hide_not_downloaded_episodes_label">未下载</string>
<string name="filtered_label">已过滤的</string>
<string name="refresh_failed_msg">{fa-exclamation-circle} 上次刷新失败</string>
+ <string name="open_podcast">打开博客</string>
<!--actions on feeditems-->
<string name="download_label">下载</string>
<string name="play_label">播放</string>
@@ -131,7 +139,9 @@
<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>
@@ -156,6 +166,7 @@
<string name="download_error_connection_error">链接错误</string>
<string name="download_error_unknown_host">未知主机</string>
<string name="download_error_unauthorized">认证错误</string>
+ <string name="download_error_file_type_type">文件类型错误</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>
@@ -165,6 +176,9 @@
<string name="download_error_io_error">IO 错误</string>
<string name="download_error_request_error">请求出错</string>
<string name="download_error_db_access">数据库访问错误</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>
@@ -176,6 +190,7 @@
<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_enable_temporarily">暂时允许</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">错误!</string>
<string name="player_stopped_msg">没有可播放媒体</string>
@@ -192,6 +207,8 @@
<!--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>
@@ -237,13 +254,17 @@
<!--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_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>
@@ -256,11 +277,14 @@
<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">蓝牙重新连接</string>
<string name="pref_mobileUpdate_title">数据网络时更新</string>
<string name="pref_mobileUpdate_sum">允许移动数据网络情况下进行数据链接</string>
<string name="refreshing_label">刷新中</string>
@@ -305,6 +329,8 @@
<string name="pref_gpodnet_logout_toast">注销成功</string>
<string name="pref_gpodnet_setlogin_information_title">改变登录信息</string>
<string name="pref_gpodnet_setlogin_information_sum">改变 gpodder.net 账户登录信息.</string>
+ <string name="pref_gpodnet_sync_title">正在同步</string>
+ <string name="pref_gpodnet_sync_started">已开始同步</string>
<string name="pref_playback_speed_title">播放速度</string>
<string name="pref_playback_speed_sum">自定义音频播放速度</string>
<string name="pref_rewind">时间倒回</string>
@@ -326,6 +352,11 @@
<string name="crash_report_title">崩溃报告</string>
<string name="crash_report_sum">通过 E-mail 发送最后崩溃报告</string>
<string name="send_email">发送 E-mail</string>
+ <string name="pref_sonic_title">内置播放器</string>
+ <string name="pref_proxy_title">代理</string>
+ <string name="pref_proxy_sum">选择一个网络代理</string>
+ <string name="pref_known_issues">已知问题</string>
+ <string name="pref_no_browser_found">未找到浏览器</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">启用自动 flattring</string>
<string name="auto_flattr_after_percent">当播放到百分之%d时Flattr改曲目</string>
@@ -348,13 +379,15 @@
<string name="opml_import_label">OPML 导入</string>
<string name="opml_directory_error">错误!</string>
<string name="reading_opml_label">OPML 文件读取中</string>
- <string name="opml_reader_error">读取 OPML 文件内容出错:</string>
- <string name="opml_import_error_dir_empty">导入目录为空.</string>
+ <string name="opml_reader_error">读取OPML文档时发生错误</string>
+ <string name="opml_import_error_no_file">没有选择文件!</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="export_error_label">导出出错</string>
<string name="opml_export_success_title">OPML 导出成功.</string>
<string name="opml_export_success_sum">.opml 文件已保存到:\u0020</string>
@@ -366,6 +399,7 @@
<string name="time_left_label">计时剩余:\u0020</string>
<string name="time_dialog_invalid_input">无效的输入, 时间是一个整数</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>
@@ -430,6 +464,7 @@
<!--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>
@@ -452,11 +487,17 @@
<!--Feed information screen-->
<string name="authentication_label">验证</string>
<string name="authentication_descr">给本播客及曲目变更用户名及密码</string>
+ <string name="auto_download_settings_label">自动下载设置</string>
+ <string name="episode_filters_label">曲目过滤器</string>
+ <string name="episode_filters_include">包含</string>
+ <string name="episode_filters_exclude">排除</string>
+ <string name="keep_updated">保持最新</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="filter">过滤器</string>
<string name="all_label">全部</string>
<string name="selected_all_label">全选</string>
<string name="none_label">无</string>
@@ -469,6 +510,10 @@
<string name="selected_downloaded_label">选择已下载的曲目</string>
<string name="not_downloaded_label">未下载</string>
<string name="selected_not_downloaded_label">选择未下载的曲目</string>
+ <string name="queued_label">已在播放列表中</string>
+ <string name="selected_queued_label">已选曲目</string>
+ <string name="not_queued_label">不在播放列表中</string>
+ <string name="selected_not_queued_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>
@@ -481,4 +526,22 @@
<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">L</string>
+ <string name="right_short">R</string>
+ <string name="audio_effects">音频效果</string>
+ <!--proxy settings-->
+ <string name="proxy_type_label">类型</string>
+ <string name="host_label">主机地址</string>
+ <string name="port_label">端口</string>
+ <string name="optional_hint">(可选)</string>
+ <string name="proxy_test_label">测试</string>
+ <string name="proxy_checking">正在测试</string>
+ <string name="proxy_test_successful">测试成功</string>
+ <string name="proxy_test_failed">测试失败</string>
+ <string name="proxy_host_empty_error">主机地址不能为空</string>
+ <string name="proxy_host_invalid_error">主机地址不是有效IP或域名</string>
+ <string name="proxy_port_invalid_error">端口不可用</string>
</resources>
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 0de515292..fba22b985 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -146,9 +146,11 @@
<string-array name="nav_drawer_titles">
<item>@string/queue_label</item>
<item>@string/episodes_label</item>
+ <item>@string/subscriptions_label</item>
<item>@string/downloads_label</item>
<item>@string/playback_history_label</item>
<item>@string/add_feed_label</item>
+ <item>@string/subscriptions_list_label</item>
</string-array>
<string-array name="nav_drawer_feed_order_options">
@@ -211,4 +213,9 @@
<item>500</item>
</string-array>
+ <string-array name="compact_notification_buttons_options">
+ <item>@string/rewind_label</item>
+ <item>@string/fast_forward_label</item>
+ <item>@string/skip_episode_label</item>
+ </string-array>
</resources>
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index 2d3379d95..04ad7ea64 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -19,17 +19,15 @@
<attr name="navigation_expand" format="reference"/>
<attr name="navigation_refresh" format="reference"/>
<attr name="navigation_up" format="reference"/>
- <attr name="navigation_shownotes" format="reference"/>
- <attr name="navigation_chapters" format="reference"/>
<attr name="social_share" format="reference"/>
<attr name="stat_playlist" format="reference"/>
+ <attr name="ic_folder" format="reference"/>
<attr name="type_audio" format="reference"/>
<attr name="type_video" format="reference"/>
<attr name="borderless_button" format="reference"/>
<attr name="overlay_drawable" format="reference"/>
<attr name="dragview_background" format="reference"/>
<attr name="dragview_float_background" format="reference"/>
- <attr name="ic_action_overflow" format="reference"/>
<attr name="ic_new" format="reference"/>
<attr name="ic_history" format="reference"/>
<attr name="av_play_big" format="reference"/>
@@ -52,6 +50,7 @@
<attr name="ic_sort" format="reference"/>
<attr name="ic_sd_storage" format="reference"/>
<attr name="ic_create_new_folder" format="reference"/>
+ <attr name="ic_cast_disconnect" format="reference"/>
<!-- Used in itemdescription -->
<attr name="non_transparent_background" format="reference"/>
diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml
index ff061be4c..a570a3fcb 100644
--- a/core/src/main/res/values/colors.xml
+++ b/core/src/main/res/values/colors.xml
@@ -31,4 +31,6 @@
<color name="highlight_light">#DDDDDD</color>
<color name="highlight_dark">#414141</color>
+ <color name="antennapod_blue">#147BAF</color>
+
</resources>
diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml
index 9aafd14e3..4f549efd7 100644
--- a/core/src/main/res/values/dimens.xml
+++ b/core/src/main/res/values/dimens.xml
@@ -30,6 +30,7 @@
<dimen name="listitem_threeline_horizontalpadding">16dp</dimen>
<dimen name="list_vertical_padding">8dp</dimen>
+ <dimen name="minimum_text_margin">8dp</dimen>
<dimen name="listitem_icon_leftpadding">16dp</dimen>
<dimen name="listitem_icon_rightpadding">16dp</dimen>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 912a60f55..e31d60ce8 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -4,23 +4,22 @@
tools:ignore="MissingTranslation">
<!-- Activitiy and fragment titles -->
- <string name="app_name">AntennaPod</string>
+ <string name="app_name" translate="false">AntennaPod</string>
<string name="feeds_label">Feeds</string>
+ <string name="statistics_label">Statistics</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="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="settings_label">Settings</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="subscriptions_label">Subscriptions</string>
+ <string name="subscriptions_list_label">Subscriptions List</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>
@@ -29,9 +28,9 @@
<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>
+ <!-- Statistics fragment -->
+ <string name="total_time_listened_to_podcasts">Total time of podcasts played:</string>
+ <string name="statistics_details_dialog">%1$d out of %2$d episodes started.\n\nPlayed %3$s out of %4$s.</string>
<!-- Main activity -->
<string name="drawer_open">Open menu</string>
@@ -84,9 +83,9 @@
<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="auto_delete_label">Auto Delete Episode</string>
<string name="parallel_downloads_suffix">\u0020parallel downloads</string>
- <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_global">Global default</string>
<string name="feed_auto_download_always">Always</string>
<string name="feed_auto_download_never">Never</string>
<string name="send_label">Send&#8230;</string>
@@ -118,8 +117,8 @@
<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="share_item_url_label">Share Episode File URL</string>
+ <string name="share_item_url_with_position_label">Share Episode File 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>
@@ -134,6 +133,7 @@
<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="open_podcast">Open Podcast</string>
<!-- actions on feeditems -->
<string name="download_label">Download</string>
@@ -155,8 +155,6 @@
<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>
@@ -178,6 +176,8 @@
<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_file_type_type">File Type Error</string>
+ <string name="download_error_forbidden">Forbidden</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>
@@ -216,7 +216,7 @@
<string name="playback_error_server_died">Server died</string>
<string name="playback_error_unknown">Unknown Error</string>
<string name="no_media_playing_label">No media playing</string>
- <string name="position_default_label">00:00:00</string>
+ <string name="position_default_label" translate="false">00:00:00</string>
<string name="player_buffering_msg">Buffering</string>
<string name="playbackservice_notification_title">Playing podcast</string>
<string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string>
@@ -278,8 +278,11 @@
<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>
+ <string name="no_shownotes_label">This episode has no shownotes.</string>
<!-- Preferences -->
+ <string name="storage_pref">Storage</string>
+ <string name="project_pref">Project</string>
<string name="other_pref">Other</string>
<string name="about_pref">About</string>
<string name="queue_label">Queue</string>
@@ -358,6 +361,10 @@
<string name="pref_gpodnet_logout_toast">Logout was successful</string>
<string name="pref_gpodnet_setlogin_information_title">Change login information</string>
<string name="pref_gpodnet_setlogin_information_sum">Change the login information for your gpodder.net account.</string>
+ <string name="pref_gpodnet_sync_title">Sync now</string>
+ <string name="pref_gpodnet_sync_sum">Sync subscriptions and episode states with gpodder.net</string>
+ <string name="pref_gpodnet_sync_started">Sync started</string>
+ <string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string>
<string name="pref_playback_speed_title">Playback Speeds</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
<string name="pref_fast_forward">Fast forward time</string>
@@ -368,6 +375,12 @@
<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_sum">Keep notification and lockscreen controls when playback is paused.</string>
+ <string name="pref_compact_notification_buttons_title">Set Lockscreen Buttons</string>
+ <string name="pref_compact_notification_buttons_sum">Change the playback buttons on the lockscreen. The play/pause button is always included.</string>
+ <string name="pref_compact_notification_buttons_dialog_title">Select a maximum of %1$d items</string>
+ <string name="pref_compact_notification_buttons_dialog_error">You can only select a maximum of %1$d items.</string>
+ <string name="pref_show_subscriptions_in_drawer_title">Show Subscriptions</string>
+ <string name="pref_show_subscriptions_in_drawer_sum">Show subscription list directly in navigation drawer</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>
@@ -385,6 +398,13 @@
<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>
+ <string name="pref_proxy_title">Proxy</string>
+ <string name="pref_proxy_sum">Set a network proxy</string>
+ <string name="pref_faq">FAQ</string>
+ <string name="pref_known_issues">Known issues</string>
+ <string name="pref_no_browser_found">No web browser found.</string>
+ <string name="pref_cast_title">Chromecast support</string>
+ <string name="pref_cast_message">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string>
<!-- Auto-Flattr dialog -->
<string name="auto_flattr_enable">Enable automatic flattring</string>
@@ -409,8 +429,8 @@
<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="opml_reader_error">An error has occurred while reading the OPML document:</string>
+ <string name="opml_import_error_no_file">No file selected!</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>
@@ -468,6 +488,7 @@
<string name="gpodnetauth_device_chooseExistingDevice">Choose existing device:</string>
<string name="gpodnetauth_device_errorEmpty">Device ID must not be empty</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Device ID already in use</string>
+ <string name="gpodnetauth_device_caption_errorEmpty">Caption must not be empty</string>
<string name="gpodnetauth_device_butChoose">Choose</string>
<string name="gpodnetauth_finish_title">Login successful!</string>
@@ -503,29 +524,19 @@
<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&#8230;</string>
<!-- Content descriptions for image buttons -->
- <string name="show_chapters_label">Show chapters</string>
- <string name="show_shownotes_label">Show shownotes</string>
- <string name="show_cover_label">Show picture</string>
<string name="rewind_label">Rewind</string>
<string name="fast_forward_label">Fast forward</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Navigate upwards</string>
- <string name="butAction_label">More actions</string>
- <string name="status_playing_label">Episode is being played</string>
<string name="status_downloading_label">Episode is being downloaded</string>
- <string name="status_downloaded_label">Episode is downloaded</string>
- <string name="status_unread_label">Item is new</string>
<string name="in_queue_label">Episode is in the queue</string>
- <string name="new_episodes_count_label">Number of new episodes</string>
- <string name="in_progress_episodes_count_label">Number of episodes you have started listening to</string>
<string name="drag_handle_content_description">Drag to change the position of this item</string>
<string name="load_next_page_label">Load next page</string>
@@ -544,12 +555,12 @@
<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="search_itunes_label">Search iTunes</string>
<string name="filter">Filter</string>
+
+ <!-- Episodes apply actions -->
<string name="all_label">All</string>
<string name="selected_all_label">Selected all Episodes</string>
<string name="none_label">None</string>
@@ -566,7 +577,8 @@
<string name="selected_queued_label">Selected queued Episodes</string>
<string name="not_queued_label">Not queued</string>
<string name="selected_not_queued_label">Selected not queued Episodes</string>
- <string name="sort_title"><b>Sort by&#8230;</b></string>
+
+ <!-- Sort -->
<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>
@@ -591,4 +603,33 @@
<string name="stereo_to_mono">Downmix: Stereo to mono</string>
<string name="sonic_only">Sonic only</string>
+ <!-- proxy settings -->
+ <string name="proxy_type_label">Type</string>
+ <string name="host_label">Host</string>
+ <string name="port_label">Port</string>
+ <string name="optional_hint">(Optional)</string>
+ <string name="proxy_test_label">Test</string>
+ <string name="proxy_checking">Checking&#8230;</string>
+ <string name="proxy_test_successful">Test successful</string>
+ <string name="proxy_test_failed">Test failed</string>
+ <string name="proxy_host_empty_error">Host can not be empty</string>
+ <string name="proxy_host_invalid_error">Host is not a valid IP address or domain</string>
+ <string name="proxy_port_invalid_error">Port not valid</string>
+
+ <!-- Casting -->
+ <string name="cast_media_route_menu_title">Play on&#8230;</string>
+ <string name="cast_disconnect_label">Disconnect the cast session</string>
+ <string name="cast_not_castable">Media selected is not compatible with cast device</string>
+ <string name="cast_failed_to_play">Failed to start the playback of media</string>
+ <string name="cast_failed_to_stop">Failed to stop the playback of media</string>
+ <string name="cast_failed_to_pause">Failed to pause the playback of media</string>
+ <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
+ <string name="cast_failed_setting_volume">Failed to set the volume</string>
+ <string name="cast_failed_no_connection">No connection to the cast device is present</string>
+ <string name="cast_failed_no_connection_trans">Connection to the cast device has been lost. Application is trying to re-establish the connection, if possible. Please wait for a few seconds and try again.</string>
+ <string name="cast_failed_perform_action">Failed to perform the action</string>
+ <string name="cast_failed_status_request">Failed to sync up with the cast device</string>
+ <string name="cast_failed_seek">Failed to seek to the new position on the cast device</string>
+ <string name="cast_failed_receiver_player_error">Receiver player has encountered a severe error</string>
+ <string name="cast_failed_media_error_skipping">Error playing media. Skipping&#8230;</string>
</resources>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index d79ba6b45..6a4dc4781 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -25,8 +25,6 @@
<item name="attr/navigation_expand">@drawable/ic_expand_more_grey600_36dp</item>
<item name="attr/navigation_refresh">@drawable/ic_refresh_grey600_24dp</item>
<item name="attr/navigation_up">@drawable/navigation_up</item>
- <item name="attr/navigation_shownotes">@drawable/ic_description_grey600_36dp</item>
- <item name="attr/navigation_chapters">@drawable/ic_toc_grey600_36dp</item>
<item name="attr/social_share">@drawable/ic_share_grey600_24dp</item>
<item name="attr/stat_playlist">@drawable/ic_list_grey600_24dp</item>
<item name="attr/type_audio">@drawable/ic_hearing_grey600_18dp</item>
@@ -37,9 +35,9 @@
<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>
<item name="attr/ic_new">@drawable/ic_new_releases_grey600_24dp</item>
<item name="attr/ic_history">@drawable/ic_history_grey600_24dp</item>
+ <item name="attr/ic_folder">@drawable/ic_folder_grey600_24dp</item>
<item name="attr/av_play_big">@drawable/ic_play_arrow_grey600_36dp</item>
<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>
@@ -59,6 +57,7 @@
<item name="attr/ic_sort">@drawable/ic_sort_grey600_24dp</item>
<item name="attr/ic_sd_storage">@drawable/ic_sd_storage_grey600_36dp</item>
<item name="attr/ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item>
+ <item name="attr/ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item>
</style>
<style name="Theme.AntennaPod.Dark" parent="Theme.AppCompat">
@@ -85,8 +84,6 @@
<item name="attr/navigation_expand">@drawable/ic_expand_more_white_36dp</item>
<item name="attr/navigation_refresh">@drawable/ic_refresh_white_24dp</item>
<item name="attr/navigation_up">@drawable/navigation_up_dark</item>
- <item name="attr/navigation_shownotes">@drawable/ic_description_white_36dp</item>
- <item name="attr/navigation_chapters">@drawable/ic_toc_white_36dp</item>
<item name="attr/social_share">@drawable/ic_share_white_24dp</item>
<item name="attr/stat_playlist">@drawable/ic_list_white_24dp</item>
<item name="attr/type_audio">@drawable/ic_hearing_white_18dp</item>
@@ -97,9 +94,9 @@
<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>
<item name="attr/ic_new">@drawable/ic_new_releases_white_24dp</item>
<item name="attr/ic_history">@drawable/ic_history_white_24dp</item>
+ <item name="attr/ic_folder">@drawable/ic_folder_white_24dp</item>
<item name="attr/av_play_big">@drawable/ic_play_arrow_white_36dp</item>
<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>
@@ -119,6 +116,7 @@
<item name="attr/ic_sort">@drawable/ic_sort_white_24dp</item>
<item name="attr/ic_sd_storage">@drawable/ic_sd_storage_white_36dp</item>
<item name="attr/ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item>
+ <item name="attr/ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item>
</style>
<style name="Theme.AntennaPod.Light.NoTitle" parent="Theme.AppCompat.Light.NoActionBar">
@@ -146,8 +144,6 @@
<item name="attr/navigation_expand">@drawable/ic_expand_more_grey600_36dp</item>
<item name="attr/navigation_refresh">@drawable/ic_refresh_grey600_24dp</item>
<item name="attr/navigation_up">@drawable/navigation_up</item>
- <item name="attr/navigation_shownotes">@drawable/ic_description_grey600_36dp</item>
- <item name="attr/navigation_chapters">@drawable/ic_toc_grey600_36dp</item>
<item name="attr/social_share">@drawable/ic_share_grey600_24dp</item>
<item name="attr/stat_playlist">@drawable/ic_list_grey600_24dp</item>
<item name="attr/type_audio">@drawable/ic_hearing_grey600_18dp</item>
@@ -158,9 +154,9 @@
<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>
<item name="attr/ic_new">@drawable/ic_new_releases_grey600_24dp</item>
<item name="attr/ic_history">@drawable/ic_history_grey600_24dp</item>
+ <item name="attr/ic_folder">@drawable/ic_folder_grey600_24dp</item>
<item name="attr/av_play_big">@drawable/ic_play_arrow_grey600_36dp</item>
<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>
@@ -180,6 +176,7 @@
<item name="attr/ic_sort">@drawable/ic_sort_grey600_24dp</item>
<item name="attr/ic_sd_storage">@drawable/ic_sd_storage_grey600_36dp</item>
<item name="attr/ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item>
+ <item name="attr/ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item>
</style>
<style name="Theme.AntennaPod.Dark.NoTitle" parent="Theme.AppCompat.NoActionBar">
@@ -207,8 +204,6 @@
<item name="attr/navigation_expand">@drawable/ic_expand_more_white_36dp</item>
<item name="attr/navigation_refresh">@drawable/ic_refresh_white_24dp</item>
<item name="attr/navigation_up">@drawable/navigation_up_dark</item>
- <item name="attr/navigation_shownotes">@drawable/ic_description_white_36dp</item>
- <item name="attr/navigation_chapters">@drawable/ic_toc_white_36dp</item>
<item name="attr/social_share">@drawable/ic_share_white_24dp</item>
<item name="attr/stat_playlist">@drawable/ic_list_white_24dp</item>
<item name="attr/type_audio">@drawable/ic_hearing_white_18dp</item>
@@ -219,9 +214,9 @@
<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>
<item name="attr/ic_new">@drawable/ic_new_releases_white_24dp</item>
<item name="attr/ic_history">@drawable/ic_history_white_24dp</item>
+ <item name="attr/ic_folder">@drawable/ic_folder_white_24dp</item>
<item name="attr/av_play_big">@drawable/ic_play_arrow_white_36dp</item>
<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>
@@ -241,6 +236,7 @@
<item name="attr/ic_sort">@drawable/ic_sort_white_24dp</item>
<item name="attr/ic_sd_storage">@drawable/ic_sd_storage_white_36dp</item>
<item name="attr/ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item>
+ <item name="attr/ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item>
</style>
<style name="Theme.AntennaPod.VideoPlayer" parent="@style/Theme.AntennaPod.Dark">