From 4e4e12496c63c96e11d375f7cf9ef49222c4d1ef Mon Sep 17 00:00:00 2001 From: Joshua Bahnsen <archrival@gmail.com> Date: Mon, 1 Apr 2013 18:16:45 -0700 Subject: [PATCH] Fix album art retrieval on main thread, search settings consistency, disable server side scaling randomize album art when viewing tracks, keep aspect ratio when scaling album art --- .classpath | 17 +- .gitattributes | 22 + .gitignore | 163 +++ AndroidManifest.xml | 4 +- .../sourceforge/subsonic/androidapp/R.java | 190 ++- lint.xml | 3 + res/values/arrays.xml | 99 +- res/values/strings.xml | 44 +- res/xml/settings.xml | 29 +- .../androidapp/activity/SearchActivity.java | 735 +++++----- .../activity/SelectAlbumActivity.java | 1285 +++++++++-------- .../activity/SubsonicTabActivity.java | 1102 +++++++------- .../service/DownloadServiceImpl.java | 13 +- .../service/OfflineMusicService.java | 2 +- .../androidapp/service/RESTMusicService.java | 94 +- .../subsonic/androidapp/util/Constants.java | 1 + .../subsonic/androidapp/util/FileUtil.java | 7 +- .../subsonic/androidapp/util/ImageLoader.java | 23 +- .../util/TabActivityBackgroundTask.java | 2 +- .../subsonic/androidapp/util/Util.java | 19 + 20 files changed, 2005 insertions(+), 1849 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 lint.xml diff --git a/.classpath b/.classpath index 2fbb5480..d3bb07b4 100644 --- a/.classpath +++ b/.classpath @@ -1,9 +1,8 @@ -<?xml version="1.0" encoding="UTF-8"?> -<classpath> - <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> - <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> - <classpathentry kind="src" path="src"/> - <classpathentry kind="src" path="gen"/> - <classpathentry kind="lib" path="D:/Data/Android/adt-bundle-windows-x86_64/sdk/tools/support/annotations.jar"/> - <classpathentry kind="output" path="bin/classes"/> -</classpath> +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="gen"/> + <classpathentry kind="output" path="bin/classes"/> +</classpath> diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..ab8ed3f3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +#* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5ebd21a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.vspscc +.builds +*.dotCover + +## TODO: If you have NuGet Package Restore enabled, uncomment this +#packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +# Mac crap +.DS_Store diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2f8c905e..95293178 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:a="http://schemas.android.com/apk/res/android" package="net.sourceforge.subsonic.androidapp" - a:versionCode="63" - a:versionName="3.9.9.22" a:installLocation="auto"> + a:versionCode="64" + a:versionName="3.9.9.23" a:installLocation="auto"> <uses-permission a:name="android.permission.INTERNET"/> <uses-permission a:name="android.permission.READ_PHONE_STATE"/> diff --git a/gen/net/sourceforge/subsonic/androidapp/R.java b/gen/net/sourceforge/subsonic/androidapp/R.java index e94bcc9c..9c9afb47 100644 --- a/gen/net/sourceforge/subsonic/androidapp/R.java +++ b/gen/net/sourceforge/subsonic/androidapp/R.java @@ -19,20 +19,14 @@ public final class R { public static final int bufferLengthValues=0x7f060008; public static final int cacheSizeNames=0x7f060005; public static final int cacheSizeValues=0x7f060004; - public static final int defaultSearchNames=0x7f060013; - public static final int defaultSearchValues=0x7f060012; - public static final int maxAlbumsNames=0x7f06000d; - public static final int maxAlbumsValues=0x7f06000c; - public static final int maxArtistsNames=0x7f060011; - public static final int maxArtistsValues=0x7f060010; public static final int maxBitrateNames=0x7f060007; public static final int maxBitrateValues=0x7f060006; - public static final int maxSongsNames=0x7f06000f; - public static final int maxSongsValues=0x7f06000e; public static final int networkTimeoutNames=0x7f06000b; public static final int networkTimeoutValues=0x7f06000a; public static final int preloadCountNames=0x7f060003; public static final int preloadCountValues=0x7f060002; + public static final int searchNames=0x7f06000d; + public static final int searchValues=0x7f06000c; public static final int themeNames=0x7f060001; public static final int themeValues=0x7f060000; } @@ -349,12 +343,12 @@ public final class R { public static final int select_album_n_songs_downloading=0x7f0a0001; } public static final class string { - public static final int background_task_loading=0x7f0900f0; - public static final int background_task_network_error=0x7f0900f2; - public static final int background_task_no_network=0x7f0900f1; - public static final int background_task_not_found=0x7f0900f3; - public static final int background_task_parse_error=0x7f0900f4; - public static final int background_task_wait=0x7f0900ef; + public static final int background_task_loading=0x7f0900e6; + public static final int background_task_network_error=0x7f0900e8; + public static final int background_task_no_network=0x7f0900e7; + public static final int background_task_not_found=0x7f0900e9; + public static final int background_task_parse_error=0x7f0900ea; + public static final int background_task_wait=0x7f0900e5; public static final int button_bar_browse=0x7f09000b; public static final int button_bar_home=0x7f09000a; public static final int button_bar_now_playing=0x7f09000e; @@ -402,9 +396,9 @@ public final class R { public static final int download_repeat_single=0x7f09005f; public static final int download_visualizer_off=0x7f090061; public static final int download_visualizer_on=0x7f090060; - public static final int equalizer_enabled=0x7f090101; - public static final int equalizer_label=0x7f090100; - public static final int equalizer_preset=0x7f090102; + public static final int equalizer_enabled=0x7f0900f7; + public static final int equalizer_label=0x7f0900f6; + public static final int equalizer_preset=0x7f0900f8; public static final int error_label=0x7f09006b; public static final int help_back=0x7f090027; public static final int help_close=0x7f090028; @@ -439,14 +433,14 @@ public final class R { /** <string name="settings.screen_lit_title">TODO: Keep screen on</string> <string name="settings.screen_lit_summary">TODO: Keeping the screen on when downloading may improve download speed</string> */ - public static final int music_service_retry=0x7f0900ee; - public static final int parser_artist_count=0x7f0900fc; - public static final int parser_not_authenticated=0x7f0900fa; - public static final int parser_not_authorized=0x7f0900fb; - public static final int parser_reading=0x7f0900f6; - public static final int parser_reading_done=0x7f0900f7; - public static final int parser_upgrade_client=0x7f0900f8; - public static final int parser_upgrade_server=0x7f0900f9; + public static final int music_service_retry=0x7f0900e4; + public static final int parser_artist_count=0x7f0900f2; + public static final int parser_not_authenticated=0x7f0900f0; + public static final int parser_not_authorized=0x7f0900f1; + public static final int parser_reading=0x7f0900ec; + public static final int parser_reading_done=0x7f0900ed; + public static final int parser_upgrade_client=0x7f0900ee; + public static final int parser_upgrade_server=0x7f0900ef; public static final int play_video_loading=0x7f09002b; public static final int play_video_noplugin=0x7f09002c; /** <string name="menu.exit">TODO: Exit</string> @@ -481,22 +475,22 @@ public final class R { public static final int select_album_play_all=0x7f09003f; public static final int select_album_searching=0x7f09003e; public static final int select_album_select=0x7f090039; - public static final int select_artist_all_folders=0x7f0900ff; - public static final int select_artist_folder=0x7f0900fe; - public static final int select_artist_refresh=0x7f0900fd; + public static final int select_artist_all_folders=0x7f0900f5; + public static final int select_artist_folder=0x7f0900f4; + public static final int select_artist_refresh=0x7f0900f3; public static final int select_playlist_empty=0x7f090047; - public static final int service_connecting=0x7f0900f5; + public static final int service_connecting=0x7f0900eb; public static final int settings_appearance_title=0x7f090080; - public static final int settings_buffer_length=0x7f0900b3; - public static final int settings_buffer_length_1=0x7f0900b5; - public static final int settings_buffer_length_10=0x7f0900b9; - public static final int settings_buffer_length_12=0x7f0900ba; - public static final int settings_buffer_length_15=0x7f0900bb; - public static final int settings_buffer_length_2=0x7f0900b6; - public static final int settings_buffer_length_20=0x7f0900bc; - public static final int settings_buffer_length_30=0x7f0900bd; - public static final int settings_buffer_length_5=0x7f0900b7; - public static final int settings_buffer_length_8=0x7f0900b8; + public static final int settings_buffer_length=0x7f0900b5; + public static final int settings_buffer_length_1=0x7f0900b7; + public static final int settings_buffer_length_10=0x7f0900bb; + public static final int settings_buffer_length_12=0x7f0900bc; + public static final int settings_buffer_length_15=0x7f0900bd; + public static final int settings_buffer_length_2=0x7f0900b8; + public static final int settings_buffer_length_20=0x7f0900be; + public static final int settings_buffer_length_30=0x7f0900bf; + public static final int settings_buffer_length_5=0x7f0900b9; + public static final int settings_buffer_length_8=0x7f0900ba; public static final int settings_cache_location=0x7f090078; public static final int settings_cache_location_error=0x7f090079; public static final int settings_cache_size=0x7f090077; @@ -512,37 +506,19 @@ public final class R { public static final int settings_cache_title=0x7f090075; public static final int settings_clear_search_history=0x7f0900a5; public static final int settings_connection_failure=0x7f09007d; - public static final int settings_default_albums=0x7f0900ec; - public static final int settings_default_artists=0x7f0900eb; - public static final int settings_default_songs=0x7f0900ed; - public static final int settings_hide_media_summary=0x7f0900ad; + public static final int settings_default_albums=0x7f0900e2; + public static final int settings_default_artists=0x7f0900e1; + public static final int settings_default_songs=0x7f0900e3; + public static final int settings_hide_media_summary=0x7f0900af; /** <string name="settings.scrobble_title">TODO: Scrobble to Last.fm</string> <string name="settings.scrobble_summary">TODO: Remember to set up your Last.fm user and password on the Subsonic server</string> */ - public static final int settings_hide_media_title=0x7f0900ac; - public static final int settings_hide_media_toast=0x7f0900ae; + public static final int settings_hide_media_title=0x7f0900ae; + public static final int settings_hide_media_toast=0x7f0900b0; public static final int settings_invalid_url=0x7f09007e; public static final int settings_invalid_username=0x7f09007f; - public static final int settings_max_albums=0x7f0900ce; - public static final int settings_max_albums_10=0x7f0900d0; - public static final int settings_max_albums_100=0x7f0900d5; - public static final int settings_max_albums_20=0x7f0900d1; - public static final int settings_max_albums_250=0x7f0900d6; - public static final int settings_max_albums_30=0x7f0900d2; - public static final int settings_max_albums_40=0x7f0900d3; - public static final int settings_max_albums_5=0x7f0900cf; - public static final int settings_max_albums_50=0x7f0900d4; - public static final int settings_max_albums_500=0x7f0900d7; - public static final int settings_max_artists=0x7f0900e1; - public static final int settings_max_artists_10=0x7f0900e4; - public static final int settings_max_artists_100=0x7f0900e8; - public static final int settings_max_artists_25=0x7f0900e5; - public static final int settings_max_artists_250=0x7f0900e9; - public static final int settings_max_artists_3=0x7f0900e2; - public static final int settings_max_artists_5=0x7f0900e3; - public static final int settings_max_artists_50=0x7f0900e6; - public static final int settings_max_artists_500=0x7f0900ea; - public static final int settings_max_artists_75=0x7f0900e7; + public static final int settings_max_albums=0x7f0900d0; + public static final int settings_max_artists=0x7f0900df; public static final int settings_max_bitrate_112=0x7f09008d; public static final int settings_max_bitrate_128=0x7f09008e; public static final int settings_max_bitrate_160=0x7f09008f; @@ -556,26 +532,18 @@ public final class R { public static final int settings_max_bitrate_mobile=0x7f090088; public static final int settings_max_bitrate_unlimited=0x7f090093; public static final int settings_max_bitrate_wifi=0x7f090087; - public static final int settings_max_songs=0x7f0900d8; - public static final int settings_max_songs_10=0x7f0900da; - public static final int settings_max_songs_100=0x7f0900de; - public static final int settings_max_songs_25=0x7f0900db; - public static final int settings_max_songs_250=0x7f0900df; - public static final int settings_max_songs_5=0x7f0900d9; - public static final int settings_max_songs_50=0x7f0900dc; - public static final int settings_max_songs_500=0x7f0900e0; - public static final int settings_max_songs_75=0x7f0900dd; - public static final int settings_media_button_summary=0x7f0900b0; - public static final int settings_media_button_title=0x7f0900af; - public static final int settings_network_timeout=0x7f0900b4; - public static final int settings_network_timeout_105000=0x7f0900c4; - public static final int settings_network_timeout_120000=0x7f0900c5; - public static final int settings_network_timeout_15000=0x7f0900be; - public static final int settings_network_timeout_30000=0x7f0900bf; - public static final int settings_network_timeout_45000=0x7f0900c0; - public static final int settings_network_timeout_60000=0x7f0900c1; - public static final int settings_network_timeout_75000=0x7f0900c2; - public static final int settings_network_timeout_90000=0x7f0900c3; + public static final int settings_max_songs=0x7f0900e0; + public static final int settings_media_button_summary=0x7f0900b2; + public static final int settings_media_button_title=0x7f0900b1; + public static final int settings_network_timeout=0x7f0900b6; + public static final int settings_network_timeout_105000=0x7f0900c6; + public static final int settings_network_timeout_120000=0x7f0900c7; + public static final int settings_network_timeout_15000=0x7f0900c0; + public static final int settings_network_timeout_30000=0x7f0900c1; + public static final int settings_network_timeout_45000=0x7f0900c2; + public static final int settings_network_timeout_60000=0x7f0900c3; + public static final int settings_network_timeout_75000=0x7f0900c4; + public static final int settings_network_timeout_90000=0x7f0900c5; public static final int settings_network_title=0x7f090086; public static final int settings_other_title=0x7f0900a7; public static final int settings_playback_control_title=0x7f0900a9; @@ -586,25 +554,41 @@ public final class R { public static final int settings_preload_3=0x7f090098; public static final int settings_preload_5=0x7f090099; public static final int settings_preload_unlimited=0x7f09009b; - public static final int settings_screen_lit_summary=0x7f0900b2; - public static final int settings_screen_lit_title=0x7f0900b1; + public static final int settings_screen_lit_summary=0x7f0900b4; + public static final int settings_screen_lit_title=0x7f0900b3; public static final int settings_scrobble_summary=0x7f0900ab; public static final int settings_scrobble_title=0x7f0900aa; + public static final int settings_search_1=0x7f0900d1; + public static final int settings_search_10=0x7f0900d4; + public static final int settings_search_100=0x7f0900dc; + public static final int settings_search_15=0x7f0900d5; + public static final int settings_search_20=0x7f0900d6; + public static final int settings_search_25=0x7f0900d7; + public static final int settings_search_250=0x7f0900dd; + public static final int settings_search_3=0x7f0900d2; + public static final int settings_search_30=0x7f0900d8; + public static final int settings_search_40=0x7f0900d9; + public static final int settings_search_5=0x7f0900d3; + public static final int settings_search_50=0x7f0900da; + public static final int settings_search_500=0x7f0900de; + public static final int settings_search_75=0x7f0900db; public static final int settings_search_history_cleared=0x7f0900a6; public static final int settings_search_title=0x7f0900a8; public static final int settings_server_address=0x7f090072; public static final int settings_server_name=0x7f090071; public static final int settings_server_password=0x7f090074; + public static final int settings_server_scaling_summary=0x7f0900ad; + public static final int settings_server_scaling_title=0x7f0900ac; public static final int settings_server_unused1=0x7f09006f; public static final int settings_server_unused2=0x7f090070; public static final int settings_server_username=0x7f090073; public static final int settings_servers_title=0x7f09006e; - public static final int settings_show_lockscreen_controls=0x7f0900ca; - public static final int settings_show_lockscreen_controls_summary=0x7f0900cb; - public static final int settings_show_notification=0x7f0900c6; - public static final int settings_show_notification_always=0x7f0900c8; - public static final int settings_show_notification_always_summary=0x7f0900c9; - public static final int settings_show_notification_summary=0x7f0900c7; + public static final int settings_show_lockscreen_controls=0x7f0900cc; + public static final int settings_show_lockscreen_controls_summary=0x7f0900cd; + public static final int settings_show_notification=0x7f0900c8; + public static final int settings_show_notification_always=0x7f0900ca; + public static final int settings_show_notification_always_summary=0x7f0900cb; + public static final int settings_show_notification_summary=0x7f0900c9; public static final int settings_test_connection_title=0x7f09006d; public static final int settings_testing_connection=0x7f09007a; public static final int settings_testing_ok=0x7f09007b; @@ -614,21 +598,21 @@ public final class R { public static final int settings_theme_fullscreenlight=0x7f090083; public static final int settings_theme_light=0x7f090084; public static final int settings_theme_title=0x7f090081; - public static final int settings_theme_wheat=0x7f09010a; + public static final int settings_theme_wheat=0x7f090100; public static final int settings_title=0x7f09006c; - public static final int settings_use_stream_proxy=0x7f0900cc; - public static final int settings_use_stream_proxy_summary=0x7f0900cd; + public static final int settings_use_stream_proxy=0x7f0900ce; + public static final int settings_use_stream_proxy_summary=0x7f0900cf; public static final int settings_wifi_required_summary=0x7f090095; public static final int settings_wifi_required_title=0x7f090094; public static final int song_details_all=0x7f090068; public static final int song_details_kbps=0x7f090069; - public static final int util_bytes_format_byte=0x7f090109; - public static final int util_bytes_format_gigabyte=0x7f090106; - public static final int util_bytes_format_kilobyte=0x7f090108; - public static final int util_bytes_format_megabyte=0x7f090107; - public static final int widget_initial_text=0x7f090103; - public static final int widget_sdcard_busy=0x7f090104; - public static final int widget_sdcard_missing=0x7f090105; + public static final int util_bytes_format_byte=0x7f0900ff; + public static final int util_bytes_format_gigabyte=0x7f0900fc; + public static final int util_bytes_format_kilobyte=0x7f0900fe; + public static final int util_bytes_format_megabyte=0x7f0900fd; + public static final int widget_initial_text=0x7f0900f9; + public static final int widget_sdcard_busy=0x7f0900fa; + public static final int widget_sdcard_missing=0x7f0900fb; } public static final class style { public static final int Dark=0x7f0b0000; diff --git a/lint.xml b/lint.xml new file mode 100644 index 00000000..ee0eead5 --- /dev/null +++ b/lint.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> +</lint> \ No newline at end of file diff --git a/res/values/arrays.xml b/res/values/arrays.xml index fb178a96..f0a2beb8 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -99,7 +99,6 @@ <string-array name="bufferLengthNames"> <item>@string/settings.buffer_length_1</item> - <item>@string/settings.buffer_length_2</item> <item>@string/settings.buffer_length_2</item> <item>@string/settings.buffer_length_5</item> <item>@string/settings.buffer_length_8</item> @@ -132,96 +131,38 @@ <item>@string/settings.network_timeout_120000</item> </string-array> - <string-array name="maxAlbumsValues"> + <string-array name="searchValues"> + <item>1</item> + <item>3</item> <item>5</item> <item>10</item> + <item>15</item> <item>20</item> + <item>25</item> <item>30</item> <item>40</item> <item>50</item> - <item>100</item> - <item>250</item> - <item>500</item> - </string-array> - - <string-array name="maxAlbumsNames"> - <item>@string/settings.max_albums_5</item> - <item>@string/settings.max_albums_10</item> - <item>@string/settings.max_albums_20</item> - <item>@string/settings.max_albums_30</item> - <item>@string/settings.max_albums_40</item> - <item>@string/settings.max_albums_50</item> - <item>@string/settings.max_albums_100</item> - <item>@string/settings.max_albums_250</item> - <item>@string/settings.max_albums_500</item> - </string-array> - - <string-array name="maxSongsValues"> - <item>5</item> - <item>10</item> - <item>25</item> - <item>50</item> <item>75</item> <item>100</item> <item>250</item> <item>500</item> </string-array> - <string-array name="maxSongsNames"> - <item>@string/settings.max_songs_5</item> - <item>@string/settings.max_songs_10</item> - <item>@string/settings.max_songs_25</item> - <item>@string/settings.max_songs_50</item> - <item>@string/settings.max_songs_75</item> - <item>@string/settings.max_songs_100</item> - <item>@string/settings.max_songs_250</item> - <item>@string/settings.max_songs_500</item> - </string-array> - - <string-array name="maxArtistsValues"> - <item>5</item> - <item>10</item> - <item>25</item> - <item>50</item> - <item>75</item> - <item>100</item> - <item>250</item> - <item>500</item> - </string-array> - - <string-array name="maxArtistsNames"> - <item>@string/settings.max_artists_5</item> - <item>@string/settings.max_artists_10</item> - <item>@string/settings.max_artists_25</item> - <item>@string/settings.max_artists_50</item> - <item>@string/settings.max_artists_75</item> - <item>@string/settings.max_artists_100</item> - <item>@string/settings.max_artists_250</item> - <item>@string/settings.max_artists_500</item> - </string-array> - - <string-array name="defaultSearchValues"> - <item>3</item> - <item>5</item> - <item>10</item> - <item>25</item> - <item>50</item> - <item>75</item> - <item>100</item> - <item>250</item> - <item>500</item> - </string-array> - - <string-array name="defaultSearchNames"> - <item>@string/settings.max_artists_3</item> - <item>@string/settings.max_artists_5</item> - <item>@string/settings.max_artists_10</item> - <item>@string/settings.max_artists_25</item> - <item>@string/settings.max_artists_50</item> - <item>@string/settings.max_artists_75</item> - <item>@string/settings.max_artists_100</item> - <item>@string/settings.max_artists_250</item> - <item>@string/settings.max_artists_500</item> + <string-array name="searchNames"> + <item>@string/settings.search_1</item> + <item>@string/settings.search_3</item> + <item>@string/settings.search_5</item> + <item>@string/settings.search_10</item> + <item>@string/settings.search_15</item> + <item>@string/settings.search_20</item> + <item>@string/settings.search_25</item> + <item>@string/settings.search_30</item> + <item>@string/settings.search_40</item> + <item>@string/settings.search_50</item> + <item>@string/settings.search_75</item> + <item>@string/settings.search_100</item> + <item>@string/settings.search_250</item> + <item>@string/settings.search_500</item> </string-array> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index e552beaa..6d760870 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -191,6 +191,8 @@ <string name="settings.playback_control_title">Playback Control Settings</string> <string name="settings.scrobble_title">Scrobble To Last.fm</string> <string name="settings.scrobble_summary">Remember to set up your Last.fm user and password on the Subsonic server</string> + <string name="settings.server_scaling_title">Server-Side Album Art Scaling</string> + <string name="settings.server_scaling_summary">Download scaled images from the server instead of full size (saves bandwidth)</string> <string name="settings.hide_media_title">Hide From Other</string> <string name="settings.hide_media_summary">Hide music files from other apps.</string> <string name="settings.hide_media_toast">Takes effect next time Android scans your phone for music.</string> @@ -226,34 +228,22 @@ <string name="settings.use_stream_proxy">Use Stream Proxy</string> <string name="settings.use_stream_proxy_summary">Stream media playback through a proxy (may help stutter)</string> <string name="settings.max_albums">Max Albums</string> - <string name="settings.max_albums_5">5</string> - <string name="settings.max_albums_10">10</string> - <string name="settings.max_albums_20">20</string> - <string name="settings.max_albums_30">30</string> - <string name="settings.max_albums_40">40</string> - <string name="settings.max_albums_50">50</string> - <string name="settings.max_albums_100">100</string> - <string name="settings.max_albums_250">250</string> - <string name="settings.max_albums_500">500</string> - <string name="settings.max_songs">Max Songs</string> - <string name="settings.max_songs_5">5</string> - <string name="settings.max_songs_10">10</string> - <string name="settings.max_songs_25">25</string> - <string name="settings.max_songs_50">50</string> - <string name="settings.max_songs_75">75</string> - <string name="settings.max_songs_100">100</string> - <string name="settings.max_songs_250">250</string> - <string name="settings.max_songs_500">500</string> + <string name="settings.search_1">1</string> + <string name="settings.search_3">3</string> + <string name="settings.search_5">5</string> + <string name="settings.search_10">10</string> + <string name="settings.search_15">15</string> + <string name="settings.search_20">20</string> + <string name="settings.search_25">25</string> + <string name="settings.search_30">30</string> + <string name="settings.search_40">40</string> + <string name="settings.search_50">50</string> + <string name="settings.search_75">75</string> + <string name="settings.search_100">100</string> + <string name="settings.search_250">250</string> + <string name="settings.search_500">500</string> <string name="settings.max_artists">Max Artists</string> - <string name="settings.max_artists_3">3</string> - <string name="settings.max_artists_5">5</string> - <string name="settings.max_artists_10">10</string> - <string name="settings.max_artists_25">25</string> - <string name="settings.max_artists_50">50</string> - <string name="settings.max_artists_75">75</string> - <string name="settings.max_artists_100">100</string> - <string name="settings.max_artists_250">250</string> - <string name="settings.max_artists_500">500</string> + <string name="settings.max_songs">Max Songs</string> <string name="settings.default_artists">Default Artists</string> <string name="settings.default_albums">Default Albums</string> <string name="settings.default_songs">Default Songs</string> diff --git a/res/xml/settings.xml b/res/xml/settings.xml index 0b620ed5..d3f2422d 100644 --- a/res/xml/settings.xml +++ b/res/xml/settings.xml @@ -134,38 +134,38 @@ <PreferenceCategory a:title="@string/settings.search_title" > <ListPreference a:defaultValue="3" - a:entries="@array/defaultSearchNames" - a:entryValues="@array/defaultSearchValues" + a:entries="@array/searchNames" + a:entryValues="@array/searchValues" a:key="defaultArtists" a:title="@string/settings.default_artists" /> <ListPreference a:defaultValue="10" - a:entries="@array/maxArtistsNames" - a:entryValues="@array/maxArtistsValues" + a:entries="@array/searchNames" + a:entryValues="@array/searchValues" a:key="maxArtists" a:title="@string/settings.max_artists" /> <ListPreference a:defaultValue="5" - a:entries="@array/defaultSearchNames" - a:entryValues="@array/defaultSearchValues" + a:entries="@array/searchNames" + a:entryValues="@array/searchValues" a:key="defaultAlbums" a:title="@string/settings.default_albums" /> <ListPreference a:defaultValue="20" - a:entries="@array/maxAlbumsNames" - a:entryValues="@array/maxAlbumsValues" + a:entries="@array/searchNames" + a:entryValues="@array/searchValues" a:key="maxAlbums" a:title="@string/settings.max_albums" /> <ListPreference a:defaultValue="10" - a:entries="@array/defaultSearchNames" - a:entryValues="@array/defaultSearchValues" + a:entries="@array/searchNames" + a:entryValues="@array/searchValues" a:key="defaultSongs" a:title="@string/settings.default_songs" /> <ListPreference a:defaultValue="25" - a:entries="@array/maxSongsNames" - a:entryValues="@array/maxSongsValues" + a:entries="@array/searchNames" + a:entryValues="@array/searchValues" a:key="maxSongs" a:title="@string/settings.max_songs" /> <Preference @@ -201,6 +201,11 @@ a:title="@string/settings.use_stream_proxy" /> </PreferenceCategory> <PreferenceCategory a:title="@string/settings.other_title" > + <CheckBoxPreference + a:defaultValue="true" + a:key="serverScaling" + a:summary="@string/settings.server_scaling_summary" + a:title="@string/settings.server_scaling_title" /> <CheckBoxPreference a:defaultValue="false" a:key="scrobble" diff --git a/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java b/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java index e21d9db1..8ea0eeab 100644 --- a/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java +++ b/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java @@ -1,369 +1,368 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2009 (C) Sindre Mehus - */ - -package net.sourceforge.subsonic.androidapp.activity; - -import java.util.ArrayList; -import java.util.List; -import java.util.Arrays; - -import android.content.Intent; -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.MenuItem; -import android.widget.AdapterView; -import android.widget.ImageButton; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; -import android.net.Uri; -import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.domain.Artist; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.domain.SearchCritera; -import net.sourceforge.subsonic.androidapp.domain.SearchResult; -import net.sourceforge.subsonic.androidapp.service.MusicService; -import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; -import net.sourceforge.subsonic.androidapp.service.DownloadService; -import net.sourceforge.subsonic.androidapp.util.ArtistAdapter; -import net.sourceforge.subsonic.androidapp.util.BackgroundTask; -import net.sourceforge.subsonic.androidapp.util.Constants; -import net.sourceforge.subsonic.androidapp.util.EntryAdapter; -import net.sourceforge.subsonic.androidapp.util.MergeAdapter; -import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; -import net.sourceforge.subsonic.androidapp.util.Util; - -/** - * Performs searches and displays the matching artists, albums and songs. - * - * @author Sindre Mehus - */ -public class SearchActivity extends SubsonicTabActivity { - - private static int DEFAULT_ARTISTS; - private static int DEFAULT_ALBUMS; - private static int DEFAULT_SONGS; - - private ListView list; - - private View artistsHeading; - private View albumsHeading; - private View songsHeading; - private TextView searchButton; - private View moreArtistsButton; - private View moreAlbumsButton; - private View moreSongsButton; - private SearchResult searchResult; - private MergeAdapter mergeAdapter; - private ArtistAdapter artistAdapter; - private ListAdapter moreArtistsAdapter; - private EntryAdapter albumAdapter; - private ListAdapter moreAlbumsAdapter; - private ListAdapter moreSongsAdapter; - private EntryAdapter songAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.search); - - setTitle(R.string.search_title); - - DEFAULT_ARTISTS = Util.getDefaultArtists(this); - DEFAULT_ALBUMS = Util.getDefaultAlbums(this); - DEFAULT_SONGS = Util.getDefaultSongs(this); - - View buttons = LayoutInflater.from(this).inflate(R.layout.search_buttons, null); - - artistsHeading = buttons.findViewById(R.id.search_artists); - albumsHeading = buttons.findViewById(R.id.search_albums); - songsHeading = buttons.findViewById(R.id.search_songs); - - searchButton = (TextView) buttons.findViewById(R.id.search_search); - moreArtistsButton = buttons.findViewById(R.id.search_more_artists); - moreAlbumsButton = buttons.findViewById(R.id.search_more_albums); - moreSongsButton = buttons.findViewById(R.id.search_more_songs); - - list = (ListView) findViewById(R.id.search_list); - - list.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (view == searchButton) { - onSearchRequested(); - } else if (view == moreArtistsButton) { - expandArtists(); - } else if (view == moreAlbumsButton) { - expandAlbums(); - } else if (view == moreSongsButton) { - expandSongs(); - } else { - Object item = parent.getItemAtPosition(position); - if (item instanceof Artist) { - onArtistSelected((Artist) item); - } else if (item instanceof MusicDirectory.Entry) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) item; - if (entry.isDirectory()) { - onAlbumSelected(entry, false); - } else if (entry.isVideo()) { - onVideoSelected(entry); - } else { - onSongSelected(entry, false, true, true, false); - } - - } - } - } - }); - list.setOnTouchListener(gestureListener); - - registerForContextMenu(list); - - onNewIntent(getIntent()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - return true; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY); - boolean autoplay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); - boolean requestsearch = intent.getBooleanExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, false); - - if (query != null) { - mergeAdapter = new MergeAdapter(); - list.setAdapter(mergeAdapter); - search(query, autoplay); - } else { - populateList(); - if (requestsearch) - onSearchRequested(); - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, view, menuInfo); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Object selectedItem = list.getItemAtPosition(info.position); - - boolean isArtist = selectedItem instanceof Artist; - boolean isAlbum = selectedItem instanceof MusicDirectory.Entry && ((MusicDirectory.Entry) selectedItem).isDirectory(); - boolean isSong = selectedItem instanceof MusicDirectory.Entry && (!((MusicDirectory.Entry) selectedItem).isDirectory()) - && (!((MusicDirectory.Entry) selectedItem).isVideo()); - - if (isArtist || isAlbum) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_album_context, menu); - } else if (isSong) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_song_context, menu); - } - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - Object selectedItem = list.getItemAtPosition(info.position); - - Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null; - MusicDirectory.Entry entry = selectedItem instanceof MusicDirectory.Entry ? (MusicDirectory.Entry) selectedItem : null; - String id = artist != null ? artist.getId() : entry.getId(); - - switch (menuItem.getItemId()) { - case R.id.album_menu_play_now: - downloadRecursively(id, false, false, true); - break; - case R.id.album_menu_play_last: - downloadRecursively(id, false, true, false); - break; - case R.id.album_menu_pin: - downloadRecursively(id, true, true, false); - break; - case R.id.song_menu_play_now: - onSongSelected(entry, false, false, true, false); - break; - case R.id.song_menu_play_next: - onSongSelected(entry, false, true, false, true); - break; - case R.id.song_menu_play_last: - onSongSelected(entry, false, true, false, false); - break; - default: - return super.onContextItemSelected(menuItem); - } - - return true; - } - - private void search(final String query, final boolean autoplay) { - final int maxArtists = Util.getMaxArtists(this); - final int maxAlbums = Util.getMaxAlbums(this); - final int maxSongs = Util.getMaxSongs(this); - - BackgroundTask<SearchResult> task = new TabActivityBackgroundTask<SearchResult>(this) { - @Override - protected SearchResult doInBackground() throws Throwable { - SearchCritera criteria = new SearchCritera(query, maxArtists, maxAlbums, maxSongs); - MusicService service = MusicServiceFactory.getMusicService(SearchActivity.this); - return service.search(criteria, SearchActivity.this, this); - } - - @Override - protected void done(SearchResult result) { - searchResult = result; - populateList(); - if (autoplay) { - autoplay(); - } - - } - }; - task.execute(); - } - - private void populateList() { - mergeAdapter = new MergeAdapter(); - mergeAdapter.addView(searchButton, true); - - if (searchResult != null) { - List<Artist> artists = searchResult.getArtists(); - if (!artists.isEmpty()) { - mergeAdapter.addView(artistsHeading); - List<Artist> displayedArtists = new ArrayList<Artist>(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size()))); - artistAdapter = new ArtistAdapter(this, displayedArtists); - mergeAdapter.addAdapter(artistAdapter); - if (artists.size() > DEFAULT_ARTISTS) { - moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true); - } - } - - List<MusicDirectory.Entry> albums = searchResult.getAlbums(); - if (!albums.isEmpty()) { - mergeAdapter.addView(albumsHeading); - List<MusicDirectory.Entry> displayedAlbums = new ArrayList<MusicDirectory.Entry>(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size()))); - albumAdapter = new EntryAdapter(this, getImageLoader(), displayedAlbums, false); - mergeAdapter.addAdapter(albumAdapter); - if (albums.size() > DEFAULT_ALBUMS) { - moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true); - } - } - - List<MusicDirectory.Entry> songs = searchResult.getSongs(); - if (!songs.isEmpty()) { - mergeAdapter.addView(songsHeading); - List<MusicDirectory.Entry> displayedSongs = new ArrayList<MusicDirectory.Entry>(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size()))); - songAdapter = new EntryAdapter(this, getImageLoader(), displayedSongs, false); - mergeAdapter.addAdapter(songAdapter); - if (songs.size() > DEFAULT_SONGS) { - moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true); - } - } - - boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty(); - searchButton.setText(empty ? R.string.search_no_match : R.string.search_search); - } - - list.setAdapter(mergeAdapter); - } - - private void expandArtists() { - artistAdapter.clear(); - for (Artist artist : searchResult.getArtists()) { - artistAdapter.add(artist); - } - artistAdapter.notifyDataSetChanged(); - mergeAdapter.removeAdapter(moreArtistsAdapter); - mergeAdapter.notifyDataSetChanged(); - } - - private void expandAlbums() { - albumAdapter.clear(); - for (MusicDirectory.Entry album : searchResult.getAlbums()) { - albumAdapter.add(album); - } - albumAdapter.notifyDataSetChanged(); - mergeAdapter.removeAdapter(moreAlbumsAdapter); - mergeAdapter.notifyDataSetChanged(); - } - - private void expandSongs() { - songAdapter.clear(); - for (MusicDirectory.Entry song : searchResult.getSongs()) { - songAdapter.add(song); - } - songAdapter.notifyDataSetChanged(); - mergeAdapter.removeAdapter(moreSongsAdapter); - mergeAdapter.notifyDataSetChanged(); - } - - private void onArtistSelected(Artist artist) { - Intent intent = new Intent(this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); - Util.startActivityWithoutTransition(this, intent); - } - - private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) { - Intent intent = new Intent(SearchActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, album.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay); - Util.startActivityWithoutTransition(SearchActivity.this, intent); - } - - private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) { - DownloadService downloadService = getDownloadService(); - if (downloadService != null) { - if (!append) { - downloadService.clear(); - } - downloadService.download(Arrays.asList(song), save, false, playNext); - if (autoplay) { - downloadService.play(downloadService.size() - 1); - } - - Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); - } - } - - private void onVideoSelected(MusicDirectory.Entry entry) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId()))); - startActivity(intent); - } - - private void autoplay() { - if (!searchResult.getSongs().isEmpty()) { - onSongSelected(searchResult.getSongs().get(0), false, false, true, false); - } else if (!searchResult.getAlbums().isEmpty()) { - onAlbumSelected(searchResult.getAlbums().get(0), true); - } - } +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Arrays; + +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.MenuItem; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.net.Uri; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Artist; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.SearchCritera; +import net.sourceforge.subsonic.androidapp.domain.SearchResult; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.util.ArtistAdapter; +import net.sourceforge.subsonic.androidapp.util.BackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.EntryAdapter; +import net.sourceforge.subsonic.androidapp.util.MergeAdapter; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * Performs searches and displays the matching artists, albums and songs. + * + * @author Sindre Mehus + */ +public class SearchActivity extends SubsonicTabActivity { + + private static int DEFAULT_ARTISTS; + private static int DEFAULT_ALBUMS; + private static int DEFAULT_SONGS; + + private ListView list; + + private View artistsHeading; + private View albumsHeading; + private View songsHeading; + private TextView searchButton; + private View moreArtistsButton; + private View moreAlbumsButton; + private View moreSongsButton; + private SearchResult searchResult; + private MergeAdapter mergeAdapter; + private ArtistAdapter artistAdapter; + private ListAdapter moreArtistsAdapter; + private EntryAdapter albumAdapter; + private ListAdapter moreAlbumsAdapter; + private ListAdapter moreSongsAdapter; + private EntryAdapter songAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.search); + + setTitle(R.string.search_title); + + DEFAULT_ARTISTS = Util.getDefaultArtists(this); + DEFAULT_ALBUMS = Util.getDefaultAlbums(this); + DEFAULT_SONGS = Util.getDefaultSongs(this); + + View buttons = LayoutInflater.from(this).inflate(R.layout.search_buttons, null); + + artistsHeading = buttons.findViewById(R.id.search_artists); + albumsHeading = buttons.findViewById(R.id.search_albums); + songsHeading = buttons.findViewById(R.id.search_songs); + + searchButton = (TextView) buttons.findViewById(R.id.search_search); + moreArtistsButton = buttons.findViewById(R.id.search_more_artists); + moreAlbumsButton = buttons.findViewById(R.id.search_more_albums); + moreSongsButton = buttons.findViewById(R.id.search_more_songs); + + list = (ListView) findViewById(R.id.search_list); + + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (view == searchButton) { + onSearchRequested(); + } else if (view == moreArtistsButton) { + expandArtists(); + } else if (view == moreAlbumsButton) { + expandAlbums(); + } else if (view == moreSongsButton) { + expandSongs(); + } else { + Object item = parent.getItemAtPosition(position); + if (item instanceof Artist) { + onArtistSelected((Artist) item); + } else if (item instanceof MusicDirectory.Entry) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) item; + if (entry.isDirectory()) { + onAlbumSelected(entry, false); + } else if (entry.isVideo()) { + onVideoSelected(entry); + } else { + onSongSelected(entry, false, true, true, false); + } + + } + } + } + }); + list.setOnTouchListener(gestureListener); + + registerForContextMenu(list); + + onNewIntent(getIntent()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + return true; + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY); + boolean autoplay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + boolean requestsearch = intent.getBooleanExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, false); + + if (query != null) { + mergeAdapter = new MergeAdapter(); + list.setAdapter(mergeAdapter); + search(query, autoplay); + } else { + populateList(); + if (requestsearch) + onSearchRequested(); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Object selectedItem = list.getItemAtPosition(info.position); + + boolean isArtist = selectedItem instanceof Artist; + boolean isAlbum = selectedItem instanceof MusicDirectory.Entry && ((MusicDirectory.Entry) selectedItem).isDirectory(); + boolean isSong = selectedItem instanceof MusicDirectory.Entry && (!((MusicDirectory.Entry) selectedItem).isDirectory()) + && (!((MusicDirectory.Entry) selectedItem).isVideo()); + + if (isArtist || isAlbum) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_album_context, menu); + } else if (isSong) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_song_context, menu); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + Object selectedItem = list.getItemAtPosition(info.position); + + Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null; + MusicDirectory.Entry entry = selectedItem instanceof MusicDirectory.Entry ? (MusicDirectory.Entry) selectedItem : null; + String id = artist != null ? artist.getId() : entry.getId(); + + switch (menuItem.getItemId()) { + case R.id.album_menu_play_now: + downloadRecursively(id, false, false, true); + break; + case R.id.album_menu_play_last: + downloadRecursively(id, false, true, false); + break; + case R.id.album_menu_pin: + downloadRecursively(id, true, true, false); + break; + case R.id.song_menu_play_now: + onSongSelected(entry, false, false, true, false); + break; + case R.id.song_menu_play_next: + onSongSelected(entry, false, true, false, true); + break; + case R.id.song_menu_play_last: + onSongSelected(entry, false, true, false, false); + break; + default: + return super.onContextItemSelected(menuItem); + } + + return true; + } + + private void search(final String query, final boolean autoplay) { + final int maxArtists = Util.getMaxArtists(this); + final int maxAlbums = Util.getMaxAlbums(this); + final int maxSongs = Util.getMaxSongs(this); + + BackgroundTask<SearchResult> task = new TabActivityBackgroundTask<SearchResult>(this) { + @Override + protected SearchResult doInBackground() throws Throwable { + SearchCritera criteria = new SearchCritera(query, maxArtists, maxAlbums, maxSongs); + MusicService service = MusicServiceFactory.getMusicService(SearchActivity.this); + return service.search(criteria, SearchActivity.this, this); + } + + @Override + protected void done(SearchResult result) { + searchResult = result; + populateList(); + if (autoplay) { + autoplay(); + } + + } + }; + task.execute(); + } + + private void populateList() { + mergeAdapter = new MergeAdapter(); + mergeAdapter.addView(searchButton, true); + + if (searchResult != null) { + List<Artist> artists = searchResult.getArtists(); + if (!artists.isEmpty()) { + mergeAdapter.addView(artistsHeading); + List<Artist> displayedArtists = new ArrayList<Artist>(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size()))); + artistAdapter = new ArtistAdapter(this, displayedArtists); + mergeAdapter.addAdapter(artistAdapter); + if (artists.size() > DEFAULT_ARTISTS) { + moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true); + } + } + + List<MusicDirectory.Entry> albums = searchResult.getAlbums(); + if (!albums.isEmpty()) { + mergeAdapter.addView(albumsHeading); + List<MusicDirectory.Entry> displayedAlbums = new ArrayList<MusicDirectory.Entry>(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size()))); + albumAdapter = new EntryAdapter(this, getImageLoader(), displayedAlbums, false); + mergeAdapter.addAdapter(albumAdapter); + if (albums.size() > DEFAULT_ALBUMS) { + moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true); + } + } + + List<MusicDirectory.Entry> songs = searchResult.getSongs(); + if (!songs.isEmpty()) { + mergeAdapter.addView(songsHeading); + List<MusicDirectory.Entry> displayedSongs = new ArrayList<MusicDirectory.Entry>(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size()))); + songAdapter = new EntryAdapter(this, getImageLoader(), displayedSongs, false); + mergeAdapter.addAdapter(songAdapter); + if (songs.size() > DEFAULT_SONGS) { + moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true); + } + } + + boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty(); + searchButton.setText(empty ? R.string.search_no_match : R.string.search_search); + } + + list.setAdapter(mergeAdapter); + } + + private void expandArtists() { + artistAdapter.clear(); + for (Artist artist : searchResult.getArtists()) { + artistAdapter.add(artist); + } + artistAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreArtistsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void expandAlbums() { + albumAdapter.clear(); + for (MusicDirectory.Entry album : searchResult.getAlbums()) { + albumAdapter.add(album); + } + albumAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreAlbumsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void expandSongs() { + songAdapter.clear(); + for (MusicDirectory.Entry song : searchResult.getSongs()) { + songAdapter.add(song); + } + songAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreSongsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void onArtistSelected(Artist artist) { + Intent intent = new Intent(this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); + Util.startActivityWithoutTransition(this, intent); + } + + private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) { + Intent intent = new Intent(SearchActivity.this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, album.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay); + Util.startActivityWithoutTransition(SearchActivity.this, intent); + } + + private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) { + DownloadService downloadService = getDownloadService(); + if (downloadService != null) { + if (!append) { + downloadService.clear(); + } + downloadService.download(Arrays.asList(song), save, false, playNext); + if (autoplay) { + downloadService.play(downloadService.size() - 1); + } + + Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); + } + } + + private void onVideoSelected(MusicDirectory.Entry entry) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId()))); + startActivity(intent); + } + + private void autoplay() { + if (!searchResult.getSongs().isEmpty()) { + onSongSelected(searchResult.getSongs().get(0), false, false, true, false); + } else if (!searchResult.getAlbums().isEmpty()) { + onAlbumSelected(searchResult.getAlbums().get(0), true); + } + } } \ No newline at end of file diff --git a/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java b/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java index 41c6b55b..9af9184b 100644 --- a/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java +++ b/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java @@ -1,641 +1,644 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.activity; - -import android.app.ActionBar; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.ListView; -import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.service.DownloadFile; -import net.sourceforge.subsonic.androidapp.service.MusicService; -import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; -import net.sourceforge.subsonic.androidapp.util.Constants; -import net.sourceforge.subsonic.androidapp.util.EntryAdapter; -import net.sourceforge.subsonic.androidapp.util.Pair; -import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; -import net.sourceforge.subsonic.androidapp.util.Util; - -import java.util.ArrayList; -import java.util.List; - -public class SelectAlbumActivity extends SubsonicTabActivity { - - private static final String TAG = SelectAlbumActivity.class.getSimpleName(); - - private ListView entryList; - private View footer; - private View emptyView; - private Button selectButton; - private Button playNowButton; - private Button playLastButton; - private Button pinButton; - private Button unpinButton; - private Button deleteButton; - private Button moreButton; - private boolean licenseValid; - private boolean playAllButtonVisible; - private MenuItem playAllButton; - - /** - * Called when the activity is first created. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.select_album); - - entryList = (ListView) findViewById(R.id.select_album_entries); - - footer = LayoutInflater.from(this).inflate(R.layout.select_album_footer, entryList, false); - entryList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - entryList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (position >= 0) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); - if (entry.isDirectory()) { - Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, entry.getId()); - intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle()); - Util.startActivityWithoutTransition(SelectAlbumActivity.this, intent); - } else if (entry.isVideo()) { - playVideo(entry); - } else { - enableButtons(); - } - } - } - }); - entryList.setOnTouchListener(gestureListener); - - selectButton = (Button) findViewById(R.id.select_album_select); - playNowButton = (Button) findViewById(R.id.select_album_play_now); - playLastButton = (Button) findViewById(R.id.select_album_play_last); - pinButton = (Button) footer.findViewById(R.id.select_album_pin); - unpinButton = (Button) footer.findViewById(R.id.select_album_unpin); - deleteButton = (Button) footer.findViewById(R.id.select_album_delete); - moreButton = (Button) footer.findViewById(R.id.select_album_more); - emptyView = findViewById(R.id.select_album_empty); - - selectButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - selectAllOrNone(); - } - }); - playNowButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - download(false, false, true, false); - selectAll(false, false); - } - }); - playLastButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - download(true, false, false, false); - selectAll(false, false); - } - }); - pinButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - download(true, true, false, false); - selectAll(false, false); - } - }); - unpinButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - unpin(); - selectAll(false, false); - } - }); - deleteButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - delete(); - selectAll(false, false); - } - }); - - registerForContextMenu(entryList); - - enableButtons(); - - String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); - String name = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME); - String playlistId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); - String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); - String albumListType = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); - int getStarredTracks = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_STARRED, 0); - int albumListSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - int albumListOffset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); - - if (playlistId != null) { - getPlaylist(playlistId, playlistName); - } else if (albumListType != null) { - getAlbumList(albumListType, albumListSize, albumListOffset); - } else if (getStarredTracks != 0) { - getStarred(); - } else { - getMusicDirectory(id, name); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - playAllButton = menu.findItem(R.id.select_album_play_all); - playAllButton.setVisible(playAllButtonVisible); - - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_album, menu); - inflater.inflate(R.menu.select_common, menu); - super.onCreateOptionsMenu(menu); - - return true; - } - - private void playAll() { - boolean hasSubFolders = false; - for (int i = 0; i < entryList.getCount(); i++) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(i); - if (entry != null && entry.isDirectory()) { - hasSubFolders = true; - break; - } - } - - String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); - if (hasSubFolders && id != null) { - downloadRecursively(id, false, false, true); - } else { - selectAll(true, false); - download(false, false, true, false); - selectAll(false, false); - } - } - - private void refresh() { - finish(); - Intent intent = getIntent(); - intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); - Util.startActivityWithoutTransition(this, intent); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, view, menuInfo); - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - - MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(info.position); - - if (entry.isDirectory()) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_album_context, menu); - } else { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_song_context, menu); - } - } - - @Override - public boolean onContextItemSelected(MenuItem menuItem) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); - MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(info.position); - List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10); - songs.add((MusicDirectory.Entry) entryList.getItemAtPosition(info.position)); - switch (menuItem.getItemId()) { - case R.id.album_menu_play_now: - downloadRecursively(entry.getId(), false, false, true); - break; - case R.id.album_menu_play_last: - downloadRecursively(entry.getId(), false, true, false); - break; - case R.id.album_menu_pin: - downloadRecursively(entry.getId(), true, true, false); - break; - case R.id.song_menu_play_now: - getDownloadService().download(songs, false, true, true); - break; - case R.id.song_menu_play_next: - getDownloadService().download(songs, false, false, true); - break; - case R.id.song_menu_play_last: - getDownloadService().download(songs, false, false, false); - break; - case R.id.select_album_play_all: - playAll(); - break; - default: - return super.onContextItemSelected(menuItem); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case R.id.main_shuffle: - Intent intent1 = new Intent(this, DownloadActivity.class); - intent1.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); - Util.startActivityWithoutTransition(this, intent1); - return true; - - case R.id.menu_refresh: - refresh(); - return true; - - case R.id.select_album_play_all: - playAll(); - return true; - - case R.id.menu_exit: - Intent intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); - Util.startActivityWithoutTransition(this, intent); - return true; - - case R.id.menu_settings: - startActivity(new Intent(this, SettingsActivity.class)); - return true; - - case R.id.menu_help: - startActivity(new Intent(this, HelpActivity.class)); - return true; - } - - return false; - } - - private void getMusicDirectory(final String id, String name) { - setTitle(name); - - new LoadTask() { - @Override - protected MusicDirectory load(MusicService service) throws Exception { - boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); - return service.getMusicDirectory(id, refresh, SelectAlbumActivity.this, this); - } - }.execute(); - } - - private void getStarred() { - setTitle(R.string.main_songs_starred); - - new LoadTask() { - @Override - protected MusicDirectory load(MusicService service) throws Exception { - return Util.getSongsFromSearchResult(service.getStarred(SelectAlbumActivity.this, this)); - } - }.execute(); - } - - private void getPlaylist(final String playlistId, String playlistName) { - setTitle(playlistName); - - new LoadTask() { - @Override - protected MusicDirectory load(MusicService service) throws Exception { - return service.getPlaylist(playlistId, SelectAlbumActivity.this, this); - } - }.execute(); - } - - private void getAlbumList(final String albumListType, final int size, final int offset) { - - if ("newest".equals(albumListType)) { - setTitle(R.string.main_albums_newest); - } else if ("random".equals(albumListType)) { - setTitle(R.string.main_albums_random); - } else if ("highest".equals(albumListType)) { - setTitle(R.string.main_albums_highest); - } else if ("recent".equals(albumListType)) { - setTitle(R.string.main_albums_recent); - } else if ("frequent".equals(albumListType)) { - setTitle(R.string.main_albums_frequent); - } else if ("starred".equals(albumListType)) { - setTitle(R.string.main_albums_starred); - } else if ("alphabeticalByName".equals(albumListType)) { - setTitle(R.string.main_albums_alphaByName); - } else if ("alphabeticalByArtist".equals(albumListType)) { - setTitle(R.string.main_albums_alphaByArtist); - } - - new LoadTask() { - @Override - protected MusicDirectory load(MusicService service) throws Exception { - return service.getAlbumList(albumListType, size, offset, SelectAlbumActivity.this, this); - } - - @Override - protected void done(Pair<MusicDirectory, Boolean> result) { - if (!result.getFirst().getChildren().isEmpty()) { - pinButton.setVisibility(View.GONE); - unpinButton.setVisibility(View.GONE); - deleteButton.setVisibility(View.GONE); - - // Hide more button when results are less than album list size - if (result.getFirst().getChildren().size() < getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)) { - moreButton.setVisibility(View.GONE); - } else { - entryList.addFooterView(footer); - moreButton.setVisibility(View.VISIBLE); - } - - moreButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); - String type = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); - int size = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); - int offset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; - - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); - intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); - Util.startActivityWithoutTransition(SelectAlbumActivity.this, intent); - } - }); - } - super.done(result); - } - }.execute(); - } - - private void selectAllOrNone() { - boolean someUnselected = false; - int count = entryList.getCount(); - for (int i = 0; i < count; i++) { - if (!entryList.isItemChecked(i) && entryList.getItemAtPosition(i) instanceof MusicDirectory.Entry) { - someUnselected = true; - break; - } - } - selectAll(someUnselected, true); - } - - private void selectAll(boolean selected, boolean toast) { - int count = entryList.getCount(); - int selectedCount = 0; - for (int i = 0; i < count; i++) { - MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(i); - if (entry != null && !entry.isDirectory() && !entry.isVideo()) { - entryList.setItemChecked(i, selected); - selectedCount++; - } - } - - // Display toast: N tracks selected / N tracks unselected - if (toast) { - int toastResId = selected ? R.string.select_album_n_selected - : R.string.select_album_n_unselected; - Util.toast(this, getString(toastResId, selectedCount)); - } - - enableButtons(); - } - - private void enableButtons() { - if (getDownloadService() == null) { - return; - } - - List<MusicDirectory.Entry> selection = getSelectedSongs(); - boolean enabled = !selection.isEmpty(); - boolean unpinEnabled = false; - boolean deleteEnabled = false; - - for (MusicDirectory.Entry song : selection) { - DownloadFile downloadFile = getDownloadService().forSong(song); - if (downloadFile.isCompleteFileAvailable()) { - deleteEnabled = true; - } - if (downloadFile.isSaved()) { - unpinEnabled = true; - } - } - - playNowButton.setEnabled(enabled); - playLastButton.setEnabled(enabled); - pinButton.setEnabled(enabled && !Util.isOffline(this)); - unpinButton.setEnabled(unpinEnabled); - deleteButton.setEnabled(deleteEnabled); - } - - private List<MusicDirectory.Entry> getSelectedSongs() { - List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10); - int count = entryList.getCount(); - for (int i = 0; i < count; i++) { - if (entryList.isItemChecked(i)) { - songs.add((MusicDirectory.Entry) entryList.getItemAtPosition(i)); - } - } - return songs; - } - - private void download(final boolean append, final boolean save, final boolean autoplay, final boolean playNext) { - if (getDownloadService() == null) { - return; - } - - final List<MusicDirectory.Entry> songs = getSelectedSongs(); - Runnable onValid = new Runnable() { - @Override - public void run() { - if (!append) { - getDownloadService().clear(); - } - - warnIfNetworkOrStorageUnavailable(); - getDownloadService().download(songs, save, autoplay, playNext); - String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); - if (playlistName != null) { - getDownloadService().setSuggestedPlaylistName(playlistName); - } - if (autoplay) { - Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class); - } else if (save) { - Util.toast(SelectAlbumActivity.this, - getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size())); - } else if (append) { - Util.toast(SelectAlbumActivity.this, - getResources().getQuantityString(R.plurals.select_album_n_songs_added, songs.size(), songs.size())); - } - } - }; - - checkLicenseAndTrialPeriod(onValid); - } - - private void delete() { - if (getDownloadService() != null) { - getDownloadService().delete(getSelectedSongs()); - } - } - - private void unpin() { - if (getDownloadService() != null) { - getDownloadService().unpin(getSelectedSongs()); - } - } - - private void playVideo(MusicDirectory.Entry entry) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId()))); - - startActivity(intent); - } - - private void checkLicenseAndTrialPeriod(Runnable onValid) { - if (licenseValid) { - onValid.run(); - return; - } - - int trialDaysLeft = Util.getRemainingTrialDays(this); - Log.i(TAG, trialDaysLeft + " trial days left."); - - if (trialDaysLeft == 0) { - showDonationDialog(trialDaysLeft, null); - } else if (trialDaysLeft < Constants.FREE_TRIAL_DAYS / 2) { - showDonationDialog(trialDaysLeft, onValid); - } else { - Util.toast(this, getResources().getString(R.string.select_album_not_licensed, trialDaysLeft)); - onValid.run(); - } - } - - private void showDonationDialog(int trialDaysLeft, final Runnable onValid) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setIcon(android.R.drawable.ic_dialog_info); - - if (trialDaysLeft == 0) { - builder.setTitle(R.string.select_album_donate_dialog_0_trial_days_left); - } else { - builder.setTitle(getResources().getQuantityString(R.plurals.select_album_donate_dialog_n_trial_days_left, - trialDaysLeft, trialDaysLeft)); - } - - builder.setMessage(R.string.select_album_donate_dialog_message); - - builder.setPositiveButton(R.string.select_album_donate_dialog_now, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.DONATION_URL))); - } - }); - - builder.setNegativeButton(R.string.select_album_donate_dialog_later, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - if (onValid != null) { - onValid.run(); - } - } - }); - - builder.create().show(); - } - - private abstract class LoadTask extends TabActivityBackgroundTask<Pair<MusicDirectory, Boolean>> { - - public LoadTask() { - super(SelectAlbumActivity.this); - } - - protected abstract MusicDirectory load(MusicService service) throws Exception; - - @Override - protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable { - MusicService musicService = MusicServiceFactory.getMusicService(SelectAlbumActivity.this); - MusicDirectory dir = load(musicService); - boolean valid = musicService.isLicenseValid(SelectAlbumActivity.this, this); - return new Pair<MusicDirectory, Boolean>(dir, valid); - } - - @Override - protected void done(Pair<MusicDirectory, Boolean> result) { - List<MusicDirectory.Entry> entries = result.getFirst().getChildren(); - - int songCount = 0; - for (MusicDirectory.Entry entry : entries) { - if (!entry.isDirectory()) { - songCount++; - } - } - - if (songCount > 0) { - ActionBar actionBar = getActionBar(); - - if (actionBar != null) { - getImageLoader().setActionBarArtwork(selectButton, entries.get(0), actionBar); - } - - entryList.addFooterView(footer); - selectButton.setVisibility(View.VISIBLE); - playNowButton.setVisibility(View.VISIBLE); - playLastButton.setVisibility(View.VISIBLE); - } - - boolean isAlbumList = getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); - playAllButtonVisible = !(isAlbumList || entries.isEmpty()); - - emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); - - if (playAllButton != null) { - playAllButton.setVisible(playAllButtonVisible); - } - - entryList.setAdapter(new EntryAdapter(SelectAlbumActivity.this, getImageLoader(), entries, true)); - licenseValid = result.getSecond(); - - boolean playAll = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); - if (playAll && songCount > 0) { - playAll(); - } - } - } -} +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.ListView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.DownloadFile; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.EntryAdapter; +import net.sourceforge.subsonic.androidapp.util.Pair; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class SelectAlbumActivity extends SubsonicTabActivity { + + private static final String TAG = SelectAlbumActivity.class.getSimpleName(); + + private ListView entryList; + private View footer; + private View emptyView; + private Button selectButton; + private Button playNowButton; + private Button playLastButton; + private Button pinButton; + private Button unpinButton; + private Button deleteButton; + private Button moreButton; + private boolean licenseValid; + private boolean playAllButtonVisible; + private MenuItem playAllButton; + private Random random = new Random(); + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.select_album); + + entryList = (ListView) findViewById(R.id.select_album_entries); + + footer = LayoutInflater.from(this).inflate(R.layout.select_album_footer, entryList, false); + entryList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + entryList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (position >= 0) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); + if (entry.isDirectory()) { + Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, entry.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle()); + Util.startActivityWithoutTransition(SelectAlbumActivity.this, intent); + } else if (entry.isVideo()) { + playVideo(entry); + } else { + enableButtons(); + } + } + } + }); + entryList.setOnTouchListener(gestureListener); + + selectButton = (Button) findViewById(R.id.select_album_select); + playNowButton = (Button) findViewById(R.id.select_album_play_now); + playLastButton = (Button) findViewById(R.id.select_album_play_last); + pinButton = (Button) footer.findViewById(R.id.select_album_pin); + unpinButton = (Button) footer.findViewById(R.id.select_album_unpin); + deleteButton = (Button) footer.findViewById(R.id.select_album_delete); + moreButton = (Button) footer.findViewById(R.id.select_album_more); + emptyView = findViewById(R.id.select_album_empty); + + selectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + selectAllOrNone(); + } + }); + playNowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + download(false, false, true, false); + selectAll(false, false); + } + }); + playLastButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + download(true, false, false, false); + selectAll(false, false); + } + }); + pinButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + download(true, true, false, false); + selectAll(false, false); + } + }); + unpinButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + unpin(); + selectAll(false, false); + } + }); + deleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + delete(); + selectAll(false, false); + } + }); + + registerForContextMenu(entryList); + + enableButtons(); + + String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); + String name = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME); + String playlistId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); + String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); + String albumListType = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + int getStarredTracks = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_STARRED, 0); + int albumListSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int albumListOffset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); + + if (playlistId != null) { + getPlaylist(playlistId, playlistName); + } else if (albumListType != null) { + getAlbumList(albumListType, albumListSize, albumListOffset); + } else if (getStarredTracks != 0) { + getStarred(); + } else { + getMusicDirectory(id, name); + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + playAllButton = menu.findItem(R.id.select_album_play_all); + playAllButton.setVisible(playAllButtonVisible); + + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_album, menu); + inflater.inflate(R.menu.select_common, menu); + super.onCreateOptionsMenu(menu); + + return true; + } + + private void playAll() { + boolean hasSubFolders = false; + for (int i = 0; i < entryList.getCount(); i++) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(i); + if (entry != null && entry.isDirectory()) { + hasSubFolders = true; + break; + } + } + + String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); + if (hasSubFolders && id != null) { + downloadRecursively(id, false, false, true); + } else { + selectAll(true, false); + download(false, false, true, false); + selectAll(false, false); + } + } + + private void refresh() { + finish(); + Intent intent = getIntent(); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + Util.startActivityWithoutTransition(this, intent); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(info.position); + + if (entry.isDirectory()) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_album_context, menu); + } else { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_song_context, menu); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(info.position); + List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10); + songs.add((MusicDirectory.Entry) entryList.getItemAtPosition(info.position)); + switch (menuItem.getItemId()) { + case R.id.album_menu_play_now: + downloadRecursively(entry.getId(), false, false, true); + break; + case R.id.album_menu_play_last: + downloadRecursively(entry.getId(), false, true, false); + break; + case R.id.album_menu_pin: + downloadRecursively(entry.getId(), true, true, false); + break; + case R.id.song_menu_play_now: + getDownloadService().download(songs, false, true, true); + break; + case R.id.song_menu_play_next: + getDownloadService().download(songs, false, false, true); + break; + case R.id.song_menu_play_last: + getDownloadService().download(songs, false, false, false); + break; + case R.id.select_album_play_all: + playAll(); + break; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.main_shuffle: + Intent intent1 = new Intent(this, DownloadActivity.class); + intent1.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); + Util.startActivityWithoutTransition(this, intent1); + return true; + + case R.id.menu_refresh: + refresh(); + return true; + + case R.id.select_album_play_all: + playAll(); + return true; + + case R.id.menu_exit: + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); + Util.startActivityWithoutTransition(this, intent); + return true; + + case R.id.menu_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; + + case R.id.menu_help: + startActivity(new Intent(this, HelpActivity.class)); + return true; + } + + return false; + } + + private void getMusicDirectory(final String id, String name) { + setTitle(name); + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); + return service.getMusicDirectory(id, refresh, SelectAlbumActivity.this, this); + } + }.execute(); + } + + private void getStarred() { + setTitle(R.string.main_songs_starred); + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + return Util.getSongsFromSearchResult(service.getStarred(SelectAlbumActivity.this, this)); + } + }.execute(); + } + + private void getPlaylist(final String playlistId, String playlistName) { + setTitle(playlistName); + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + return service.getPlaylist(playlistId, SelectAlbumActivity.this, this); + } + }.execute(); + } + + private void getAlbumList(final String albumListType, final int size, final int offset) { + + if ("newest".equals(albumListType)) { + setTitle(R.string.main_albums_newest); + } else if ("random".equals(albumListType)) { + setTitle(R.string.main_albums_random); + } else if ("highest".equals(albumListType)) { + setTitle(R.string.main_albums_highest); + } else if ("recent".equals(albumListType)) { + setTitle(R.string.main_albums_recent); + } else if ("frequent".equals(albumListType)) { + setTitle(R.string.main_albums_frequent); + } else if ("starred".equals(albumListType)) { + setTitle(R.string.main_albums_starred); + } else if ("alphabeticalByName".equals(albumListType)) { + setTitle(R.string.main_albums_alphaByName); + } else if ("alphabeticalByArtist".equals(albumListType)) { + setTitle(R.string.main_albums_alphaByArtist); + } + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + return service.getAlbumList(albumListType, size, offset, SelectAlbumActivity.this, this); + } + + @Override + protected void done(Pair<MusicDirectory, Boolean> result) { + if (!result.getFirst().getChildren().isEmpty()) { + pinButton.setVisibility(View.GONE); + unpinButton.setVisibility(View.GONE); + deleteButton.setVisibility(View.GONE); + + // Hide more button when results are less than album list size + if (result.getFirst().getChildren().size() < getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)) { + moreButton.setVisibility(View.GONE); + } else { + entryList.addFooterView(footer); + moreButton.setVisibility(View.VISIBLE); + } + + moreButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); + String type = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + int size = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int offset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; + + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); + Util.startActivityWithoutTransition(SelectAlbumActivity.this, intent); + } + }); + } + super.done(result); + } + }.execute(); + } + + private void selectAllOrNone() { + boolean someUnselected = false; + int count = entryList.getCount(); + for (int i = 0; i < count; i++) { + if (!entryList.isItemChecked(i) && entryList.getItemAtPosition(i) instanceof MusicDirectory.Entry) { + someUnselected = true; + break; + } + } + selectAll(someUnselected, true); + } + + private void selectAll(boolean selected, boolean toast) { + int count = entryList.getCount(); + int selectedCount = 0; + for (int i = 0; i < count; i++) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(i); + if (entry != null && !entry.isDirectory() && !entry.isVideo()) { + entryList.setItemChecked(i, selected); + selectedCount++; + } + } + + // Display toast: N tracks selected / N tracks unselected + if (toast) { + int toastResId = selected ? R.string.select_album_n_selected + : R.string.select_album_n_unselected; + Util.toast(this, getString(toastResId, selectedCount)); + } + + enableButtons(); + } + + private void enableButtons() { + if (getDownloadService() == null) { + return; + } + + List<MusicDirectory.Entry> selection = getSelectedSongs(); + boolean enabled = !selection.isEmpty(); + boolean unpinEnabled = false; + boolean deleteEnabled = false; + + for (MusicDirectory.Entry song : selection) { + DownloadFile downloadFile = getDownloadService().forSong(song); + if (downloadFile.isCompleteFileAvailable()) { + deleteEnabled = true; + } + if (downloadFile.isSaved()) { + unpinEnabled = true; + } + } + + playNowButton.setEnabled(enabled); + playLastButton.setEnabled(enabled); + pinButton.setEnabled(enabled && !Util.isOffline(this)); + unpinButton.setEnabled(unpinEnabled); + deleteButton.setEnabled(deleteEnabled); + } + + private List<MusicDirectory.Entry> getSelectedSongs() { + List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10); + int count = entryList.getCount(); + for (int i = 0; i < count; i++) { + if (entryList.isItemChecked(i)) { + songs.add((MusicDirectory.Entry) entryList.getItemAtPosition(i)); + } + } + return songs; + } + + private void download(final boolean append, final boolean save, final boolean autoplay, final boolean playNext) { + if (getDownloadService() == null) { + return; + } + + final List<MusicDirectory.Entry> songs = getSelectedSongs(); + Runnable onValid = new Runnable() { + @Override + public void run() { + if (!append) { + getDownloadService().clear(); + } + + warnIfNetworkOrStorageUnavailable(); + getDownloadService().download(songs, save, autoplay, playNext); + String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); + if (playlistName != null) { + getDownloadService().setSuggestedPlaylistName(playlistName); + } + if (autoplay) { + Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class); + } else if (save) { + Util.toast(SelectAlbumActivity.this, + getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size())); + } else if (append) { + Util.toast(SelectAlbumActivity.this, + getResources().getQuantityString(R.plurals.select_album_n_songs_added, songs.size(), songs.size())); + } + } + }; + + checkLicenseAndTrialPeriod(onValid); + } + + private void delete() { + if (getDownloadService() != null) { + getDownloadService().delete(getSelectedSongs()); + } + } + + private void unpin() { + if (getDownloadService() != null) { + getDownloadService().unpin(getSelectedSongs()); + } + } + + private void playVideo(MusicDirectory.Entry entry) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId()))); + + startActivity(intent); + } + + private void checkLicenseAndTrialPeriod(Runnable onValid) { + if (licenseValid) { + onValid.run(); + return; + } + + int trialDaysLeft = Util.getRemainingTrialDays(this); + Log.i(TAG, trialDaysLeft + " trial days left."); + + if (trialDaysLeft == 0) { + showDonationDialog(trialDaysLeft, null); + } else if (trialDaysLeft < Constants.FREE_TRIAL_DAYS / 2) { + showDonationDialog(trialDaysLeft, onValid); + } else { + Util.toast(this, getResources().getString(R.string.select_album_not_licensed, trialDaysLeft)); + onValid.run(); + } + } + + private void showDonationDialog(int trialDaysLeft, final Runnable onValid) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(android.R.drawable.ic_dialog_info); + + if (trialDaysLeft == 0) { + builder.setTitle(R.string.select_album_donate_dialog_0_trial_days_left); + } else { + builder.setTitle(getResources().getQuantityString(R.plurals.select_album_donate_dialog_n_trial_days_left, + trialDaysLeft, trialDaysLeft)); + } + + builder.setMessage(R.string.select_album_donate_dialog_message); + + builder.setPositiveButton(R.string.select_album_donate_dialog_now, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.DONATION_URL))); + } + }); + + builder.setNegativeButton(R.string.select_album_donate_dialog_later, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + if (onValid != null) { + onValid.run(); + } + } + }); + + builder.create().show(); + } + + private abstract class LoadTask extends TabActivityBackgroundTask<Pair<MusicDirectory, Boolean>> { + + public LoadTask() { + super(SelectAlbumActivity.this); + } + + protected abstract MusicDirectory load(MusicService service) throws Exception; + + @Override + protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(SelectAlbumActivity.this); + MusicDirectory dir = load(musicService); + boolean valid = musicService.isLicenseValid(SelectAlbumActivity.this, this); + return new Pair<MusicDirectory, Boolean>(dir, valid); + } + + @Override + protected void done(Pair<MusicDirectory, Boolean> result) { + List<MusicDirectory.Entry> entries = result.getFirst().getChildren(); + + int songCount = 0; + for (MusicDirectory.Entry entry : entries) { + if (!entry.isDirectory()) { + songCount++; + } + } + + if (songCount > 0) { + ActionBar actionBar = getActionBar(); + + if (actionBar != null) { + // Use random entry selection for artwork in list of tracks + int artworkSelection = random.nextInt(entries.size()); + getImageLoader().setActionBarArtwork(selectButton, entries.get(artworkSelection), actionBar); + } + + entryList.addFooterView(footer); + selectButton.setVisibility(View.VISIBLE); + playNowButton.setVisibility(View.VISIBLE); + playLastButton.setVisibility(View.VISIBLE); + } + + boolean isAlbumList = getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + playAllButtonVisible = !(isAlbumList || entries.isEmpty()); + + emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); + + if (playAllButton != null) { + playAllButton.setVisible(playAllButtonVisible); + } + + entryList.setAdapter(new EntryAdapter(SelectAlbumActivity.this, getImageLoader(), entries, true)); + licenseValid = result.getSecond(); + + boolean playAll = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + if (playAll && songCount > 0) { + playAll(); + } + } + } +} diff --git a/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java b/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java index b5c86d91..b778e89c 100644 --- a/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java +++ b/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java @@ -1,551 +1,551 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see <http://www.gnu.org/licenses/>. - - Copyright 2009 (C) Sindre Mehus - */ -package net.sourceforge.subsonic.androidapp.activity; - -import java.io.File; -import java.io.PrintWriter; -import java.util.LinkedList; -import java.util.List; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.media.AudioManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.util.Log; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.Window; -import android.widget.TextView; -import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.service.DownloadService; -import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; -import net.sourceforge.subsonic.androidapp.service.MusicService; -import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; -import net.sourceforge.subsonic.androidapp.util.Constants; -import net.sourceforge.subsonic.androidapp.util.ImageLoader; -import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask; -import net.sourceforge.subsonic.androidapp.util.Util; - -/** - * @author Sindre Mehus - */ -public class SubsonicTabActivity extends Activity implements OnClickListener{ - private static final String TAG = SubsonicTabActivity.class.getSimpleName(); - private static ImageLoader IMAGE_LOADER; - private static final int SWIPE_MIN_DISTANCE = 120; - private static final int SWIPE_MAX_OFF_PATH = 250; - private static final int SWIPE_THRESHOLD_VELOCITY = 200; - - private boolean destroyed; - private View homeButton; - private View musicButton; - private View searchButton; - private View playlistButton; - private View nowPlayingButton; - - //private boolean shortPress = false; - - private GestureDetector gestureDetector; - View.OnTouchListener gestureListener; - - enum SwipeDirection - { - Left, - Right - }; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.common, menu); - - return true; - } - - @Override - protected void onCreate(Bundle bundle) { - setUncaughtExceptionHandler(); - applyTheme(); - super.onCreate(bundle); - - startService(new Intent(this, DownloadServiceImpl.class)); - setVolumeControlStream(AudioManager.STREAM_MUSIC); - - gestureDetector = new GestureDetector(new GestureActivity()); - gestureListener = new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return gestureDetector.onTouchEvent(event); - } - }; - } - - @Override - protected void onPostCreate(Bundle bundle) { - super.onPostCreate(bundle); - - homeButton = findViewById(R.id.button_bar_home); - homeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); - } - }); - - musicButton = findViewById(R.id.button_bar_music); - musicButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); - } - }); - - searchButton = findViewById(R.id.button_bar_search); - searchButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); - Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); - } - }); - - playlistButton = findViewById(R.id.button_bar_playlists); - playlistButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); - } - }); - - nowPlayingButton = findViewById(R.id.button_bar_now_playing); - nowPlayingButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); - } - }); - - if (this instanceof MainActivity) { - homeButton.setEnabled(false); - } else if (this instanceof SelectAlbumActivity || this instanceof SelectArtistActivity) { - musicButton.setEnabled(false); - } else if (this instanceof SearchActivity) { - searchButton.setEnabled(false); - } else if (this instanceof SelectPlaylistActivity) { - playlistButton.setEnabled(false); - } else if (this instanceof DownloadActivity || this instanceof LyricsActivity) { - nowPlayingButton.setEnabled(false); - } - - updateButtonVisibility(); - } - - @Override - protected void onResume() { - super.onResume(); - Util.registerMediaButtonEventReceiver(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case R.id.menu_exit: - Intent intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); - Util.startActivityWithoutTransition(this, intent); - return true; - - case R.id.menu_settings: - startActivity(new Intent(this, SettingsActivity.class)); - return true; - - case R.id.menu_help: - startActivity(new Intent(this, HelpActivity.class)); - return true; - } - - return false; - } - - @Override - protected void onDestroy() { - Util.unregisterMediaButtonEventReceiver(this); - super.onDestroy(); - destroyed = true; - getImageLoader().clear(); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN; - boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP; - boolean isVolumeAdjust = isVolumeDown || isVolumeUp; - boolean isJukebox = getDownloadService() != null && getDownloadService().isJukeboxEnabled(); - - if (isVolumeAdjust && isJukebox) { - getDownloadService().adjustJukeboxVolume(isVolumeUp); - return true; - } - -// if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { -// if(event.getAction() == KeyEvent.ACTION_DOWN){ -// event.startTracking(); -// -// if(event.getRepeatCount() == 0){ -// shortPress = true; -// } -// -// return true; -// } -// } - - return super.onKeyDown(keyCode, event); - } - - @Override - public void finish() { - super.finish(); - Util.disablePendingTransition(this); - } - - private void applyTheme() { - String theme = Util.getTheme(this); - if ("dark".equalsIgnoreCase(theme)) { - setTheme(R.style.Dark); - } else if ("light".equalsIgnoreCase(theme)) { - setTheme(R.style.Light); - } else if ("fullscreen".equalsIgnoreCase(theme)) { - setTheme(R.style.Fullscreen); - } else if ("fullscreenlight".equalsIgnoreCase(theme)) { - setTheme(R.style.Fullscreenlight); - } - } - - public boolean isDestroyed() { - return destroyed; - } - - private void updateButtonVisibility() { - int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE; - searchButton.setVisibility(visibility); - playlistButton.setVisibility(visibility); - } - - public void setProgressVisible(boolean visible) { - View view = findViewById(R.id.tab_progress); - if (view != null) { - view.setVisibility(visible ? View.VISIBLE : View.GONE); - } - } - - public void updateProgress(String message) { - TextView view = (TextView) findViewById(R.id.tab_progress_message); - if (view != null) { - view.setText(message); - } - } - - public DownloadService getDownloadService() { - // If service is not available, request it to start and wait for it. - for (int i = 0; i < 5; i++) { - DownloadService downloadService = DownloadServiceImpl.getInstance(); - if (downloadService != null) { - return downloadService; - } - Log.w(TAG, "DownloadService not running. Attempting to start it."); - startService(new Intent(this, DownloadServiceImpl.class)); - Util.sleepQuietly(50L); - } - return DownloadServiceImpl.getInstance(); - } - - protected void warnIfNetworkOrStorageUnavailable() { - if (!Util.isExternalStoragePresent()) { - Util.toast(this, R.string.select_album_no_sdcard); - } else if (!Util.isOffline(this) && !Util.isNetworkConnected(this)) { - Util.toast(this, R.string.select_album_no_network); - } - } - - protected synchronized ImageLoader getImageLoader() { - if (IMAGE_LOADER == null) { - IMAGE_LOADER = new ImageLoader(this); - } - return IMAGE_LOADER; - } - - protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay) { - ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) { - - private static final int MAX_SONGS = 500; - - @Override - protected List<MusicDirectory.Entry> doInBackground() throws Throwable { - MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); - MusicDirectory root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, this); - List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>(); - getSongsRecursively(root, songs); - return songs; - } - - private void getSongsRecursively(MusicDirectory parent, List<MusicDirectory.Entry> songs) throws Exception { - if (songs.size() > MAX_SONGS) { - return; - } - - for (MusicDirectory.Entry song : parent.getChildren(false, true)) { - if (!song.isVideo()) { - songs.add(song); - } - } - for (MusicDirectory.Entry dir : parent.getChildren(true, false)) { - MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); - getSongsRecursively(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, this), songs); - } - } - - @Override - protected void done(List<MusicDirectory.Entry> songs) { - DownloadService downloadService = getDownloadService(); - if (!songs.isEmpty() && downloadService != null) { - if (!append) { - downloadService.clear(); - } - warnIfNetworkOrStorageUnavailable(); - downloadService.download(songs, save, autoplay, false); - Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); - } - } - }; - - task.execute(); - } - - private void setUncaughtExceptionHandler() { - Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); - if (!(handler instanceof SubsonicUncaughtExceptionHandler)) { - Thread.setDefaultUncaughtExceptionHandler(new SubsonicUncaughtExceptionHandler(this)); - } - } - - /** - * Logs the stack trace of uncaught exceptions to a file on the SD card. - */ - private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { - - private final Thread.UncaughtExceptionHandler defaultHandler; - private final Context context; - - private SubsonicUncaughtExceptionHandler(Context context) { - this.context = context; - defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - } - - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - File file = null; - PrintWriter printWriter = null; - try { - - PackageInfo packageInfo = context.getPackageManager().getPackageInfo("net.sourceforge.subsonic.androidapp", 0); - file = new File(Environment.getExternalStorageDirectory(), "subsonic-stacktrace.txt"); - printWriter = new PrintWriter(file); - printWriter.println("Android API level: " + Build.VERSION.SDK_INT); - printWriter.println("Subsonic version name: " + packageInfo.versionName); - printWriter.println("Subsonic version code: " + packageInfo.versionCode); - printWriter.println(); - throwable.printStackTrace(printWriter); - Log.i(TAG, "Stack trace written to " + file); - } catch (Throwable x) { - Log.e(TAG, "Failed to write stack trace to " + file, x); - } finally { - Util.close(printWriter); - if (defaultHandler != null) { - defaultHandler.uncaughtException(thread, throwable); - } - - } - } - } - - @Override - public void onClick(View arg0) { - // TODO Auto-generated method stub - - } - - class GestureActivity extends SimpleOnGestureListener { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - try { - if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) - return false; - - SwipeDirection swipe = null; - - // right to left swipe - if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { - swipe = SwipeDirection.Left; - } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { - swipe = SwipeDirection.Right; - } - - String name = SubsonicTabActivity.this.getClass().getSimpleName(); - - switch (swipe) - { - case Right: - if (name.equalsIgnoreCase("MainActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, DownloadActivity.class); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("SelectArtistActivity") || name.equalsIgnoreCase("SelectAlbumActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("SearchActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("SelectPlaylistActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("DownloadActivity") || name.equalsIgnoreCase("LyricsActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - SubsonicTabActivity.this.startActivity(intent); - } - - break; - case Left: - if (name.equalsIgnoreCase("MainActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - SubsonicTabActivity.this.startActivity(intent); - } - if (name.equalsIgnoreCase("SelectArtistActivity") || name.equalsIgnoreCase("SelectAlbumActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); - intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("SearchActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("SelectPlaylistActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, DownloadActivity.class); - SubsonicTabActivity.this.startActivity(intent); - } - else if (name.equalsIgnoreCase("DownloadActivity") || name.equalsIgnoreCase("LyricsActivity")) - { - Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - SubsonicTabActivity.this.startActivity(intent); - } - - break; - } - } catch (Exception e) { - // nothing - } - - return false; - } - } - -// @Override -// public boolean onKeyLongPress(int keyCode, KeyEvent event) { -// DownloadService service = getDownloadService(); -// int current = service.getCurrentPlayingIndex(); -// -// switch(keyCode){ -// case KeyEvent.KEYCODE_VOLUME_UP: -// shortPress = false; -// -// if (current == -1) { -// service.play(0); -// } else { -// current++; -// service.play(current); -// } -// return true; -// case KeyEvent.KEYCODE_VOLUME_DOWN: -// shortPress = false; -// -// if (current == -1 || current == 0) { -// service.play(0); -// } else { -// current--; -// service.play(current); -// } -// return true; -// } -// -// return super.onKeyLongPress(keyCode, event); -// } -// -// @Override -// public boolean onKeyUp(int keyCode, KeyEvent event) { -// if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { -// shortPress = false; -// return true; -// } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP){ -// shortPress = false; -// return true; -// } -// -// return super.onKeyUp(keyCode, event); -// } -} - +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import java.io.File; +import java.io.PrintWriter; +import java.util.LinkedList; +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.media.AudioManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.Window; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.ImageLoader; +import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * @author Sindre Mehus + */ +public class SubsonicTabActivity extends Activity implements OnClickListener{ + private static final String TAG = SubsonicTabActivity.class.getSimpleName(); + private static ImageLoader IMAGE_LOADER; + private static final int SWIPE_MIN_DISTANCE = 120; + private static final int SWIPE_MAX_OFF_PATH = 250; + private static final int SWIPE_THRESHOLD_VELOCITY = 200; + + private boolean destroyed; + private View homeButton; + private View musicButton; + private View searchButton; + private View playlistButton; + private View nowPlayingButton; + + //private boolean shortPress = false; + + private GestureDetector gestureDetector; + View.OnTouchListener gestureListener; + + enum SwipeDirection + { + Left, + Right + }; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.common, menu); + + return true; + } + + @Override + protected void onCreate(Bundle bundle) { + setUncaughtExceptionHandler(); + applyTheme(); + super.onCreate(bundle); + + startService(new Intent(this, DownloadServiceImpl.class)); + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + gestureDetector = new GestureDetector(new GestureActivity()); + gestureListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return gestureDetector.onTouchEvent(event); + } + }; + } + + @Override + protected void onPostCreate(Bundle bundle) { + super.onPostCreate(bundle); + + homeButton = findViewById(R.id.button_bar_home); + homeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + musicButton = findViewById(R.id.button_bar_music); + musicButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + searchButton = findViewById(R.id.button_bar_search); + searchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + playlistButton = findViewById(R.id.button_bar_playlists); + playlistButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + nowPlayingButton = findViewById(R.id.button_bar_now_playing); + nowPlayingButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); + } + }); + + if (this instanceof MainActivity) { + homeButton.setEnabled(false); + } else if (this instanceof SelectAlbumActivity || this instanceof SelectArtistActivity) { + musicButton.setEnabled(false); + } else if (this instanceof SearchActivity) { + searchButton.setEnabled(false); + } else if (this instanceof SelectPlaylistActivity) { + playlistButton.setEnabled(false); + } else if (this instanceof DownloadActivity || this instanceof LyricsActivity) { + nowPlayingButton.setEnabled(false); + } + + updateButtonVisibility(); + } + + @Override + protected void onResume() { + super.onResume(); + Util.registerMediaButtonEventReceiver(this); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_exit: + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); + Util.startActivityWithoutTransition(this, intent); + return true; + + case R.id.menu_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; + + case R.id.menu_help: + startActivity(new Intent(this, HelpActivity.class)); + return true; + } + + return false; + } + + @Override + protected void onDestroy() { + Util.unregisterMediaButtonEventReceiver(this); + super.onDestroy(); + destroyed = true; + getImageLoader().clear(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN; + boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP; + boolean isVolumeAdjust = isVolumeDown || isVolumeUp; + boolean isJukebox = getDownloadService() != null && getDownloadService().isJukeboxEnabled(); + + if (isVolumeAdjust && isJukebox) { + getDownloadService().adjustJukeboxVolume(isVolumeUp); + return true; + } + +// if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { +// if(event.getAction() == KeyEvent.ACTION_DOWN){ +// event.startTracking(); +// +// if(event.getRepeatCount() == 0){ +// shortPress = true; +// } +// +// return true; +// } +// } + + return super.onKeyDown(keyCode, event); + } + + @Override + public void finish() { + super.finish(); + Util.disablePendingTransition(this); + } + + private void applyTheme() { + String theme = Util.getTheme(this); + if ("dark".equalsIgnoreCase(theme)) { + setTheme(R.style.Dark); + } else if ("light".equalsIgnoreCase(theme)) { + setTheme(R.style.Light); + } else if ("fullscreen".equalsIgnoreCase(theme)) { + setTheme(R.style.Fullscreen); + } else if ("fullscreenlight".equalsIgnoreCase(theme)) { + setTheme(R.style.Fullscreenlight); + } + } + + public boolean getIsDestroyed() { + return destroyed; + } + + private void updateButtonVisibility() { + int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE; + searchButton.setVisibility(visibility); + playlistButton.setVisibility(visibility); + } + + public void setProgressVisible(boolean visible) { + View view = findViewById(R.id.tab_progress); + if (view != null) { + view.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + public void updateProgress(String message) { + TextView view = (TextView) findViewById(R.id.tab_progress_message); + if (view != null) { + view.setText(message); + } + } + + public DownloadService getDownloadService() { + // If service is not available, request it to start and wait for it. + for (int i = 0; i < 5; i++) { + DownloadService downloadService = DownloadServiceImpl.getInstance(); + if (downloadService != null) { + return downloadService; + } + Log.w(TAG, "DownloadService not running. Attempting to start it."); + startService(new Intent(this, DownloadServiceImpl.class)); + Util.sleepQuietly(50L); + } + return DownloadServiceImpl.getInstance(); + } + + protected void warnIfNetworkOrStorageUnavailable() { + if (!Util.isExternalStoragePresent()) { + Util.toast(this, R.string.select_album_no_sdcard); + } else if (!Util.isOffline(this) && !Util.isNetworkConnected(this)) { + Util.toast(this, R.string.select_album_no_network); + } + } + + protected synchronized ImageLoader getImageLoader() { + if (IMAGE_LOADER == null) { + IMAGE_LOADER = new ImageLoader(this); + } + return IMAGE_LOADER; + } + + protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay) { + ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) { + + private static final int MAX_SONGS = 500; + + @Override + protected List<MusicDirectory.Entry> doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); + MusicDirectory root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, this); + List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>(); + getSongsRecursively(root, songs); + return songs; + } + + private void getSongsRecursively(MusicDirectory parent, List<MusicDirectory.Entry> songs) throws Exception { + if (songs.size() > MAX_SONGS) { + return; + } + + for (MusicDirectory.Entry song : parent.getChildren(false, true)) { + if (!song.isVideo()) { + songs.add(song); + } + } + for (MusicDirectory.Entry dir : parent.getChildren(true, false)) { + MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); + getSongsRecursively(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, this), songs); + } + } + + @Override + protected void done(List<MusicDirectory.Entry> songs) { + DownloadService downloadService = getDownloadService(); + if (!songs.isEmpty() && downloadService != null) { + if (!append) { + downloadService.clear(); + } + warnIfNetworkOrStorageUnavailable(); + downloadService.download(songs, save, autoplay, false); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); + } + } + }; + + task.execute(); + } + + private void setUncaughtExceptionHandler() { + Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); + if (!(handler instanceof SubsonicUncaughtExceptionHandler)) { + Thread.setDefaultUncaughtExceptionHandler(new SubsonicUncaughtExceptionHandler(this)); + } + } + + /** + * Logs the stack trace of uncaught exceptions to a file on the SD card. + */ + private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + private final Thread.UncaughtExceptionHandler defaultHandler; + private final Context context; + + private SubsonicUncaughtExceptionHandler(Context context) { + this.context = context; + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + File file = null; + PrintWriter printWriter = null; + try { + + PackageInfo packageInfo = context.getPackageManager().getPackageInfo("net.sourceforge.subsonic.androidapp", 0); + file = new File(Environment.getExternalStorageDirectory(), "subsonic-stacktrace.txt"); + printWriter = new PrintWriter(file); + printWriter.println("Android API level: " + Build.VERSION.SDK_INT); + printWriter.println("Subsonic version name: " + packageInfo.versionName); + printWriter.println("Subsonic version code: " + packageInfo.versionCode); + printWriter.println(); + throwable.printStackTrace(printWriter); + Log.i(TAG, "Stack trace written to " + file); + } catch (Throwable x) { + Log.e(TAG, "Failed to write stack trace to " + file, x); + } finally { + Util.close(printWriter); + if (defaultHandler != null) { + defaultHandler.uncaughtException(thread, throwable); + } + + } + } + } + + @Override + public void onClick(View arg0) { + // TODO Auto-generated method stub + + } + + class GestureActivity extends SimpleOnGestureListener { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + try { + if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) + return false; + + SwipeDirection swipe = null; + + // right to left swipe + if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { + swipe = SwipeDirection.Left; + } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { + swipe = SwipeDirection.Right; + } + + String name = SubsonicTabActivity.this.getClass().getSimpleName(); + + switch (swipe) + { + case Right: + if (name.equalsIgnoreCase("MainActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, DownloadActivity.class); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("SelectArtistActivity") || name.equalsIgnoreCase("SelectAlbumActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("SearchActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("SelectPlaylistActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("DownloadActivity") || name.equalsIgnoreCase("LyricsActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + SubsonicTabActivity.this.startActivity(intent); + } + + break; + case Left: + if (name.equalsIgnoreCase("MainActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + SubsonicTabActivity.this.startActivity(intent); + } + if (name.equalsIgnoreCase("SelectArtistActivity") || name.equalsIgnoreCase("SelectAlbumActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("SearchActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("SelectPlaylistActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, DownloadActivity.class); + SubsonicTabActivity.this.startActivity(intent); + } + else if (name.equalsIgnoreCase("DownloadActivity") || name.equalsIgnoreCase("LyricsActivity")) + { + Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + SubsonicTabActivity.this.startActivity(intent); + } + + break; + } + } catch (Exception e) { + // nothing + } + + return false; + } + } + +// @Override +// public boolean onKeyLongPress(int keyCode, KeyEvent event) { +// DownloadService service = getDownloadService(); +// int current = service.getCurrentPlayingIndex(); +// +// switch(keyCode){ +// case KeyEvent.KEYCODE_VOLUME_UP: +// shortPress = false; +// +// if (current == -1) { +// service.play(0); +// } else { +// current++; +// service.play(current); +// } +// return true; +// case KeyEvent.KEYCODE_VOLUME_DOWN: +// shortPress = false; +// +// if (current == -1 || current == 0) { +// service.play(0); +// } else { +// current--; +// service.play(current); +// } +// return true; +// } +// +// return super.onKeyLongPress(keyCode, event); +// } +// +// @Override +// public boolean onKeyUp(int keyCode, KeyEvent event) { +// if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { +// shortPress = false; +// return true; +// } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP){ +// shortPress = false; +// return true; +// } +// +// return super.onKeyUp(keyCode, event); +// } +} + diff --git a/src/net/sourceforge/subsonic/androidapp/service/DownloadServiceImpl.java b/src/net/sourceforge/subsonic/androidapp/service/DownloadServiceImpl.java index 195de7fc..34dd2a7c 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/DownloadServiceImpl.java +++ b/src/net/sourceforge/subsonic/androidapp/service/DownloadServiceImpl.java @@ -47,6 +47,7 @@ import net.sourceforge.subsonic.androidapp.domain.RepeatMode; import net.sourceforge.subsonic.androidapp.provider.SubsonicAppWidgetProvider4x1; import net.sourceforge.subsonic.androidapp.receiver.MediaButtonIntentReceiver; import net.sourceforge.subsonic.androidapp.util.CancellableTask; +import net.sourceforge.subsonic.androidapp.util.FileUtil; import net.sourceforge.subsonic.androidapp.util.LRUCache; import net.sourceforge.subsonic.androidapp.util.ShufflePlayBuffer; import net.sourceforge.subsonic.androidapp.util.SimpleServiceBinder; @@ -796,10 +797,10 @@ public class DownloadServiceImpl extends Service implements DownloadService { String title = artist + " - " + currentSong.getTitle(); Integer duration = currentSong.getDuration(); - MusicService musicService = MusicServiceFactory.getMusicService(this); DisplayMetrics metrics = this.getResources().getDisplayMetrics(); int size = Math.min(metrics.widthPixels, metrics.heightPixels); - Bitmap bitmap = musicService.getCoverArt(this, currentSong, size, true, null); + // Always get the album art from disk + Bitmap bitmap = FileUtil.getAlbumArtBitmap(this, currentSong, size); // Update the remote controls remoteControlClient @@ -808,8 +809,14 @@ public class DownloadServiceImpl extends Service implements DownloadService { .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, artist) .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, album) .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration) - .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap) .apply(); + + if (bitmap != null) { + remoteControlClient + .editMetadata(false) + .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap) + .apply(); + } } } catch (Exception e) { diff --git a/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java b/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java index 11893d5a..11c8e11f 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java +++ b/src/net/sourceforge/subsonic/androidapp/service/OfflineMusicService.java @@ -134,7 +134,7 @@ public class OfflineMusicService extends RESTMusicService { byte[] bytes = Util.toByteArray(in); Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); Log.i("getCoverArt", "getCoverArt"); - return Bitmap.createScaledBitmap(bitmap, size, size, true); + return Util.scaleBitmap(bitmap, size); } finally { Util.close(in); } diff --git a/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java b/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java index ed69924a..eeabf1cd 100644 --- a/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java +++ b/src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java @@ -448,52 +448,64 @@ public class RESTMusicService implements MusicService { } @Override - public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception { + public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception { + // Synchronize on the entry so that we don't download concurrently for + // the same song. + synchronized (entry) { + // Use cached file, if existing. + Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size); + boolean serverScaling = Util.isServerScalingEnabled(context); + + if (bitmap == null) { + String url = Util.getRestUrl(context, "getCoverArt"); - // Synchronize on the entry so that we don't download concurrently for the same song. - synchronized (entry) { + InputStream in = null; + try { + List<String> parameterNames; + List<Object> parameterValues; + + if (serverScaling) { + parameterNames = Arrays.asList("id", "size"); + parameterValues = Arrays.<Object>asList(entry.getCoverArt(), size); + } + else { + parameterNames = Arrays.asList("id"); + parameterValues = Arrays.<Object> asList(entry.getCoverArt()); + } + + HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener); + in = entity.getContent(); - // Use cached file, if existing. - Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size); - if (bitmap != null) { - return bitmap; - } + // If content type is XML, an error occurred. Get it. + String contentType = Util.getContentType(entity); + if (contentType != null && contentType.startsWith("text/xml")) { + new ErrorParser(context).parse(new InputStreamReader(in, Constants.UTF_8)); + return null; // Never reached. + } - String url = Util.getRestUrl(context, "getCoverArt"); + byte[] bytes = Util.toByteArray(in); - InputStream in = null; - try { - List<String> parameterNames = Arrays.asList("id", "size"); - List<Object> parameterValues = Arrays.<Object>asList(entry.getCoverArt(), size); - HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener); - in = entity.getContent(); + // If we aren't allowing server-side scaling, always save the file to disk because it will be unmodified + if (!serverScaling || saveToFile) { + OutputStream out = null; + try { + out = new FileOutputStream(FileUtil.getAlbumArtFile(context, entry)); + out.write(bytes); + } finally { + Util.close(out); + } + } - // If content type is XML, an error occured. Get it. - String contentType = Util.getContentType(entity); - if (contentType != null && contentType.startsWith("text/xml")) { - new ErrorParser(context).parse(new InputStreamReader(in, Constants.UTF_8)); - return null; // Never reached. - } - - byte[] bytes = Util.toByteArray(in); - - if (saveToFile) { - OutputStream out = null; - try { - out = new FileOutputStream(FileUtil.getAlbumArtFile(context, entry)); - out.write(bytes); - } finally { - Util.close(out); - } - } - - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - - } finally { - Util.close(in); - } - } - } + bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + } finally { + Util.close(in); + } + } + + // Return scaled bitmap + return Util.scaleBitmap(bitmap, size); + } + } @Override public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception { diff --git a/src/net/sourceforge/subsonic/androidapp/util/Constants.java b/src/net/sourceforge/subsonic/androidapp/util/Constants.java index e1adb80c..532824b9 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/Constants.java +++ b/src/net/sourceforge/subsonic/androidapp/util/Constants.java @@ -73,6 +73,7 @@ public final class Constants { public static final String PREFERENCES_KEY_MEDIA_BUTTONS = "mediaButtons"; public static final String PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD = "screenLitOnDownload"; public static final String PREFERENCES_KEY_SCROBBLE = "scrobble"; + public static final String PREFERENCES_KEY_SERVER_SCALING = "serverScaling"; public static final String PREFERENCES_KEY_REPEAT_MODE = "repeatMode"; public static final String PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD = "wifiRequiredForDownload"; public static final String PREFERENCES_KEY_BUFFER_LENGTH = "bufferLength"; diff --git a/src/net/sourceforge/subsonic/androidapp/util/FileUtil.java b/src/net/sourceforge/subsonic/androidapp/util/FileUtil.java index 5934d3d9..5567fba7 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/FileUtil.java +++ b/src/net/sourceforge/subsonic/androidapp/util/FileUtil.java @@ -84,11 +84,13 @@ public class FileUtil { public static Bitmap getAlbumArtBitmap(Context context, MusicDirectory.Entry entry, int size) { File albumArtFile = getAlbumArtFile(context, entry); + if (albumArtFile.exists()) { Bitmap bitmap = BitmapFactory.decodeFile(albumArtFile.getPath()); Log.i("getAlbumArtBitmap", String.valueOf(size)); - return bitmap == null ? null : Bitmap.createScaledBitmap(bitmap, size, size, true); + return bitmap == null ? null : Util.scaleBitmap(bitmap, size); } + return null; } @@ -107,6 +109,9 @@ public class FileUtil { } else { String artist = fileSystemSafe(entry.getArtist()); String album = fileSystemSafe(entry.getAlbum()); + if (album == "unnamed") { + album = fileSystemSafe(entry.getTitle()); + } dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album); } return dir; diff --git a/src/net/sourceforge/subsonic/androidapp/util/ImageLoader.java b/src/net/sourceforge/subsonic/androidapp/util/ImageLoader.java index 9af71a2d..039b03cb 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/ImageLoader.java +++ b/src/net/sourceforge/subsonic/androidapp/util/ImageLoader.java @@ -21,11 +21,6 @@ package net.sourceforge.subsonic.androidapp.util; import android.app.ActionBar; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; @@ -37,7 +32,6 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.activity.DownloadActivity; import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; import net.sourceforge.subsonic.androidapp.service.MusicService; import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; @@ -82,20 +76,28 @@ public class ImageLoader implements Runnable { private void createLargeUnknownImage(Context context) { BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.unknown_album_large); Log.i(TAG, "createLargeUnknownImage"); - Bitmap bitmap = Bitmap.createScaledBitmap(drawable.getBitmap(), imageSizeLarge, imageSizeLarge, true); + Bitmap bitmap = Util.scaleBitmap(drawable.getBitmap(), imageSizeLarge); //bitmap = createReflection(bitmap); largeUnknownImage = Util.createDrawableFromBitmap(context, bitmap); } public void loadImage(View view, MusicDirectory.Entry entry, boolean large, boolean crossfade) { - if (entry == null || entry.getCoverArt() == null) { + if (entry == null) { + setUnknownImage(view, large); + return; + } + + String coverArt = entry.getCoverArt(); + + if (coverArt == null) { setUnknownImage(view, large); return; } - + int size = large ? imageSizeLarge : imageSizeDefault; - Drawable drawable = cache.get(getKey(entry.getCoverArt(), size)); + Drawable drawable = cache.get(getKey(coverArt, size)); + if (drawable != null) { setImage(view, drawable, large); return; @@ -104,6 +106,7 @@ public class ImageLoader implements Runnable { if (!large) { setUnknownImage(view, large); } + queue.offer(new Task(view, entry, size, large, large, crossfade)); } diff --git a/src/net/sourceforge/subsonic/androidapp/util/TabActivityBackgroundTask.java b/src/net/sourceforge/subsonic/androidapp/util/TabActivityBackgroundTask.java index 033a51ad..91b67f3b 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/TabActivityBackgroundTask.java +++ b/src/net/sourceforge/subsonic/androidapp/util/TabActivityBackgroundTask.java @@ -52,7 +52,7 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T> { } private boolean isCancelled() { - return tabActivity.isDestroyed(); + return tabActivity.getIsDestroyed(); } @Override diff --git a/src/net/sourceforge/subsonic/androidapp/util/Util.java b/src/net/sourceforge/subsonic/androidapp/util/Util.java index 759f51cf..78ce38c9 100644 --- a/src/net/sourceforge/subsonic/androidapp/util/Util.java +++ b/src/net/sourceforge/subsonic/androidapp/util/Util.java @@ -137,6 +137,14 @@ public class Util extends DownloadActivity { return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false); } + public static boolean isServerScalingEnabled(Context context) { + if (isOffline(context)) { + return false; + } + SharedPreferences prefs = getPreferences(context); + return prefs.getBoolean(Constants.PREFERENCES_KEY_SERVER_SCALING, false); + } + public static boolean isNotificationEnabled(Context context) { SharedPreferences prefs = getPreferences(context); return prefs.getBoolean(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION, false); @@ -693,6 +701,17 @@ public class Util extends DownloadActivity { return new BitmapDrawable(bitmap); } } + + public static Bitmap scaleBitmap(Bitmap bitmap, int size) { + // Try to keep correct aspect ratio of the original image, do not force a square + double aspectRatio = (double)bitmap.getHeight() / (double)bitmap.getWidth(); + + // Assume the size given refers to the width of the image, so calculate the new height using + // the previously determined aspect ratio + int newHeight = (int) Math.round(size * aspectRatio); + + return Bitmap.createScaledBitmap(bitmap, size, newHeight, true); + } public static void registerMediaButtonEventReceiver(Context context) {