Compare commits
299 Commits
Author | SHA1 | Date |
---|---|---|
Andrew Rabert | 4c1bb5375d | |
Andrew Rabert | f1a9686eb4 | |
Andrew Rabert | 279adc166b | |
Andrew Rabert | c20a64ab15 | |
Andrew Rabert | aa4c877046 | |
Jordi Masip | 09c5d7ee51 | |
Andrew Rabert | 27b0dc742c | |
Andrew Rabert | 63df26cdfd | |
Andrew Rabert | 49f15f2a0d | |
Andrew Rabert | 2c3c9ede14 | |
Andrew Rabert | ef2e19ebf2 | |
Andrew Rabert | 4c5406c916 | |
Andrew Rabert | 072a6d7870 | |
Andrew Rabert | 5ae04b3643 | |
Andrew Rabert | 79ea6cb082 | |
Robert Robinson | 8cfae03550 | |
Andrew Rabert | 298a06ab5b | |
anasofiagribeiro | 41d17f960e | |
Andrew Rabert | a861e035ab | |
Morgan Lim | 4598f849ff | |
Morgan Lim | d37a72b2a0 | |
Andrew Rabert | e102dea3d3 | |
Andrew Rabert | a69b1385c0 | |
Morgan Lim | f506bfb14a | |
Morgan Lim | 0f525befca | |
Morgan Lim | d3d6186fb7 | |
Andrew Rabert | d10ea92edd | |
Andrew Rabert | d1f1331f35 | |
Andrew Rabert | 4fd53e74c5 | |
Andrew Rabert | 6704e05da2 | |
Andrew Rabert | e79aff9e98 | |
Andrew Rabert | ffa048177e | |
Andrew Rabert | f6d308c37c | |
Simão Mata | 3c17e91ca8 | |
Andrew Rabert | ec083cd79d | |
Simão Mata | cfae0d30b5 | |
Andrew Rabert | ecdae8c6e3 | |
Andrew Rabert | 76efc2c62c | |
Andrew Rabert | 26848852ac | |
Simão Mata | 377f21d732 | |
Simão Mata | 8af307da28 | |
Andrew Rabert | 3d7b173cb1 | |
Andrew Rabert | e7ea8d4dfc | |
dddddd-mmmmmm | e2280304e4 | |
emaiannone | 061c318e05 | |
Andrew Rabert | 8fc54ac68c | |
Andrew Rabert | 8f4d3be90b | |
Andrew Rabert | a6a784037e | |
dddddd-mmmmmm | 2ed89ab72f | |
Andrew Rabert | d5b56eecdd | |
dddddd-mmmmmm | 81086c4a3a | |
dddddd-mmmmmm | 02d94e1a6c | |
dddddd-mmmmmm | 5141eb6e81 | |
dddddd-mmmmmm | 4254c8ad9f | |
Andrew Rabert | bf9e9ad1e6 | |
Andrew Rabert | 02a8b1909a | |
Morgan Lim | ff4a1f3b13 | |
Morgan Lim | 153ccdd46a | |
Morgan Lim | 507c9008aa | |
Morgan Lim | 35a112509e | |
Andrew Rabert | af89b8ea76 | |
Morgan Lim | 0a84e9376a | |
Morgan Lim | 717cab5dd5 | |
Andrew Rabert | 13810a07a5 | |
Andrew Rabert | b147cc10dc | |
mlim15 | 357cbd2ba1 | |
Andrew Rabert | 435d770716 | |
Andrew Rabert | 4503ae4133 | |
Andrew Rabert | d43cfb1ce3 | |
mlim15 | cc0faa19ce | |
mlim15 | 8078eeff74 | |
mlim15 | d176ed4036 | |
mlim15 | cf4ab6bf05 | |
Andrew Rabert | 0239248b23 | |
Andrew Rabert | 8ee8858098 | |
Andrew Rabert | 23054ffab0 | |
Andrew Rabert | f623ad2b63 | |
Andrew Rabert | 4eac05e57f | |
Andrew Rabert | 1c269aac53 | |
Andrew Rabert | c5c4185e2f | |
Andrew Rabert | f56c976de7 | |
Andrew Rabert | 572024da52 | |
Andrew Rabert | 97507a25a1 | |
Andrew Rabert | e7628a97c7 | |
Andrew Rabert | f8a9d8ad34 | |
Andrew Rabert | 92c41fd463 | |
Andrew Rabert | 22c98d9606 | |
Andrew Rabert | bf3233d6b2 | |
Andrew Rabert | 93658f32d3 | |
Andrew Rabert | c0ee2d908e | |
Andrew Rabert | e4febf7260 | |
Andrew Rabert | 70f6e22be7 | |
Andrew Rabert | 54d6269a9e | |
Andrew Rabert | eaef36849a | |
Andrew Rabert | e28a70a6e6 | |
Andrew Rabert | 2ca504d7e9 | |
Andrew Rabert | 5e34f2d8e9 | |
Andrew Rabert | 9e57cf1433 | |
Andrew Rabert | 687f64c115 | |
Andrew Rabert | 3ebb48682b | |
Andrew Rabert | a334494186 | |
Andrew Rabert | d43cca9436 | |
Andrew Rabert | a6fa354622 | |
Andrew Rabert | 89ede8ff47 | |
Andrew Rabert | 3243f320b1 | |
Andrew Rabert | e12fbf68ad | |
Andrew Rabert | b943580fa8 | |
Andrew Rabert | a24faf6fae | |
Andrew Rabert | 5144f82051 | |
Andrew Rabert | 63fc23d9fb | |
Andrew Rabert | 11223bd44d | |
Andrew Rabert | 17091f8f86 | |
Andrew Rabert | 3648f64fa4 | |
Andrew Rabert | b0750949f0 | |
Andrew Rabert | 66db8db769 | |
Andrew Rabert | d62e010894 | |
Andrew Rabert | 39a7c39b94 | |
Andrew Rabert | 963aace2c5 | |
Andrew Rabert | 2e49bcadb2 | |
Andrew Rabert | f3b91bff2d | |
Andrew Rabert | 9d9ff7b728 | |
Andrew Rabert | ca15682a5e | |
Andrew Rabert | b213a99e7c | |
Andrew Rabert | cf579043f2 | |
Andrew Rabert | c6729e427b | |
Andrew Rabert | 514eb00797 | |
Andrew Rabert | 5fc74d7cfc | |
Andrew Rabert | 3dffd84f18 | |
Andrew Rabert | 49d5c007d6 | |
Andrew Rabert | 8e2bdadef5 | |
Andrew Rabert | 9acf64c42b | |
Andrew Rabert | 02ffb3b8f0 | |
Andrew Rabert | f4fbdc2bb1 | |
Andrew Rabert | 625f7ba97e | |
dddddd-mmmmmm | 01d4b270cf | |
dddddd-mmmmmm | 05ce1509ec | |
dddddd-mmmmmm | 13ffabdb20 | |
dddddd-mmmmmm | 2015039c3d | |
dddddd-mmmmmm | 2301e2e015 | |
Andrew Rabert | ae55b32ac5 | |
Andrew Rabert | b86cc33bbf | |
Andrew Rabert | 94bf3cbb57 | |
Andrew Rabert | 20cc63f152 | |
Andrew Rabert | e5702c1761 | |
Andrew Rabert | 665bbb3788 | |
Andrew Rabert | 7b1f690d5f | |
Andrew Rabert | cd6a3b0054 | |
Andrew Rabert | 3a4fda9b7c | |
Andrew Rabert | 81a1a44b87 | |
Andrew Rabert | b6f0d7ccc9 | |
Andrew Rabert | 9614cf9445 | |
Andrew Rabert | f1fe5a087b | |
Andrew Rabert | c7f1fa8665 | |
Andrew Rabert | e7953958da | |
Andrew Rabert | 115f211e9f | |
Andrew Rabert | d3a317dc9e | |
Andrew Rabert | 2054ce5427 | |
Andrew Rabert | 405fc8cafd | |
Andrew Rabert | 59c814cf16 | |
Andrew Rabert | 09e01d5641 | |
Andrew Rabert | be2c7d7e90 | |
Andrew Rabert | ec15274984 | |
Andrew Rabert | 092e2b4f6b | |
Andrew Rabert | 49173bc090 | |
Andrew Rabert | 7eeca7af1b | |
Andrew Rabert | 6990fe825e | |
Andrew Rabert | 07aa43aed8 | |
Andrew Rabert | 06aadeb776 | |
Andrew Rabert | bc0157c1b1 | |
Andrew Rabert | b75db5bb86 | |
Andrew Rabert | f34753dfc1 | |
Andrew Rabert | 904186baef | |
Andrew Rabert | d84327db02 | |
Andrew Rabert | d8ee4d012c | |
Andrew Rabert | 0c67244749 | |
Andrew Rabert | ff8862c723 | |
Andrew Rabert | 99c03d59b3 | |
Andrew Rabert | 9f3ecd5e8e | |
Andrew Rabert | 82a8d0fff8 | |
Andrew Rabert | bb9e3a506e | |
Andrew Rabert | 4fed3bd3a3 | |
Andrew Rabert | b7b90ea8d5 | |
Andrew Rabert | 561eb4add9 | |
Andrew Rabert | 7e151ce5ae | |
Andrew Rabert | 4a167d3548 | |
Andrew Rabert | 345b717f86 | |
Andrew Rabert | 72424659ab | |
Andrew Rabert | 7d1e55321a | |
Andrew Rabert | a4396eb375 | |
Andrew Rabert | 8222a3f449 | |
Andrew Rabert | 0a1cc75cc4 | |
Andrew Rabert | 81b562a2cb | |
Andrew Rabert | 7bb5b0f91d | |
Andrew Rabert | 8f0a27bfec | |
Andrew Rabert | 727219a1ca | |
Andrew Rabert | 7cc4495d19 | |
Andrew Rabert | 7b0edf77d6 | |
Andrew Rabert | a6571d2c4c | |
Andrew Rabert | e4f0ea25a3 | |
Andrew Rabert | dbe49846d9 | |
Andrew Rabert | b1d0e91b8a | |
Andrew Rabert | be1dc7d8c9 | |
Andrew Rabert | c7a49ee88e | |
Andrew Rabert | 1396ff5e48 | |
Andrew Rabert | 7e56cba6e5 | |
Andrew Rabert | 4f617ed270 | |
Andrew Rabert | ebe64ccccd | |
Andrew Rabert | 515aa47e59 | |
Andrew Rabert | e4456ef62a | |
Andrew Rabert | a72f978001 | |
Andrew Rabert | 75ad0a0239 | |
Andrew Rabert | cab37665c6 | |
Andrew Rabert | 13b35c5e4e | |
Andrew Rabert | 8d6a035e84 | |
Andrew Rabert | 8a6292c363 | |
Andrew Rabert | dd228a20e5 | |
Andrew Rabert | cb2e9a8f01 | |
Andrew Rabert | 0212910f5f | |
Andrew Rabert | f33340bd25 | |
Andrew Rabert | 8cb5e63852 | |
Andrew Rabert | 4ccbf5190b | |
Andrew Rabert | 2f8c2655eb | |
Andrew Rabert | 2bdf230284 | |
Andrew Rabert | 3ae6ff3ca4 | |
Andrew Rabert | be055d1bb3 | |
Andrew Rabert | 54158252b0 | |
Andrew Rabert | a7f6737ea6 | |
Andrew Rabert | 222cfe7a92 | |
Andrew Rabert | 521cdf49fc | |
Andrew Rabert | 8c26737492 | |
Andrew Rabert | 10eb5c0674 | |
Andrew Rabert | 1ed78a6d98 | |
Andrew Rabert | 860c8d783b | |
Andrew Rabert | c3fa27df57 | |
Andrew Rabert | 39da819765 | |
Andrew Rabert | 707476cd65 | |
Andrew Rabert | 4a580af041 | |
Andrew Rabert | 9ccd750d5a | |
Andrew Rabert | 08e7727644 | |
Andrew Rabert | 1a9e0a4ef0 | |
Andrew Rabert | 7efb614ba1 | |
Andrew Rabert | cb8ac22d09 | |
Andrew Rabert | c64edc2567 | |
Andrew Rabert | 1a2bfa97ae | |
Andrew Rabert | fdf9f57f53 | |
Andrew Rabert | 6a5b432e3b | |
Andrew Rabert | 382fcc0acc | |
Andrew Rabert | 588e827eec | |
Andrew Rabert | d1337ae3f0 | |
Andrew Rabert | d9239a3456 | |
Josip Sokcevic | fb71facfea | |
Andrew Rabert | 1a31b4b645 | |
Andrew Rabert | aa4cfb82ad | |
Andrew Rabert | 50676b6079 | |
Andrew Rabert | 9b4cc35aa3 | |
Andrew Rabert | 0f974c59d5 | |
Andrew Rabert | b42143ed10 | |
Andrew Rabert | 2bd2ec3bf4 | |
Andrew Rabert | b13f71952c | |
Andrew Rabert | cff384d311 | |
Andrew Rabert | 6c5929db20 | |
Andrew Rabert | 91437558ef | |
Andrew Rabert | 5e4d707141 | |
Andrew Rabert | f5ee9c3181 | |
Andrew Rabert | 62cd06997a | |
Andrew Rabert | c101be0bc9 | |
Andrew Rabert | a1f5a60b8f | |
Andrew Rabert | 9fd800acb8 | |
Andrew Rabert | 416846499a | |
Andrew Rabert | 17401fd7e1 | |
Andrew Rabert | 4bf393e73e | |
Andrew Rabert | 5d1889fd1d | |
Andrew Rabert | 90dc14c7bc | |
Andrew Rabert | ccdef60311 | |
Andrew Rabert | 79e2b9a336 | |
Andrew Rabert | 25dd053834 | |
Andrew Rabert | f89cff23bf | |
Andrew Rabert | fff6dd9d75 | |
Andrew Rabert | 1fcdf57248 | |
Andrew Rabert | e5c9a7b566 | |
Andrew Rabert | 7f5c726583 | |
Andrew Rabert | 2f2e5c5620 | |
Andrew Rabert | 39a457d1ec | |
Andrew Rabert | 7cd3d5122a | |
Clayton Craft | 451dda94e3 | |
Clayton Craft | 54d47f4101 | |
Andrew Rabert | 3997654461 | |
Andrew Rabert | 2de0eac2f1 | |
Andrew Rabert | 10c9fe0740 | |
Andrew Rabert | 9980ff0415 | |
Andrew Rabert | 44fd27ad33 | |
Andrew Rabert | 3a797f2484 | |
Andrew Rabert | a3e9370860 | |
Andrew Rabert | 366e946389 | |
Andrew Rabert | 20e981fe59 | |
Andrew Rabert | 293fa9748c | |
Andrew Rabert | 3a74db5287 | |
Andrew Rabert | e3c1d21afd | |
Andrew Rabert | 9aff6ec872 |
|
@ -1,16 +1,54 @@
|
|||
.classpath
|
||||
.project
|
||||
bin/*
|
||||
gen/*
|
||||
private/*
|
||||
nbandroid/*
|
||||
.idea
|
||||
subsonic-android.iml
|
||||
releases/
|
||||
proguard_logs/
|
||||
/gen/
|
||||
/out/
|
||||
.gradle/*
|
||||
/build/
|
||||
local.properties
|
||||
*Thumbs.db
|
||||
# Created by https://www.gitignore.io/api/android
|
||||
|
||||
### Android ###
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
### Android Patch ###
|
||||
gen-external-apklibs
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/android
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "ServerProxy"]
|
||||
path = ServerProxy
|
||||
url = https://github.com/daneren2005/ServerProxy.git
|
19
Audinaut.iml
19
Audinaut.iml
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="DSub" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="false" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,117 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
## Version 0.5.1
|
||||
_2020-04-30_
|
||||
* Add option to force server-side media scan
|
||||
* Change to local artist sorting (case-insensitive)
|
||||
* Fix crash while offline (#25)
|
||||
* Fix read timeout not being respected
|
||||
* Fix switching to playlist on app resume
|
||||
|
||||
## Version 0.5.0
|
||||
_2020-01-15_
|
||||
* Add 24kbps and 48kbps options
|
||||
* Add adaptive icon
|
||||
* Add support for p= authentication
|
||||
* Change to MediaStyle playback notification
|
||||
* Fix SSID selection
|
||||
* Fix keyboard being visible when switching to now playing
|
||||
* Fix now playing icon when using light theme
|
||||
|
||||
## Version 0.4.1
|
||||
_2019-12-28_
|
||||
* Revert attempt to fix infinite loop as it sometimes deleted valid files.
|
||||
|
||||
## Version 0.4.0
|
||||
_2019-12-22_
|
||||
* Add support for .opus files
|
||||
* Fix HTTP support
|
||||
* Fix infinite loop when playing contains only invalid files
|
||||
* Overhaul themes
|
||||
* Replace raster images with vector images
|
||||
|
||||
## Version 0.3.3
|
||||
_2019-03-17_
|
||||
* Fix [Funkwhale](https://funkwhale.audio/) (Subsonic API) support
|
||||
* Use query parameters instead of body
|
||||
* Disable most "now playing" swipe gestures - too sensitive and often
|
||||
accidentally triggered.
|
||||
Only swipe left/right to change track ramains.
|
||||
|
||||
|
||||
## Version 0.3.2
|
||||
_2018-07-05_
|
||||
* Prevent now playing from closing upon resuming
|
||||
* Only show save and delete playlist functions in menu
|
||||
* Stop hiding playlist on resume
|
||||
|
||||
|
||||
## Version 0.3.1
|
||||
_2018-06-03_
|
||||
* Fix crash on now playing & playlist while in landscape
|
||||
|
||||
|
||||
## Version 0.3.0
|
||||
_2018-05-12_
|
||||
* Center cover art on now playing screen
|
||||
* Use blurred cover art to fill empty space on now playing screen
|
||||
* Fix extra whitespace on now playing while offline
|
||||
|
||||
|
||||
## Version 0.2.5
|
||||
_2018-03-24_
|
||||
* Fix pausing playback on disconnect on API 26+
|
||||
* Allow testing connection in settings while offline
|
||||
|
||||
|
||||
## Version 0.2.4
|
||||
_2018-03-24_
|
||||
* Fix launch crash on Lollipop and earlier (added READ_PHONE_STATE for API <= 22) [#23](https://github.com/nvllsvm/Audinaut/issues/23)
|
||||
|
||||
|
||||
## Version 0.2.3
|
||||
_2018-02-27_
|
||||
* Fix notifications on API 26+
|
||||
* Target SDK 27
|
||||
* Dependency updates
|
||||
|
||||
|
||||
## Version 0.2.2
|
||||
_2017-06-11_
|
||||
* Use black background when loading application (previously white)
|
||||
* Begin transition to Kotlin
|
||||
* Target SDK 26
|
||||
* Dependency updates
|
||||
* Cleanup
|
||||
|
||||
|
||||
## Version 0.2.1
|
||||
_2017-04-13_
|
||||
* Form correct REST path. Fixes #7
|
||||
|
||||
|
||||
## Version 0.2.0
|
||||
_2017-03-14_
|
||||
* Use OkHttp for all HTTP calls
|
||||
* Dependency upgrade and cleanup
|
||||
* Bug fixes
|
||||
* Cleanup
|
||||
|
||||
|
||||
## Version 0.1.2
|
||||
_2017-03-04_
|
||||
* Forgot to bump the app version in tag 0.1.1, so now it's 0.1.2.
|
||||
* By default, hide music from other apps.
|
||||
* Change account type to be specific to Audinaut.
|
||||
|
||||
|
||||
## Version 0.1.1
|
||||
_2017-02-27_
|
||||
* Merge ServerProxy into Audinaut
|
||||
* Update Kryo to 4.0.0
|
||||
|
||||
|
||||
## Version 0.1.0
|
||||
_2017-12-18_
|
||||
* Initial release.
|
|
@ -0,0 +1,677 @@
|
|||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
32
README.md
32
README.md
|
@ -1,19 +1,21 @@
|
|||
# Audinaut
|
||||
<img src="/app/src/main/res/drawable/audinaut.png" width="200" hspace="10" vspace="10"></br>
|
||||
A FOSS Libresonic client for Android.
|
||||
⚠ No longer maintained.
|
||||
|
||||
## Building
|
||||
```
|
||||
git submodule update --init
|
||||
gradle assemble
|
||||
```
|
||||
Although not a direct replacement, I've since moved onto syncing an Opus version
|
||||
of my entire library to my phone using [harmonize](https://github.com/nvllsvm/harmonize)
|
||||
and [Syncthing](https://syncthing.net/).
|
||||
|
||||
## SDK Project Dependencies
|
||||
Under sdk -> extras:</br>
|
||||
android -> support -> v7 -> appcompat</br>
|
||||
android -> support -> v7 -> mediarouter</br>
|
||||
---
|
||||
<img src="audinaut.png" align="left" width="200" hspace="10" vspace="10">
|
||||
|
||||
## SDK Library Dependencies
|
||||
android -> support -> v4 -> android-support-v4.jar</br>
|
||||
android -> support -> v7 -> appcompat -> libs android-support-v7-appcompat.jar<br>
|
||||
android -> support -> v7 -> mediarouter -> libs -> android-support-v7-mediarouter.jar</br>
|
||||
A Subsonic client for Android.
|
||||
|
||||
|
||||
<a href="https://f-droid.org/app/net.nullsum.audinaut">
|
||||
<img src="https://gitlab.com/fdroid/artwork/raw/master/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid" height="80">
|
||||
</a>
|
||||
<a href="https://play.google.com/store/apps/details?id=net.nullsum.audinaut">
|
||||
<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||
alt="Get it on Google Play" height="80" />
|
||||
</a>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a4d957353db2634906e0d5099d7a078a111bfab9
|
|
@ -1,2 +0,0 @@
|
|||
/build
|
||||
*.iml
|
|
@ -1,63 +1,47 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
compileSdkVersion 31
|
||||
buildToolsVersion "31.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "github.nvllsvm.audinaut"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 23
|
||||
versionCode 186
|
||||
versionName '0.1.0'
|
||||
setProperty("archivesBaseName", "Audinaut $versionName")
|
||||
resConfigs "de", "es", "fr", "hu", "nl", "pt-rPT", "ru", "sv"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles 'proguard.cfg'
|
||||
zipAlignEnabled true
|
||||
}
|
||||
fix {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles 'proguard.cfg'
|
||||
zipAlignEnabled true
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId "net.nullsum.audinaut"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 202
|
||||
versionName '0.5.1'
|
||||
setProperty("archivesBaseName", "Audinaut $versionName")
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/beans.xml'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
warning 'InvalidPackage'
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('../debug.keystore')
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':Server Proxy')
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
compile 'com.android.support:support-v4:23.4.+'
|
||||
compile 'com.android.support:appcompat-v7:23.4.+'
|
||||
compile 'com.android.support:mediarouter-v7:23.4.+'
|
||||
compile 'com.android.support:recyclerview-v7:23.4.+'
|
||||
compile 'com.android.support:design:23.4.+'
|
||||
compile 'com.sothree.slidinguppanel:library:3.0.0'
|
||||
compile 'de.hdodenhof:circleimageview:1.2.1'
|
||||
compile group: 'org.fourthline.cling', name: 'cling-core', version:'2.1.1'
|
||||
compile group: 'org.fourthline.cling', name: 'cling-support', version:'2.1.1'
|
||||
compile group: 'org.eclipse.jetty', name: 'jetty-server', version:'8.1.16.v20140903'
|
||||
compile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'8.1.16.v20140903'
|
||||
compile group: 'org.eclipse.jetty', name: 'jetty-client', version:'8.1.16.v20140903'
|
||||
implementation 'com.esotericsoftware:kryo:4.0.2'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'com.github.hannesa2:AndroidSlidingUpPanel:4.4.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "androidx.media:media:1.5.0"
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.0'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1,62 +0,0 @@
|
|||
-dontobfuscate
|
||||
-optimizationpasses 5
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontpreverify
|
||||
-verbose
|
||||
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable
|
||||
|
||||
-keep public class * extends android.app.Activity
|
||||
-keep public class * extends android.app.Application
|
||||
-keep public class * extends android.app.Service
|
||||
-keep public class * extends android.content.BroadcastReceiver
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||
-keep public class * extends android.preference.Preference
|
||||
|
||||
# Kryo
|
||||
-keep,allowshrinking class java.beans.** { *; }
|
||||
-keep,allowshrinking class sun.reflect.** { *; }
|
||||
-dontwarn sun.reflect.**
|
||||
-dontwarn java.beans.**
|
||||
-keepclassmembers public class com.esotericsoftware.** { *; }
|
||||
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keep class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
||||
|
||||
-keep class android.support.v7.app.MediaRouteButton { *; }
|
||||
-keep class android.support.v7.widget.SearchView { *; }
|
||||
|
||||
-dontwarn android.support.**
|
||||
|
||||
# DLNA/Cling
|
||||
-keep class org.fourthline.cling.** { *; }
|
||||
-keep interface org.fourthline.cling.** { *; }
|
||||
-dontwarn javax.**
|
||||
-dontwarn org.objectweb.**
|
||||
-dontwarn org.slf4j.**
|
||||
-dontwarn org.mortbay.**
|
||||
-dontwarn org.fourthline.**
|
||||
-dontwarn org.seamless.**
|
||||
-dontwarn org.eclipse.**
|
||||
-dontwarn java.**
|
||||
-keepattributes *Annotation*, InnerClasses
|
|
@ -1,13 +0,0 @@
|
|||
package github.nvllsvm.audinaut;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package github.nvllsvm.audinaut.activity;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
public class SubsonicFragmentActivityTest extends
|
||||
ActivityInstrumentationTestCase2<SubsonicFragmentActivity> {
|
||||
|
||||
private SubsonicFragmentActivity activity;
|
||||
|
||||
public SubsonicFragmentActivityTest() {
|
||||
super(SubsonicFragmentActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
activity = getActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the main layout.
|
||||
*/
|
||||
public void testLayout() {
|
||||
assertNotNull(activity.findViewById(R.id.content_frame));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the bottom bar.
|
||||
*/
|
||||
public void testBottomBar() {
|
||||
assertNotNull(activity.findViewById(R.id.bottom_bar));
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package github.nvllsvm.audinaut.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class GenreComparatorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Sort genres which doesn't have name
|
||||
*/
|
||||
public void testSortGenreWithoutNameComparator() {
|
||||
Genre g1 = new Genre();
|
||||
g1.setName("Genre");
|
||||
|
||||
Genre g2 = new Genre();
|
||||
|
||||
List<Genre> genres = new ArrayList<Genre>();
|
||||
genres.add(g1);
|
||||
genres.add(g2);
|
||||
|
||||
List<Genre> sortedGenre = Genre.GenreComparator.sort(genres);
|
||||
assertEquals(sortedGenre.get(0), g2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort genre with same name
|
||||
*/
|
||||
public void testSortGenreWithSameName() {
|
||||
Genre g1 = new Genre();
|
||||
g1.setName("Genre");
|
||||
|
||||
Genre g2 = new Genre();
|
||||
g2.setName("genre");
|
||||
|
||||
List<Genre> genres = new ArrayList<Genre>();
|
||||
genres.add(g1);
|
||||
genres.add(g2);
|
||||
|
||||
List<Genre> sortedGenre = Genre.GenreComparator.sort(genres);
|
||||
assertEquals(sortedGenre.get(0), g1);
|
||||
}
|
||||
|
||||
/**
|
||||
* test nominal genre sort
|
||||
*/
|
||||
public void testSortGenre() {
|
||||
Genre g1 = new Genre();
|
||||
g1.setName("Rock");
|
||||
|
||||
Genre g2 = new Genre();
|
||||
g2.setName("Pop");
|
||||
|
||||
Genre g3 = new Genre();
|
||||
g2.setName("Rap");
|
||||
|
||||
List<Genre> genres = new ArrayList<Genre>();
|
||||
genres.add(g1);
|
||||
genres.add(g2);
|
||||
genres.add(g3);
|
||||
|
||||
List<Genre> sortedGenre = Genre.GenreComparator.sort(genres);
|
||||
assertEquals(sortedGenre.get(0), g2);
|
||||
assertEquals(sortedGenre.get(1), g3);
|
||||
assertEquals(sortedGenre.get(2), g1);
|
||||
}
|
||||
}
|
|
@ -1,296 +0,0 @@
|
|||
package github.nvllsvm.audinaut.service;
|
||||
|
||||
import static github.nvllsvm.audinaut.domain.PlayerState.COMPLETED;
|
||||
import static github.nvllsvm.audinaut.domain.PlayerState.IDLE;
|
||||
import static github.nvllsvm.audinaut.domain.PlayerState.PAUSED;
|
||||
import static github.nvllsvm.audinaut.domain.PlayerState.STARTED;
|
||||
import static github.nvllsvm.audinaut.domain.PlayerState.STOPPED;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.activity.SubsonicFragmentActivity;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.PlayerState;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.util.Log;
|
||||
|
||||
public class DownloadServiceTest extends
|
||||
ActivityInstrumentationTestCase2<SubsonicFragmentActivity> {
|
||||
|
||||
private SubsonicFragmentActivity activity;
|
||||
private DownloadService downloadService;
|
||||
|
||||
public DownloadServiceTest() {
|
||||
super(SubsonicFragmentActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
activity = getActivity();
|
||||
downloadService = activity.getDownloadService();
|
||||
downloadService.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get player duration without playlist.
|
||||
*/
|
||||
public void testGetPlayerDurationWithoutPlayList() {
|
||||
int duration = downloadService.getPlayerDuration();
|
||||
assertEquals(0, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get player position without playlist.
|
||||
*/
|
||||
public void testGetPlayerPositionWithoutPlayList() {
|
||||
int position = downloadService.getPlayerPosition();
|
||||
assertEquals(0, position);
|
||||
}
|
||||
|
||||
public void testGetCurrentPlayingIndexWithoutPlayList() {
|
||||
int currentPlayingIndex = activity.getDownloadService()
|
||||
.getCurrentPlayingIndex();
|
||||
assertEquals(currentPlayingIndex, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test next action without playlist.
|
||||
*/
|
||||
public void testNextWithoutPlayList() {
|
||||
int oldCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
downloadService.next();
|
||||
int newCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
assertTrue(oldCurrentPlayingIndex == newCurrentPlayingIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test previous action without playlist.
|
||||
*/
|
||||
public void testPreviousWithoutPlayList() {
|
||||
int oldCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
downloadService.previous();
|
||||
int newCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
assertTrue(oldCurrentPlayingIndex == newCurrentPlayingIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test next action with playlist.
|
||||
*/
|
||||
public void testNextWithPlayList() throws InterruptedException {
|
||||
// Download two songs
|
||||
downloadService.getDownloads().clear();
|
||||
downloadService.download(this.createMusicSongs(2), false, false, false,
|
||||
false, 0, 0);
|
||||
|
||||
Log.w("testPrevWithPlayList", "Start waiting to downloads");
|
||||
Thread.sleep(5000);
|
||||
Log.w("testPrevWithPlayList", "Stop waiting downloads");
|
||||
|
||||
// Get the current index
|
||||
int oldCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
|
||||
// Do the next
|
||||
downloadService.next();
|
||||
|
||||
// Check that the new current index is incremented
|
||||
int newCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
assertEquals(oldCurrentPlayingIndex + 1, newCurrentPlayingIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test previous action with playlist.
|
||||
*/
|
||||
public void testPrevWithPlayList() throws InterruptedException {
|
||||
// Download two songs
|
||||
downloadService.getDownloads().clear();
|
||||
downloadService.download(this.createMusicSongs(2), false, false, false,
|
||||
false, 0, 0);
|
||||
|
||||
Log.w("testPrevWithPlayList", "Start waiting downloads");
|
||||
Thread.sleep(5000);
|
||||
Log.w("testPrevWithPlayList", "Stop waiting downloads");
|
||||
|
||||
// Get the current index
|
||||
int oldCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
|
||||
// Do a next before the previous
|
||||
downloadService.next();
|
||||
|
||||
// Do the previous
|
||||
downloadService.previous();
|
||||
|
||||
// Check that the new current index is incremented
|
||||
int newCurrentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
assertEquals(oldCurrentPlayingIndex, newCurrentPlayingIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test seek feature.
|
||||
*/
|
||||
public void testSeekTo() {
|
||||
// seek with negative
|
||||
downloadService.seekTo(Integer.MIN_VALUE);
|
||||
|
||||
// seek with null
|
||||
downloadService.seekTo(0);
|
||||
|
||||
// seek with big value
|
||||
downloadService.seekTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test toggle play pause.
|
||||
*/
|
||||
public void testTogglePlayPause() {
|
||||
PlayerState oldPlayState = downloadService.getPlayerState();
|
||||
downloadService.togglePlayPause();
|
||||
PlayerState newPlayState = downloadService.getPlayerState();
|
||||
if (oldPlayState == PAUSED || oldPlayState == COMPLETED
|
||||
|| oldPlayState == STOPPED) {
|
||||
assertEquals(STARTED, newPlayState);
|
||||
} else if (oldPlayState == STOPPED || oldPlayState == IDLE) {
|
||||
if (downloadService.size() == 0) {
|
||||
assertEquals(IDLE, newPlayState);
|
||||
} else {
|
||||
assertEquals(STARTED, newPlayState);
|
||||
}
|
||||
} else if (oldPlayState == STARTED) {
|
||||
assertEquals(PAUSED, newPlayState);
|
||||
}
|
||||
downloadService.togglePlayPause();
|
||||
newPlayState = downloadService.getPlayerState();
|
||||
assertEquals(oldPlayState, newPlayState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test toggle play pause without playlist.
|
||||
*/
|
||||
public void testTogglePlayPauseWithoutPlayList() {
|
||||
PlayerState oldPlayState = downloadService.getPlayerState();
|
||||
downloadService.togglePlayPause();
|
||||
PlayerState newPlayState = downloadService.getPlayerState();
|
||||
|
||||
assertEquals(IDLE, oldPlayState);
|
||||
assertEquals(IDLE, newPlayState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test toggle play pause without playlist.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void testTogglePlayPauseWithPlayList() throws InterruptedException {
|
||||
// Download two songs
|
||||
downloadService.getDownloads().clear();
|
||||
downloadService.download(this.createMusicSongs(2), false, false, false,
|
||||
false, 0, 0);
|
||||
|
||||
Log.w("testPrevWithPlayList", "Start waiting downloads");
|
||||
Thread.sleep(5000);
|
||||
Log.w("testPrevWithPlayList", "Stop waiting downloads");
|
||||
|
||||
PlayerState oldPlayState = downloadService.getPlayerState();
|
||||
downloadService.togglePlayPause();
|
||||
Thread.sleep(500);
|
||||
assertEquals(STARTED, downloadService.getPlayerState());
|
||||
downloadService.togglePlayPause();
|
||||
PlayerState newPlayState = downloadService.getPlayerState();
|
||||
assertEquals(PAUSED, newPlayState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the autoplay.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void testAutoplay() throws InterruptedException {
|
||||
// Download one songs
|
||||
downloadService.getDownloads().clear();
|
||||
downloadService.download(this.createMusicSongs(1), false, true, false,
|
||||
false, 0, 0);
|
||||
|
||||
Log.w("testPrevWithPlayList", "Start waiting downloads");
|
||||
Thread.sleep(5000);
|
||||
Log.w("testPrevWithPlayList", "Stop waiting downloads");
|
||||
|
||||
PlayerState playerState = downloadService.getPlayerState();
|
||||
assertEquals(STARTED, playerState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the download list is empty.
|
||||
*/
|
||||
public void testGetDownloadsEmptyList() {
|
||||
List<DownloadFile> list = downloadService.getDownloads();
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the download service add the given song to its queue.
|
||||
*/
|
||||
public void testAddMusicToDownload() {
|
||||
assertNotNull(downloadService);
|
||||
|
||||
// Download list before
|
||||
List<DownloadFile> downloadList = downloadService.getDownloads();
|
||||
int beforeDownloadAction = 0;
|
||||
if (downloadList != null) {
|
||||
beforeDownloadAction = downloadList.size();
|
||||
}
|
||||
|
||||
// Launch download
|
||||
downloadService.download(this.createMusicSongs(1), false, false, false,
|
||||
false, 0, 0);
|
||||
|
||||
// Check number of download after
|
||||
int afterDownloadAction = 0;
|
||||
downloadList = downloadService.getDownloads();
|
||||
if (downloadList != null && !downloadList.isEmpty()) {
|
||||
afterDownloadAction = downloadList.size();
|
||||
}
|
||||
assertEquals(beforeDownloadAction + 1, afterDownloadAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a list containing some music directory entries.
|
||||
*
|
||||
* @return list containing some music directory entries.
|
||||
*/
|
||||
private List<MusicDirectory.Entry> createMusicSongs(int size) {
|
||||
MusicDirectory.Entry musicEntry = new MusicDirectory.Entry();
|
||||
musicEntry.setAlbum("Itchy Hitchhiker");
|
||||
musicEntry.setBitRate(198);
|
||||
musicEntry.setAlbumId("49");
|
||||
musicEntry.setDuration(247);
|
||||
musicEntry.setSize(Long.valueOf(6162717));
|
||||
musicEntry.setArtistId("23");
|
||||
musicEntry.setArtist("The Dada Weatherman");
|
||||
musicEntry.setCloseness(0);
|
||||
musicEntry.setContentType("audio/mpeg");
|
||||
musicEntry.setCoverArt("433");
|
||||
musicEntry.setDirectory(false);
|
||||
musicEntry.setGenre("Easy Listening/New Age");
|
||||
musicEntry.setGrandParent("306");
|
||||
musicEntry.setId("466");
|
||||
musicEntry.setParent("433");
|
||||
musicEntry
|
||||
.setPath("The Dada Weatherman/Itchy Hitchhiker/08 - The Dada Weatherman - Harmonies.mp3");
|
||||
musicEntry.setStarred(true);
|
||||
musicEntry.setSuffix("mp3");
|
||||
musicEntry.setTitle("Harmonies");
|
||||
musicEntry.setType(0);
|
||||
musicEntry.setVideo(false);
|
||||
|
||||
List<MusicDirectory.Entry> musicEntries = new LinkedList<MusicDirectory.Entry>();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
musicEntries.add(musicEntry);
|
||||
}
|
||||
|
||||
return musicEntries;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
Android API level: 23
|
||||
Subsonic version name: 5.3
|
||||
Subsonic version code: 186
|
||||
|
||||
android.content.res.Resources$NotFoundException: Resource ID #0xff33b5e5
|
||||
at android.content.res.Resources.getValue(Resources.java:1432)
|
||||
at android.content.res.Resources.getValue(Resources.java:1412)
|
||||
at android.content.res.Resources.getColor(Resources.java:1028)
|
||||
at android.content.res.Resources.getColor(Resources.java:1001)
|
||||
at android.support.v4.widget.SwipeRefreshLayout.setColorSchemeResources(SwipeRefreshLayout.java:529)
|
||||
at github.nvllsvm.audinaut.fragments.SubsonicFragment.setupScrollList(SubsonicFragment.java:691)
|
||||
at github.nvllsvm.audinaut.fragments.SelectRecyclerFragment.onCreateView(SelectRecyclerFragment.java:89)
|
||||
at github.nvllsvm.audinaut.fragments.SelectArtistFragment.onCreateView(SelectArtistFragment.java:77)
|
||||
at android.support.v4.app.Fragment.performCreateView(Fragment.java:1974)
|
||||
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1067)
|
||||
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1252)
|
||||
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:742)
|
||||
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1617)
|
||||
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:517)
|
||||
at android.os.Handler.handleCallback(Handler.java:739)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:95)
|
||||
at android.os.Looper.loop(Looper.java:148)
|
||||
at android.app.ActivityThread.main(ActivityThread.java:5461)
|
||||
at java.lang.reflect.Method.invoke(Native Method)
|
||||
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
|
||||
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
|
|
@ -1 +0,0 @@
|
|||
#Sat Oct 01 15:10:08 EDT 2016
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,171 +1,195 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="github.nvllsvm.audinaut"
|
||||
android:installLocation="internalOnly">
|
||||
package="net.nullsum.audinaut"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="github.nvllsvm.audinaut"
|
||||
android:label="Tests" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"
|
||||
android:maxSdkVersion="22" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="22"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" android:maxSdkVersion="22"/>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.wifi"
|
||||
android:required="false" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
||||
<uses-feature android:name="android.software.leanback" android:required="false"/>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<supports-screens android:anyDensity="true" android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
|
||||
<application
|
||||
android:backupAgent="net.nullsum.audinaut.util.SettingsBackupAgent"
|
||||
android:icon="@drawable/launch"
|
||||
android:label="@string/common.appname"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/LaunchScreen">
|
||||
|
||||
<application android:label="@string/common.appname"
|
||||
android:backupAgent="github.nvllsvm.audinaut.util.SettingsBackupAgent"
|
||||
android:icon="@drawable/launch"
|
||||
android:theme="@style/Theme.Audinaut.Light">
|
||||
|
||||
<uses-library android:name="android.test.runner" />
|
||||
|
||||
<activity android:name="github.nvllsvm.audinaut.activity.SubsonicFragmentActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.nullsum.audinaut.activity.SubsonicFragmentActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="github.nvllsvm.audinaut.activity.SettingsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name="net.nullsum.audinaut.activity.SettingsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity android:name="github.nvllsvm.audinaut.activity.VoiceQueryReceiverActivity"
|
||||
android:launchMode="singleTask">
|
||||
<activity
|
||||
android:name="net.nullsum.audinaut.activity.VoiceQueryReceiverActivity"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="github.nvllsvm.audinaut.activity.QueryReceiverActivity"
|
||||
android:launchMode="singleTask">
|
||||
<activity
|
||||
android:name="net.nullsum.audinaut.activity.QueryReceiverActivity"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH"/>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="github.nvllsvm.audinaut.activity.EditPlayActionActivity"
|
||||
android:label="@string/tasker.start_playing"
|
||||
android:icon="@drawable/launch">
|
||||
<activity
|
||||
android:name="net.nullsum.audinaut.activity.EditPlayActionActivity"
|
||||
android:icon="@drawable/launch"
|
||||
android:label="@string/tasker.start_playing">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".service.DownloadService"
|
||||
android:label="Audinaut Playback Service"/>
|
||||
<service
|
||||
android:name=".service.DownloadService"
|
||||
android:label="Audinaut Playback Service" />
|
||||
|
||||
<service android:name="org.fourthline.cling.android.AndroidUpnpServiceImpl"/>
|
||||
<service android:name="github.nvllsvm.audinaut.service.sync.AuthenticatorService">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator"/>
|
||||
</intent-filter>
|
||||
<service android:name="net.nullsum.audinaut.service.sync.AuthenticatorService">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/authenticator" />
|
||||
</service>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/authenticator" />
|
||||
</service>
|
||||
|
||||
<service android:name="github.nvllsvm.audinaut.service.HeadphoneListenerService"
|
||||
android:label="Audinaut Headphone Listener"/>
|
||||
<receiver
|
||||
android:name="github.nvllsvm.audinaut.receiver.BootReceiver">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service
|
||||
android:name="net.nullsum.audinaut.service.HeadphoneListenerService"
|
||||
android:label="Audinaut Headphone Listener" />
|
||||
|
||||
<receiver android:name="github.nvllsvm.audinaut.receiver.MediaButtonIntentReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
<receiver android:name="net.nullsum.audinaut.receiver.BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="github.nvllsvm.audinaut.receiver.AudioNoisyReceiver">
|
||||
<intent-filter android:priority="999">
|
||||
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="github.nvllsvm.audinaut.receiver.A2dpIntentReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.music.playstatusrequest"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="net.nullsum.audinaut.receiver.MediaButtonIntentReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="net.nullsum.audinaut.receiver.A2dpIntentReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.music.playstatusrequest" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="github.nvllsvm.audinaut.provider.AudinautWidget4x1"
|
||||
android:label="@string/widget.4x1">
|
||||
android:name="net.nullsum.audinaut.provider.AudinautWidget4x1"
|
||||
android:label="@string/widget.4x1">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget4x1"/>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/appwidget4x1" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="github.nvllsvm.audinaut.provider.AudinautWidget4x2"
|
||||
android:label="@string/widget.4x2">
|
||||
<receiver
|
||||
android:name="net.nullsum.audinaut.provider.AudinautWidget4x2"
|
||||
android:label="@string/widget.4x2">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget4x2"/>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/appwidget4x2" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="github.nvllsvm.audinaut.provider.AudinautWidget4x3"
|
||||
android:label="@string/widget.4x3">
|
||||
<receiver
|
||||
android:name="net.nullsum.audinaut.provider.AudinautWidget4x3"
|
||||
android:label="@string/widget.4x3">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget4x3"/>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/appwidget4x3" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="github.nvllsvm.audinaut.provider.AudinautWidget4x4"
|
||||
android:label="@string/widget.4x4">
|
||||
<receiver
|
||||
android:name="net.nullsum.audinaut.provider.AudinautWidget4x4"
|
||||
android:label="@string/widget.4x4">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget4x4"/>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/appwidget4x4" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="github.nvllsvm.audinaut.receiver.PlayActionReceiver">
|
||||
<receiver
|
||||
android:name="net.nullsum.audinaut.receiver.PlayActionReceiver"
|
||||
android:permission="">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider android:name="github.nvllsvm.audinaut.provider.AudinautSearchProvider"
|
||||
android:authorities="github.nvllsvm.audinaut.provider.AudinautSearchProvider"/>
|
||||
<provider
|
||||
android:name="net.nullsum.audinaut.provider.AudinautSearchProvider"
|
||||
android:authorities="net.nullsum.audinaut.provider.AudinautSearchProvider" />
|
||||
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="github.nvllsvm.audinaut.activity.QueryReceiverActivity"/>
|
||||
</application>
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="net.nullsum.audinaut.activity.QueryReceiverActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -1,245 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.Genre;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.service.OfflineException;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.LoadingTask;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
public class EditPlayActionActivity extends SubsonicActivity {
|
||||
private CheckBox shuffleCheckbox;
|
||||
private CheckBox startYearCheckbox;
|
||||
private EditText startYearBox;
|
||||
private CheckBox endYearCheckbox;
|
||||
private EditText endYearBox;
|
||||
private Button genreButton;
|
||||
private Spinner offlineSpinner;
|
||||
|
||||
private String doNothing;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.tasker_start_playing_title);
|
||||
setContentView(R.layout.edit_play_action);
|
||||
final Activity context = this;
|
||||
doNothing = context.getResources().getString(R.string.tasker_edit_do_nothing);
|
||||
|
||||
shuffleCheckbox = (CheckBox) findViewById(R.id.edit_shuffle_checkbox);
|
||||
shuffleCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton view, boolean isChecked) {
|
||||
startYearCheckbox.setEnabled(isChecked);
|
||||
endYearCheckbox.setEnabled(isChecked);
|
||||
genreButton.setEnabled(isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
startYearCheckbox = (CheckBox) findViewById(R.id.edit_start_year_checkbox);
|
||||
startYearBox = (EditText) findViewById(R.id.edit_start_year);
|
||||
// Disable/enable number box if checked
|
||||
startYearCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton view, boolean isChecked) {
|
||||
startYearBox.setEnabled(isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
endYearCheckbox = (CheckBox) findViewById(R.id.edit_end_year_checkbox);
|
||||
endYearBox = (EditText) findViewById(R.id.edit_end_year);
|
||||
endYearCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton view, boolean isChecked) {
|
||||
endYearBox.setEnabled(isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
genreButton = (Button) findViewById(R.id.edit_genre_spinner);
|
||||
genreButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
new LoadingTask<List<Genre>>(context, true) {
|
||||
@Override
|
||||
protected List<Genre> doInBackground() throws Throwable {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
return musicService.getGenres(false, context, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(final List<Genre> genres) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
String blank = context.getResources().getString(R.string.select_genre_blank);
|
||||
names.add(doNothing);
|
||||
names.add(blank);
|
||||
for(Genre genre: genres) {
|
||||
names.add(genre.getName());
|
||||
}
|
||||
final List<String> finalNames = names;
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.shuffle_pick_genre)
|
||||
.setItems(names.toArray(new CharSequence[names.size()]), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if(which == 1) {
|
||||
genreButton.setText("");
|
||||
} else {
|
||||
genreButton.setText(finalNames.get(which));
|
||||
}
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
String msg;
|
||||
if (error instanceof OfflineException) {
|
||||
msg = getErrorMessage(error);
|
||||
} else {
|
||||
msg = context.getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error);
|
||||
}
|
||||
|
||||
Util.toast(context, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
genreButton.setText(doNothing);
|
||||
|
||||
offlineSpinner = (Spinner) findViewById(R.id.edit_offline_spinner);
|
||||
ArrayAdapter<CharSequence> offlineAdapter = ArrayAdapter.createFromResource(this, R.array.editServerOptions, android.R.layout.simple_spinner_item);
|
||||
offlineAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
offlineSpinner.setAdapter(offlineAdapter);
|
||||
|
||||
// Setup default for everything
|
||||
Bundle extras = getIntent().getBundleExtra(Constants.TASKER_EXTRA_BUNDLE);
|
||||
if(extras != null) {
|
||||
if(extras.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE)) {
|
||||
shuffleCheckbox.setChecked(true);
|
||||
}
|
||||
|
||||
String startYear = extras.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, null);
|
||||
if(startYear != null) {
|
||||
startYearCheckbox.setEnabled(true);
|
||||
startYearBox.setText(startYear);
|
||||
}
|
||||
String endYear = extras.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, null);
|
||||
if(endYear != null) {
|
||||
endYearCheckbox.setEnabled(true);
|
||||
endYearBox.setText(endYear);
|
||||
}
|
||||
|
||||
String genre = extras.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, doNothing);
|
||||
if(genre != null) {
|
||||
genreButton.setText(genre);
|
||||
}
|
||||
|
||||
int offline = extras.getInt(Constants.PREFERENCES_KEY_OFFLINE, 0);
|
||||
if(offline != 0) {
|
||||
offlineSpinner.setSelection(offline);
|
||||
}
|
||||
}
|
||||
|
||||
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater menuInflater = getMenuInflater();
|
||||
menuInflater.inflate(R.menu.tasker_configuration, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(item.getItemId() == android.R.id.home) {
|
||||
cancel();
|
||||
return true;
|
||||
} else if(item.getItemId() == R.id.menu_accept) {
|
||||
accept();
|
||||
return true;
|
||||
} else if(item.getItemId() == R.id.menu_cancel) {
|
||||
cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void accept() {
|
||||
Intent intent = new Intent();
|
||||
|
||||
String blurb = getResources().getString(shuffleCheckbox.isChecked() ? R.string.tasker_start_playing_shuffled : R.string.tasker_start_playing);
|
||||
intent.putExtra("com.twofortyfouram.locale.intent.extra.BLURB", blurb);
|
||||
|
||||
// Get settings user specified
|
||||
Bundle data = new Bundle();
|
||||
boolean shuffle = shuffleCheckbox.isChecked();
|
||||
data.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, shuffle);
|
||||
if(shuffle) {
|
||||
if(startYearCheckbox.isChecked()) {
|
||||
data.putString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, startYearBox.getText().toString());
|
||||
}
|
||||
if(endYearCheckbox.isChecked()) {
|
||||
data.putString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, endYearBox.getText().toString());
|
||||
}
|
||||
String genre = genreButton.getText().toString();
|
||||
if(!genre.equals(doNothing)) {
|
||||
data.putString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, genre);
|
||||
}
|
||||
}
|
||||
|
||||
int offline = offlineSpinner.getSelectedItemPosition();
|
||||
if(offline != 0) {
|
||||
data.putInt(Constants.PREFERENCES_KEY_OFFLINE, offline);
|
||||
}
|
||||
|
||||
intent.putExtra(Constants.TASKER_EXTRA_BUNDLE, data);
|
||||
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
private void cancel() {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
import android.util.Log;
|
||||
|
||||
import github.nvllsvm.audinaut.fragments.SubsonicFragment;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.provider.AudinautSearchProvider;
|
||||
|
||||
/**
|
||||
* Receives search queries and forwards to the SearchFragment.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class QueryReceiverActivity extends Activity {
|
||||
|
||||
private static final String TAG = QueryReceiverActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
doSearch();
|
||||
} else if(Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
showResult(intent.getDataString(), intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
|
||||
}
|
||||
finish();
|
||||
Util.disablePendingTransition(this);
|
||||
}
|
||||
|
||||
private void doSearch() {
|
||||
String query = getIntent().getStringExtra(SearchManager.QUERY);
|
||||
if (query != null) {
|
||||
Intent intent = new Intent(QueryReceiverActivity.this, SubsonicFragmentActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_QUERY, query);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
Util.startActivityWithoutTransition(QueryReceiverActivity.this, intent);
|
||||
}
|
||||
}
|
||||
private void showResult(String albumId, String name) {
|
||||
if (albumId != null) {
|
||||
Intent intent = new Intent(this, SubsonicFragmentActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_VIEW_ALBUM, true);
|
||||
if(albumId.indexOf("ar-") == 0) {
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ARTIST, true);
|
||||
albumId = albumId.replace("ar-", "");
|
||||
} else if(albumId.indexOf("so-") == 0) {
|
||||
intent.putExtra(Constants.INTENT_EXTRA_SEARCH_SONG, name);
|
||||
albumId = albumId.replace("so-", "");
|
||||
}
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, albumId);
|
||||
if (name != null) {
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, name);
|
||||
}
|
||||
Util.startActivityWithoutTransition(this, intent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.activity;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.fragments.PreferenceCompatFragment;
|
||||
import github.nvllsvm.audinaut.fragments.SettingsFragment;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
|
||||
public class SettingsActivity extends SubsonicActivity {
|
||||
private static final String TAG = SettingsActivity.class.getSimpleName();
|
||||
private PreferenceCompatFragment fragment;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
lastSelectedPosition = R.id.drawer_settings;
|
||||
setContentView(R.layout.settings_activity);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
fragment = new SettingsFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(Constants.INTENT_EXTRA_FRAGMENT_TYPE, R.xml.settings);
|
||||
|
||||
fragment.setArguments(args);
|
||||
fragment.setRetainInstance(true);
|
||||
|
||||
currentFragment = fragment;
|
||||
currentFragment.setPrimaryFragment(true);
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, currentFragment, currentFragment.getSupportTag() + "").commit();
|
||||
}
|
||||
|
||||
Toolbar mainToolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(mainToolbar);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,929 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.activity;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Dialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.PlayerQueue;
|
||||
import github.nvllsvm.audinaut.domain.PlayerState;
|
||||
import github.nvllsvm.audinaut.fragments.DownloadFragment;
|
||||
import github.nvllsvm.audinaut.fragments.NowPlayingFragment;
|
||||
import github.nvllsvm.audinaut.fragments.SearchFragment;
|
||||
import github.nvllsvm.audinaut.fragments.SelectArtistFragment;
|
||||
import github.nvllsvm.audinaut.fragments.SelectDirectoryFragment;
|
||||
import github.nvllsvm.audinaut.fragments.SelectPlaylistFragment;
|
||||
import github.nvllsvm.audinaut.fragments.SubsonicFragment;
|
||||
import github.nvllsvm.audinaut.service.DownloadFile;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.updates.Updater;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.UserUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* Created by Scott on 10/14/13.
|
||||
*/
|
||||
public class SubsonicFragmentActivity extends SubsonicActivity implements DownloadService.OnSongChangedListener {
|
||||
private static String TAG = SubsonicFragmentActivity.class.getSimpleName();
|
||||
private static boolean infoDialogDisplayed;
|
||||
private static boolean sessionInitialized = false;
|
||||
private static long ALLOWED_SKEW = 30000L;
|
||||
|
||||
private SlidingUpPanelLayout slideUpPanel;
|
||||
private SlidingUpPanelLayout.PanelSlideListener panelSlideListener;
|
||||
private boolean isPanelClosing = false;
|
||||
private NowPlayingFragment nowPlayingFragment;
|
||||
private SubsonicFragment secondaryFragment;
|
||||
private Toolbar mainToolbar;
|
||||
private Toolbar nowPlayingToolbar;
|
||||
|
||||
private View bottomBar;
|
||||
private ImageView coverArtView;
|
||||
private TextView trackView;
|
||||
private TextView artistView;
|
||||
private ImageButton startButton;
|
||||
private long lastBackPressTime = 0;
|
||||
private DownloadFile currentPlaying;
|
||||
private PlayerState currentState;
|
||||
private ImageButton previousButton;
|
||||
private ImageButton nextButton;
|
||||
private ImageButton rewindButton;
|
||||
private ImageButton fastforwardButton;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
if(savedInstanceState == null) {
|
||||
String fragmentType = getIntent().getStringExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE);
|
||||
boolean firstRun = false;
|
||||
if (fragmentType == null) {
|
||||
fragmentType = Util.openToTab(this);
|
||||
if (fragmentType != null) {
|
||||
firstRun = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ("".equals(fragmentType) || fragmentType == null || firstRun) {
|
||||
// Initial startup stuff
|
||||
if (!sessionInitialized) {
|
||||
loadSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_EXIT)) {
|
||||
stopService(new Intent(this, DownloadService.class));
|
||||
finish();
|
||||
getImageLoader().clearCache();
|
||||
} else if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD_VIEW)) {
|
||||
getIntent().putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, "Download");
|
||||
lastSelectedPosition = R.id.drawer_downloading;
|
||||
}
|
||||
setContentView(R.layout.abstract_fragment_activity);
|
||||
|
||||
if (findViewById(R.id.fragment_container) != null && savedInstanceState == null) {
|
||||
String fragmentType = getIntent().getStringExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE);
|
||||
if(fragmentType == null) {
|
||||
fragmentType = Util.openToTab(this);
|
||||
if(fragmentType != null) {
|
||||
getIntent().putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, fragmentType);
|
||||
lastSelectedPosition = getDrawerItemId(fragmentType);
|
||||
} else {
|
||||
lastSelectedPosition = R.id.drawer_library;
|
||||
}
|
||||
|
||||
MenuItem item = drawerList.getMenu().findItem(lastSelectedPosition);
|
||||
if(item != null) {
|
||||
item.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
lastSelectedPosition = getDrawerItemId(fragmentType);
|
||||
}
|
||||
|
||||
currentFragment = getNewFragment(fragmentType);
|
||||
if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ID)) {
|
||||
Bundle currentArguments = currentFragment.getArguments();
|
||||
if(currentArguments == null) {
|
||||
currentArguments = new Bundle();
|
||||
}
|
||||
currentArguments.putString(Constants.INTENT_EXTRA_NAME_ID, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID));
|
||||
currentFragment.setArguments(currentArguments);
|
||||
}
|
||||
currentFragment.setPrimaryFragment(true);
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, currentFragment, currentFragment.getSupportTag() + "").commit();
|
||||
|
||||
if(getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY) != null) {
|
||||
SearchFragment fragment = new SearchFragment();
|
||||
replaceFragment(fragment, fragment.getSupportTag());
|
||||
}
|
||||
|
||||
// If a album type is set, switch to that album type view
|
||||
String albumType = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE);
|
||||
if(albumType != null) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, albumType);
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
|
||||
fragment.setArguments(args);
|
||||
replaceFragment(fragment, fragment.getSupportTag());
|
||||
}
|
||||
}
|
||||
|
||||
slideUpPanel = (SlidingUpPanelLayout) findViewById(R.id.slide_up_panel);
|
||||
panelSlideListener = new SlidingUpPanelLayout.PanelSlideListener() {
|
||||
@Override
|
||||
public void onPanelSlide(View panel, float slideOffset) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelCollapsed(View panel) {
|
||||
isPanelClosing = false;
|
||||
if(bottomBar.getVisibility() == View.GONE) {
|
||||
bottomBar.setVisibility(View.VISIBLE);
|
||||
nowPlayingToolbar.setVisibility(View.GONE);
|
||||
nowPlayingFragment.setPrimaryFragment(false);
|
||||
setSupportActionBar(mainToolbar);
|
||||
recreateSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelExpanded(View panel) {
|
||||
isPanelClosing = false;
|
||||
currentFragment.stopActionMode();
|
||||
|
||||
// Disable custom view before switching
|
||||
getSupportActionBar().setDisplayShowCustomEnabled(false);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||
|
||||
bottomBar.setVisibility(View.GONE);
|
||||
nowPlayingToolbar.setVisibility(View.VISIBLE);
|
||||
setSupportActionBar(nowPlayingToolbar);
|
||||
|
||||
if(secondaryFragment == null) {
|
||||
nowPlayingFragment.setPrimaryFragment(true);
|
||||
} else {
|
||||
secondaryFragment.setPrimaryFragment(true);
|
||||
}
|
||||
|
||||
drawerToggle.setDrawerIndicatorEnabled(false);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelAnchored(View panel) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelHidden(View panel) {
|
||||
|
||||
}
|
||||
};
|
||||
slideUpPanel.setPanelSlideListener(panelSlideListener);
|
||||
|
||||
if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD)) {
|
||||
// Post this later so it actually runs
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
openNowPlaying();
|
||||
}
|
||||
}, 200);
|
||||
|
||||
getIntent().removeExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD);
|
||||
}
|
||||
|
||||
bottomBar = findViewById(R.id.bottom_bar);
|
||||
mainToolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
nowPlayingToolbar = (Toolbar) findViewById(R.id.now_playing_toolbar);
|
||||
coverArtView = (ImageView) bottomBar.findViewById(R.id.album_art);
|
||||
trackView = (TextView) bottomBar.findViewById(R.id.track_name);
|
||||
artistView = (TextView) bottomBar.findViewById(R.id.artist_name);
|
||||
|
||||
setSupportActionBar(mainToolbar);
|
||||
|
||||
if (findViewById(R.id.fragment_container) != null && savedInstanceState == null) {
|
||||
nowPlayingFragment = new NowPlayingFragment();
|
||||
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
|
||||
trans.add(R.id.now_playing_fragment_container, nowPlayingFragment, nowPlayingFragment.getTag() + "");
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
rewindButton = (ImageButton) findViewById(R.id.download_rewind);
|
||||
rewindButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
if (getDownloadService() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
getDownloadService().rewind();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
previousButton = (ImageButton) findViewById(R.id.download_previous);
|
||||
previousButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
if(getDownloadService() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
getDownloadService().previous();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
startButton = (ImageButton) findViewById(R.id.download_start);
|
||||
startButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
PlayerState state = getDownloadService().getPlayerState();
|
||||
if(state == PlayerState.STARTED) {
|
||||
getDownloadService().pause();
|
||||
} else {
|
||||
getDownloadService().start();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
nextButton = (ImageButton) findViewById(R.id.download_next);
|
||||
nextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
if(getDownloadService() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
getDownloadService().next();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
fastforwardButton = (ImageButton) findViewById(R.id.download_fastforward);
|
||||
fastforwardButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
if (getDownloadService() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
getDownloadService().fastForward();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle bundle) {
|
||||
super.onPostCreate(bundle);
|
||||
|
||||
showInfoDialog();
|
||||
checkUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
if(currentFragment != null && intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY) != null) {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
closeNowPlaying();
|
||||
}
|
||||
|
||||
if(currentFragment instanceof SearchFragment) {
|
||||
String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY);
|
||||
boolean autoplay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
|
||||
|
||||
if (query != null) {
|
||||
((SearchFragment)currentFragment).search(query, autoplay);
|
||||
}
|
||||
getIntent().removeExtra(Constants.INTENT_EXTRA_NAME_QUERY);
|
||||
} else {
|
||||
setIntent(intent);
|
||||
|
||||
SearchFragment fragment = new SearchFragment();
|
||||
replaceFragment(fragment, fragment.getSupportTag());
|
||||
}
|
||||
} else if(intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, false)) {
|
||||
if(slideUpPanel.getPanelState() != SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
openNowPlaying();
|
||||
}
|
||||
} else {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
closeNowPlaying();
|
||||
}
|
||||
setIntent(intent);
|
||||
}
|
||||
if(drawer != null) {
|
||||
drawer.closeDrawers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if(getIntent().hasExtra(Constants.INTENT_EXTRA_VIEW_ALBUM)) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ID, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID));
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_NAME, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME));
|
||||
args.putString(Constants.INTENT_EXTRA_SEARCH_SONG, getIntent().getStringExtra(Constants.INTENT_EXTRA_SEARCH_SONG));
|
||||
if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ARTIST)) {
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
|
||||
}
|
||||
if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_CHILD_ID)) {
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_CHILD_ID));
|
||||
}
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment, fragment.getSupportTag());
|
||||
getIntent().removeExtra(Constants.INTENT_EXTRA_VIEW_ALBUM);
|
||||
}
|
||||
|
||||
UserUtil.seedCurrentUser(this);
|
||||
createAccount();
|
||||
runWhenServiceAvailable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getDownloadService().addOnSongChangedListener(SubsonicFragmentActivity.this, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
DownloadService downloadService = getDownloadService();
|
||||
if(downloadService != null) {
|
||||
downloadService.removeOnSongChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
savedInstanceState.putString(Constants.MAIN_NOW_PLAYING, nowPlayingFragment.getTag());
|
||||
if(secondaryFragment != null) {
|
||||
savedInstanceState.putString(Constants.MAIN_NOW_PLAYING_SECONDARY, secondaryFragment.getTag());
|
||||
}
|
||||
savedInstanceState.putInt(Constants.MAIN_SLIDE_PANEL_STATE, slideUpPanel.getPanelState().hashCode());
|
||||
}
|
||||
@Override
|
||||
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
String id = savedInstanceState.getString(Constants.MAIN_NOW_PLAYING);
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
nowPlayingFragment = (NowPlayingFragment) fm.findFragmentByTag(id);
|
||||
|
||||
String secondaryId = savedInstanceState.getString(Constants.MAIN_NOW_PLAYING_SECONDARY);
|
||||
if(secondaryId != null) {
|
||||
secondaryFragment = (SubsonicFragment) fm.findFragmentByTag(secondaryId);
|
||||
|
||||
nowPlayingFragment.setPrimaryFragment(false);
|
||||
secondaryFragment.setPrimaryFragment(true);
|
||||
|
||||
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
|
||||
trans.hide(nowPlayingFragment);
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
if(drawerToggle != null && backStack.size() > 0) {
|
||||
drawerToggle.setDrawerIndicatorEnabled(false);
|
||||
}
|
||||
|
||||
if(savedInstanceState.getInt(Constants.MAIN_SLIDE_PANEL_STATE, -1) == SlidingUpPanelLayout.PanelState.EXPANDED.hashCode()) {
|
||||
panelSlideListener.onPanelExpanded(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(int viewId) {
|
||||
super.setContentView(viewId);
|
||||
if(drawerToggle != null){
|
||||
drawerToggle.setDrawerIndicatorEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED && secondaryFragment == null) {
|
||||
slideUpPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
} else if(onBackPressedSupport()) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressedSupport() {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
removeCurrent();
|
||||
return false;
|
||||
} else {
|
||||
return super.onBackPressedSupport();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubsonicFragment getCurrentFragment() {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
if(secondaryFragment == null) {
|
||||
return nowPlayingFragment;
|
||||
} else {
|
||||
return secondaryFragment;
|
||||
}
|
||||
} else {
|
||||
return super.getCurrentFragment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceFragment(SubsonicFragment fragment, int tag, boolean replaceCurrent) {
|
||||
if(slideUpPanel != null && slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED && !isPanelClosing) {
|
||||
secondaryFragment = fragment;
|
||||
nowPlayingFragment.setPrimaryFragment(false);
|
||||
secondaryFragment.setPrimaryFragment(true);
|
||||
supportInvalidateOptionsMenu();
|
||||
|
||||
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
|
||||
trans.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
|
||||
trans.hide(nowPlayingFragment);
|
||||
trans.add(R.id.now_playing_fragment_container, secondaryFragment, tag + "");
|
||||
trans.commit();
|
||||
} else {
|
||||
super.replaceFragment(fragment, tag, replaceCurrent);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void removeCurrent() {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED && secondaryFragment != null) {
|
||||
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
|
||||
trans.setCustomAnimations(R.anim.enter_from_left, R.anim.exit_to_right, R.anim.enter_from_right, R.anim.exit_to_left);
|
||||
trans.remove(secondaryFragment);
|
||||
trans.show(nowPlayingFragment);
|
||||
trans.commit();
|
||||
|
||||
secondaryFragment = null;
|
||||
nowPlayingFragment.setPrimaryFragment(true);
|
||||
supportInvalidateOptionsMenu();
|
||||
} else {
|
||||
super.removeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
getSupportActionBar().setTitle(title);
|
||||
} else {
|
||||
super.setTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawerItemSelected(String fragmentType) {
|
||||
super.drawerItemSelected(fragmentType);
|
||||
|
||||
if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
slideUpPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startFragmentActivity(String fragmentType) {
|
||||
// Create a transaction that does all of this
|
||||
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
// Clear existing stack
|
||||
for(int i = backStack.size() - 1; i >= 0; i--) {
|
||||
trans.remove(backStack.get(i));
|
||||
}
|
||||
trans.remove(currentFragment);
|
||||
backStack.clear();
|
||||
|
||||
// Create new stack
|
||||
currentFragment = getNewFragment(fragmentType);
|
||||
currentFragment.setPrimaryFragment(true);
|
||||
trans.add(R.id.fragment_container, currentFragment, currentFragment.getSupportTag() + "");
|
||||
|
||||
// Done, cleanup
|
||||
trans.commit();
|
||||
supportInvalidateOptionsMenu();
|
||||
recreateSpinner();
|
||||
if(drawer != null) {
|
||||
drawer.closeDrawers();
|
||||
}
|
||||
|
||||
if(secondaryContainer != null) {
|
||||
secondaryContainer.setVisibility(View.GONE);
|
||||
}
|
||||
if(drawerToggle != null) {
|
||||
drawerToggle.setDrawerIndicatorEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openNowPlaying() {
|
||||
slideUpPanel.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
|
||||
}
|
||||
@Override
|
||||
public void closeNowPlaying() {
|
||||
slideUpPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
isPanelClosing = true;
|
||||
}
|
||||
|
||||
private SubsonicFragment getNewFragment(String fragmentType) {
|
||||
if("Artist".equals(fragmentType)) {
|
||||
return new SelectArtistFragment();
|
||||
} else if("Playlist".equals(fragmentType)) {
|
||||
return new SelectPlaylistFragment();
|
||||
} else if("Download".equals(fragmentType)) {
|
||||
return new DownloadFragment();
|
||||
} else {
|
||||
return new SelectArtistFragment();
|
||||
}
|
||||
}
|
||||
|
||||
public void checkUpdates() {
|
||||
try {
|
||||
String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||
int ver = Integer.parseInt(version.replace(".", ""));
|
||||
Updater updater = new Updater(ver);
|
||||
updater.checkUpdates(this);
|
||||
}
|
||||
catch(Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSession() {
|
||||
loadSettings();
|
||||
// If we are on Subsonic 5.2+, save play queue
|
||||
if(!Util.isOffline(this)) {
|
||||
loadRemotePlayQueue();
|
||||
}
|
||||
|
||||
sessionInitialized = true;
|
||||
}
|
||||
private void loadSettings() {
|
||||
PreferenceManager.setDefaultValues(this, R.xml.settings_appearance, false);
|
||||
PreferenceManager.setDefaultValues(this, R.xml.settings_cache, false);
|
||||
PreferenceManager.setDefaultValues(this, R.xml.settings_playback, false);
|
||||
|
||||
SharedPreferences prefs = Util.getPreferences(this);
|
||||
if (!prefs.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION) || prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null) == null) {
|
||||
resetCacheLocation(prefs);
|
||||
} else {
|
||||
String path = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
|
||||
File cacheLocation = new File(path);
|
||||
if(!FileUtil.verifyCanWrite(cacheLocation)) {
|
||||
// Only warn user if there is a difference saved
|
||||
if(resetCacheLocation(prefs)) {
|
||||
Util.info(this, R.string.common_warning, R.string.settings_cache_location_reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefs.contains(Constants.PREFERENCES_KEY_OFFLINE)) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, false);
|
||||
|
||||
editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + 1, "Demo Server");
|
||||
editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + 1, "http://demo.subsonic.org");
|
||||
editor.putString(Constants.PREFERENCES_KEY_USERNAME + 1, "guest");
|
||||
editor.putString(Constants.PREFERENCES_KEY_PASSWORD + 1, "guest");
|
||||
editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
||||
editor.commit();
|
||||
}
|
||||
if(!prefs.contains(Constants.PREFERENCES_KEY_SERVER_COUNT)) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean resetCacheLocation(SharedPreferences prefs) {
|
||||
String newDirectory = FileUtil.getDefaultMusicDirectory(this).getPath();
|
||||
String oldDirectory = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
|
||||
if(newDirectory == null || (oldDirectory != null && newDirectory.equals(oldDirectory))) {
|
||||
return false;
|
||||
} else {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, newDirectory);
|
||||
editor.commit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadRemotePlayQueue() {
|
||||
if(Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_RESUME_PLAY_QUEUE_NEVER, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SubsonicActivity context = this;
|
||||
new SilentBackgroundTask<Void>(this) {
|
||||
private PlayerQueue playerQueue;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
try {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
PlayerQueue remoteState = musicService.getPlayQueue(context, null);
|
||||
|
||||
// Make sure we wait until download service is ready
|
||||
DownloadService downloadService = getDownloadService();
|
||||
while(downloadService == null || !downloadService.isInitialized()) {
|
||||
Util.sleepQuietly(100L);
|
||||
downloadService = getDownloadService();
|
||||
}
|
||||
|
||||
// If we had a remote state and it's changed is more recent than our existing state
|
||||
if(remoteState != null && remoteState.changed != null) {
|
||||
// Check if changed + 30 seconds since some servers have slight skew
|
||||
Date remoteChange = new Date(remoteState.changed.getTime() - ALLOWED_SKEW);
|
||||
Date localChange = downloadService.getLastStateChanged();
|
||||
if(localChange == null || localChange.before(remoteChange)) {
|
||||
playerQueue = remoteState;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to get playing queue to server", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void arg) {
|
||||
if(!context.isDestroyedCompat() && playerQueue != null) {
|
||||
promptRestoreFromRemoteQueue(playerQueue);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
private void promptRestoreFromRemoteQueue(final PlayerQueue remoteState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
String message = getResources().getString(R.string.common_confirm_message, Util.formatDate(remoteState.changed));
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.common_confirm)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
downloadService.clear();
|
||||
downloadService.download(remoteState.songs, false, false, false, false, remoteState.currentPlayingIndex, remoteState.currentPlayingPosition);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
downloadService.serializeQueue(false);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.common_never, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
downloadService.serializeQueue(false);
|
||||
|
||||
SharedPreferences.Editor editor = Util.getPreferences(SubsonicFragmentActivity.this).edit();
|
||||
editor.putBoolean(Constants.PREFERENCES_KEY_RESUME_PLAY_QUEUE_NEVER, true);
|
||||
editor.commit();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void createAccount() {
|
||||
final Context context = this;
|
||||
|
||||
new SilentBackgroundTask<Void>(this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
AccountManager accountManager = (AccountManager) context.getSystemService(ACCOUNT_SERVICE);
|
||||
Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE);
|
||||
accountManager.addAccountExplicitly(account, null, null);
|
||||
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
boolean syncEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_SYNC_ENABLED, true);
|
||||
int syncInterval = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_SYNC_INTERVAL, "60"));
|
||||
|
||||
// Add enabled/frequency to playlist syncing
|
||||
ContentResolver.setSyncAutomatically(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, syncEnabled);
|
||||
ContentResolver.addPeriodicSync(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, new Bundle(), 60L * syncInterval);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void showInfoDialog() {
|
||||
if (!infoDialogDisplayed) {
|
||||
infoDialogDisplayed = true;
|
||||
if (Util.getRestUrl(this, null).contains("demo.subsonic.org")) {
|
||||
Util.info(this, R.string.main_welcome_title, R.string.main_welcome_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Toolbar getActiveToolbar() {
|
||||
return slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED ? nowPlayingToolbar : mainToolbar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex) {
|
||||
this.currentPlaying = currentPlaying;
|
||||
|
||||
MusicDirectory.Entry song = null;
|
||||
if (currentPlaying != null) {
|
||||
song = currentPlaying.getSong();
|
||||
trackView.setText(song.getTitle());
|
||||
|
||||
if(song.getArtist() != null) {
|
||||
artistView.setVisibility(View.VISIBLE);
|
||||
artistView.setText(song.getArtist());
|
||||
} else {
|
||||
artistView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
trackView.setText(R.string.main_title);
|
||||
artistView.setText(R.string.main_artist);
|
||||
}
|
||||
|
||||
if (coverArtView != null) {
|
||||
int height = coverArtView.getHeight();
|
||||
if (height <= 0) {
|
||||
int[] attrs = new int[]{R.attr.actionBarSize};
|
||||
TypedArray typedArray = this.obtainStyledAttributes(attrs);
|
||||
height = typedArray.getDimensionPixelSize(0, 0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
getImageLoader().loadImage(coverArtView, song, false, height, false);
|
||||
}
|
||||
|
||||
previousButton.setVisibility(View.VISIBLE);
|
||||
nextButton.setVisibility(View.VISIBLE);
|
||||
|
||||
rewindButton.setVisibility(View.GONE);
|
||||
fastforwardButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex) {
|
||||
if(this.currentPlaying != currentPlaying || this.currentPlaying == null) {
|
||||
onSongChanged(currentPlaying, currentPlayingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSongProgress(DownloadFile currentPlaying, int millisPlayed, Integer duration, boolean isSeekable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdate(DownloadFile downloadFile, PlayerState playerState) {
|
||||
int[] attrs = new int[]{(playerState == PlayerState.STARTED) ? R.attr.actionbar_pause : R.attr.actionbar_start};
|
||||
TypedArray typedArray = this.obtainStyledAttributes(attrs);
|
||||
startButton.setImageResource(typedArray.getResourceId(0, 0));
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataUpdate(MusicDirectory.Entry song, int fieldChange) {
|
||||
if(song != null && coverArtView != null && fieldChange == DownloadService.METADATA_UPDATED_COVER_ART) {
|
||||
int height = coverArtView.getHeight();
|
||||
if (height <= 0) {
|
||||
int[] attrs = new int[]{R.attr.actionBarSize};
|
||||
TypedArray typedArray = this.obtainStyledAttributes(attrs);
|
||||
height = typedArray.getDimensionPixelSize(0, 0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
getImageLoader().loadImage(coverArtView, song, false, height, false);
|
||||
|
||||
// We need to update it immediately since it won't update if updater is not running for it
|
||||
if(nowPlayingFragment != null && slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
|
||||
nowPlayingFragment.onMetadataUpdate(song, fieldChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
|
||||
public class AlphabeticalAlbumAdapter extends EntryInfiniteGridAdapter implements FastScroller.BubbleTextGetter {
|
||||
public AlphabeticalAlbumAdapter(Context context, List<MusicDirectory.Entry> entries, ImageLoader imageLoader, boolean largeCell) {
|
||||
super(context, entries, imageLoader, largeCell);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextToShowInBubble(int position) {
|
||||
// Make sure that we are not trying to get an item for the loading placeholder
|
||||
if(position >= sections.get(0).size()) {
|
||||
if(sections.get(0).size() > 0) {
|
||||
return getTextToShowInBubble(position - 1);
|
||||
} else {
|
||||
return "*";
|
||||
}
|
||||
} else {
|
||||
return getNameIndex(getItemForPosition(position).getAlbum());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
import github.nvllsvm.audinaut.domain.MusicFolder;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.ArtistView;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
import github.nvllsvm.audinaut.view.SongView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class ArtistAdapter extends SectionAdapter<Serializable> implements FastScroller.BubbleTextGetter {
|
||||
public static int VIEW_TYPE_SONG = 3;
|
||||
public static int VIEW_TYPE_ARTIST = 4;
|
||||
|
||||
private List<MusicFolder> musicFolders;
|
||||
private OnMusicFolderChanged onMusicFolderChanged;
|
||||
|
||||
public ArtistAdapter(Context context, List<Serializable> artists, OnItemClickedListener listener) {
|
||||
this(context, artists, null, listener, null);
|
||||
}
|
||||
|
||||
public ArtistAdapter(Context context, List<Serializable> artists, List<MusicFolder> musicFolders, OnItemClickedListener onItemClickedListener, OnMusicFolderChanged onMusicFolderChanged) {
|
||||
super(context, artists);
|
||||
this.musicFolders = musicFolders;
|
||||
this.onItemClickedListener = onItemClickedListener;
|
||||
this.onMusicFolderChanged = onMusicFolderChanged;
|
||||
|
||||
if(musicFolders != null) {
|
||||
this.singleSectionHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
|
||||
final View header = LayoutInflater.from(context).inflate(R.layout.select_artist_header, parent, false);
|
||||
header.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
PopupMenu popup = new PopupMenu(context, header.findViewById(R.id.select_artist_folder_2));
|
||||
|
||||
popup.getMenu().add(R.string.select_artist_all_folders);
|
||||
for (MusicFolder musicFolder : musicFolders) {
|
||||
popup.getMenu().add(musicFolder.getName());
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
for (MusicFolder musicFolder : musicFolders) {
|
||||
if(item.getTitle().equals(musicFolder.getName())) {
|
||||
if(onMusicFolderChanged != null) {
|
||||
onMusicFolderChanged.onMusicFolderChanged(musicFolder);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(onMusicFolderChanged != null) {
|
||||
onMusicFolderChanged.onMusicFolderChanged(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
});
|
||||
|
||||
return new UpdateView.UpdateViewHolder(header, false);
|
||||
}
|
||||
@Override
|
||||
public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header, int sectionIndex) {
|
||||
TextView folderName = (TextView) holder.getView().findViewById(R.id.select_artist_folder_2);
|
||||
|
||||
String musicFolderId = Util.getSelectedMusicFolderId(context);
|
||||
if(musicFolderId != null) {
|
||||
for (MusicFolder musicFolder : musicFolders) {
|
||||
if (musicFolder.getId().equals(musicFolderId)) {
|
||||
folderName.setText(musicFolder.getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
folderName.setText(R.string.select_artist_all_folders);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
UpdateView updateView = null;
|
||||
if(viewType == VIEW_TYPE_ARTIST) {
|
||||
updateView = new ArtistView(context);
|
||||
} else if(viewType == VIEW_TYPE_SONG) {
|
||||
updateView = new SongView(context);
|
||||
}
|
||||
|
||||
return new UpdateView.UpdateViewHolder(updateView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Serializable item, int viewType) {
|
||||
UpdateView view = holder.getUpdateView();
|
||||
if(viewType == VIEW_TYPE_ARTIST) {
|
||||
view.setObject(item);
|
||||
} else if(viewType == VIEW_TYPE_SONG) {
|
||||
SongView songView = (SongView) view;
|
||||
Entry entry = (Entry) item;
|
||||
songView.setObject(entry, checkable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Serializable item) {
|
||||
if(item instanceof Artist) {
|
||||
return VIEW_TYPE_ARTIST;
|
||||
} else {
|
||||
return VIEW_TYPE_SONG;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextToShowInBubble(int position) {
|
||||
Object item = getItemForPosition(position);
|
||||
if(item instanceof Artist) {
|
||||
return getNameIndex(((Artist) item).getName(), true);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnMusicFolderChanged {
|
||||
void onMusicFolderChanged(MusicFolder musicFolder);
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.view.BasicListView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class BasicListAdapter extends SectionAdapter<String> {
|
||||
public static int VIEW_TYPE_LINE = 1;
|
||||
|
||||
public BasicListAdapter(Context context, List<String> strings, OnItemClickedListener listener) {
|
||||
super(context, strings);
|
||||
this.onItemClickedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
return new UpdateView.UpdateViewHolder(new BasicListView(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, String item, int viewType) {
|
||||
holder.getUpdateView().setObject(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(String item) {
|
||||
return VIEW_TYPE_LINE;
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
|
||||
public class DetailsAdapter extends ArrayAdapter<String> {
|
||||
private List<String> headers;
|
||||
private List<String> details;
|
||||
|
||||
public DetailsAdapter(Context context, int layout, List<String> headers, List<String> details) {
|
||||
super(context, layout, headers);
|
||||
|
||||
this.headers = headers;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent){
|
||||
View view;
|
||||
if(convertView == null) {
|
||||
view = LayoutInflater.from(getContext()).inflate(R.layout.details_item, null);
|
||||
} else {
|
||||
view = convertView;
|
||||
}
|
||||
|
||||
TextView nameView = (TextView) view.findViewById(R.id.detail_name);
|
||||
TextView detailsView = (TextView) view.findViewById(R.id.detail_value);
|
||||
|
||||
nameView.setText(headers.get(position));
|
||||
|
||||
detailsView.setText(details.get(position));
|
||||
Linkify.addLinks(detailsView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.service.DownloadFile;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
import github.nvllsvm.audinaut.view.SongView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class DownloadFileAdapter extends SectionAdapter<DownloadFile> implements FastScroller.BubbleTextGetter {
|
||||
public static int VIEW_TYPE_DOWNLOAD_FILE = 1;
|
||||
|
||||
public DownloadFileAdapter(Context context, List<DownloadFile> entries, OnItemClickedListener onItemClickedListener) {
|
||||
super(context, entries);
|
||||
this.onItemClickedListener = onItemClickedListener;
|
||||
this.checkable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
return new UpdateView.UpdateViewHolder(new SongView(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, DownloadFile item, int viewType) {
|
||||
SongView songView = (SongView) holder.getUpdateView();
|
||||
songView.setObject(item.getSong(), Util.isBatchMode(context));
|
||||
songView.setDownloadFile(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(DownloadFile item) {
|
||||
return VIEW_TYPE_DOWNLOAD_FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextToShowInBubble(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateActionModeMenu(Menu menu, MenuInflater menuInflater) {
|
||||
if(Util.isOffline(context)) {
|
||||
menuInflater.inflate(R.menu.multiselect_nowplaying_offline, menu);
|
||||
} else {
|
||||
menuInflater.inflate(R.menu.multiselect_nowplaying, menu);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.AlbumView;
|
||||
import github.nvllsvm.audinaut.view.SongView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView.UpdateViewHolder;
|
||||
|
||||
public class EntryGridAdapter extends SectionAdapter<Entry> {
|
||||
private static String TAG = EntryGridAdapter.class.getSimpleName();
|
||||
|
||||
public static int VIEW_TYPE_ALBUM_CELL = 1;
|
||||
public static int VIEW_TYPE_ALBUM_LINE = 2;
|
||||
public static int VIEW_TYPE_SONG = 3;
|
||||
|
||||
private ImageLoader imageLoader;
|
||||
private boolean largeAlbums;
|
||||
private boolean showArtist = false;
|
||||
private boolean showAlbum = false;
|
||||
private boolean removeFromPlaylist = false;
|
||||
private View header;
|
||||
|
||||
public EntryGridAdapter(Context context, List<Entry> entries, ImageLoader imageLoader, boolean largeCell) {
|
||||
super(context, entries);
|
||||
this.imageLoader = imageLoader;
|
||||
this.largeAlbums = largeCell;
|
||||
|
||||
// Always show artist if they aren't all the same
|
||||
String artist = null;
|
||||
for(MusicDirectory.Entry entry: entries) {
|
||||
if(artist == null) {
|
||||
artist = entry.getArtist();
|
||||
}
|
||||
|
||||
if(artist != null && !artist.equals(entry.getArtist())) {
|
||||
showArtist = true;
|
||||
}
|
||||
}
|
||||
checkable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
UpdateView updateView = null;
|
||||
if(viewType == VIEW_TYPE_ALBUM_LINE || viewType == VIEW_TYPE_ALBUM_CELL) {
|
||||
updateView = new AlbumView(context, viewType == VIEW_TYPE_ALBUM_CELL);
|
||||
} else if(viewType == VIEW_TYPE_SONG) {
|
||||
updateView = new SongView(context);
|
||||
}
|
||||
|
||||
return new UpdateViewHolder(updateView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateViewHolder holder, Entry entry, int viewType) {
|
||||
UpdateView view = holder.getUpdateView();
|
||||
if(viewType == VIEW_TYPE_ALBUM_CELL || viewType == VIEW_TYPE_ALBUM_LINE) {
|
||||
AlbumView albumView = (AlbumView) view;
|
||||
albumView.setShowArtist(showArtist);
|
||||
albumView.setObject(entry, imageLoader);
|
||||
} else if(viewType == VIEW_TYPE_SONG) {
|
||||
SongView songView = (SongView) view;
|
||||
songView.setShowAlbum(showAlbum);
|
||||
songView.setObject(entry, checkable);
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
|
||||
return new UpdateViewHolder(header, false);
|
||||
}
|
||||
public void onBindHeaderHolder(UpdateViewHolder holder, String header, int sectionIndex) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Entry entry) {
|
||||
if(entry.isDirectory()) {
|
||||
if (largeAlbums) {
|
||||
return VIEW_TYPE_ALBUM_CELL;
|
||||
} else {
|
||||
return VIEW_TYPE_ALBUM_LINE;
|
||||
}
|
||||
} else {
|
||||
return VIEW_TYPE_SONG;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHeader(View header) {
|
||||
this.header = header;
|
||||
this.singleSectionHeader = true;
|
||||
}
|
||||
public View getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setShowArtist(boolean showArtist) {
|
||||
this.showArtist = showArtist;
|
||||
}
|
||||
|
||||
public void setShowAlbum(boolean showAlbum) {
|
||||
this.showAlbum = showAlbum;
|
||||
}
|
||||
|
||||
public void removeAt(int index) {
|
||||
sections.get(0).remove(index);
|
||||
if(header != null) {
|
||||
index++;
|
||||
}
|
||||
notifyItemRemoved(index);
|
||||
}
|
||||
|
||||
public void setRemoveFromPlaylist(boolean removeFromPlaylist) {
|
||||
this.removeFromPlaylist = removeFromPlaylist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateActionModeMenu(Menu menu, MenuInflater menuInflater) {
|
||||
if(Util.isOffline(context)) {
|
||||
menuInflater.inflate(R.menu.multiselect_media_offline, menu);
|
||||
} else {
|
||||
menuInflater.inflate(R.menu.multiselect_media, menu);
|
||||
}
|
||||
|
||||
if(!removeFromPlaylist) {
|
||||
menu.removeItem(R.id.menu_remove_playlist);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
import github.nvllsvm.audinaut.fragments.MainFragment;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class EntryInfiniteGridAdapter extends EntryGridAdapter {
|
||||
public static int VIEW_TYPE_LOADING = 4;
|
||||
|
||||
private String type;
|
||||
private String extra;
|
||||
private int size;
|
||||
|
||||
private boolean loading = false;
|
||||
private boolean allLoaded = false;
|
||||
|
||||
public EntryInfiniteGridAdapter(Context context, List<Entry> entries, ImageLoader imageLoader, boolean largeCell) {
|
||||
super(context, entries, imageLoader, largeCell);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if(viewType == VIEW_TYPE_LOADING) {
|
||||
View progress = LayoutInflater.from(context).inflate(R.layout.tab_progress, null);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
return new UpdateView.UpdateViewHolder(progress, false);
|
||||
}
|
||||
|
||||
return super.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if(isLoadingView(position)) {
|
||||
return VIEW_TYPE_LOADING;
|
||||
}
|
||||
|
||||
return super.getItemViewType(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, int position) {
|
||||
if(!isLoadingView(position)) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int size = super.getItemCount();
|
||||
|
||||
if(!allLoaded) {
|
||||
size++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setData(String type, String extra, int size) {
|
||||
this.type = type;
|
||||
this.extra = extra;
|
||||
this.size = size;
|
||||
|
||||
if(super.getItemCount() < size) {
|
||||
allLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void loadMore() {
|
||||
if(loading || allLoaded) {
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
|
||||
new SilentBackgroundTask<Void>(context) {
|
||||
private List<Entry> newData;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
newData = cacheInBackground();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
appendCachedData(newData);
|
||||
loading = false;
|
||||
|
||||
if(newData.size() < size) {
|
||||
allLoaded = true;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
protected List<Entry> cacheInBackground() throws Exception {
|
||||
MusicService service = MusicServiceFactory.getMusicService(context);
|
||||
MusicDirectory result;
|
||||
int offset = sections.get(0).size();
|
||||
if("genres".equals(type) || "years".equals(type)) {
|
||||
result = service.getAlbumList(type, extra, size, offset, false, context, null);
|
||||
} else if("genres".equals(type) || "genres-songs".equals(type)) {
|
||||
result = service.getSongsByGenre(extra, size, offset, context, null);
|
||||
}else if(type.indexOf(MainFragment.SONGS_LIST_PREFIX) != -1) {
|
||||
result = service.getSongList(type, size, offset, context, null);
|
||||
} else {
|
||||
result = service.getAlbumList(type, size, offset, false, context, null);
|
||||
}
|
||||
return result.getChildren();
|
||||
}
|
||||
|
||||
protected void appendCachedData(List<Entry> newData) {
|
||||
if(newData.size() > 0) {
|
||||
int start = sections.get(0).size();
|
||||
sections.get(0).addAll(newData);
|
||||
this.notifyItemRangeInserted(start, newData.size());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isLoadingView(int position) {
|
||||
return !allLoaded && position >= sections.get(0).size();
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.util.DrawableTint;
|
||||
import github.nvllsvm.audinaut.view.BasicHeaderView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public abstract class ExpandableSectionAdapter<T> extends SectionAdapter<T> {
|
||||
private static final String TAG = ExpandableSectionAdapter.class.getSimpleName();
|
||||
private static final int DEFAULT_VISIBLE = 4;
|
||||
private static final int EXPAND_TOGGLE = R.attr.select_server;
|
||||
private static final int COLLAPSE_TOGGLE = R.attr.select_tabs;
|
||||
|
||||
protected List<Integer> sectionsDefaultVisible;
|
||||
protected List<List<T>> sectionsExtras;
|
||||
protected int expandToggleRes;
|
||||
protected int collapseToggleRes;
|
||||
|
||||
protected ExpandableSectionAdapter() {}
|
||||
public ExpandableSectionAdapter(Context context, List<T> section) {
|
||||
List<List<T>> sections = new ArrayList<>();
|
||||
sections.add(section);
|
||||
|
||||
init(context, Arrays.asList("Section"), sections, Arrays.asList((Integer) null));
|
||||
}
|
||||
public ExpandableSectionAdapter(Context context, List<String> headers, List<List<T>> sections) {
|
||||
init(context, headers, sections, null);
|
||||
}
|
||||
public ExpandableSectionAdapter(Context context, List<String> headers, List<List<T>> sections, List<Integer> sectionsDefaultVisible) {
|
||||
init(context, headers, sections, sectionsDefaultVisible);
|
||||
}
|
||||
protected void init(Context context, List<String> headers, List<List<T>> fullSections, List<Integer> sectionsDefaultVisible) {
|
||||
this.context = context;
|
||||
this.headers = headers;
|
||||
this.sectionsDefaultVisible = sectionsDefaultVisible;
|
||||
if(sectionsDefaultVisible == null) {
|
||||
sectionsDefaultVisible = new ArrayList<>(fullSections.size());
|
||||
for(int i = 0; i < fullSections.size(); i++) {
|
||||
sectionsDefaultVisible.add(DEFAULT_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
this.sections = new ArrayList<>();
|
||||
this.sectionsExtras = new ArrayList<>();
|
||||
int i = 0;
|
||||
for(List<T> fullSection: fullSections) {
|
||||
List<T> visibleSection = new ArrayList<>();
|
||||
|
||||
Integer defaultVisible = sectionsDefaultVisible.get(i);
|
||||
if(defaultVisible == null || defaultVisible >= fullSection.size()) {
|
||||
visibleSection.addAll(fullSection);
|
||||
this.sectionsExtras.add(null);
|
||||
} else {
|
||||
visibleSection.addAll(fullSection.subList(0, defaultVisible));
|
||||
this.sectionsExtras.add(fullSection.subList(defaultVisible, fullSection.size()));
|
||||
}
|
||||
this.sections.add(visibleSection);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
expandToggleRes = DrawableTint.getDrawableRes(context, EXPAND_TOGGLE);
|
||||
collapseToggleRes = DrawableTint.getDrawableRes(context, COLLAPSE_TOGGLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
|
||||
return new UpdateView.UpdateViewHolder(new BasicHeaderView(context, R.layout.expandable_header));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header, final int sectionIndex) {
|
||||
UpdateView view = holder.getUpdateView();
|
||||
ImageView toggleSelectionView = (ImageView) view.findViewById(R.id.item_select);
|
||||
|
||||
List<T> visibleSelection = sections.get(sectionIndex);
|
||||
List<T> sectionExtras = sectionsExtras.get(sectionIndex);
|
||||
|
||||
if(sectionExtras != null && !sectionExtras.isEmpty()) {
|
||||
toggleSelectionView.setVisibility(View.VISIBLE);
|
||||
toggleSelectionView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
List<T> visibleSelection = sections.get(sectionIndex);
|
||||
List<T> sectionExtras = sectionsExtras.get(sectionIndex);
|
||||
|
||||
// Update icon
|
||||
int selectToggleAttr;
|
||||
if (!visibleSelection.contains(sectionExtras.get(0))) {
|
||||
selectToggleAttr = COLLAPSE_TOGGLE;
|
||||
|
||||
// Update how many are displayed
|
||||
int lastIndex = getItemPosition(visibleSelection.get(visibleSelection.size() - 1));
|
||||
visibleSelection.addAll(sectionExtras);
|
||||
notifyItemRangeInserted(lastIndex, sectionExtras.size());
|
||||
} else {
|
||||
selectToggleAttr = EXPAND_TOGGLE;
|
||||
|
||||
// Update how many are displayed
|
||||
visibleSelection.removeAll(sectionExtras);
|
||||
int lastIndex = getItemPosition(visibleSelection.get(visibleSelection.size() - 1));
|
||||
notifyItemRangeRemoved(lastIndex, sectionExtras.size());
|
||||
}
|
||||
|
||||
((ImageView) v).setImageResource(DrawableTint.getDrawableRes(context, selectToggleAttr));
|
||||
}
|
||||
});
|
||||
|
||||
int selectToggleAttr;
|
||||
if (!visibleSelection.contains(sectionExtras.get(0))) {
|
||||
selectToggleAttr = EXPAND_TOGGLE;
|
||||
} else {
|
||||
selectToggleAttr = COLLAPSE_TOGGLE;
|
||||
}
|
||||
|
||||
toggleSelectionView.setImageResource(DrawableTint.getDrawableRes(context, selectToggleAttr));
|
||||
} else {
|
||||
toggleSelectionView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(view != null) {
|
||||
view.setObject(header);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
import github.nvllsvm.audinaut.domain.Genre;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
import github.nvllsvm.audinaut.view.GenreView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GenreAdapter extends SectionAdapter<Genre> implements FastScroller.BubbleTextGetter{
|
||||
public static int VIEW_TYPE_GENRE = 1;
|
||||
|
||||
public GenreAdapter(Context context, List<Genre> genres, OnItemClickedListener listener) {
|
||||
super(context, genres);
|
||||
this.onItemClickedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
return new UpdateView.UpdateViewHolder(new GenreView(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Genre item, int viewType) {
|
||||
holder.getUpdateView().setObject(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Genre item) {
|
||||
return VIEW_TYPE_GENRE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextToShowInBubble(int position) {
|
||||
return getNameIndex(getItemForPosition(position).getName());
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.AlbumListCountView;
|
||||
import github.nvllsvm.audinaut.view.BasicHeaderView;
|
||||
import github.nvllsvm.audinaut.view.BasicListView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class MainAdapter extends SectionAdapter<Integer> {
|
||||
public static final int VIEW_TYPE_ALBUM_LIST = 1;
|
||||
public static final int VIEW_TYPE_ALBUM_COUNT_LIST = 2;
|
||||
|
||||
public MainAdapter(Context context, List<String> headers, List<List<Integer>> sections, OnItemClickedListener onItemClickedListener) {
|
||||
super(context, headers, sections);
|
||||
this.onItemClickedListener = onItemClickedListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
UpdateView updateView;
|
||||
if(viewType == VIEW_TYPE_ALBUM_LIST) {
|
||||
updateView = new BasicListView(context);
|
||||
} else {
|
||||
updateView = new AlbumListCountView(context);
|
||||
}
|
||||
|
||||
return new UpdateView.UpdateViewHolder(updateView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Integer item, int viewType) {
|
||||
UpdateView updateView = holder.getUpdateView();
|
||||
|
||||
if(viewType == VIEW_TYPE_ALBUM_LIST) {
|
||||
updateView.setObject(context.getResources().getString(item));
|
||||
} else {
|
||||
updateView.setObject(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Integer item) {
|
||||
if(item == R.string.main_albums_newest) {
|
||||
return VIEW_TYPE_ALBUM_COUNT_LIST;
|
||||
} else {
|
||||
return VIEW_TYPE_ALBUM_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
|
||||
return new UpdateView.UpdateViewHolder(new BasicHeaderView(context, R.layout.album_list_header));
|
||||
}
|
||||
@Override
|
||||
public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header, int sectionIndex) {
|
||||
UpdateView view = holder.getUpdateView();
|
||||
CheckBox checkBox = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
|
||||
String display;
|
||||
if("songs".equals(header)) {
|
||||
display = context.getResources().getString(R.string.search_songs);
|
||||
checkBox.setVisibility(View.GONE);
|
||||
} else {
|
||||
display = header;
|
||||
checkBox.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(view != null) {
|
||||
view.setObject(display);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import github.nvllsvm.audinaut.domain.Playlist;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
import github.nvllsvm.audinaut.view.PlaylistView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class PlaylistAdapter extends SectionAdapter<Playlist> implements FastScroller.BubbleTextGetter {
|
||||
public static int VIEW_TYPE_PLAYLIST = 1;
|
||||
|
||||
private ImageLoader imageLoader;
|
||||
private boolean largeCell;
|
||||
|
||||
public PlaylistAdapter(Context context, List<Playlist> playlists, ImageLoader imageLoader, boolean largeCell, OnItemClickedListener listener) {
|
||||
super(context, playlists);
|
||||
this.imageLoader = imageLoader;
|
||||
this.largeCell = largeCell;
|
||||
this.onItemClickedListener = listener;
|
||||
}
|
||||
public PlaylistAdapter(Context context, List<String> headers, List<List<Playlist>> sections, ImageLoader imageLoader, boolean largeCell, OnItemClickedListener listener) {
|
||||
super(context, headers, sections);
|
||||
this.imageLoader = imageLoader;
|
||||
this.largeCell = largeCell;
|
||||
this.onItemClickedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
return new UpdateView.UpdateViewHolder(new PlaylistView(context, imageLoader, largeCell));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Playlist playlist, int viewType) {
|
||||
holder.getUpdateView().setObject(playlist);
|
||||
holder.setItem(playlist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Playlist playlist) {
|
||||
return VIEW_TYPE_PLAYLIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextToShowInBubble(int position) {
|
||||
Object item = getItemForPosition(position);
|
||||
if(item instanceof Playlist) {
|
||||
return getNameIndex(((Playlist) item).getName());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
import github.nvllsvm.audinaut.domain.SearchResult;
|
||||
import github.nvllsvm.audinaut.util.DrawableTint;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.AlbumView;
|
||||
import github.nvllsvm.audinaut.view.ArtistView;
|
||||
import github.nvllsvm.audinaut.view.BasicHeaderView;
|
||||
import github.nvllsvm.audinaut.view.SongView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import static github.nvllsvm.audinaut.adapter.ArtistAdapter.VIEW_TYPE_ARTIST;
|
||||
import static github.nvllsvm.audinaut.adapter.EntryGridAdapter.VIEW_TYPE_ALBUM_CELL;
|
||||
import static github.nvllsvm.audinaut.adapter.EntryGridAdapter.VIEW_TYPE_ALBUM_LINE;
|
||||
import static github.nvllsvm.audinaut.adapter.EntryGridAdapter.VIEW_TYPE_SONG;
|
||||
|
||||
public class SearchAdapter extends ExpandableSectionAdapter<Serializable> {
|
||||
private ImageLoader imageLoader;
|
||||
private boolean largeAlbums;
|
||||
|
||||
private static final int MAX_ARTISTS = 10;
|
||||
private static final int MAX_ALBUMS = 4;
|
||||
private static final int MAX_SONGS = 10;
|
||||
|
||||
public SearchAdapter(Context context, SearchResult searchResult, ImageLoader imageLoader, boolean largeAlbums, OnItemClickedListener listener) {
|
||||
this.imageLoader = imageLoader;
|
||||
this.largeAlbums = largeAlbums;
|
||||
|
||||
List<List<Serializable>> sections = new ArrayList<>();
|
||||
List<String> headers = new ArrayList<>();
|
||||
List<Integer> defaultVisible = new ArrayList<>();
|
||||
Resources res = context.getResources();
|
||||
if(!searchResult.getArtists().isEmpty()) {
|
||||
sections.add((List<Serializable>) (List<?>) searchResult.getArtists());
|
||||
headers.add(res.getString(R.string.search_artists));
|
||||
defaultVisible.add(MAX_ARTISTS);
|
||||
}
|
||||
if(!searchResult.getAlbums().isEmpty()) {
|
||||
sections.add((List<Serializable>) (List<?>) searchResult.getAlbums());
|
||||
headers.add(res.getString(R.string.search_albums));
|
||||
defaultVisible.add(MAX_ALBUMS);
|
||||
}
|
||||
if(!searchResult.getSongs().isEmpty()) {
|
||||
sections.add((List<Serializable>) (List<?>) searchResult.getSongs());
|
||||
headers.add(res.getString(R.string.search_songs));
|
||||
defaultVisible.add(MAX_SONGS);
|
||||
}
|
||||
init(context, headers, sections, defaultVisible);
|
||||
|
||||
this.onItemClickedListener = listener;
|
||||
checkable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
UpdateView updateView = null;
|
||||
if(viewType == VIEW_TYPE_ALBUM_CELL || viewType == VIEW_TYPE_ALBUM_LINE) {
|
||||
updateView = new AlbumView(context, viewType == VIEW_TYPE_ALBUM_CELL);
|
||||
} else if(viewType == VIEW_TYPE_SONG) {
|
||||
updateView = new SongView(context);
|
||||
} else if(viewType == VIEW_TYPE_ARTIST) {
|
||||
updateView = new ArtistView(context);
|
||||
}
|
||||
|
||||
return new UpdateView.UpdateViewHolder(updateView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Serializable item, int viewType) {
|
||||
UpdateView view = holder.getUpdateView();
|
||||
if(viewType == VIEW_TYPE_ALBUM_CELL || viewType == VIEW_TYPE_ALBUM_LINE) {
|
||||
AlbumView albumView = (AlbumView) view;
|
||||
albumView.setObject((Entry) item, imageLoader);
|
||||
} else if(viewType == VIEW_TYPE_SONG) {
|
||||
SongView songView = (SongView) view;
|
||||
songView.setObject((Entry) item, true);
|
||||
} else if(viewType == VIEW_TYPE_ARTIST) {
|
||||
view.setObject(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Serializable item) {
|
||||
if(item instanceof Entry) {
|
||||
Entry entry = (Entry) item;
|
||||
if (entry.isDirectory()) {
|
||||
if (largeAlbums) {
|
||||
return VIEW_TYPE_ALBUM_CELL;
|
||||
} else {
|
||||
return VIEW_TYPE_ALBUM_LINE;
|
||||
}
|
||||
} else {
|
||||
return VIEW_TYPE_SONG;
|
||||
}
|
||||
} else {
|
||||
return VIEW_TYPE_ARTIST;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateActionModeMenu(Menu menu, MenuInflater menuInflater) {
|
||||
if(Util.isOffline(context)) {
|
||||
menuInflater.inflate(R.menu.multiselect_media_offline, menu);
|
||||
} else {
|
||||
menuInflater.inflate(R.menu.multiselect_media, menu);
|
||||
}
|
||||
|
||||
menu.removeItem(R.id.menu_remove_playlist);
|
||||
}
|
||||
}
|
|
@ -1,516 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.activity.SubsonicFragmentActivity;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.MenuUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.BasicHeaderView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView.UpdateViewHolder;
|
||||
|
||||
public abstract class SectionAdapter<T> extends RecyclerView.Adapter<UpdateViewHolder<T>> {
|
||||
private static String TAG = SectionAdapter.class.getSimpleName();
|
||||
public static int VIEW_TYPE_HEADER = 0;
|
||||
public static String[] ignoredArticles;
|
||||
|
||||
protected Context context;
|
||||
protected List<String> headers;
|
||||
protected List<List<T>> sections;
|
||||
protected boolean singleSectionHeader;
|
||||
protected OnItemClickedListener<T> onItemClickedListener;
|
||||
protected List<T> selected = new ArrayList<>();
|
||||
protected List<UpdateView> selectedViews = new ArrayList<>();
|
||||
protected ActionMode currentActionMode;
|
||||
protected boolean checkable = false;
|
||||
|
||||
protected SectionAdapter() {}
|
||||
public SectionAdapter(Context context, List<T> section) {
|
||||
this(context, section, false);
|
||||
}
|
||||
public SectionAdapter(Context context, List<T> section, boolean singleSectionHeader) {
|
||||
this.context = context;
|
||||
this.headers = Arrays.asList("Section");
|
||||
this.sections = new ArrayList<>();
|
||||
this.sections.add(section);
|
||||
this.singleSectionHeader = singleSectionHeader;
|
||||
}
|
||||
public SectionAdapter(Context context, List<String> headers, List<List<T>> sections) {
|
||||
this(context, headers, sections, true);
|
||||
}
|
||||
public SectionAdapter(Context context, List<String> headers, List<List<T>> sections, boolean singleSectionHeader){
|
||||
this.context = context;
|
||||
this.headers = headers;
|
||||
this.sections = sections;
|
||||
this.singleSectionHeader = singleSectionHeader;
|
||||
}
|
||||
|
||||
public void replaceExistingData(List<T> section) {
|
||||
this.sections = new ArrayList<>();
|
||||
this.sections.add(section);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
public void replaceExistingData(List<String> headers, List<List<T>> sections) {
|
||||
this.headers = headers;
|
||||
this.sections = sections;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if(viewType == VIEW_TYPE_HEADER) {
|
||||
return onCreateHeaderHolder(parent);
|
||||
} else {
|
||||
final UpdateViewHolder<T> holder = onCreateSectionViewHolder(parent, viewType);
|
||||
final UpdateView updateView = holder.getUpdateView();
|
||||
|
||||
if(updateView != null) {
|
||||
updateView.getChildAt(0).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
T item = holder.getItem();
|
||||
updateView.onClick();
|
||||
if (currentActionMode != null) {
|
||||
if(updateView.isCheckable()) {
|
||||
if (selected.contains(item)) {
|
||||
selected.remove(item);
|
||||
selectedViews.remove(updateView);
|
||||
setChecked(updateView, false);
|
||||
} else {
|
||||
selected.add(item);
|
||||
selectedViews.add(updateView);
|
||||
setChecked(updateView, true);
|
||||
}
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
currentActionMode.finish();
|
||||
} else {
|
||||
currentActionMode.setTitle(context.getResources().getString(R.string.select_album_n_selected, selected.size()));
|
||||
}
|
||||
}
|
||||
} else if (onItemClickedListener != null) {
|
||||
onItemClickedListener.onItemClicked(updateView, item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
View moreButton = updateView.findViewById(R.id.item_more);
|
||||
if (moreButton != null) {
|
||||
moreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
final T item = holder.getItem();
|
||||
if (onItemClickedListener != null) {
|
||||
PopupMenu popup = new PopupMenu(context, v);
|
||||
onItemClickedListener.onCreateContextMenu(popup.getMenu(), popup.getMenuInflater(), updateView, item);
|
||||
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
return onItemClickedListener.onContextItemSelected(menuItem, updateView, item);
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to show popup", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(checkable) {
|
||||
updateView.getChildAt(0).setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if(updateView.isCheckable()) {
|
||||
if (currentActionMode == null) {
|
||||
startActionMode(holder);
|
||||
} else {
|
||||
updateView.getChildAt(0).performClick();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateViewHolder holder, int position) {
|
||||
UpdateView updateView = holder.getUpdateView();
|
||||
|
||||
if(sections.size() == 1 && !singleSectionHeader) {
|
||||
T item = sections.get(0).get(position);
|
||||
onBindViewHolder(holder, item, getItemViewType(position));
|
||||
postBindView(updateView, item);
|
||||
holder.setItem(item);
|
||||
return;
|
||||
}
|
||||
|
||||
int subPosition = 0;
|
||||
int subHeader = 0;
|
||||
for(List<T> section: sections) {
|
||||
boolean validHeader = headers.get(subHeader) != null;
|
||||
if(position == subPosition && validHeader) {
|
||||
onBindHeaderHolder(holder, headers.get(subHeader), subHeader);
|
||||
return;
|
||||
}
|
||||
|
||||
int headerOffset = validHeader ? 1 : 0;
|
||||
if(position < (subPosition + section.size() + headerOffset)) {
|
||||
T item = section.get(position - subPosition - headerOffset);
|
||||
onBindViewHolder(holder, item, getItemViewType(item));
|
||||
|
||||
postBindView(updateView, item);
|
||||
holder.setItem(item);
|
||||
return;
|
||||
}
|
||||
|
||||
subPosition += section.size();
|
||||
if(validHeader) {
|
||||
subPosition += 1;
|
||||
}
|
||||
subHeader++;
|
||||
}
|
||||
}
|
||||
|
||||
private void postBindView(UpdateView updateView, T item) {
|
||||
if(updateView.isCheckable()) {
|
||||
setChecked(updateView, selected.contains(item));
|
||||
}
|
||||
|
||||
View moreButton = updateView.findViewById(R.id.item_more);
|
||||
if(moreButton != null) {
|
||||
if(onItemClickedListener != null) {
|
||||
PopupMenu popup = new PopupMenu(context, moreButton);
|
||||
Menu menu = popup.getMenu();
|
||||
onItemClickedListener.onCreateContextMenu(popup.getMenu(), popup.getMenuInflater(), updateView, item);
|
||||
if (menu.size() == 0) {
|
||||
moreButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
moreButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
moreButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if(sections.size() == 1 && !singleSectionHeader) {
|
||||
return sections.get(0).size();
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for(String header: headers) {
|
||||
if(header != null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
for(List<T> section: sections) {
|
||||
count += section.size();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if(sections.size() == 1 && !singleSectionHeader) {
|
||||
return getItemViewType(sections.get(0).get(position));
|
||||
}
|
||||
|
||||
int subPosition = 0;
|
||||
int subHeader = 0;
|
||||
for(List<T> section: sections) {
|
||||
boolean validHeader = headers.get(subHeader) != null;
|
||||
if(position == subPosition && validHeader) {
|
||||
return VIEW_TYPE_HEADER;
|
||||
}
|
||||
|
||||
int headerOffset = validHeader ? 1 : 0;
|
||||
if(position < (subPosition + section.size() + headerOffset)) {
|
||||
return getItemViewType(section.get(position - subPosition - headerOffset));
|
||||
}
|
||||
|
||||
subPosition += section.size();
|
||||
if(validHeader) {
|
||||
subPosition += 1;
|
||||
}
|
||||
subHeader++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
|
||||
return new UpdateViewHolder(new BasicHeaderView(context));
|
||||
}
|
||||
public void onBindHeaderHolder(UpdateViewHolder holder, String header, int sectionIndex) {
|
||||
UpdateView view = holder.getUpdateView();
|
||||
if(view != null) {
|
||||
view.setObject(header);
|
||||
}
|
||||
}
|
||||
|
||||
public T getItemForPosition(int position) {
|
||||
if(sections.size() == 1 && !singleSectionHeader) {
|
||||
return sections.get(0).get(position);
|
||||
}
|
||||
|
||||
int subPosition = 0;
|
||||
for(List<T> section: sections) {
|
||||
if(position == subPosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(position <= (subPosition + section.size())) {
|
||||
return section.get(position - subPosition - 1);
|
||||
}
|
||||
|
||||
subPosition += section.size() + 1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public int getItemPosition(T item) {
|
||||
if(sections.size() == 1 && !singleSectionHeader) {
|
||||
return sections.get(0).indexOf(item);
|
||||
}
|
||||
|
||||
int subPosition = 0;
|
||||
for(List<T> section: sections) {
|
||||
subPosition += section.size() + 1;
|
||||
|
||||
int position = section.indexOf(item);
|
||||
if(position != -1) {
|
||||
return position + subPosition;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setOnItemClickedListener(OnItemClickedListener<T> onItemClickedListener) {
|
||||
this.onItemClickedListener = onItemClickedListener;
|
||||
}
|
||||
|
||||
public void addSelected(T item) {
|
||||
selected.add(item);
|
||||
}
|
||||
public List<T> getSelected() {
|
||||
List<T> selected = new ArrayList<>();
|
||||
selected.addAll(this.selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void clearSelected() {
|
||||
// TODO: This needs to work with multiple sections
|
||||
for(T item: selected) {
|
||||
int index = sections.get(0).indexOf(item);
|
||||
|
||||
if(singleSectionHeader) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
selected.clear();
|
||||
|
||||
for(UpdateView updateView: selectedViews) {
|
||||
updateView.setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void moveItem(int from, int to) {
|
||||
List<T> section = sections.get(0);
|
||||
int max = section.size();
|
||||
if(to >= max) {
|
||||
to = max - 1;
|
||||
} else if(to < 0) {
|
||||
to = 0;
|
||||
}
|
||||
|
||||
T moved = section.remove(from);
|
||||
section.add(to, moved);
|
||||
|
||||
notifyItemMoved(from, to);
|
||||
}
|
||||
public void removeItem(T item) {
|
||||
int subPosition = 0;
|
||||
for(List<T> section: sections) {
|
||||
if(sections.size() > 1 || singleSectionHeader) {
|
||||
subPosition++;
|
||||
}
|
||||
|
||||
int index = section.indexOf(item);
|
||||
if (index != -1) {
|
||||
section.remove(item);
|
||||
notifyItemRemoved(subPosition + index);
|
||||
break;
|
||||
}
|
||||
|
||||
subPosition += section.size();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType);
|
||||
public abstract void onBindViewHolder(UpdateViewHolder holder, T item, int viewType);
|
||||
public abstract int getItemViewType(T item);
|
||||
public void setCheckable(boolean checkable) {
|
||||
this.checkable = checkable;
|
||||
}
|
||||
public void setChecked(UpdateView updateView, boolean checked) {
|
||||
updateView.setChecked(checked);
|
||||
}
|
||||
public void onCreateActionModeMenu(Menu menu, MenuInflater menuInflater) {}
|
||||
|
||||
private void startActionMode(final UpdateView.UpdateViewHolder<T> holder) {
|
||||
final UpdateView<T> updateView = holder.getUpdateView();
|
||||
if (context instanceof SubsonicFragmentActivity && currentActionMode == null) {
|
||||
final SubsonicFragmentActivity fragmentActivity = (SubsonicFragmentActivity) context;
|
||||
fragmentActivity.startSupportActionMode(new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
currentActionMode = mode;
|
||||
|
||||
T item = holder.getItem();
|
||||
selected.add(item);
|
||||
selectedViews.add(updateView);
|
||||
setChecked(updateView, true);
|
||||
|
||||
onCreateActionModeMenu(menu, mode.getMenuInflater());
|
||||
MenuUtil.hideMenuItems(context, menu, updateView);
|
||||
|
||||
mode.setTitle(context.getResources().getString(R.string.select_album_n_selected, selected.size()));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true);
|
||||
int colorPrimaryDark = typedValue.data;
|
||||
|
||||
Window window = ((SubsonicFragmentActivity) context).getWindow();
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
window.setStatusBarColor(colorPrimaryDark);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (fragmentActivity.onOptionsItemSelected(item)) {
|
||||
currentActionMode.finish();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
currentActionMode = null;
|
||||
selected.clear();
|
||||
for (UpdateView<T> updateView : selectedViews) {
|
||||
updateView.setChecked(false);
|
||||
}
|
||||
selectedViews.clear();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
|
||||
Window window = ((SubsonicFragmentActivity) context).getWindow();
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
public void stopActionMode() {
|
||||
if(currentActionMode != null) {
|
||||
currentActionMode.finish();
|
||||
}
|
||||
}
|
||||
|
||||
public String getNameIndex(String name) {
|
||||
return getNameIndex(name, false);
|
||||
}
|
||||
public String getNameIndex(String name, boolean removeIgnoredArticles) {
|
||||
if(name == null) {
|
||||
return "*";
|
||||
}
|
||||
|
||||
if(removeIgnoredArticles) {
|
||||
if (ignoredArticles == null) {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
String ignoredArticlesString = prefs.getString(Constants.CACHE_KEY_IGNORE, "The El La Los Las Le Les");
|
||||
ignoredArticles = ignoredArticlesString.split(" ");
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
for (String article : ignoredArticles) {
|
||||
int index = name.indexOf(article.toLowerCase() + " ");
|
||||
if (index == 0) {
|
||||
name = name.substring(article.length() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String index = name.substring(0, 1).toUpperCase();
|
||||
if (!Character.isLetter(index.charAt(0))) {
|
||||
index = "#";
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public interface OnItemClickedListener<T> {
|
||||
void onItemClicked(UpdateView<T> updateView, T item);
|
||||
void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<T> updateView, T item);
|
||||
boolean onContextItemSelected(MenuItem menuItem, UpdateView<T> updateView, T item);
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.User;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
import github.nvllsvm.audinaut.util.UserUtil;
|
||||
import github.nvllsvm.audinaut.view.BasicHeaderView;
|
||||
import github.nvllsvm.audinaut.view.RecyclingImageView;
|
||||
import github.nvllsvm.audinaut.view.SettingView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import static github.nvllsvm.audinaut.domain.User.Setting;
|
||||
|
||||
public class SettingsAdapter extends SectionAdapter<Setting> {
|
||||
private static final String TAG = SettingsAdapter.class.getSimpleName();
|
||||
public final int VIEW_TYPE_SETTING = 1;
|
||||
public final int VIEW_TYPE_SETTING_HEADER = 2;
|
||||
|
||||
private final User user;
|
||||
private final boolean editable;
|
||||
private final ImageLoader imageLoader;
|
||||
|
||||
public SettingsAdapter(Context context, User user, List<String> headers, List<List<User.Setting>> settingSections, ImageLoader imageLoader, boolean editable, OnItemClickedListener<Setting> onItemClickedListener) {
|
||||
super(context, headers, settingSections, imageLoader != null);
|
||||
this.user = user;
|
||||
this.imageLoader = imageLoader;
|
||||
this.editable = editable;
|
||||
this.onItemClickedListener = onItemClickedListener;
|
||||
|
||||
for(List<Setting> settings: sections) {
|
||||
for (Setting setting : settings) {
|
||||
if (setting.getValue()) {
|
||||
addSelected(setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
int viewType = super.getItemViewType(position);
|
||||
if(viewType == SectionAdapter.VIEW_TYPE_HEADER) {
|
||||
if(position == 0 && imageLoader != null) {
|
||||
return VIEW_TYPE_HEADER;
|
||||
} else {
|
||||
return VIEW_TYPE_SETTING_HEADER;
|
||||
}
|
||||
} else {
|
||||
return viewType;
|
||||
}
|
||||
}
|
||||
|
||||
public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String description, int sectionIndex) {
|
||||
View header = holder.getView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
|
||||
if(viewType == VIEW_TYPE_SETTING_HEADER) {
|
||||
return new UpdateView.UpdateViewHolder(new BasicHeaderView(context));
|
||||
} else {
|
||||
return new UpdateView.UpdateViewHolder(new SettingView(context));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Setting item, int viewType) {
|
||||
holder.getUpdateView().setObject(item, editable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(Setting item) {
|
||||
return VIEW_TYPE_SETTING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(UpdateView updateView, boolean checked) {
|
||||
if(updateView instanceof SettingView) {
|
||||
updateView.setChecked(checked);
|
||||
}
|
||||
}
|
||||
|
||||
public static SettingsAdapter getSettingsAdapter(Context context, User user, ImageLoader imageLoader, OnItemClickedListener<Setting> onItemClickedListener) {
|
||||
return getSettingsAdapter(context, user, imageLoader, true, onItemClickedListener);
|
||||
}
|
||||
public static SettingsAdapter getSettingsAdapter(Context context, User user, ImageLoader imageLoader, boolean isEditable, OnItemClickedListener<Setting> onItemClickedListener) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
List<List<User.Setting>> settingsSections = new ArrayList<>();
|
||||
settingsSections.add(user.getSettings());
|
||||
|
||||
if(user.getMusicFolderSettings() != null) {
|
||||
settingsSections.add(user.getMusicFolderSettings());
|
||||
}
|
||||
|
||||
return new SettingsAdapter(context, user, headers, settingsSections, imageLoader, isEditable, onItemClickedListener);
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
package github.nvllsvm.audinaut.audiofx;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.media.audiofx.LoudnessEnhancer;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
public class AudioEffectsController {
|
||||
private static final String TAG = AudioEffectsController.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private int audioSessionId = 0;
|
||||
|
||||
private boolean available = false;
|
||||
|
||||
private EqualizerController equalizerController;
|
||||
|
||||
public AudioEffectsController(Context context, int audioSessionId) {
|
||||
this.context = context;
|
||||
this.audioSessionId = audioSessionId;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
||||
available = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if(equalizerController != null) {
|
||||
equalizerController.release();
|
||||
}
|
||||
}
|
||||
|
||||
public EqualizerController getEqualizerController() {
|
||||
if (available && equalizerController == null) {
|
||||
equalizerController = new EqualizerController(context, audioSessionId);
|
||||
if (!equalizerController.isAvailable()) {
|
||||
equalizerController = null;
|
||||
} else {
|
||||
equalizerController.loadSettings();
|
||||
}
|
||||
}
|
||||
return equalizerController;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
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 2011 (C) Sindre Mehus
|
||||
*/
|
||||
package github.nvllsvm.audinaut.audiofx;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.audiofx.BassBoost;
|
||||
import android.media.audiofx.Equalizer;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
|
||||
/**
|
||||
* Backward-compatible wrapper for {@link Equalizer}, which is API Level 9.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class EqualizerController {
|
||||
|
||||
private static final String TAG = EqualizerController.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private Equalizer equalizer;
|
||||
private BassBoost bass;
|
||||
private boolean loudnessAvailable = false;
|
||||
private LoudnessEnhancerController loudnessEnhancerController;
|
||||
private boolean released = false;
|
||||
private int audioSessionId = 0;
|
||||
|
||||
public EqualizerController(Context context, int audioSessionId) {
|
||||
this.context = context;
|
||||
this.audioSessionId = audioSessionId;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
equalizer = new Equalizer(0, audioSessionId);
|
||||
bass = new BassBoost(0, audioSessionId);
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
loudnessAvailable = true;
|
||||
loudnessEnhancerController = new LoudnessEnhancerController(context, audioSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveSettings() {
|
||||
try {
|
||||
if (isAvailable()) {
|
||||
FileUtil.serialize(context, new EqualizerSettings(equalizer, bass, loudnessEnhancerController), "equalizer.dat");
|
||||
}
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to save equalizer settings.", x);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSettings() {
|
||||
try {
|
||||
if (isAvailable()) {
|
||||
EqualizerSettings settings = FileUtil.deserialize(context, "equalizer.dat", EqualizerSettings.class);
|
||||
if (settings != null) {
|
||||
settings.apply(equalizer, bass, loudnessEnhancerController);
|
||||
}
|
||||
}
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to load equalizer settings.", x);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return equalizer != null && bass != null;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
try {
|
||||
return isAvailable() && equalizer.getEnabled();
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (isAvailable()) {
|
||||
released = true;
|
||||
equalizer.release();
|
||||
bass.release();
|
||||
if(loudnessEnhancerController != null && loudnessEnhancerController.isAvailable()) {
|
||||
loudnessEnhancerController.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Equalizer getEqualizer() {
|
||||
if(released) {
|
||||
released = false;
|
||||
try {
|
||||
init();
|
||||
} catch (Throwable x) {
|
||||
equalizer = null;
|
||||
released = true;
|
||||
Log.w(TAG, "Failed to create equalizer.", x);
|
||||
}
|
||||
}
|
||||
return equalizer;
|
||||
}
|
||||
public BassBoost getBassBoost() {
|
||||
if(released) {
|
||||
released = false;
|
||||
try {
|
||||
init();
|
||||
} catch (Throwable x) {
|
||||
bass = null;
|
||||
Log.w(TAG, "Failed to create bass booster.", x);
|
||||
}
|
||||
}
|
||||
return bass;
|
||||
}
|
||||
public LoudnessEnhancerController getLoudnessEnhancerController() {
|
||||
if(loudnessAvailable && released) {
|
||||
released = false;
|
||||
try {
|
||||
init();
|
||||
} catch (Throwable x) {
|
||||
loudnessEnhancerController = null;
|
||||
Log.w(TAG, "Failed to create loudness enhancer.", x);
|
||||
}
|
||||
}
|
||||
return loudnessEnhancerController;
|
||||
}
|
||||
|
||||
private static class EqualizerSettings implements Serializable {
|
||||
|
||||
private short[] bandLevels;
|
||||
private short preset;
|
||||
private boolean enabled;
|
||||
private short bass;
|
||||
private int loudness;
|
||||
|
||||
public EqualizerSettings() {
|
||||
|
||||
}
|
||||
public EqualizerSettings(Equalizer equalizer, BassBoost boost, LoudnessEnhancerController loudnessEnhancerController) {
|
||||
enabled = equalizer.getEnabled();
|
||||
bandLevels = new short[equalizer.getNumberOfBands()];
|
||||
for (short i = 0; i < equalizer.getNumberOfBands(); i++) {
|
||||
bandLevels[i] = equalizer.getBandLevel(i);
|
||||
}
|
||||
try {
|
||||
preset = equalizer.getCurrentPreset();
|
||||
} catch (Exception x) {
|
||||
preset = -1;
|
||||
}
|
||||
try {
|
||||
bass = boost.getRoundedStrength();
|
||||
} catch(Exception e) {
|
||||
bass = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
loudness = (int) loudnessEnhancerController.getGain();
|
||||
} catch(Exception e) {
|
||||
loudness = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void apply(Equalizer equalizer, BassBoost boost, LoudnessEnhancerController loudnessController) {
|
||||
for (short i = 0; i < bandLevels.length; i++) {
|
||||
equalizer.setBandLevel(i, bandLevels[i]);
|
||||
}
|
||||
equalizer.setEnabled(enabled);
|
||||
if(bass != 0) {
|
||||
boost.setEnabled(true);
|
||||
boost.setStrength(bass);
|
||||
}
|
||||
if(loudness != 0) {
|
||||
loudnessController.enable();
|
||||
loudnessController.setGain(loudness);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
package github.nvllsvm.audinaut.audiofx;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.audiofx.LoudnessEnhancer;
|
||||
import android.util.Log;
|
||||
|
||||
public class LoudnessEnhancerController {
|
||||
private static final String TAG = LoudnessEnhancerController.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private LoudnessEnhancer enhancer;
|
||||
private boolean released = false;
|
||||
private int audioSessionId = 0;
|
||||
|
||||
public LoudnessEnhancerController(Context context, int audioSessionId) {
|
||||
this.context = context;
|
||||
try {
|
||||
this.audioSessionId = audioSessionId;
|
||||
enhancer = new LoudnessEnhancer(audioSessionId);
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to create enhancer", x);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return enhancer != null;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
try {
|
||||
return isAvailable() && enhancer.getEnabled();
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enhancer.setEnabled(true);
|
||||
}
|
||||
public void disable() {
|
||||
enhancer.setEnabled(false);
|
||||
}
|
||||
|
||||
public float getGain() {
|
||||
return enhancer.getTargetGain();
|
||||
}
|
||||
public void setGain(int gain) {
|
||||
enhancer.setTargetGain(gain);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (isAvailable()) {
|
||||
enhancer.release();
|
||||
released = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.Collator;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class Artist implements Serializable {
|
||||
private static final String TAG = Artist.class.getSimpleName();
|
||||
public static final String ROOT_ID = "-1";
|
||||
public static final String MISSING_ID = "-2";
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String index;
|
||||
private int closeness;
|
||||
|
||||
public Artist() {
|
||||
|
||||
}
|
||||
public Artist(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
public void setIndex(String index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getCloseness() {
|
||||
return closeness;
|
||||
}
|
||||
public void setCloseness(int closeness) {
|
||||
this.closeness = closeness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Artist entry = (Artist) o;
|
||||
return id.equals(entry.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static class ArtistComparator implements Comparator<Artist> {
|
||||
private String[] ignoredArticles;
|
||||
private Collator collator;
|
||||
|
||||
public ArtistComparator(String[] ignoredArticles) {
|
||||
this.ignoredArticles = ignoredArticles;
|
||||
this.collator = Collator.getInstance(Locale.US);
|
||||
this.collator.setStrength(Collator.PRIMARY);
|
||||
}
|
||||
|
||||
public int compare(Artist lhsArtist, Artist rhsArtist) {
|
||||
String lhs = lhsArtist.getName().toLowerCase();
|
||||
String rhs = rhsArtist.getName().toLowerCase();
|
||||
|
||||
for (String article : ignoredArticles) {
|
||||
int index = lhs.indexOf(article.toLowerCase() + " ");
|
||||
if (index == 0) {
|
||||
lhs = lhs.substring(article.length() + 1);
|
||||
}
|
||||
index = rhs.indexOf(article.toLowerCase() + " ");
|
||||
if (index == 0) {
|
||||
rhs = rhs.substring(article.length() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return collator.compare(lhs, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sort(List<Artist> artists, String[] ignoredArticles) {
|
||||
try {
|
||||
Collections.sort(artists, new ArtistComparator(ignoredArticles));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to sort artists", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
public class Genre implements Serializable {
|
||||
private String name;
|
||||
private String index;
|
||||
private Integer albumCount;
|
||||
private Integer songCount;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(String index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getAlbumCount() {
|
||||
return albumCount;
|
||||
}
|
||||
|
||||
public void setAlbumCount(Integer albumCount) {
|
||||
this.albumCount = albumCount;
|
||||
}
|
||||
|
||||
public Integer getSongCount() {
|
||||
return songCount;
|
||||
}
|
||||
|
||||
public void setSongCount(Integer songCount) {
|
||||
this.songCount = songCount;
|
||||
}
|
||||
|
||||
public static class GenreComparator implements Comparator<Genre> {
|
||||
@Override
|
||||
public int compare(Genre genre1, Genre genre2) {
|
||||
return genre1.getName().compareToIgnoreCase(genre2.getName());
|
||||
}
|
||||
|
||||
public static List<Genre> sort(List<Genre> genres) {
|
||||
Collections.sort(genres, new GenreComparator());
|
||||
return genres;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.Serializable;
|
||||
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class Indexes implements Serializable {
|
||||
|
||||
private long lastModified;
|
||||
private List<Artist> shortcuts;
|
||||
private List<Artist> artists;
|
||||
private List<MusicDirectory.Entry> entries;
|
||||
|
||||
public Indexes() {
|
||||
|
||||
}
|
||||
public Indexes(long lastModified, List<Artist> shortcuts, List<Artist> artists) {
|
||||
this.lastModified = lastModified;
|
||||
this.shortcuts = shortcuts;
|
||||
this.artists = artists;
|
||||
this.entries = new ArrayList<MusicDirectory.Entry>();
|
||||
}
|
||||
public Indexes(long lastModified, List<Artist> shortcuts, List<Artist> artists, List<MusicDirectory.Entry> entries) {
|
||||
this.lastModified = lastModified;
|
||||
this.shortcuts = shortcuts;
|
||||
this.artists = artists;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
public List<Artist> getShortcuts() {
|
||||
return shortcuts;
|
||||
}
|
||||
|
||||
public List<Artist> getArtists() {
|
||||
return artists;
|
||||
}
|
||||
|
||||
public void setArtists(List<Artist> artists) {
|
||||
this.shortcuts = new ArrayList<Artist>();
|
||||
this.artists.clear();
|
||||
this.artists.addAll(artists);
|
||||
}
|
||||
|
||||
public List<MusicDirectory.Entry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void sortChildren(Context context) {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
String ignoredArticlesString = prefs.getString(Constants.CACHE_KEY_IGNORE, "The El La Los Las Le Les");
|
||||
final String[] ignoredArticles = ignoredArticlesString.split(" ");
|
||||
|
||||
Artist.sort(shortcuts, ignoredArticles);
|
||||
Artist.sort(artists, ignoredArticles);
|
||||
}
|
||||
}
|
|
@ -1,628 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.UpdateHelper;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class MusicDirectory implements Serializable {
|
||||
private static final String TAG = MusicDirectory.class.getSimpleName();
|
||||
|
||||
private String name;
|
||||
private String id;
|
||||
private String parent;
|
||||
private List<Entry> children;
|
||||
|
||||
public MusicDirectory() {
|
||||
children = new ArrayList<Entry>();
|
||||
}
|
||||
public MusicDirectory(List<Entry> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(String parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void addChild(Entry child) {
|
||||
if(child != null) {
|
||||
children.add(child);
|
||||
}
|
||||
}
|
||||
public void addChildren(List<Entry> children) {
|
||||
this.children.addAll(children);
|
||||
}
|
||||
|
||||
public void replaceChildren(List<Entry> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public synchronized List<Entry> getChildren() {
|
||||
return getChildren(true, true);
|
||||
}
|
||||
|
||||
public synchronized List<Entry> getChildren(boolean includeDirs, boolean includeFiles) {
|
||||
if (includeDirs && includeFiles) {
|
||||
return children;
|
||||
}
|
||||
|
||||
List<Entry> result = new ArrayList<Entry>(children.size());
|
||||
for (Entry child : children) {
|
||||
if (child != null && child.isDirectory() && includeDirs || !child.isDirectory() && includeFiles) {
|
||||
result.add(child);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public synchronized List<Entry> getSongs() {
|
||||
List<Entry> result = new ArrayList<Entry>();
|
||||
for (Entry child : children) {
|
||||
if (child != null && !child.isDirectory()) {
|
||||
result.add(child);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized int getChildrenSize() {
|
||||
return children.size();
|
||||
}
|
||||
|
||||
public void shuffleChildren() {
|
||||
Collections.shuffle(this.children);
|
||||
}
|
||||
|
||||
public void sortChildren(Context context, int instance) {
|
||||
sortChildren(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CUSTOM_SORT_ENABLED, true));
|
||||
}
|
||||
public void sortChildren(boolean byYear) {
|
||||
EntryComparator.sort(children, byYear);
|
||||
}
|
||||
|
||||
public synchronized boolean updateMetadata(MusicDirectory refreshedDirectory) {
|
||||
boolean metadataUpdated = false;
|
||||
Iterator<Entry> it = children.iterator();
|
||||
while(it.hasNext()) {
|
||||
Entry entry = it.next();
|
||||
int index = refreshedDirectory.children.indexOf(entry);
|
||||
if(index != -1) {
|
||||
final Entry refreshed = refreshedDirectory.children.get(index);
|
||||
|
||||
entry.setTitle(refreshed.getTitle());
|
||||
entry.setAlbum(refreshed.getAlbum());
|
||||
entry.setArtist(refreshed.getArtist());
|
||||
entry.setTrack(refreshed.getTrack());
|
||||
entry.setYear(refreshed.getYear());
|
||||
entry.setGenre(refreshed.getGenre());
|
||||
entry.setTranscodedContentType(refreshed.getTranscodedContentType());
|
||||
entry.setTranscodedSuffix(refreshed.getTranscodedSuffix());
|
||||
entry.setDiscNumber(refreshed.getDiscNumber());
|
||||
entry.setType(refreshed.getType());
|
||||
if(!Util.equals(entry.getCoverArt(), refreshed.getCoverArt())) {
|
||||
metadataUpdated = true;
|
||||
entry.setCoverArt(refreshed.getCoverArt());
|
||||
}
|
||||
|
||||
new UpdateHelper.EntryInstanceUpdater(entry) {
|
||||
@Override
|
||||
public void update(Entry found) {
|
||||
found.setTitle(refreshed.getTitle());
|
||||
found.setAlbum(refreshed.getAlbum());
|
||||
found.setArtist(refreshed.getArtist());
|
||||
found.setTrack(refreshed.getTrack());
|
||||
found.setYear(refreshed.getYear());
|
||||
found.setGenre(refreshed.getGenre());
|
||||
found.setTranscodedContentType(refreshed.getTranscodedContentType());
|
||||
found.setTranscodedSuffix(refreshed.getTranscodedSuffix());
|
||||
found.setDiscNumber(refreshed.getDiscNumber());
|
||||
found.setType(refreshed.getType());
|
||||
if(!Util.equals(found.getCoverArt(), refreshed.getCoverArt())) {
|
||||
found.setCoverArt(refreshed.getCoverArt());
|
||||
metadataUpdate = DownloadService.METADATA_UPDATED_COVER_ART;
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
return metadataUpdated;
|
||||
}
|
||||
public synchronized boolean updateEntriesList(Context context, int instance, MusicDirectory refreshedDirectory) {
|
||||
boolean changed = false;
|
||||
Iterator<Entry> it = children.iterator();
|
||||
while(it.hasNext()) {
|
||||
Entry entry = it.next();
|
||||
// No longer exists in here
|
||||
if(refreshedDirectory.children.indexOf(entry) == -1) {
|
||||
it.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we contain all children from refreshed set
|
||||
boolean resort = false;
|
||||
for(Entry refreshed: refreshedDirectory.children) {
|
||||
if(!this.children.contains(refreshed)) {
|
||||
this.children.add(refreshed);
|
||||
resort = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(resort) {
|
||||
this.sortChildren(context, instance);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static class Entry implements Serializable {
|
||||
public static final int TYPE_SONG = 0;
|
||||
|
||||
private String id;
|
||||
private String parent;
|
||||
private String grandParent;
|
||||
private String albumId;
|
||||
private String artistId;
|
||||
private boolean directory;
|
||||
private String title;
|
||||
private String album;
|
||||
private String artist;
|
||||
private Integer track;
|
||||
private Integer year;
|
||||
private String genre;
|
||||
private String contentType;
|
||||
private String suffix;
|
||||
private String transcodedContentType;
|
||||
private String transcodedSuffix;
|
||||
private String coverArt;
|
||||
private Long size;
|
||||
private Integer duration;
|
||||
private Integer bitRate;
|
||||
private String path;
|
||||
private Integer discNumber;
|
||||
private int type = 0;
|
||||
private int closeness;
|
||||
private transient Artist linkedArtist;
|
||||
|
||||
public Entry() {
|
||||
|
||||
}
|
||||
public Entry(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
public Entry(Artist artist) {
|
||||
this.id = artist.getId();
|
||||
this.title = artist.getName();
|
||||
this.directory = true;
|
||||
this.linkedArtist = artist;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public void loadMetadata(File file) {
|
||||
try {
|
||||
MediaMetadataRetriever metadata = new MediaMetadataRetriever();
|
||||
metadata.setDataSource(file.getAbsolutePath());
|
||||
String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER);
|
||||
if(discNumber == null) {
|
||||
discNumber = "1/1";
|
||||
}
|
||||
int slashIndex = discNumber.indexOf("/");
|
||||
if(slashIndex > 0) {
|
||||
discNumber = discNumber.substring(0, slashIndex);
|
||||
}
|
||||
try {
|
||||
setDiscNumber(Integer.parseInt(discNumber));
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Non numbers in disc field!");
|
||||
}
|
||||
String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE);
|
||||
setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000);
|
||||
String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||
setDuration(Integer.parseInt(length) / 1000);
|
||||
String artist = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
|
||||
if(artist != null) {
|
||||
setArtist(artist);
|
||||
}
|
||||
String album = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
|
||||
if(album != null) {
|
||||
setAlbum(album);
|
||||
}
|
||||
metadata.release();
|
||||
} catch(Exception e) {
|
||||
Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver", e);
|
||||
}
|
||||
}
|
||||
public void rebaseTitleOffPath() {
|
||||
try {
|
||||
String filename = getPath();
|
||||
if(filename == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = filename.lastIndexOf('/');
|
||||
if (index != -1) {
|
||||
filename = filename.substring(index + 1);
|
||||
if (getTrack() != null) {
|
||||
filename = filename.replace(String.format("%02d ", getTrack()), "");
|
||||
}
|
||||
|
||||
index = filename.lastIndexOf('.');
|
||||
if(index != -1) {
|
||||
filename = filename.substring(0, index);
|
||||
}
|
||||
|
||||
setTitle(filename);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to update title based off of path", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(String parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public String getGrandParent() {
|
||||
return grandParent;
|
||||
}
|
||||
|
||||
public void setGrandParent(String grandParent) {
|
||||
this.grandParent = grandParent;
|
||||
}
|
||||
|
||||
public String getAlbumId() {
|
||||
return albumId;
|
||||
}
|
||||
|
||||
public void setAlbumId(String albumId) {
|
||||
this.albumId = albumId;
|
||||
}
|
||||
|
||||
public String getArtistId() {
|
||||
return artistId;
|
||||
}
|
||||
|
||||
public void setArtistId(String artistId) {
|
||||
this.artistId = artistId;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(boolean directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getAlbum() {
|
||||
return album;
|
||||
}
|
||||
|
||||
public boolean isAlbum() {
|
||||
return getParent() != null || getArtist() != null;
|
||||
}
|
||||
|
||||
public String getAlbumDisplay() {
|
||||
if(album != null && title.startsWith("Disc ")) {
|
||||
return album;
|
||||
} else {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
public void setAlbum(String album) {
|
||||
this.album = album;
|
||||
}
|
||||
|
||||
public String getArtist() {
|
||||
return artist;
|
||||
}
|
||||
|
||||
public void setArtist(String artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public Integer getTrack() {
|
||||
return track;
|
||||
}
|
||||
|
||||
public void setTrack(Integer track) {
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
public Integer getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(Integer year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public String getGenre() {
|
||||
return genre;
|
||||
}
|
||||
|
||||
public void setGenre(String genre) {
|
||||
this.genre = genre;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public void setSuffix(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public String getTranscodedContentType() {
|
||||
return transcodedContentType;
|
||||
}
|
||||
|
||||
public void setTranscodedContentType(String transcodedContentType) {
|
||||
this.transcodedContentType = transcodedContentType;
|
||||
}
|
||||
|
||||
public String getTranscodedSuffix() {
|
||||
return transcodedSuffix;
|
||||
}
|
||||
|
||||
public void setTranscodedSuffix(String transcodedSuffix) {
|
||||
this.transcodedSuffix = transcodedSuffix;
|
||||
}
|
||||
|
||||
public Long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public Integer getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(Integer duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public Integer getBitRate() {
|
||||
return bitRate;
|
||||
}
|
||||
|
||||
public void setBitRate(Integer bitRate) {
|
||||
this.bitRate = bitRate;
|
||||
}
|
||||
|
||||
public String getCoverArt() {
|
||||
return coverArt;
|
||||
}
|
||||
|
||||
public void setCoverArt(String coverArt) {
|
||||
this.coverArt = coverArt;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Integer getDiscNumber() {
|
||||
return discNumber;
|
||||
}
|
||||
|
||||
public void setDiscNumber(Integer discNumber) {
|
||||
this.discNumber = discNumber;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
public boolean isSong() {
|
||||
return type == TYPE_SONG;
|
||||
}
|
||||
|
||||
public int getCloseness() {
|
||||
return closeness;
|
||||
}
|
||||
|
||||
public void setCloseness(int closeness) {
|
||||
this.closeness = closeness;
|
||||
}
|
||||
|
||||
public boolean isOnlineId(Context context) {
|
||||
try {
|
||||
String cacheLocation = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
|
||||
return cacheLocation == null || id == null || id.indexOf(cacheLocation) == -1;
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to check online id validity");
|
||||
|
||||
// Err on the side of default functionality
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry entry = (Entry) o;
|
||||
return id.equals(entry.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EntryComparator implements Comparator<Entry> {
|
||||
private boolean byYear;
|
||||
private Collator collator;
|
||||
|
||||
public EntryComparator(boolean byYear) {
|
||||
this.byYear = byYear;
|
||||
this.collator = Collator.getInstance(Locale.US);
|
||||
this.collator.setStrength(Collator.PRIMARY);
|
||||
}
|
||||
|
||||
public int compare(Entry lhs, Entry rhs) {
|
||||
if(lhs.isDirectory() && !rhs.isDirectory()) {
|
||||
return -1;
|
||||
} else if(!lhs.isDirectory() && rhs.isDirectory()) {
|
||||
return 1;
|
||||
} else if(lhs.isDirectory() && rhs.isDirectory()) {
|
||||
if(byYear) {
|
||||
Integer lhsYear = lhs.getYear();
|
||||
Integer rhsYear = rhs.getYear();
|
||||
if(lhsYear != null && rhsYear != null) {
|
||||
return lhsYear.compareTo(rhsYear);
|
||||
} else if(lhsYear != null) {
|
||||
return -1;
|
||||
} else if(rhsYear != null) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return collator.compare(lhs.getAlbumDisplay(), rhs.getAlbumDisplay());
|
||||
}
|
||||
|
||||
Integer lhsDisc = lhs.getDiscNumber();
|
||||
Integer rhsDisc = rhs.getDiscNumber();
|
||||
|
||||
if(lhsDisc != null && rhsDisc != null) {
|
||||
if(lhsDisc < rhsDisc) {
|
||||
return -1;
|
||||
} else if(lhsDisc > rhsDisc) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Integer lhsTrack = lhs.getTrack();
|
||||
Integer rhsTrack = rhs.getTrack();
|
||||
if(lhsTrack != null && rhsTrack != null && lhsTrack != rhsTrack) {
|
||||
return lhsTrack.compareTo(rhsTrack);
|
||||
} else if(lhsTrack != null) {
|
||||
return -1;
|
||||
} else if(rhsTrack != null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return collator.compare(lhs.getTitle(), rhs.getTitle());
|
||||
}
|
||||
|
||||
public static void sort(List<Entry> entries) {
|
||||
sort(entries, true);
|
||||
}
|
||||
public static void sort(List<Entry> entries, boolean byYear) {
|
||||
try {
|
||||
Collections.sort(entries, new EntryComparator(byYear));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to sort MusicDirectory");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a top level directory in which music or other media is stored.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class MusicFolder implements Serializable {
|
||||
private static final String TAG = MusicFolder.class.getSimpleName();
|
||||
private String id;
|
||||
private String name;
|
||||
private boolean enabled;
|
||||
|
||||
public MusicFolder() {
|
||||
|
||||
}
|
||||
public MusicFolder(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public static class MusicFolderComparator implements Comparator<MusicFolder> {
|
||||
public int compare(MusicFolder lhsMusicFolder, MusicFolder rhsMusicFolder) {
|
||||
if(lhsMusicFolder == rhsMusicFolder || lhsMusicFolder.getName().equals(rhsMusicFolder.getName())) {
|
||||
return 0;
|
||||
} else {
|
||||
return lhsMusicFolder.getName().compareToIgnoreCase(rhsMusicFolder.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void sort(List<MusicFolder> musicFolders) {
|
||||
try {
|
||||
Collections.sort(musicFolders, new MusicFolderComparator());
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to sort music folders", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class PlayerQueue implements Serializable {
|
||||
public List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
|
||||
public List<MusicDirectory.Entry> toDelete = new ArrayList<MusicDirectory.Entry>();
|
||||
public int currentPlayingIndex;
|
||||
public int currentPlayingPosition;
|
||||
public boolean renameCurrent = false;
|
||||
public Date changed = null;
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.media.RemoteControlClient;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public enum PlayerState {
|
||||
IDLE(RemoteControlClient.PLAYSTATE_STOPPED),
|
||||
DOWNLOADING(RemoteControlClient.PLAYSTATE_BUFFERING),
|
||||
PREPARING(RemoteControlClient.PLAYSTATE_BUFFERING),
|
||||
PREPARED(RemoteControlClient.PLAYSTATE_STOPPED),
|
||||
STARTED(RemoteControlClient.PLAYSTATE_PLAYING),
|
||||
STOPPED(RemoteControlClient.PLAYSTATE_STOPPED),
|
||||
PAUSED(RemoteControlClient.PLAYSTATE_PAUSED),
|
||||
PAUSED_TEMP(RemoteControlClient.PLAYSTATE_PAUSED),
|
||||
COMPLETED(RemoteControlClient.PLAYSTATE_STOPPED);
|
||||
|
||||
private final int mRemoteControlClientPlayState;
|
||||
|
||||
private PlayerState(int playState) {
|
||||
mRemoteControlClientPlayState = playState;
|
||||
}
|
||||
|
||||
public int getRemoteControlClientPlayState() {
|
||||
return mRemoteControlClientPlayState;
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class Playlist implements Serializable {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String owner;
|
||||
private String comment;
|
||||
private String songCount;
|
||||
private Boolean pub;
|
||||
private Date created;
|
||||
private Date changed;
|
||||
private Integer duration;
|
||||
|
||||
public Playlist() {
|
||||
|
||||
}
|
||||
public Playlist(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
public Playlist(String id, String name, String owner, String comment, String songCount, String pub, String created, String changed, Integer duration) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.owner = (owner == null) ? "" : owner;
|
||||
this.comment = (comment == null) ? "" : comment;
|
||||
this.songCount = (songCount == null) ? "" : songCount;
|
||||
this.pub = (pub == null) ? null : (pub.equals("true"));
|
||||
setCreated(created);
|
||||
setChanged(changed);
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return this.comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getSongCount() {
|
||||
return this.songCount;
|
||||
}
|
||||
|
||||
public void setSongCount(String songCount) {
|
||||
this.songCount = songCount;
|
||||
}
|
||||
|
||||
public Boolean getPublic() {
|
||||
return this.pub;
|
||||
}
|
||||
public void setPublic(Boolean pub) {
|
||||
this.pub = pub;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(String created) {
|
||||
if (created != null) {
|
||||
try {
|
||||
this.created = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(created);
|
||||
} catch (ParseException e) {
|
||||
this.created = null;
|
||||
}
|
||||
} else {
|
||||
this.created = null;
|
||||
}
|
||||
}
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Date getChanged() {
|
||||
return changed;
|
||||
}
|
||||
public void setChanged(String changed) {
|
||||
if (changed != null) {
|
||||
try {
|
||||
this.changed = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(changed);
|
||||
} catch (ParseException e) {
|
||||
this.changed = null;
|
||||
}
|
||||
} else {
|
||||
this.changed = null;
|
||||
}
|
||||
}
|
||||
public void setChanged(Date changed) {
|
||||
this.changed = changed;
|
||||
}
|
||||
|
||||
public Integer getDuration() {
|
||||
return duration;
|
||||
}
|
||||
public void setDuration(Integer duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(o == this) {
|
||||
return true;
|
||||
} else if(o == null) {
|
||||
return false;
|
||||
} else if(o instanceof String) {
|
||||
return o.equals(this.id);
|
||||
} else if(o.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Playlist playlist = (Playlist) o;
|
||||
return playlist.id.equals(this.id);
|
||||
}
|
||||
|
||||
public static class PlaylistComparator implements Comparator<Playlist> {
|
||||
@Override
|
||||
public int compare(Playlist playlist1, Playlist playlist2) {
|
||||
return playlist1.getName().compareToIgnoreCase(playlist2.getName());
|
||||
}
|
||||
|
||||
public static List<Playlist> sort(List<Playlist> playlists) {
|
||||
Collections.sort(playlists, new PlaylistComparator());
|
||||
return playlists;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class RemoteStatus {
|
||||
|
||||
private Integer positionSeconds;
|
||||
private Integer currentPlayingIndex;
|
||||
private Float gain;
|
||||
private boolean playing;
|
||||
|
||||
public Integer getPositionSeconds() {
|
||||
return positionSeconds;
|
||||
}
|
||||
|
||||
public void setPositionSeconds(Integer positionSeconds) {
|
||||
this.positionSeconds = positionSeconds;
|
||||
}
|
||||
|
||||
public Integer getCurrentPlayingIndex() {
|
||||
return currentPlayingIndex;
|
||||
}
|
||||
|
||||
public void setCurrentIndex(Integer currentPlayingIndex) {
|
||||
this.currentPlayingIndex = currentPlayingIndex;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
public void setPlaying(boolean playing) {
|
||||
this.playing = playing;
|
||||
}
|
||||
|
||||
public Float getGain() {
|
||||
return gain;
|
||||
}
|
||||
|
||||
public void setGain(float gain) {
|
||||
this.gain = gain;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package github.nvllsvm.audinaut.domain;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public enum RepeatMode {
|
||||
OFF {
|
||||
@Override
|
||||
public RepeatMode next() {
|
||||
return ALL;
|
||||
}
|
||||
},
|
||||
ALL {
|
||||
@Override
|
||||
public RepeatMode next() {
|
||||
return SINGLE;
|
||||
}
|
||||
},
|
||||
SINGLE {
|
||||
@Override
|
||||
public RepeatMode next() {
|
||||
return OFF;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract RepeatMode next();
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The criteria for a music search.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class SearchCritera {
|
||||
|
||||
private final String query;
|
||||
private final int artistCount;
|
||||
private final int albumCount;
|
||||
private final int songCount;
|
||||
private Pattern pattern;
|
||||
|
||||
public SearchCritera(String query, int artistCount, int albumCount, int songCount) {
|
||||
this.query = query;
|
||||
this.artistCount = artistCount;
|
||||
this.albumCount = albumCount;
|
||||
this.songCount = songCount;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public int getArtistCount() {
|
||||
return artistCount;
|
||||
}
|
||||
|
||||
public int getAlbumCount() {
|
||||
return albumCount;
|
||||
}
|
||||
|
||||
public int getSongCount() {
|
||||
return songCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and caches a pattern instance that can be used to check if a
|
||||
* string matches the query.
|
||||
*/
|
||||
public Pattern getPattern() {
|
||||
|
||||
// If the pattern wasn't already cached, create a new regular expression
|
||||
// from the search string :
|
||||
// * Surround the search string with ".*" (match anything)
|
||||
// * Replace spaces and wildcard '*' characters with ".*"
|
||||
// * All other characters are properly quoted
|
||||
if (this.pattern == null) {
|
||||
String regex = ".*";
|
||||
String currentPart = "";
|
||||
for (int i = 0; i < query.length(); i++) {
|
||||
char c = query.charAt(i);
|
||||
if (c == '*' || c == ' ') {
|
||||
regex += Pattern.quote(currentPart);
|
||||
regex += ".*";
|
||||
currentPart = "";
|
||||
} else {
|
||||
currentPart += c;
|
||||
}
|
||||
}
|
||||
if (currentPart.length() > 0) {
|
||||
regex += Pattern.quote(currentPart);
|
||||
}
|
||||
|
||||
regex += ".*";
|
||||
this.pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
return this.pattern;
|
||||
}
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.domain;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class User implements Serializable {
|
||||
public static final String ADMIN = "adminRole";
|
||||
public static final String SETTINGS = "settingsRole";
|
||||
public static final String DOWNLOAD = "downloadRole";
|
||||
public static final String UPLOAD = "uploadRole";
|
||||
public static final String COVERART = "coverArtRole";
|
||||
public static final String COMMENT = "commentRole";
|
||||
public static final String STREAM = "streamRole";
|
||||
public static final List<String> ROLES = new ArrayList<>();
|
||||
|
||||
static {
|
||||
ROLES.add(ADMIN);
|
||||
ROLES.add(SETTINGS);
|
||||
ROLES.add(STREAM);
|
||||
ROLES.add(DOWNLOAD);
|
||||
ROLES.add(UPLOAD);
|
||||
ROLES.add(COVERART);
|
||||
ROLES.add(COMMENT);
|
||||
}
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
private String email;
|
||||
|
||||
private List<Setting> settings = new ArrayList<Setting>();
|
||||
private List<Setting> musicFolders;
|
||||
|
||||
public User() {
|
||||
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public List<Setting> getSettings() {
|
||||
return settings;
|
||||
}
|
||||
public void setSettings(List<Setting> settings) {
|
||||
this.settings.clear();
|
||||
this.settings.addAll(settings);
|
||||
}
|
||||
public void addSetting(String name, Boolean value) {
|
||||
settings.add(new Setting(name, value));
|
||||
}
|
||||
|
||||
public void addMusicFolder(MusicFolder musicFolder) {
|
||||
if(musicFolders == null) {
|
||||
musicFolders = new ArrayList<>();
|
||||
}
|
||||
|
||||
musicFolders.add(new MusicFolderSetting(musicFolder.getId(), musicFolder.getName(), false));
|
||||
}
|
||||
public void addMusicFolder(MusicFolderSetting musicFolderSetting, boolean defaultValue) {
|
||||
if(musicFolders == null) {
|
||||
musicFolders = new ArrayList<>();
|
||||
}
|
||||
|
||||
musicFolders.add(new MusicFolderSetting(musicFolderSetting.getName(), musicFolderSetting.getLabel(), defaultValue));
|
||||
}
|
||||
public List<Setting> getMusicFolderSettings() {
|
||||
return musicFolders;
|
||||
}
|
||||
|
||||
public static class Setting implements Serializable {
|
||||
private String name;
|
||||
private Boolean value;
|
||||
|
||||
public Setting() {
|
||||
|
||||
}
|
||||
public Setting(String name, Boolean value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public Boolean getValue() {
|
||||
return value;
|
||||
}
|
||||
public void setValue(Boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MusicFolderSetting extends Setting {
|
||||
private String label;
|
||||
|
||||
public MusicFolderSetting() {
|
||||
|
||||
}
|
||||
public MusicFolderSetting(String name, String label, Boolean value) {
|
||||
super(name, value);
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Represents the version number of the Subsonic Android app.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
* @version $Revision: 1.3 $ $Date: 2006/01/20 21:25:16 $
|
||||
*/
|
||||
public class Version implements Comparable<Version>, Serializable {
|
||||
private int major;
|
||||
private int minor;
|
||||
private int beta;
|
||||
private int bugfix;
|
||||
|
||||
public Version() {
|
||||
// For Kryo
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new version instance by parsing the given string.
|
||||
* @param version A string of the format "1.27", "1.27.2" or "1.27.beta3".
|
||||
*/
|
||||
public Version(String version) {
|
||||
String[] s = version.split("\\.");
|
||||
major = Integer.valueOf(s[0]);
|
||||
minor = Integer.valueOf(s[1]);
|
||||
|
||||
if (s.length > 2) {
|
||||
if (s[2].contains("beta")) {
|
||||
beta = Integer.valueOf(s[2].replace("beta", ""));
|
||||
} else {
|
||||
bugfix = Integer.valueOf(s[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
switch(major) {
|
||||
case 1:
|
||||
switch(minor) {
|
||||
case 0:
|
||||
return "3.8";
|
||||
case 1:
|
||||
return "3.9";
|
||||
case 2:
|
||||
return "4.0";
|
||||
case 3:
|
||||
return "4.1";
|
||||
case 4:
|
||||
return "4.2";
|
||||
case 5:
|
||||
return "4.3.1";
|
||||
case 6:
|
||||
return "4.5";
|
||||
case 7:
|
||||
return "4.6";
|
||||
case 8:
|
||||
return "4.7";
|
||||
case 9:
|
||||
return "4.8";
|
||||
case 10:
|
||||
return "4.9";
|
||||
case 11:
|
||||
return "5.1";
|
||||
case 12:
|
||||
return "5.2";
|
||||
case 13:
|
||||
return "5.3";
|
||||
case 14:
|
||||
return "6.0";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this object is equal to another.
|
||||
* @param o Object to compare to.
|
||||
* @return Whether this object is equals to another.
|
||||
*/
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
final Version version = (Version) o;
|
||||
|
||||
if (beta != version.beta) return false;
|
||||
if (bugfix != version.bugfix) return false;
|
||||
if (major != version.major) return false;
|
||||
return minor == version.minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code for this object.
|
||||
* @return A hash code for this object.
|
||||
*/
|
||||
public int hashCode() {
|
||||
int result;
|
||||
result = major;
|
||||
result = 29 * result + minor;
|
||||
result = 29 * result + beta;
|
||||
result = 29 * result + bugfix;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the form "1.27", "1.27.2" or "1.27.beta3".
|
||||
* @return A string representation of the form "1.27", "1.27.2" or "1.27.beta3".
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(major).append('.').append(minor);
|
||||
if (beta != 0) {
|
||||
buf.append(".beta").append(beta);
|
||||
} else if (bugfix != 0) {
|
||||
buf.append('.').append(bugfix);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this object with the specified object for order.
|
||||
* @param version The object to compare to.
|
||||
* @return A negative integer, zero, or a positive integer as this object is less than, equal to, or
|
||||
* greater than the specified object.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Version version) {
|
||||
if (major < version.major) {
|
||||
return -1;
|
||||
} else if (major > version.major) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (minor < version.minor) {
|
||||
return -1;
|
||||
} else if (minor > version.minor) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bugfix < version.bugfix) {
|
||||
return -1;
|
||||
} else if (bugfix > version.bugfix) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int thisBeta = beta == 0 ? Integer.MAX_VALUE : beta;
|
||||
int otherBeta = version.beta == 0 ? Integer.MAX_VALUE : version.beta;
|
||||
|
||||
if (thisBeta < otherBeta) {
|
||||
return -1;
|
||||
} else if (thisBeta > otherBeta) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.service.DownloadFile;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.util.DownloadFileItemHelperCallback;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.adapter.DownloadFileAdapter;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class DownloadFragment extends SelectRecyclerFragment<DownloadFile> implements SectionAdapter.OnItemClickedListener<DownloadFile> {
|
||||
private long currentRevision;
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
public DownloadFragment() {
|
||||
serialize = false;
|
||||
pullToRefresh = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
super.onCreateView(inflater, container, bundle);
|
||||
|
||||
ItemTouchHelper touchHelper = new ItemTouchHelper(new DownloadFileItemHelperCallback(this, false));
|
||||
touchHelper.attachToRecyclerView(recyclerView);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
final Handler handler = new Handler();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
update();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
executorService.scheduleWithFixedDelay(runnable, 0L, 1000L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOptionsMenu() {
|
||||
return R.menu.downloading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter getAdapter(List<DownloadFile> objs) {
|
||||
return new DownloadFileAdapter(context, objs, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DownloadFile> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
if(downloadService == null) {
|
||||
return new ArrayList<DownloadFile>();
|
||||
}
|
||||
|
||||
List<DownloadFile> songList = new ArrayList<DownloadFile>();
|
||||
songList.addAll(downloadService.getBackgroundDownloads());
|
||||
currentRevision = downloadService.getDownloadListUpdateRevision();
|
||||
return songList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleResource() {
|
||||
return R.string.button_bar_downloading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<DownloadFile> updateView, DownloadFile item) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<DownloadFile> updateView, DownloadFile downloadFile) {
|
||||
MusicDirectory.Entry selectedItem = downloadFile.getSong();
|
||||
onCreateContextMenuSupport(menu, menuInflater, updateView, selectedItem);
|
||||
if(!Util.isOffline(context)) {
|
||||
menu.removeItem(R.id.song_menu_remove_playlist);
|
||||
}
|
||||
|
||||
recreateContextMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<DownloadFile> updateView, DownloadFile downloadFile) {
|
||||
MusicDirectory.Entry selectedItem = downloadFile.getSong();
|
||||
return onContextItemSelected(menuItem, selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
if(super.onOptionsItemSelected(menuItem)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_remove_all:
|
||||
Util.confirmDialog(context, R.string.download_menu_remove_all, "", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new SilentBackgroundTask<Void>(context) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
getDownloadService().clearBackground();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
update();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
if (downloadService == null || objects == null || adapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRevision != downloadService.getDownloadListUpdateRevision()) {
|
||||
List<DownloadFile> downloadFileList = downloadService.getBackgroundDownloads();
|
||||
objects.clear();
|
||||
objects.addAll(downloadFileList);
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
currentRevision = downloadService.getDownloadListUpdateRevision();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,459 +0,0 @@
|
|||
/*
|
||||
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 2010 (C) Sindre Mehus
|
||||
*/
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.audiofx.BassBoost;
|
||||
import android.media.audiofx.Equalizer;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.audiofx.EqualizerController;
|
||||
import github.nvllsvm.audinaut.audiofx.LoudnessEnhancerController;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* Created by Scott on 10/27/13.
|
||||
*/
|
||||
public class EqualizerFragment extends SubsonicFragment {
|
||||
private static final String TAG = EqualizerFragment.class.getSimpleName();
|
||||
|
||||
private static final int MENU_GROUP_PRESET = 100;
|
||||
|
||||
private final Map<Short, SeekBar> bars = new HashMap<Short, SeekBar>();
|
||||
private SeekBar bassBar;
|
||||
private SeekBar loudnessBar;
|
||||
private EqualizerController equalizerController;
|
||||
private Equalizer equalizer;
|
||||
private BassBoost bass;
|
||||
private LoudnessEnhancerController loudnessEnhancer;
|
||||
private short masterLevel = 0;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
rootView = inflater.inflate(R.layout.equalizer, container, false);
|
||||
|
||||
try {
|
||||
DownloadService service = DownloadService.getInstance();
|
||||
equalizerController = service.getEqualizerController();
|
||||
equalizer = equalizerController.getEqualizer();
|
||||
bass = equalizerController.getBassBoost();
|
||||
loudnessEnhancer = equalizerController.getLoudnessEnhancerController();
|
||||
|
||||
initEqualizer();
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to initialize EQ", e);
|
||||
Util.toast(context, "Failed to initialize EQ");
|
||||
context.onBackPressed();
|
||||
}
|
||||
|
||||
final View presetButton = rootView.findViewById(R.id.equalizer_preset);
|
||||
registerForContextMenu(presetButton);
|
||||
presetButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
presetButton.showContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
CheckBox enabledCheckBox = (CheckBox) rootView.findViewById(R.id.equalizer_enabled);
|
||||
enabledCheckBox.setChecked(equalizer.getEnabled());
|
||||
enabledCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
try {
|
||||
setEqualizerEnabled(b);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to set EQ enabled", e);
|
||||
Util.toast(context, "Failed to set EQ enabled");
|
||||
context.onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTitle(R.string.equalizer_label);
|
||||
setSubtitle(null);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
try {
|
||||
equalizerController.saveSettings();
|
||||
|
||||
if (!equalizer.getEnabled()) {
|
||||
equalizerController.release();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to release controller", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
equalizerController = DownloadService.getInstance().getEqualizerController();
|
||||
equalizer = equalizerController.getEqualizer();
|
||||
bass = equalizerController.getBassBoost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
if(!primaryFragment) {
|
||||
return;
|
||||
}
|
||||
|
||||
short currentPreset;
|
||||
try {
|
||||
currentPreset = equalizer.getCurrentPreset();
|
||||
} catch (Exception x) {
|
||||
currentPreset = -1;
|
||||
}
|
||||
|
||||
for (short preset = 0; preset < equalizer.getNumberOfPresets(); preset++) {
|
||||
MenuItem menuItem = menu.add(MENU_GROUP_PRESET, preset, preset, equalizer.getPresetName(preset));
|
||||
if (preset == currentPreset) {
|
||||
menuItem.setChecked(true);
|
||||
}
|
||||
}
|
||||
menu.setGroupCheckable(MENU_GROUP_PRESET, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||
short preset = (short) menuItem.getItemId();
|
||||
for(int i = 0; i < 10; i++) {
|
||||
try {
|
||||
equalizer.usePreset(preset);
|
||||
i = 10;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
equalizerController.release();
|
||||
equalizer = equalizerController.getEqualizer();
|
||||
bass = equalizerController.getBassBoost();
|
||||
loudnessEnhancer = equalizerController.getLoudnessEnhancerController();
|
||||
}
|
||||
}
|
||||
updateBars(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setEqualizerEnabled(boolean enabled) {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(Constants.PREFERENCES_EQUALIZER_ON, enabled);
|
||||
editor.commit();
|
||||
for(int i = 0; i < 10; i++) {
|
||||
try {
|
||||
equalizer.setEnabled(enabled);
|
||||
updateBars(true);
|
||||
i = 10;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
equalizerController.release();
|
||||
equalizer = equalizerController.getEqualizer();
|
||||
bass = equalizerController.getBassBoost();
|
||||
loudnessEnhancer = equalizerController.getLoudnessEnhancerController();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBars(boolean changedEnabled) {
|
||||
try {
|
||||
boolean isEnabled = equalizer.getEnabled();
|
||||
short minEQLevel = equalizer.getBandLevelRange()[0];
|
||||
short maxEQLevel = equalizer.getBandLevelRange()[1];
|
||||
for (Map.Entry<Short, SeekBar> entry : bars.entrySet()) {
|
||||
short band = entry.getKey();
|
||||
SeekBar bar = entry.getValue();
|
||||
bar.setEnabled(isEnabled);
|
||||
if (band >= (short) 0) {
|
||||
short setLevel;
|
||||
if (changedEnabled) {
|
||||
setLevel = (short) (equalizer.getBandLevel(band) - masterLevel);
|
||||
if (isEnabled) {
|
||||
bar.setProgress(equalizer.getBandLevel(band) - minEQLevel);
|
||||
} else {
|
||||
bar.setProgress(-minEQLevel);
|
||||
}
|
||||
} else {
|
||||
bar.setProgress(equalizer.getBandLevel(band) - minEQLevel);
|
||||
setLevel = (short) (equalizer.getBandLevel(band) + masterLevel);
|
||||
}
|
||||
if (setLevel < minEQLevel) {
|
||||
setLevel = minEQLevel;
|
||||
} else if (setLevel > maxEQLevel) {
|
||||
setLevel = maxEQLevel;
|
||||
}
|
||||
equalizer.setBandLevel(band, setLevel);
|
||||
} else if (!isEnabled) {
|
||||
bar.setProgress(-minEQLevel);
|
||||
}
|
||||
}
|
||||
|
||||
bassBar.setEnabled(isEnabled);
|
||||
if (loudnessBar != null) {
|
||||
loudnessBar.setEnabled(isEnabled);
|
||||
}
|
||||
if (changedEnabled && !isEnabled) {
|
||||
bass.setStrength((short) 0);
|
||||
bassBar.setProgress(0);
|
||||
if (loudnessBar != null) {
|
||||
loudnessEnhancer.setGain(0);
|
||||
loudnessBar.setProgress(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEnabled) {
|
||||
masterLevel = 0;
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(Constants.PREFERENCES_EQUALIZER_SETTINGS, masterLevel);
|
||||
editor.commit();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to update bars");
|
||||
}
|
||||
}
|
||||
|
||||
private void initEqualizer() {
|
||||
LinearLayout layout = (LinearLayout) rootView.findViewById(R.id.equalizer_layout);
|
||||
|
||||
final short minEQLevel = equalizer.getBandLevelRange()[0];
|
||||
final short maxEQLevel = equalizer.getBandLevelRange()[1];
|
||||
|
||||
// Setup Pregain
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
masterLevel = (short)prefs.getInt(Constants.PREFERENCES_EQUALIZER_SETTINGS, 0);
|
||||
initPregain(layout, minEQLevel, maxEQLevel);
|
||||
|
||||
for (short i = 0; i < equalizer.getNumberOfBands(); i++) {
|
||||
final short band = i;
|
||||
|
||||
View bandBar = LayoutInflater.from(context).inflate(R.layout.equalizer_bar, null);
|
||||
TextView freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency);
|
||||
final TextView levelTextView = (TextView) bandBar.findViewById(R.id.equalizer_level);
|
||||
SeekBar bar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar);
|
||||
|
||||
freqTextView.setText((equalizer.getCenterFreq(band) / 1000) + " Hz");
|
||||
|
||||
bars.put(band, bar);
|
||||
bar.setMax(maxEQLevel - minEQLevel);
|
||||
short level = equalizer.getBandLevel(band);
|
||||
if(equalizer.getEnabled()) {
|
||||
level = (short) (level - masterLevel);
|
||||
}
|
||||
bar.setProgress(level - minEQLevel);
|
||||
bar.setEnabled(equalizer.getEnabled());
|
||||
updateLevelText(levelTextView, level);
|
||||
|
||||
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
try {
|
||||
short level = (short) (progress + minEQLevel);
|
||||
if (fromUser) {
|
||||
equalizer.setBandLevel(band, (short) (level + masterLevel));
|
||||
}
|
||||
updateLevelText(levelTextView, level);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to change equalizer", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
layout.addView(bandBar);
|
||||
}
|
||||
|
||||
LinearLayout specialLayout = (LinearLayout) rootView.findViewById(R.id.special_effects_layout);
|
||||
|
||||
// Setup bass booster
|
||||
View bandBar = LayoutInflater.from(context).inflate(R.layout.equalizer_bar, null);
|
||||
TextView freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency);
|
||||
final TextView bassTextView = (TextView) bandBar.findViewById(R.id.equalizer_level);
|
||||
bassBar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar);
|
||||
|
||||
freqTextView.setText(R.string.equalizer_bass_booster);
|
||||
bassBar.setEnabled(equalizer.getEnabled());
|
||||
short bassLevel = 0;
|
||||
if(bass.getEnabled()) {
|
||||
bassLevel = bass.getRoundedStrength();
|
||||
}
|
||||
bassTextView.setText(context.getResources().getString(R.string.equalizer_bass_size, bassLevel));
|
||||
bassBar.setMax(1000);
|
||||
bassBar.setProgress(bassLevel);
|
||||
bassBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
try {
|
||||
bassTextView.setText(context.getResources().getString(R.string.equalizer_bass_size, progress));
|
||||
if (fromUser) {
|
||||
if (progress > 0) {
|
||||
if (!bass.getEnabled()) {
|
||||
bass.setEnabled(true);
|
||||
}
|
||||
bass.setStrength((short) progress);
|
||||
} else if (progress == 0 && bass.getEnabled()) {
|
||||
bass.setStrength((short) progress);
|
||||
bass.setEnabled(false);
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Error on changing bass: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
});
|
||||
specialLayout.addView(bandBar);
|
||||
|
||||
if(loudnessEnhancer != null && loudnessEnhancer.isAvailable()) {
|
||||
// Setup loudness enhancer
|
||||
bandBar = LayoutInflater.from(context).inflate(R.layout.equalizer_bar, null);
|
||||
freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency);
|
||||
final TextView loudnessTextView = (TextView) bandBar.findViewById(R.id.equalizer_level);
|
||||
loudnessBar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar);
|
||||
|
||||
freqTextView.setText(R.string.equalizer_voice_booster);
|
||||
loudnessBar.setEnabled(equalizer.getEnabled());
|
||||
int loudnessLevel = 0;
|
||||
if(loudnessEnhancer.isEnabled()) {
|
||||
loudnessLevel = (int) loudnessEnhancer.getGain();
|
||||
}
|
||||
loudnessBar.setProgress(loudnessLevel / 100);
|
||||
loudnessTextView.setText(context.getResources().getString(R.string.equalizer_db_size, loudnessLevel / 100));
|
||||
loudnessBar.setMax(15);
|
||||
loudnessBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
try {
|
||||
loudnessTextView.setText(context.getResources().getString(R.string.equalizer_db_size, progress));
|
||||
if(fromUser) {
|
||||
if(progress > 0) {
|
||||
if(!loudnessEnhancer.isEnabled()) {
|
||||
loudnessEnhancer.enable();
|
||||
}
|
||||
loudnessEnhancer.setGain(progress * 100);
|
||||
} else if(progress == 0 && loudnessEnhancer.isEnabled()) {
|
||||
loudnessEnhancer.setGain(progress * 100);
|
||||
loudnessEnhancer.disable();
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Error on changing loudness: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
});
|
||||
specialLayout.addView(bandBar);
|
||||
}
|
||||
}
|
||||
|
||||
private void initPregain(LinearLayout layout, final short minEQLevel, final short maxEQLevel) {
|
||||
View bandBar = LayoutInflater.from(context).inflate(R.layout.equalizer_bar, null);
|
||||
TextView freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency);
|
||||
final TextView levelTextView = (TextView) bandBar.findViewById(R.id.equalizer_level);
|
||||
SeekBar bar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar);
|
||||
|
||||
freqTextView.setText("Master");
|
||||
|
||||
bars.put((short)-1, bar);
|
||||
bar.setMax(maxEQLevel - minEQLevel);
|
||||
bar.setProgress(masterLevel - minEQLevel);
|
||||
bar.setEnabled(equalizer.getEnabled());
|
||||
updateLevelText(levelTextView, masterLevel);
|
||||
|
||||
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
try {
|
||||
masterLevel = (short) (progress + minEQLevel);
|
||||
if (fromUser) {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(Constants.PREFERENCES_EQUALIZER_SETTINGS, masterLevel);
|
||||
editor.commit();
|
||||
for (short i = 0; i < equalizer.getNumberOfBands(); i++) {
|
||||
short level = (short) ((bars.get(i).getProgress() + minEQLevel) + masterLevel);
|
||||
equalizer.setBandLevel(i, level);
|
||||
}
|
||||
}
|
||||
updateLevelText(levelTextView, masterLevel);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to change equalizer", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
layout.addView(bandBar);
|
||||
}
|
||||
|
||||
private void updateLevelText(TextView levelTextView, short level) {
|
||||
levelTextView.setText((level > 0 ? "+" : "") + context.getResources().getString(R.string.equalizer_db_size, level / 100));
|
||||
}
|
||||
}
|
|
@ -1,357 +0,0 @@
|
|||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.StatFs;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.MainAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.EnvironmentVariables;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.LoadingTask;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.UserUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
public class MainFragment extends SelectRecyclerFragment<Integer> {
|
||||
private static final String TAG = MainFragment.class.getSimpleName();
|
||||
public static final String SONGS_LIST_PREFIX = "songs-";
|
||||
public static final String SONGS_NEWEST = SONGS_LIST_PREFIX + "newest";
|
||||
public static final String SONGS_TOP_PLAYED = SONGS_LIST_PREFIX + "topPlayed";
|
||||
public static final String SONGS_RECENT = SONGS_LIST_PREFIX + "recent";
|
||||
public static final String SONGS_FREQUENT = SONGS_LIST_PREFIX + "frequent";
|
||||
|
||||
public MainFragment() {
|
||||
super();
|
||||
pullToRefresh = false;
|
||||
serialize = false;
|
||||
backgroundUpdate = false;
|
||||
alwaysFullscreen = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.main, menu);
|
||||
onFinishSetupOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(super.onOptionsItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOptionsMenu() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter getAdapter(List objs) {
|
||||
List<List<Integer>> sections = new ArrayList<>();
|
||||
List<String> headers = new ArrayList<>();
|
||||
|
||||
List<Integer> albums = new ArrayList<>();
|
||||
albums.add(R.string.main_albums_random);
|
||||
albums.add(R.string.main_albums_alphabetical);
|
||||
albums.add(R.string.main_albums_genres);
|
||||
albums.add(R.string.main_albums_year);
|
||||
albums.add(R.string.main_albums_recent);
|
||||
albums.add(R.string.main_albums_frequent);
|
||||
|
||||
sections.add(albums);
|
||||
headers.add("albums");
|
||||
|
||||
return new MainAdapter(context, headers, sections, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
|
||||
return Arrays.asList(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleResource() {
|
||||
return R.string.common_appname;
|
||||
}
|
||||
|
||||
private void showAlbumList(String type) {
|
||||
if("genres".equals(type)) {
|
||||
SubsonicFragment fragment = new SelectGenreFragment();
|
||||
replaceFragment(fragment);
|
||||
} else if("years".equals(type)) {
|
||||
SubsonicFragment fragment = new SelectYearFragment();
|
||||
replaceFragment(fragment);
|
||||
} else {
|
||||
// Clear out recently added count when viewing
|
||||
if("newest".equals(type)) {
|
||||
SharedPreferences.Editor editor = Util.getPreferences(context).edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_RECENT_COUNT + Util.getActiveServer(context), 0);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
private void showAboutDialog() {
|
||||
new LoadingTask<Void>(context) {
|
||||
Long[] used;
|
||||
long bytesTotalFs;
|
||||
long bytesAvailableFs;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
File rootFolder = FileUtil.getMusicDirectory(context);
|
||||
StatFs stat = new StatFs(rootFolder.getPath());
|
||||
bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
||||
bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
||||
|
||||
used = FileUtil.getUsedSize(context, rootFolder);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
List<Integer> headers = new ArrayList<>();
|
||||
List<String> details = new ArrayList<>();
|
||||
|
||||
headers.add(R.string.details_author);
|
||||
details.add("Andrew Rabert");
|
||||
|
||||
headers.add(R.string.details_email);
|
||||
details.add("ar@nullsum.net");
|
||||
|
||||
try {
|
||||
headers.add(R.string.details_version);
|
||||
details.add(context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName);
|
||||
} catch(Exception e) {
|
||||
details.add("");
|
||||
}
|
||||
|
||||
Resources res = context.getResources();
|
||||
headers.add(R.string.details_files_cached);
|
||||
details.add(Long.toString(used[0]));
|
||||
|
||||
headers.add(R.string.details_files_permanent);
|
||||
details.add(Long.toString(used[1]));
|
||||
|
||||
headers.add(R.string.details_used_space);
|
||||
details.add(res.getString(R.string.details_of, Util.formatLocalizedBytes(used[2], context), Util.formatLocalizedBytes(Util.getCacheSizeMB(context) * 1024L * 1024L, context)));
|
||||
|
||||
headers.add(R.string.details_available_space);
|
||||
details.add(res.getString(R.string.details_of, Util.formatLocalizedBytes(bytesAvailableFs, context), Util.formatLocalizedBytes(bytesTotalFs, context)));
|
||||
|
||||
Util.showDetailsDialog(context, R.string.main_about_title, headers, details);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void rescanServer() {
|
||||
new LoadingTask<Void>(context, false) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
musicService.startRescan(context, this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void value) {
|
||||
Util.toast(context, R.string.main_scan_complete);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void getLogs() {
|
||||
try {
|
||||
final PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
new LoadingTask<String>(context) {
|
||||
@Override
|
||||
protected String doInBackground() throws Throwable {
|
||||
updateProgress("Gathering Logs");
|
||||
File logcat = new File(Environment.getExternalStorageDirectory(), "audinaut-logcat.txt");
|
||||
Util.delete(logcat);
|
||||
Process logcatProc = null;
|
||||
|
||||
try {
|
||||
List<String> progs = new ArrayList<String>();
|
||||
progs.add("logcat");
|
||||
progs.add("-v");
|
||||
progs.add("time");
|
||||
progs.add("-d");
|
||||
progs.add("-f");
|
||||
progs.add(logcat.getCanonicalPath());
|
||||
progs.add("*:I");
|
||||
|
||||
logcatProc = Runtime.getRuntime().exec(progs.toArray(new String[progs.size()]));
|
||||
logcatProc.waitFor();
|
||||
} finally {
|
||||
if(logcatProc != null) {
|
||||
logcatProc.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
URL url = new URL("https://pastebin.com/api/api_post.php");
|
||||
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
StringBuffer responseBuffer = new StringBuffer();
|
||||
try {
|
||||
urlConnection.setReadTimeout(10000);
|
||||
urlConnection.setConnectTimeout(15000);
|
||||
urlConnection.setRequestMethod("POST");
|
||||
urlConnection.setDoInput(true);
|
||||
urlConnection.setDoOutput(true);
|
||||
|
||||
OutputStream os = urlConnection.getOutputStream();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, Constants.UTF_8));
|
||||
writer.write("api_dev_key=" + URLEncoder.encode(EnvironmentVariables.PASTEBIN_DEV_KEY, Constants.UTF_8) + "&api_option=paste&api_paste_private=1&api_paste_code=");
|
||||
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(new FileInputStream(logcat)));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
writer.write(URLEncoder.encode(line + "\n", Constants.UTF_8));
|
||||
}
|
||||
} finally {
|
||||
Util.close(reader);
|
||||
}
|
||||
|
||||
File stacktrace = new File(Environment.getExternalStorageDirectory(), "audinaut-stacktrace.txt");
|
||||
if(stacktrace.exists() && stacktrace.isFile()) {
|
||||
writer.write("\n\nMost Recent Stacktrace:\n\n");
|
||||
|
||||
reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(new FileInputStream(stacktrace)));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
writer.write(URLEncoder.encode(line + "\n", Constants.UTF_8));
|
||||
}
|
||||
} finally {
|
||||
Util.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
writer.close();
|
||||
os.close();
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
responseBuffer.append(inputLine);
|
||||
}
|
||||
in.close();
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
|
||||
String response = responseBuffer.toString();
|
||||
if(response.indexOf("http") == 0) {
|
||||
return response.replace("http:", "https:");
|
||||
} else {
|
||||
throw new Exception("Pastebin Error: " + response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
Log.e(TAG, "Failed to gather logs", error);
|
||||
Util.toast(context, "Failed to gather logs");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(String logcat) {
|
||||
String footer = "Android SDK: " + Build.VERSION.SDK;
|
||||
footer += "\nDevice Model: " + Build.MODEL;
|
||||
footer += "\nDevice Name: " + Build.MANUFACTURER + " " + Build.PRODUCT;
|
||||
footer += "\nROM: " + Build.DISPLAY;
|
||||
footer += "\nLogs: " + logcat;
|
||||
footer += "\nBuild Number: " + packageInfo.versionCode;
|
||||
|
||||
Intent email = new Intent(Intent.ACTION_SENDTO,
|
||||
Uri.fromParts("mailto", "ar@nullsum.net", null));
|
||||
email.putExtra(Intent.EXTRA_SUBJECT, "Audinaut " + packageInfo.versionName + " Error Logs");
|
||||
email.putExtra(Intent.EXTRA_TEXT, "Describe the problem here\n\n\n" + footer);
|
||||
startActivity(email);
|
||||
}
|
||||
}.execute();
|
||||
} catch(Exception e) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<Integer> updateView, Integer item) {
|
||||
if (item == R.string.main_albums_newest) {
|
||||
showAlbumList("newest");
|
||||
} else if (item == R.string.main_albums_random) {
|
||||
showAlbumList("random");
|
||||
} else if (item == R.string.main_albums_recent) {
|
||||
showAlbumList("recent");
|
||||
} else if (item == R.string.main_albums_frequent) {
|
||||
showAlbumList("frequent");
|
||||
} else if(item == R.string.main_albums_genres) {
|
||||
showAlbumList("genres");
|
||||
} else if(item == R.string.main_albums_year) {
|
||||
showAlbumList("years");
|
||||
} else if(item == R.string.main_albums_alphabetical) {
|
||||
showAlbumList("alphabeticalByName");
|
||||
} else if (item == R.string.main_songs_newest) {
|
||||
showAlbumList(SONGS_NEWEST);
|
||||
} else if (item == R.string.main_songs_top_played) {
|
||||
showAlbumList(SONGS_TOP_PLAYED);
|
||||
} else if (item == R.string.main_songs_recent) {
|
||||
showAlbumList(SONGS_RECENT);
|
||||
} else if (item == R.string.main_songs_frequent) {
|
||||
showAlbumList(SONGS_FREQUENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Integer> updateView, Integer item) {}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Integer> updateView, Integer item) {
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,334 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
|
||||
public abstract class PreferenceCompatFragment extends SubsonicFragment {
|
||||
private static final String TAG = PreferenceCompatFragment.class.getSimpleName();
|
||||
private static final int FIRST_REQUEST_CODE = 100;
|
||||
private static final int MSG_BIND_PREFERENCES = 1;
|
||||
private static final String PREFERENCES_TAG = "android:preferences";
|
||||
private boolean mHavePrefs;
|
||||
private boolean mInitDone;
|
||||
private ListView mList;
|
||||
private PreferenceManager mPreferenceManager;
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
|
||||
case MSG_BIND_PREFERENCES:
|
||||
bindPreferences();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final private Runnable mRequestFocus = new Runnable() {
|
||||
public void run() {
|
||||
mList.focusableViewAvailable(mList);
|
||||
}
|
||||
};
|
||||
|
||||
private void bindPreferences() {
|
||||
PreferenceScreen localPreferenceScreen = getPreferenceScreen();
|
||||
if (localPreferenceScreen != null) {
|
||||
ListView localListView = getListView();
|
||||
localPreferenceScreen.bind(localListView);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureList() {
|
||||
if (mList == null) {
|
||||
View view = getView();
|
||||
if (view == null) {
|
||||
throw new IllegalStateException("Content view not yet created");
|
||||
}
|
||||
|
||||
View listView = view.findViewById(android.R.id.list);
|
||||
if (!(listView instanceof ListView)) {
|
||||
throw new RuntimeException("Content has view with id attribute 'android.R.id.list' that is not a ListView class");
|
||||
}
|
||||
|
||||
mList = (ListView)listView;
|
||||
if (mList == null) {
|
||||
throw new RuntimeException("Your content must have a ListView whose id attribute is 'android.R.id.list'");
|
||||
}
|
||||
|
||||
mHandler.post(mRequestFocus);
|
||||
}
|
||||
}
|
||||
|
||||
private void postBindPreferences() {
|
||||
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) {
|
||||
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void requirePreferenceManager() {
|
||||
if (this.mPreferenceManager == null) {
|
||||
throw new RuntimeException("This should be called after super.onCreate.");
|
||||
}
|
||||
}
|
||||
|
||||
public void addPreferencesFromIntent(Intent intent) {
|
||||
requirePreferenceManager();
|
||||
PreferenceScreen screen = inflateFromIntent(intent, getPreferenceScreen());
|
||||
setPreferenceScreen(screen);
|
||||
}
|
||||
|
||||
public PreferenceScreen addPreferencesFromResource(int resId) {
|
||||
requirePreferenceManager();
|
||||
PreferenceScreen screen = inflateFromResource(getActivity(), resId, getPreferenceScreen());
|
||||
setPreferenceScreen(screen);
|
||||
|
||||
for(int i = 0; i < screen.getPreferenceCount(); i++) {
|
||||
Preference preference = screen.getPreference(i);
|
||||
if(preference instanceof PreferenceScreen && preference.getKey() != null) {
|
||||
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
onStartNewFragment(preference.getKey());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
public Preference findPreference(CharSequence key) {
|
||||
if (mPreferenceManager == null) {
|
||||
return null;
|
||||
}
|
||||
return mPreferenceManager.findPreference(key);
|
||||
}
|
||||
|
||||
public ListView getListView() {
|
||||
ensureList();
|
||||
return mList;
|
||||
}
|
||||
|
||||
public PreferenceManager getPreferenceManager() {
|
||||
return mPreferenceManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
|
||||
if (mHavePrefs) {
|
||||
bindPreferences();
|
||||
}
|
||||
mInitDone = true;
|
||||
if (savedInstanceState != null) {
|
||||
Bundle localBundle = savedInstanceState.getBundle(PREFERENCES_TAG);
|
||||
if (localBundle != null) {
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
if (screen != null) {
|
||||
screen.restoreHierarchyState(localBundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
dispatchActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
mPreferenceManager = createPreferenceManager();
|
||||
|
||||
int res = this.getArguments().getInt(Constants.INTENT_EXTRA_FRAGMENT_TYPE, 0);
|
||||
if(res != 0) {
|
||||
PreferenceScreen preferenceScreen = addPreferencesFromResource(res);
|
||||
onInitPreferences(preferenceScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {
|
||||
return paramLayoutInflater.inflate(R.layout.preferences, paramViewGroup, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
dispatchActivityDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mList = null;
|
||||
mHandler.removeCallbacks(mRequestFocus);
|
||||
mHandler.removeMessages(MSG_BIND_PREFERENCES);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle bundle) {
|
||||
super.onSaveInstanceState(bundle);
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
if (screen != null) {
|
||||
Bundle localBundle = new Bundle();
|
||||
screen.saveHierarchyState(localBundle);
|
||||
bundle.putBundle(PREFERENCES_TAG, localBundle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
dispatchActivityStop();
|
||||
}
|
||||
|
||||
/** Access methods with visibility private **/
|
||||
|
||||
private PreferenceManager createPreferenceManager() {
|
||||
try {
|
||||
Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class);
|
||||
c.setAccessible(true);
|
||||
return c.newInstance(this.getActivity(), FIRST_REQUEST_CODE);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PreferenceScreen getPreferenceScreen() {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen");
|
||||
m.setAccessible(true);
|
||||
return (PreferenceScreen) m.invoke(mPreferenceManager);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setPreferenceScreen(PreferenceScreen preferenceScreen) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class);
|
||||
m.setAccessible(true);
|
||||
boolean result = (Boolean) m.invoke(mPreferenceManager, preferenceScreen);
|
||||
if (result && preferenceScreen != null) {
|
||||
mHavePrefs = true;
|
||||
if (mInitDone) {
|
||||
postBindPreferences();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class);
|
||||
m.setAccessible(true);
|
||||
m.invoke(mPreferenceManager, requestCode, resultCode, data);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchActivityDestroy() {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy");
|
||||
m.setAccessible(true);
|
||||
m.invoke(mPreferenceManager);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchActivityStop() {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop");
|
||||
m.setAccessible(true);
|
||||
m.invoke(mPreferenceManager);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setFragment(PreferenceFragment preferenceFragment) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("setFragment", PreferenceFragment.class);
|
||||
m.setAccessible(true);
|
||||
m.invoke(mPreferenceManager, preferenceFragment);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreferenceScreen inflateFromResource(Context context, int resId, PreferenceScreen rootPreferences) {
|
||||
PreferenceScreen preferenceScreen ;
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
|
||||
m.setAccessible(true);
|
||||
preferenceScreen = (PreferenceScreen) m.invoke(mPreferenceManager, context, resId, rootPreferences);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return preferenceScreen;
|
||||
}
|
||||
|
||||
public PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
|
||||
PreferenceScreen preferenceScreen ;
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class);
|
||||
m.setAccessible(true);
|
||||
preferenceScreen = (PreferenceScreen) m.invoke(mPreferenceManager, queryIntent, rootPreferences);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return preferenceScreen;
|
||||
}
|
||||
|
||||
protected abstract void onInitPreferences(PreferenceScreen preferenceScreen);
|
||||
protected abstract void onStartNewFragment(String name);
|
||||
}
|
|
@ -1,291 +0,0 @@
|
|||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.MenuItem;
|
||||
import android.net.Uri;
|
||||
import android.view.ViewGroup;
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.ArtistAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.EntryGridAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.SearchAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.SearchCritera;
|
||||
import github.nvllsvm.audinaut.domain.SearchResult;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.BackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.TabBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class SearchFragment extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<Serializable> {
|
||||
private static final String TAG = SearchFragment.class.getSimpleName();
|
||||
|
||||
private static final int MAX_ARTISTS = 20;
|
||||
private static final int MAX_ALBUMS = 20;
|
||||
private static final int MAX_SONGS = 50;
|
||||
private static final int MIN_CLOSENESS = 1;
|
||||
|
||||
protected RecyclerView recyclerView;
|
||||
protected SearchAdapter adapter;
|
||||
protected boolean largeAlbums = false;
|
||||
|
||||
private SearchResult searchResult;
|
||||
private boolean skipSearch = false;
|
||||
private String currentQuery;
|
||||
|
||||
public SearchFragment() {
|
||||
super();
|
||||
alwaysStartFullscreen = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
searchResult = (SearchResult) savedInstanceState.getSerializable(Constants.FRAGMENT_LIST);
|
||||
}
|
||||
largeAlbums = Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(Constants.FRAGMENT_LIST, searchResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
rootView = inflater.inflate(R.layout.abstract_recycler_fragment, container, false);
|
||||
setTitle(R.string.search_title);
|
||||
|
||||
refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout);
|
||||
refreshLayout.setEnabled(false);
|
||||
|
||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler);
|
||||
setupLayoutManager(recyclerView, largeAlbums);
|
||||
|
||||
registerForContextMenu(recyclerView);
|
||||
context.onNewIntent(context.getIntent());
|
||||
|
||||
if(searchResult != null) {
|
||||
skipSearch = true;
|
||||
recyclerView.setAdapter(adapter = new SearchAdapter(context, searchResult, getImageLoader(), largeAlbums, this));
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIsOnlyVisible(boolean isOnlyVisible) {
|
||||
boolean update = this.isOnlyVisible != isOnlyVisible;
|
||||
super.setIsOnlyVisible(isOnlyVisible);
|
||||
if(update && adapter != null) {
|
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
if(layoutManager instanceof GridLayoutManager) {
|
||||
((GridLayoutManager) layoutManager).setSpanCount(getRecyclerColumnCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
|
||||
return new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
int viewType = adapter.getItemViewType(position);
|
||||
if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == ArtistAdapter.VIEW_TYPE_ARTIST) {
|
||||
return gridLayoutManager.getSpanCount();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.search, menu);
|
||||
onFinishSetupOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Serializable> updateView, Serializable item) {
|
||||
onCreateContextMenuSupport(menu, menuInflater, updateView, item);
|
||||
if(item instanceof MusicDirectory.Entry && !Util.isOffline(context)) {
|
||||
menu.removeItem(R.id.song_menu_remove_playlist);
|
||||
}
|
||||
recreateContextMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Serializable> updateView, Serializable item) {
|
||||
return onContextItemSelected(menuItem, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(boolean refresh) {
|
||||
context.onNewIntent(context.getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<Serializable> updateView, Serializable item) {
|
||||
if (item instanceof Artist) {
|
||||
onArtistSelected((Artist) item, false);
|
||||
} else if (item instanceof MusicDirectory.Entry) {
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) item;
|
||||
if (entry.isDirectory()) {
|
||||
onAlbumSelected(entry, false);
|
||||
} else {
|
||||
onSongSelected(entry, false, true, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<MusicDirectory.Entry> getSelectedEntries() {
|
||||
List<Serializable> selected = adapter.getSelected();
|
||||
List<MusicDirectory.Entry> selectedMedia = new ArrayList<>();
|
||||
for(Serializable ser: selected) {
|
||||
if(ser instanceof MusicDirectory.Entry) {
|
||||
selectedMedia.add((MusicDirectory.Entry) ser);
|
||||
}
|
||||
}
|
||||
|
||||
return selectedMedia;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isShowArtistEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void search(final String query, final boolean autoplay) {
|
||||
if(skipSearch) {
|
||||
skipSearch = false;
|
||||
return;
|
||||
}
|
||||
currentQuery = query;
|
||||
|
||||
BackgroundTask<SearchResult> task = new TabBackgroundTask<SearchResult>(this) {
|
||||
@Override
|
||||
protected SearchResult doInBackground() throws Throwable {
|
||||
SearchCritera criteria = new SearchCritera(query, MAX_ARTISTS, MAX_ALBUMS, MAX_SONGS);
|
||||
MusicService service = MusicServiceFactory.getMusicService(context);
|
||||
return service.search(criteria, context, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(SearchResult result) {
|
||||
searchResult = result;
|
||||
recyclerView.setAdapter(adapter = new SearchAdapter(context, searchResult, getImageLoader(), largeAlbums, SearchFragment.this));
|
||||
if (autoplay) {
|
||||
autoplay(query);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
|
||||
if(searchItem != null) {
|
||||
MenuItemCompat.collapseActionView(searchItem);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getCurrentQuery() {
|
||||
return currentQuery;
|
||||
}
|
||||
|
||||
private void onArtistSelected(Artist artist, boolean autoplay) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
|
||||
if(autoplay) {
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
}
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
}
|
||||
|
||||
private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ID, album.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle());
|
||||
if(autoplay) {
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
}
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
}
|
||||
|
||||
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, false);
|
||||
if (autoplay) {
|
||||
downloadService.play(downloadService.size() - 1);
|
||||
}
|
||||
|
||||
Util.toast(context, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void autoplay(String query) {
|
||||
query = query.toLowerCase();
|
||||
|
||||
Artist artist = null;
|
||||
if(!searchResult.getArtists().isEmpty()) {
|
||||
artist = searchResult.getArtists().get(0);
|
||||
artist.setCloseness(Util.getStringDistance(artist.getName().toLowerCase(), query));
|
||||
}
|
||||
MusicDirectory.Entry album = null;
|
||||
if(!searchResult.getAlbums().isEmpty()) {
|
||||
album = searchResult.getAlbums().get(0);
|
||||
album.setCloseness(Util.getStringDistance(album.getTitle().toLowerCase(), query));
|
||||
}
|
||||
MusicDirectory.Entry song = null;
|
||||
if(!searchResult.getSongs().isEmpty()) {
|
||||
song = searchResult.getSongs().get(0);
|
||||
song.setCloseness(Util.getStringDistance(song.getTitle().toLowerCase(), query));
|
||||
}
|
||||
|
||||
if(artist != null && (artist.getCloseness() <= MIN_CLOSENESS ||
|
||||
(album == null || artist.getCloseness() <= album.getCloseness()) &&
|
||||
(song == null || artist.getCloseness() <= song.getCloseness()))) {
|
||||
onArtistSelected(artist, true);
|
||||
} else if(album != null && (album.getCloseness() <= MIN_CLOSENESS ||
|
||||
song == null || album.getCloseness() <= song.getCloseness())) {
|
||||
onAlbumSelected(album, true);
|
||||
} else if(song != null) {
|
||||
onSongSelected(song, false, false, true, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.ArtistAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.domain.Indexes;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
import github.nvllsvm.audinaut.domain.MusicFolder;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SelectArtistFragment extends SelectRecyclerFragment<Serializable> implements ArtistAdapter.OnMusicFolderChanged {
|
||||
private static final String TAG = SelectArtistFragment.class.getSimpleName();
|
||||
|
||||
private List<MusicFolder> musicFolders = null;
|
||||
private List<Entry> entries;
|
||||
private String groupId;
|
||||
private String groupName;
|
||||
|
||||
public SelectArtistFragment() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
if(bundle != null) {
|
||||
musicFolders = (List<MusicFolder>) bundle.getSerializable(Constants.FRAGMENT_LIST2);
|
||||
}
|
||||
artist = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(Constants.FRAGMENT_LIST2, (Serializable) musicFolders);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
Bundle args = getArguments();
|
||||
if(args != null) {
|
||||
if(args.getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false)) {
|
||||
groupId = args.getString(Constants.INTENT_EXTRA_NAME_ID);
|
||||
groupName = args.getString(Constants.INTENT_EXTRA_NAME_NAME);
|
||||
|
||||
if (groupName != null) {
|
||||
setTitle(groupName);
|
||||
context.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreateView(inflater, container, bundle);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Serializable> updateView, Serializable item) {
|
||||
onCreateContextMenuSupport(menu, menuInflater, updateView, item);
|
||||
recreateContextMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Serializable> updateView, Serializable item) {
|
||||
return onContextItemSelected(menuItem, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<Serializable> updateView, Serializable item) {
|
||||
SubsonicFragment fragment;
|
||||
if(item instanceof Artist) {
|
||||
Artist artist = (Artist) item;
|
||||
|
||||
if ((Util.isFirstLevelArtist(context) || Util.isOffline(context) || Util.isTagBrowsing(context)) || groupId != null) {
|
||||
fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
|
||||
|
||||
if (!Util.isOffline(context)) {
|
||||
args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, new Entry(artist));
|
||||
}
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
|
||||
|
||||
fragment.setArguments(args);
|
||||
} else {
|
||||
fragment = new SelectArtistFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
|
||||
if (!Util.isOffline(context)) {
|
||||
args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, new Entry(artist));
|
||||
}
|
||||
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
|
||||
replaceFragment(fragment);
|
||||
} else {
|
||||
Entry entry = (Entry) item;
|
||||
onSongPress(entries, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
super.onCreateOptionsMenu(menu, menuInflater);
|
||||
|
||||
if(Util.isOffline(context) || Util.isTagBrowsing(context) || groupId != null) {
|
||||
menu.removeItem(R.id.menu_first_level_artist);
|
||||
} else {
|
||||
if (Util.isFirstLevelArtist(context)) {
|
||||
menu.findItem(R.id.menu_first_level_artist).setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOptionsMenu() {
|
||||
return R.menu.select_artist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(super.onOptionsItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_first_level_artist:
|
||||
toggleFirstLevelArtist();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter getAdapter(List<Serializable> objects) {
|
||||
return new ArtistAdapter(context, objects, musicFolders, this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Serializable> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
|
||||
List<Serializable> items;
|
||||
if(groupId == null) {
|
||||
if (!Util.isOffline(context) && !Util.isTagBrowsing(context)) {
|
||||
musicFolders = musicService.getMusicFolders(refresh, context, listener);
|
||||
|
||||
// Hide folders option if there is only one
|
||||
if (musicFolders.size() == 1) {
|
||||
musicFolders = null;
|
||||
Util.setSelectedMusicFolderId(context, null);
|
||||
}
|
||||
} else {
|
||||
musicFolders = null;
|
||||
}
|
||||
String musicFolderId = Util.getSelectedMusicFolderId(context);
|
||||
|
||||
Indexes indexes = musicService.getIndexes(musicFolderId, refresh, context, listener);
|
||||
indexes.sortChildren(context);
|
||||
items = new ArrayList<>(indexes.getShortcuts().size() + indexes.getArtists().size());
|
||||
items.addAll(indexes.getShortcuts());
|
||||
items.addAll(indexes.getArtists());
|
||||
entries = indexes.getEntries();
|
||||
items.addAll(entries);
|
||||
} else {
|
||||
List<Artist> artists = new ArrayList<>();
|
||||
items = new ArrayList<>();
|
||||
MusicDirectory dir = musicService.getMusicDirectory(groupId, groupName, refresh, context, listener);
|
||||
for(Entry entry: dir.getChildren(true, false)) {
|
||||
Artist artist = new Artist();
|
||||
artist.setId(entry.getId());
|
||||
artist.setName(entry.getTitle());
|
||||
artists.add(artist);
|
||||
}
|
||||
|
||||
Indexes indexes = new Indexes(0, new ArrayList<Artist>(), artists);
|
||||
indexes.sortChildren(context);
|
||||
items.addAll(indexes.getArtists());
|
||||
|
||||
entries = dir.getChildren(false, true);
|
||||
for(Entry entry: entries) {
|
||||
items.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleResource() {
|
||||
return groupId == null ? R.string.button_bar_browse : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmpty(boolean empty) {
|
||||
super.setEmpty(empty);
|
||||
|
||||
if(empty && !Util.isOffline(context)) {
|
||||
objects.clear();
|
||||
recyclerView.setAdapter(new ArtistAdapter(context, objects, musicFolders, this, this));
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
|
||||
View view = rootView.findViewById(R.id.tab_progress);
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
|
||||
params.height = 0;
|
||||
params.weight = 5;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleFirstLevelArtist() {
|
||||
Util.toggleFirstLevelArtist(context);
|
||||
context.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicFolderChanged(MusicFolder selectedFolder) {
|
||||
String startMusicFolderId = Util.getSelectedMusicFolderId(context);
|
||||
String musicFolderId = selectedFolder == null ? null : selectedFolder.getId();
|
||||
|
||||
if(!Util.equals(startMusicFolderId, musicFolderId)) {
|
||||
Util.setSelectedMusicFolderId(context, musicFolderId);
|
||||
context.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,840 +0,0 @@
|
|||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.AlphabeticalAlbumAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.EntryInfiniteGridAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.EntryGridAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.service.CachedMusicService;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.DrawableTint;
|
||||
import github.nvllsvm.audinaut.util.ImageLoader;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.service.OfflineException;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.LoadingTask;
|
||||
import github.nvllsvm.audinaut.util.Pair;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.TabBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.UpdateHelper;
|
||||
import github.nvllsvm.audinaut.util.UserUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
import github.nvllsvm.audinaut.view.GridSpacingDecoration;
|
||||
import github.nvllsvm.audinaut.view.MyLeadingMarginSpan2;
|
||||
import github.nvllsvm.audinaut.view.RecyclingImageView;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
|
||||
public class SelectDirectoryFragment extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<Entry> {
|
||||
private static final String TAG = SelectDirectoryFragment.class.getSimpleName();
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private FastScroller fastScroller;
|
||||
private EntryGridAdapter entryGridAdapter;
|
||||
private List<Entry> albums;
|
||||
private List<Entry> entries;
|
||||
private LoadTask currentTask;
|
||||
|
||||
private SilentBackgroundTask updateCoverArtTask;
|
||||
private ImageView coverArtView;
|
||||
private Entry coverArtRep;
|
||||
private String coverArtId;
|
||||
|
||||
String id;
|
||||
String name;
|
||||
Entry directory;
|
||||
String playlistId;
|
||||
String playlistName;
|
||||
boolean playlistOwner;
|
||||
String albumListType;
|
||||
String albumListExtra;
|
||||
int albumListSize;
|
||||
boolean refreshListing = false;
|
||||
boolean restoredInstance = false;
|
||||
boolean lookupParent = false;
|
||||
boolean largeAlbums = false;
|
||||
boolean topTracks = false;
|
||||
String lookupEntry;
|
||||
|
||||
public SelectDirectoryFragment() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
if(bundle != null) {
|
||||
entries = (List<Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST);
|
||||
albums = (List<Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST2);
|
||||
if(albums == null) {
|
||||
albums = new ArrayList<>();
|
||||
}
|
||||
restoredInstance = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(Constants.FRAGMENT_LIST, (Serializable) entries);
|
||||
outState.putSerializable(Constants.FRAGMENT_LIST2, (Serializable) albums);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
Bundle args = getArguments();
|
||||
if(args != null) {
|
||||
id = args.getString(Constants.INTENT_EXTRA_NAME_ID);
|
||||
name = args.getString(Constants.INTENT_EXTRA_NAME_NAME);
|
||||
directory = (Entry) args.getSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY);
|
||||
playlistId = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
|
||||
playlistName = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME);
|
||||
playlistOwner = args.getBoolean(Constants.INTENT_EXTRA_NAME_PLAYLIST_OWNER, false);
|
||||
Object shareObj = args.getSerializable(Constants.INTENT_EXTRA_NAME_SHARE);
|
||||
albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE);
|
||||
albumListExtra = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA);
|
||||
albumListSize = args.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0);
|
||||
refreshListing = args.getBoolean(Constants.INTENT_EXTRA_REFRESH_LISTINGS);
|
||||
artist = args.getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false);
|
||||
lookupEntry = args.getString(Constants.INTENT_EXTRA_SEARCH_SONG);
|
||||
topTracks = args.getBoolean(Constants.INTENT_EXTRA_TOP_TRACKS);
|
||||
|
||||
String childId = args.getString(Constants.INTENT_EXTRA_NAME_CHILD_ID);
|
||||
if(childId != null) {
|
||||
id = childId;
|
||||
lookupParent = true;
|
||||
}
|
||||
if(entries == null) {
|
||||
entries = (List<Entry>) args.getSerializable(Constants.FRAGMENT_LIST);
|
||||
albums = (List<Entry>) args.getSerializable(Constants.FRAGMENT_LIST2);
|
||||
|
||||
if(albums == null) {
|
||||
albums = new ArrayList<Entry>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootView = inflater.inflate(R.layout.abstract_recycler_fragment, container, false);
|
||||
|
||||
refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout);
|
||||
refreshLayout.setOnRefreshListener(this);
|
||||
|
||||
if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true)) {
|
||||
largeAlbums = true;
|
||||
}
|
||||
|
||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
fastScroller = (FastScroller) rootView.findViewById(R.id.fragment_fast_scroller);
|
||||
setupScrollList(recyclerView);
|
||||
setupLayoutManager(recyclerView, largeAlbums);
|
||||
|
||||
if(entries == null) {
|
||||
if(primaryFragment || secondaryFragment) {
|
||||
load(false);
|
||||
} else {
|
||||
invalidated = true;
|
||||
}
|
||||
} else {
|
||||
finishLoading();
|
||||
}
|
||||
|
||||
if(name != null) {
|
||||
setTitle(name);
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIsOnlyVisible(boolean isOnlyVisible) {
|
||||
boolean update = this.isOnlyVisible != isOnlyVisible;
|
||||
super.setIsOnlyVisible(isOnlyVisible);
|
||||
if(update && entryGridAdapter != null) {
|
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
if(layoutManager instanceof GridLayoutManager) {
|
||||
((GridLayoutManager) layoutManager).setSpanCount(getRecyclerColumnCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
if(albumListType != null) {
|
||||
menuInflater.inflate(R.menu.select_album_list, menu);
|
||||
} else if(artist) {
|
||||
menuInflater.inflate(R.menu.select_album, menu);
|
||||
} else {
|
||||
if(Util.isOffline(context)) {
|
||||
menuInflater.inflate(R.menu.select_song_offline, menu);
|
||||
}
|
||||
else {
|
||||
menuInflater.inflate(R.menu.select_song, menu);
|
||||
|
||||
if(playlistId == null || !playlistOwner) {
|
||||
menu.removeItem(R.id.menu_remove_playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_remove_playlist:
|
||||
removeFromPlaylist(playlistId, playlistName, getSelectedIndexes());
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView updateView, Entry entry) {
|
||||
onCreateContextMenuSupport(menu, menuInflater, updateView, entry);
|
||||
if(!Util.isOffline(context) && (playlistId == null || !playlistOwner)) {
|
||||
menu.removeItem(R.id.song_menu_remove_playlist);
|
||||
}
|
||||
|
||||
recreateContextMenu(menu);
|
||||
}
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Entry> updateView, Entry entry) {
|
||||
if(onContextItemSelected(menuItem, entry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.song_menu_remove_playlist:
|
||||
removeFromPlaylist(playlistId, playlistName, Arrays.<Integer>asList(entries.indexOf(entry)));
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<Entry> updateView, Entry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle());
|
||||
args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, entry);
|
||||
if ("newest".equals(albumListType)) {
|
||||
args.putBoolean(Constants.INTENT_EXTRA_REFRESH_LISTINGS, true);
|
||||
}
|
||||
if(!entry.isAlbum()) {
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
|
||||
}
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment, true);
|
||||
} else {
|
||||
onSongPress(entries, entry, albumListType == null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refresh(boolean refresh) {
|
||||
load(refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isShowArtistEnabled() {
|
||||
return albumListType != null;
|
||||
}
|
||||
|
||||
private void load(boolean refresh) {
|
||||
if(refreshListing) {
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
if(currentTask != null) {
|
||||
currentTask.cancel();
|
||||
}
|
||||
|
||||
recyclerView.setVisibility(View.INVISIBLE);
|
||||
if (playlistId != null) {
|
||||
getPlaylist(playlistId, playlistName, refresh);
|
||||
} else if (albumListType != null) {
|
||||
getAlbumList(albumListType, albumListSize, refresh);
|
||||
} else {
|
||||
getMusicDirectory(id, name, refresh);
|
||||
}
|
||||
}
|
||||
|
||||
private void getMusicDirectory(final String id, final String name, final boolean refresh) {
|
||||
setTitle(name);
|
||||
|
||||
new LoadTask(refresh) {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
MusicDirectory dir = getMusicDirectory(id, name, refresh, service, this);
|
||||
|
||||
if(lookupParent && dir.getParent() != null) {
|
||||
dir = getMusicDirectory(dir.getParent(), name, refresh, service, this);
|
||||
|
||||
// Update the fragment pointers so other stuff works correctly
|
||||
SelectDirectoryFragment.this.id = dir.getId();
|
||||
SelectDirectoryFragment.this.name = dir.getName();
|
||||
} else if(id != null && directory == null && dir.getParent() != null && !artist) {
|
||||
MusicDirectory parentDir = getMusicDirectory(dir.getParent(), name, refresh, true, service, this);
|
||||
for(Entry child: parentDir.getChildren()) {
|
||||
if(id.equals(child.getId())) {
|
||||
directory = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Pair<MusicDirectory, Boolean> result) {
|
||||
SelectDirectoryFragment.this.name = result.getFirst().getName();
|
||||
setTitle(SelectDirectoryFragment.this.name);
|
||||
super.done(result);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void getRecursiveMusicDirectory(final String id, final String name, final boolean refresh) {
|
||||
setTitle(name);
|
||||
|
||||
new LoadTask(refresh) {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
MusicDirectory root = getMusicDirectory(id, name, refresh, service, this);
|
||||
List<Entry> songs = new ArrayList<Entry>();
|
||||
getSongsRecursively(root, songs);
|
||||
root.replaceChildren(songs);
|
||||
return root;
|
||||
}
|
||||
|
||||
private void getSongsRecursively(MusicDirectory parent, List<Entry> songs) throws Exception {
|
||||
songs.addAll(parent.getChildren(false, true));
|
||||
for (Entry dir : parent.getChildren(true, false)) {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
|
||||
MusicDirectory musicDirectory;
|
||||
if(Util.isTagBrowsing(context) && !Util.isOffline(context)) {
|
||||
musicDirectory = musicService.getAlbum(dir.getId(), dir.getTitle(), false, context, this);
|
||||
} else {
|
||||
musicDirectory = musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, context, this);
|
||||
}
|
||||
getSongsRecursively(musicDirectory, songs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Pair<MusicDirectory, Boolean> result) {
|
||||
SelectDirectoryFragment.this.name = result.getFirst().getName();
|
||||
setTitle(SelectDirectoryFragment.this.name);
|
||||
super.done(result);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void getPlaylist(final String playlistId, final String playlistName, final boolean refresh) {
|
||||
setTitle(playlistName);
|
||||
|
||||
new LoadTask(refresh) {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
return service.getPlaylist(refresh, playlistId, playlistName, context, this);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void getTopTracks(final String id, final String name, final boolean refresh) {
|
||||
setTitle(name);
|
||||
|
||||
new LoadTask(refresh) {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
return service.getTopTrackSongs(name, 50, context, this);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void getAlbumList(final String albumListType, final int size, final boolean refresh) {
|
||||
if ("newest".equals(albumListType)) {
|
||||
setTitle(R.string.main_albums_newest);
|
||||
} else if ("random".equals(albumListType)) {
|
||||
setTitle(R.string.main_albums_random);
|
||||
} else if ("recent".equals(albumListType)) {
|
||||
setTitle(R.string.main_albums_recent);
|
||||
} else if ("frequent".equals(albumListType)) {
|
||||
setTitle(R.string.main_albums_frequent);
|
||||
} else if("genres".equals(albumListType) || "years".equals(albumListType)) {
|
||||
setTitle(albumListExtra);
|
||||
} else if("alphabeticalByName".equals(albumListType)) {
|
||||
setTitle(R.string.main_albums_alphabetical);
|
||||
} if (MainFragment.SONGS_NEWEST.equals(albumListType)) {
|
||||
setTitle(R.string.main_songs_newest);
|
||||
} else if (MainFragment.SONGS_TOP_PLAYED.equals(albumListType)) {
|
||||
setTitle(R.string.main_songs_top_played);
|
||||
} else if (MainFragment.SONGS_RECENT.equals(albumListType)) {
|
||||
setTitle(R.string.main_songs_recent);
|
||||
} else if (MainFragment.SONGS_FREQUENT.equals(albumListType)) {
|
||||
setTitle(R.string.main_songs_frequent);
|
||||
}
|
||||
|
||||
new LoadTask(true) {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
MusicDirectory result;
|
||||
if("genres".equals(albumListType) || "years".equals(albumListType)) {
|
||||
result = service.getAlbumList(albumListType, albumListExtra, size, 0, refresh, context, this);
|
||||
if(result.getChildrenSize() == 0 && "genres".equals(albumListType)) {
|
||||
SelectDirectoryFragment.this.albumListType = "genres-songs";
|
||||
result = service.getSongsByGenre(albumListExtra, size, 0, context, this);
|
||||
}
|
||||
} else if("genres".equals(albumListType) || "genres-songs".equals(albumListType)) {
|
||||
result = service.getSongsByGenre(albumListExtra, size, 0, context, this);
|
||||
} else if(albumListType.indexOf(MainFragment.SONGS_LIST_PREFIX) != -1) {
|
||||
result = service.getSongList(albumListType, size, 0, context, this);
|
||||
} else {
|
||||
result = service.getAlbumList(albumListType, size, 0, refresh, context, this);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private abstract class LoadTask extends TabBackgroundTask<Pair<MusicDirectory, Boolean>> {
|
||||
private boolean refresh;
|
||||
|
||||
public LoadTask(boolean refresh) {
|
||||
super(SelectDirectoryFragment.this);
|
||||
this.refresh = refresh;
|
||||
|
||||
currentTask = this;
|
||||
}
|
||||
|
||||
protected abstract MusicDirectory load(MusicService service) throws Exception;
|
||||
|
||||
@Override
|
||||
protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
MusicDirectory dir = load(musicService);
|
||||
|
||||
albums = dir.getChildren(true, false);
|
||||
entries = dir.getChildren();
|
||||
|
||||
// This isn't really an artist if no albums on it!
|
||||
if(albums.size() == 0) {
|
||||
artist = false;
|
||||
}
|
||||
|
||||
return new Pair<>(dir, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Pair<MusicDirectory, Boolean> result) {
|
||||
finishLoading();
|
||||
currentTask = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCache(int changeCode) {
|
||||
if(entryGridAdapter != null && changeCode == CachedMusicService.CACHE_UPDATE_LIST) {
|
||||
entryGridAdapter.notifyDataSetChanged();
|
||||
} else if(changeCode == CachedMusicService.CACHE_UPDATE_METADATA) {
|
||||
if(coverArtView != null && coverArtRep != null && !Util.equals(coverArtRep.getCoverArt(), coverArtId)) {
|
||||
synchronized (coverArtRep) {
|
||||
if (updateCoverArtTask != null && updateCoverArtTask.isRunning()) {
|
||||
updateCoverArtTask.cancel();
|
||||
}
|
||||
updateCoverArtTask = getImageLoader().loadImage(coverArtView, coverArtRep, false, true);
|
||||
coverArtId = coverArtRep.getCoverArt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter<Entry> getCurrentAdapter() {
|
||||
return entryGridAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
|
||||
return new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
int viewType = entryGridAdapter.getItemViewType(position);
|
||||
if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == EntryInfiniteGridAdapter.VIEW_TYPE_LOADING) {
|
||||
return gridLayoutManager.getSpanCount();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void finishLoading() {
|
||||
boolean validData = !entries.isEmpty() || !albums.isEmpty();
|
||||
if(!validData) {
|
||||
setEmpty(true);
|
||||
}
|
||||
|
||||
if(validData) {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if(albumListType == null) {
|
||||
entryGridAdapter = new EntryGridAdapter(context, entries, getImageLoader(), largeAlbums);
|
||||
entryGridAdapter.setRemoveFromPlaylist(playlistId != null);
|
||||
} else {
|
||||
if("alphabeticalByName".equals(albumListType)) {
|
||||
entryGridAdapter = new AlphabeticalAlbumAdapter(context, entries, getImageLoader(), largeAlbums);
|
||||
} else {
|
||||
entryGridAdapter = new EntryInfiniteGridAdapter(context, entries, getImageLoader(), largeAlbums);
|
||||
}
|
||||
|
||||
// Setup infinite loading based on scrolling
|
||||
final EntryInfiniteGridAdapter infiniteGridAdapter = (EntryInfiniteGridAdapter) entryGridAdapter;
|
||||
infiniteGridAdapter.setData(albumListType, albumListExtra, albumListSize);
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
|
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
int totalItemCount = layoutManager.getItemCount();
|
||||
int lastVisibleItem;
|
||||
if(layoutManager instanceof GridLayoutManager) {
|
||||
lastVisibleItem = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
|
||||
} else if(layoutManager instanceof LinearLayoutManager) {
|
||||
lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if(totalItemCount > 0 && lastVisibleItem >= totalItemCount - 2) {
|
||||
infiniteGridAdapter.loadMore();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
entryGridAdapter.setOnItemClickedListener(this);
|
||||
// Always show artist if this is not a artist we are viewing
|
||||
if(!artist) {
|
||||
entryGridAdapter.setShowArtist(true);
|
||||
}
|
||||
if(topTracks) {
|
||||
entryGridAdapter.setShowAlbum(true);
|
||||
}
|
||||
|
||||
int scrollToPosition = -1;
|
||||
if(lookupEntry != null) {
|
||||
for(int i = 0; i < entries.size(); i++) {
|
||||
if(lookupEntry.equals(entries.get(i).getTitle())) {
|
||||
scrollToPosition = i;
|
||||
entryGridAdapter.addSelected(entries.get(i));
|
||||
lookupEntry = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recyclerView.setAdapter(entryGridAdapter);
|
||||
fastScroller.attachRecyclerView(recyclerView);
|
||||
context.supportInvalidateOptionsMenu();
|
||||
|
||||
if(scrollToPosition != -1) {
|
||||
recyclerView.scrollToPosition(scrollToPosition);
|
||||
}
|
||||
|
||||
Bundle args = getArguments();
|
||||
boolean playAll = args.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
|
||||
if (playAll && !restoredInstance) {
|
||||
playAll(args.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void playNow(final boolean shuffle, final boolean append, final boolean playNext) {
|
||||
List<Entry> songs = getSelectedEntries();
|
||||
if(!songs.isEmpty()) {
|
||||
download(songs, append, false, !append, playNext, shuffle);
|
||||
entryGridAdapter.clearSelected();
|
||||
} else {
|
||||
playAll(shuffle, append, playNext);
|
||||
}
|
||||
}
|
||||
private void playAll(final boolean shuffle, final boolean append, final boolean playNext) {
|
||||
boolean hasSubFolders = albums != null && !albums.isEmpty();
|
||||
|
||||
if (hasSubFolders && id != null) {
|
||||
downloadRecursively(id, false, append, !append, shuffle, false, playNext);
|
||||
} else if(hasSubFolders && albumListType != null) {
|
||||
downloadRecursively(albums, shuffle, append, playNext);
|
||||
} else {
|
||||
download(entries, append, false, !append, playNext, shuffle);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> getSelectedIndexes() {
|
||||
List<Entry> selected = entryGridAdapter.getSelected();
|
||||
List<Integer> indexes = new ArrayList<Integer>();
|
||||
|
||||
for(Entry entry: selected) {
|
||||
indexes.add(entries.indexOf(entry));
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void downloadBackground(final boolean save) {
|
||||
List<Entry> songs = getSelectedEntries();
|
||||
if(playlistId != null) {
|
||||
songs = entries;
|
||||
}
|
||||
|
||||
if(songs.isEmpty()) {
|
||||
// Get both songs and albums
|
||||
downloadRecursively(id, save, false, false, false, true);
|
||||
} else {
|
||||
downloadBackground(save, songs);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void downloadBackground(final boolean save, final List<Entry> entries) {
|
||||
if (getDownloadService() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
warnIfStorageUnavailable();
|
||||
RecursiveLoader onValid = new RecursiveLoader(context) {
|
||||
@Override
|
||||
protected Boolean doInBackground() throws Throwable {
|
||||
getSongsRecursively(entries, true);
|
||||
getDownloadService().downloadBackground(songs, save);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Boolean result) {
|
||||
Util.toast(context, context.getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void download(List<Entry> entries, boolean append, boolean save, boolean autoplay, boolean playNext, boolean shuffle) {
|
||||
download(entries, append, save, autoplay, playNext, shuffle, playlistName, playlistId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void delete() {
|
||||
List<Entry> songs = getSelectedEntries();
|
||||
if(songs.isEmpty()) {
|
||||
for(Entry entry: entries) {
|
||||
if(entry.isDirectory()) {
|
||||
deleteRecursively(entry);
|
||||
} else {
|
||||
songs.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getDownloadService() != null) {
|
||||
getDownloadService().delete(songs);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromPlaylist(final String id, final String name, final List<Integer> indexes) {
|
||||
new LoadingTask<Void>(context, true) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
musicService.removeFromPlaylist(id, indexes, context, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
for(Integer index: indexes) {
|
||||
entryGridAdapter.removeAt(index);
|
||||
}
|
||||
Util.toast(context, context.getResources().getString(R.string.removed_playlist, indexes.size(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
String msg;
|
||||
if (error instanceof OfflineException) {
|
||||
msg = getErrorMessage(error);
|
||||
} else {
|
||||
msg = context.getResources().getString(R.string.updated_playlist_error, name) + " " + getErrorMessage(error);
|
||||
}
|
||||
|
||||
Util.toast(context, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void showTopTracks() {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle(getArguments());
|
||||
args.putBoolean(Constants.INTENT_EXTRA_TOP_TRACKS, true);
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment, true);
|
||||
}
|
||||
|
||||
private View createHeader() {
|
||||
View header = LayoutInflater.from(context).inflate(R.layout.select_album_header, null, false);
|
||||
|
||||
setupCoverArt(header);
|
||||
setupTextDisplay(header);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private void setupCoverArt(View header) {
|
||||
setupCoverArtImpl((RecyclingImageView) header.findViewById(R.id.select_album_art));
|
||||
}
|
||||
private void setupCoverArtImpl(RecyclingImageView coverArtView) {
|
||||
final ImageLoader imageLoader = getImageLoader();
|
||||
|
||||
if(entries.size() > 0) {
|
||||
coverArtRep = null;
|
||||
this.coverArtView = coverArtView;
|
||||
for (int i = 0; (i < 3) && (coverArtRep == null || coverArtRep.getCoverArt() == null); i++) {
|
||||
coverArtRep = entries.get(random.nextInt(entries.size()));
|
||||
}
|
||||
|
||||
coverArtView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (coverArtRep == null || coverArtRep.getCoverArt() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
ImageView fullScreenView = new ImageView(context);
|
||||
imageLoader.loadImage(fullScreenView, coverArtRep, true, true);
|
||||
builder.setCancelable(true);
|
||||
|
||||
AlertDialog imageDialog = builder.create();
|
||||
// Set view here with unecessary 0's to remove top/bottom border
|
||||
imageDialog.setView(fullScreenView, 0, 0, 0, 0);
|
||||
imageDialog.show();
|
||||
}
|
||||
});
|
||||
synchronized (coverArtRep) {
|
||||
coverArtId = coverArtRep.getCoverArt();
|
||||
updateCoverArtTask = imageLoader.loadImage(coverArtView, coverArtRep, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
coverArtView.setOnInvalidated(new RecyclingImageView.OnInvalidated() {
|
||||
@Override
|
||||
public void onInvalidated(RecyclingImageView imageView) {
|
||||
setupCoverArtImpl(imageView);
|
||||
}
|
||||
});
|
||||
}
|
||||
private void setupTextDisplay(final View header) {
|
||||
final TextView titleView = (TextView) header.findViewById(R.id.select_album_title);
|
||||
if(playlistName != null) {
|
||||
titleView.setText(playlistName);
|
||||
} else if(name != null) {
|
||||
titleView.setText(name);
|
||||
}
|
||||
|
||||
int songCount = 0;
|
||||
|
||||
Set<String> artists = new HashSet<String>();
|
||||
Set<Integer> years = new HashSet<Integer>();
|
||||
Integer totalDuration = 0;
|
||||
for (Entry entry : entries) {
|
||||
if (!entry.isDirectory()) {
|
||||
songCount++;
|
||||
if (entry.getArtist() != null) {
|
||||
artists.add(entry.getArtist());
|
||||
}
|
||||
if(entry.getYear() != null) {
|
||||
years.add(entry.getYear());
|
||||
}
|
||||
Integer duration = entry.getDuration();
|
||||
if(duration != null) {
|
||||
totalDuration += duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final TextView artistView = (TextView) header.findViewById(R.id.select_album_artist);
|
||||
if (artists.size() == 1) {
|
||||
String artistText = artists.iterator().next();
|
||||
if(years.size() == 1) {
|
||||
artistText += " - " + years.iterator().next();
|
||||
}
|
||||
artistView.setText(artistText);
|
||||
artistView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
artistView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
TextView songCountView = (TextView) header.findViewById(R.id.select_album_song_count);
|
||||
TextView songLengthView = (TextView) header.findViewById(R.id.select_album_song_length);
|
||||
String s = context.getResources().getQuantityString(R.plurals.select_album_n_songs, songCount, songCount);
|
||||
songCountView.setText(s.toUpperCase());
|
||||
songLengthView.setText(Util.formatDuration(totalDuration));
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.domain.Genre;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.adapter.GenreAdapter;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SelectGenreFragment extends SelectRecyclerFragment<Genre> {
|
||||
private static final String TAG = SelectGenreFragment.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public int getOptionsMenu() {
|
||||
return R.menu.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter getAdapter(List<Genre> objs) {
|
||||
return new GenreAdapter(context, objs, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Genre> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
|
||||
return musicService.getGenres(refresh, context, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleResource() {
|
||||
return R.string.main_albums_genres;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<Genre> updateView, Genre genre) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, "genres");
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA, genre.getName());
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Genre> updateView, Genre item) {}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Genre> updateView, Genre item) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.Playlist;
|
||||
import github.nvllsvm.audinaut.service.DownloadFile;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.service.OfflineException;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.SyncUtil;
|
||||
import github.nvllsvm.audinaut.util.CacheCleaner;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.LoadingTask;
|
||||
import github.nvllsvm.audinaut.util.UserUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.adapter.PlaylistAdapter;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class SelectPlaylistFragment extends SelectRecyclerFragment<Playlist> {
|
||||
private static final String TAG = SelectPlaylistFragment.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
if (Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true)) {
|
||||
largeAlbums = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Playlist> updateView, Playlist playlist) {
|
||||
if (Util.isOffline(context)) {
|
||||
menuInflater.inflate(R.menu.select_playlist_context_offline, menu);
|
||||
}
|
||||
else {
|
||||
menuInflater.inflate(R.menu.select_playlist_context, menu);
|
||||
|
||||
if(playlist.getPublic() != null && playlist.getPublic() == true && playlist.getId().indexOf(".m3u") == -1 && !UserUtil.getCurrentUsername(context).equals(playlist.getOwner())) {
|
||||
menu.removeItem(R.id.playlist_update_info);
|
||||
menu.removeItem(R.id.playlist_menu_delete);
|
||||
}
|
||||
}
|
||||
|
||||
recreateContextMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Playlist> updateView, Playlist playlist) {
|
||||
SubsonicFragment fragment;
|
||||
Bundle args;
|
||||
FragmentTransaction trans;
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.playlist_menu_download:
|
||||
downloadPlaylist(playlist.getId(), playlist.getName(), false, true, false, false, true);
|
||||
break;
|
||||
case R.id.playlist_menu_play_now:
|
||||
fragment = new SelectDirectoryFragment();
|
||||
args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
break;
|
||||
case R.id.playlist_menu_play_shuffled:
|
||||
fragment = new SelectDirectoryFragment();
|
||||
args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
break;
|
||||
case R.id.playlist_menu_delete:
|
||||
deletePlaylist(playlist);
|
||||
break;
|
||||
case R.id.playlist_info:
|
||||
displayPlaylistInfo(playlist);
|
||||
break;
|
||||
case R.id.playlist_update_info:
|
||||
updatePlaylistInfo(playlist);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOptionsMenu() {
|
||||
return R.menu.abstract_top_menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter<Playlist> getAdapter(List<Playlist> playlists) {
|
||||
List<Playlist> mine = new ArrayList<>();
|
||||
|
||||
String currentUsername = UserUtil.getCurrentUsername(context);
|
||||
for(Playlist playlist: playlists) {
|
||||
if(playlist.getOwner() == null || playlist.getOwner().equals(currentUsername)) {
|
||||
mine.add(playlist);
|
||||
}
|
||||
}
|
||||
|
||||
return new PlaylistAdapter(context, playlists, getImageLoader(), largeAlbums, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Playlist> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
|
||||
List<Playlist> playlists = musicService.getPlaylists(refresh, context, listener);
|
||||
if(!Util.isOffline(context) && refresh) {
|
||||
new CacheCleaner(context, getDownloadService()).cleanPlaylists(playlists);
|
||||
}
|
||||
return playlists;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleResource() {
|
||||
return R.string.playlist_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<Playlist> updateView, Playlist playlist) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
if((playlist.getOwner() != null && playlist.getOwner().equals(UserUtil.getCurrentUsername(context)) || playlist.getId().indexOf(".m3u") != -1)) {
|
||||
args.putBoolean(Constants.INTENT_EXTRA_NAME_PLAYLIST_OWNER, true);
|
||||
}
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishRefresh() {
|
||||
Bundle args = getArguments();
|
||||
if(args != null) {
|
||||
String playlistId = args.getString(Constants.INTENT_EXTRA_NAME_ID, null);
|
||||
if (playlistId != null && objects != null) {
|
||||
for (Playlist playlist : objects) {
|
||||
if (playlistId.equals(playlist.getId())) {
|
||||
onItemClicked(null, playlist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deletePlaylist(final Playlist playlist) {
|
||||
Util.confirmDialog(context, R.string.common_delete, playlist.getName(), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new LoadingTask<Void>(context, false) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
musicService.deletePlaylist(playlist.getId(), context, null);
|
||||
SyncUtil.removeSyncedPlaylist(context, playlist.getId());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
adapter.removeItem(playlist);
|
||||
Util.toast(context, context.getResources().getString(R.string.menu_deleted_playlist, playlist.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
String msg;
|
||||
if (error instanceof OfflineException) {
|
||||
msg = getErrorMessage(error);
|
||||
} else {
|
||||
msg = context.getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()) + " " + getErrorMessage(error);
|
||||
}
|
||||
|
||||
Util.toast(context, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayPlaylistInfo(final Playlist playlist) {
|
||||
List<Integer> headers = new ArrayList<>();
|
||||
List<String> details = new ArrayList<>();
|
||||
|
||||
headers.add(R.string.details_title);
|
||||
details.add(playlist.getName());
|
||||
|
||||
if(playlist.getOwner() != null) {
|
||||
headers.add(R.string.details_owner);
|
||||
details.add(playlist.getOwner());
|
||||
}
|
||||
|
||||
if(playlist.getComment() != null) {
|
||||
headers.add(R.string.details_comments);
|
||||
details.add(playlist.getComment());
|
||||
}
|
||||
|
||||
headers.add(R.string.details_song_count);
|
||||
details.add(playlist.getSongCount());
|
||||
|
||||
if(playlist.getDuration() != null) {
|
||||
headers.add(R.string.details_length);
|
||||
details.add(Util.formatDuration(playlist.getDuration()));
|
||||
}
|
||||
|
||||
if(playlist.getPublic() != null) {
|
||||
headers.add(R.string.details_public);
|
||||
details.add(Util.formatBoolean(context, playlist.getPublic()));
|
||||
}
|
||||
|
||||
if(playlist.getCreated() != null) {
|
||||
headers.add(R.string.details_created);
|
||||
details.add(Util.formatDate(playlist.getCreated()));
|
||||
}
|
||||
if(playlist.getChanged() != null) {
|
||||
headers.add(R.string.details_updated);
|
||||
details.add(Util.formatDate(playlist.getChanged()));
|
||||
}
|
||||
|
||||
Util.showDetailsDialog(context, R.string.details_title_playlist, headers, details);
|
||||
}
|
||||
|
||||
private void updatePlaylistInfo(final Playlist playlist) {
|
||||
View dialogView = context.getLayoutInflater().inflate(R.layout.update_playlist, null);
|
||||
final EditText nameBox = (EditText)dialogView.findViewById(R.id.get_playlist_name);
|
||||
final EditText commentBox = (EditText)dialogView.findViewById(R.id.get_playlist_comment);
|
||||
final CheckBox publicBox = (CheckBox)dialogView.findViewById(R.id.get_playlist_public);
|
||||
|
||||
nameBox.setText(playlist.getName());
|
||||
commentBox.setText(playlist.getComment());
|
||||
Boolean pub = playlist.getPublic();
|
||||
if(pub == null) {
|
||||
publicBox.setEnabled(false);
|
||||
} else {
|
||||
publicBox.setChecked(pub);
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.playlist_update_info)
|
||||
.setView(dialogView)
|
||||
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new LoadingTask<Void>(context, false) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
String name = nameBox.getText().toString();
|
||||
String comment = commentBox.getText().toString();
|
||||
boolean isPublic = publicBox.isChecked();
|
||||
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
musicService.updatePlaylist(playlist.getId(), name, comment, isPublic, context, null);
|
||||
|
||||
playlist.setName(name);
|
||||
playlist.setComment(comment);
|
||||
playlist.setPublic(isPublic);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
Util.toast(context, context.getResources().getString(R.string.playlist_updated_info, playlist.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
String msg;
|
||||
if (error instanceof OfflineException) {
|
||||
msg = getErrorMessage(error);
|
||||
} else {
|
||||
msg = context.getResources().getString(R.string.playlist_updated_info_error, playlist.getName()) + " " + getErrorMessage(error);
|
||||
}
|
||||
|
||||
Util.toast(context, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
})
|
||||
.setNegativeButton(R.string.common_cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void syncPlaylist(Playlist playlist) {
|
||||
SyncUtil.addSyncedPlaylist(context, playlist.getId());
|
||||
downloadPlaylist(playlist.getId(), playlist.getName(), true, true, false, false, true);
|
||||
}
|
||||
|
||||
private void stopSyncPlaylist(final Playlist playlist) {
|
||||
SyncUtil.removeSyncedPlaylist(context, playlist.getId());
|
||||
|
||||
new LoadingTask<Void>(context, false) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
// Unpin all of the songs in playlist
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
MusicDirectory root = musicService.getPlaylist(true, playlist.getId(), playlist.getName(), context, this);
|
||||
for(MusicDirectory.Entry entry: root.getChildren()) {
|
||||
DownloadFile file = new DownloadFile(context, entry, false);
|
||||
file.unpin();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.app.SearchableInfo;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.TabBackgroundTask;
|
||||
import github.nvllsvm.audinaut.view.FastScroller;
|
||||
|
||||
public abstract class SelectRecyclerFragment<T> extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<T> {
|
||||
private static final String TAG = SelectRecyclerFragment.class.getSimpleName();
|
||||
protected RecyclerView recyclerView;
|
||||
protected FastScroller fastScroller;
|
||||
protected SectionAdapter<T> adapter;
|
||||
protected UpdateTask currentTask;
|
||||
protected List<T> objects;
|
||||
protected boolean serialize = true;
|
||||
protected boolean largeAlbums = false;
|
||||
protected boolean pullToRefresh = true;
|
||||
protected boolean backgroundUpdate = true;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
if(bundle != null && serialize) {
|
||||
objects = (List<T>) bundle.getSerializable(Constants.FRAGMENT_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if(serialize) {
|
||||
outState.putSerializable(Constants.FRAGMENT_LIST, (Serializable) objects);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
rootView = inflater.inflate(R.layout.abstract_recycler_fragment, container, false);
|
||||
|
||||
refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout);
|
||||
refreshLayout.setOnRefreshListener(this);
|
||||
|
||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler);
|
||||
fastScroller = (FastScroller) rootView.findViewById(R.id.fragment_fast_scroller);
|
||||
setupLayoutManager();
|
||||
|
||||
if(pullToRefresh) {
|
||||
setupScrollList(recyclerView);
|
||||
} else {
|
||||
refreshLayout.setEnabled(false);
|
||||
}
|
||||
|
||||
if(objects == null) {
|
||||
refresh(false);
|
||||
} else {
|
||||
recyclerView.setAdapter(adapter = getAdapter(objects));
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
if(!primaryFragment) {
|
||||
return;
|
||||
}
|
||||
|
||||
menuInflater.inflate(getOptionsMenu(), menu);
|
||||
onFinishSetupOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIsOnlyVisible(boolean isOnlyVisible) {
|
||||
boolean update = this.isOnlyVisible != isOnlyVisible;
|
||||
super.setIsOnlyVisible(isOnlyVisible);
|
||||
if(update && adapter != null) {
|
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
if(layoutManager instanceof GridLayoutManager) {
|
||||
((GridLayoutManager) layoutManager).setSpanCount(getRecyclerColumnCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refresh(final boolean refresh) {
|
||||
int titleRes = getTitleResource();
|
||||
if(titleRes != 0) {
|
||||
setTitle(getTitleResource());
|
||||
}
|
||||
if(backgroundUpdate) {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Cancel current running task before starting another one
|
||||
if(currentTask != null) {
|
||||
currentTask.cancel();
|
||||
}
|
||||
|
||||
currentTask = new UpdateTask(this, refresh);
|
||||
|
||||
if(backgroundUpdate) {
|
||||
currentTask.execute();
|
||||
} else {
|
||||
objects = new ArrayList<T>();
|
||||
|
||||
try {
|
||||
objects = getObjects(null, refresh, null);
|
||||
} catch (Exception x) {
|
||||
Log.e(TAG, "Failed to load", x);
|
||||
}
|
||||
|
||||
currentTask.done(objects);
|
||||
}
|
||||
}
|
||||
|
||||
public SectionAdapter getCurrentAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
private void setupLayoutManager() {
|
||||
setupLayoutManager(recyclerView, largeAlbums);
|
||||
}
|
||||
|
||||
public abstract int getOptionsMenu();
|
||||
public abstract SectionAdapter<T> getAdapter(List<T> objs);
|
||||
public abstract List<T> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception;
|
||||
public abstract int getTitleResource();
|
||||
|
||||
public void onFinishRefresh() {
|
||||
|
||||
}
|
||||
|
||||
private class UpdateTask extends TabBackgroundTask<List<T>> {
|
||||
private boolean refresh;
|
||||
|
||||
public UpdateTask(SubsonicFragment fragment, boolean refresh) {
|
||||
super(fragment);
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> doInBackground() throws Exception {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
|
||||
objects = new ArrayList<T>();
|
||||
|
||||
try {
|
||||
objects = getObjects(musicService, refresh, this);
|
||||
} catch (Exception x) {
|
||||
Log.e(TAG, "Failed to load", x);
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done(List<T> result) {
|
||||
if (result != null && !result.isEmpty()) {
|
||||
recyclerView.setAdapter(adapter = getAdapter(result));
|
||||
if(!fastScroller.isAttached()) {
|
||||
fastScroller.attachRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
onFinishRefresh();
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
setEmpty(true);
|
||||
}
|
||||
|
||||
currentTask = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.adapter.BasicListAdapter;
|
||||
import github.nvllsvm.audinaut.adapter.SectionAdapter;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.view.UpdateView;
|
||||
|
||||
public class SelectYearFragment extends SelectRecyclerFragment<String> {
|
||||
|
||||
public SelectYearFragment() {
|
||||
super();
|
||||
pullToRefresh = false;
|
||||
serialize = false;
|
||||
backgroundUpdate = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOptionsMenu() {
|
||||
return R.menu.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionAdapter getAdapter(List<String> objs) {
|
||||
return new BasicListAdapter(context, objs, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
|
||||
List<String> decades = new ArrayList<>();
|
||||
for(int i = 2010; i >= 1800; i -= 10) {
|
||||
decades.add(String.valueOf(i));
|
||||
}
|
||||
|
||||
return decades;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleResource() {
|
||||
return R.string.main_albums_year;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(UpdateView<String> updateView, String decade) {
|
||||
SubsonicFragment fragment = new SelectDirectoryFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, "years");
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
|
||||
args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA, decade);
|
||||
fragment.setArguments(args);
|
||||
|
||||
replaceFragment(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<String> updateView, String item) {}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem, UpdateView<String> updateView, String item) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,806 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.fragments;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.service.HeadphoneListenerService;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.LoadingTask;
|
||||
import github.nvllsvm.audinaut.util.SyncUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.view.CacheLocationPreference;
|
||||
import github.nvllsvm.audinaut.view.ErrorDialog;
|
||||
|
||||
public class SettingsFragment extends PreferenceCompatFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private final static String TAG = SettingsFragment.class.getSimpleName();
|
||||
|
||||
private final Map<String, ServerSettings> serverSettings = new LinkedHashMap<String, ServerSettings>();
|
||||
private boolean testingConnection;
|
||||
private ListPreference theme;
|
||||
private ListPreference maxBitrateWifi;
|
||||
private ListPreference maxBitrateMobile;
|
||||
private ListPreference networkTimeout;
|
||||
private CacheLocationPreference cacheLocation;
|
||||
private ListPreference preloadCountWifi;
|
||||
private ListPreference preloadCountMobile;
|
||||
private ListPreference keepPlayedCount;
|
||||
private ListPreference tempLoss;
|
||||
private ListPreference pauseDisconnect;
|
||||
private Preference addServerPreference;
|
||||
private PreferenceCategory serversCategory;
|
||||
private ListPreference songPressAction;
|
||||
private ListPreference syncInterval;
|
||||
private CheckBoxPreference syncEnabled;
|
||||
private CheckBoxPreference syncWifi;
|
||||
private CheckBoxPreference syncNotification;
|
||||
private CheckBoxPreference syncMostRecent;
|
||||
private CheckBoxPreference replayGain;
|
||||
private ListPreference replayGainType;
|
||||
private Preference replayGainBump;
|
||||
private Preference replayGainUntagged;
|
||||
private String internalSSID;
|
||||
private String internalSSIDDisplay;
|
||||
private EditTextPreference cacheSize;
|
||||
|
||||
private int serverCount = 3;
|
||||
private SharedPreferences settings;
|
||||
private DecimalFormat megabyteFromat;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
int instance = this.getArguments().getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1);
|
||||
if (instance != -1) {
|
||||
PreferenceScreen preferenceScreen = expandServer(instance);
|
||||
setPreferenceScreen(preferenceScreen);
|
||||
|
||||
serverSettings.put(Integer.toString(instance), new ServerSettings(instance));
|
||||
onInitPreferences(preferenceScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartNewFragment(String name) {
|
||||
SettingsFragment newFragment = new SettingsFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
int xml = 0;
|
||||
if("appearance".equals(name)) {
|
||||
xml = R.xml.settings_appearance;
|
||||
} else if("cache".equals(name)) {
|
||||
xml = R.xml.settings_cache;
|
||||
} else if("playback".equals(name)) {
|
||||
xml = R.xml.settings_playback;
|
||||
} else if("servers".equals(name)) {
|
||||
xml = R.xml.settings_servers;
|
||||
}
|
||||
|
||||
if(xml != 0) {
|
||||
args.putInt(Constants.INTENT_EXTRA_FRAGMENT_TYPE, xml);
|
||||
newFragment.setArguments(args);
|
||||
replaceFragment(newFragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
// Random error I have no idea how to reproduce
|
||||
if(sharedPreferences == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
if (Constants.PREFERENCES_KEY_HIDE_MEDIA.equals(key)) {
|
||||
setHideMedia(sharedPreferences.getBoolean(key, false));
|
||||
}
|
||||
else if (Constants.PREFERENCES_KEY_MEDIA_BUTTONS.equals(key)) {
|
||||
setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true));
|
||||
}
|
||||
else if (Constants.PREFERENCES_KEY_CACHE_LOCATION.equals(key)) {
|
||||
setCacheLocation(sharedPreferences.getString(key, ""));
|
||||
}
|
||||
else if(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT.equals(key)) {
|
||||
SyncUtil.removeMostRecentSyncFiles(context);
|
||||
} else if(Constants.PREFERENCES_KEY_REPLAY_GAIN.equals(key) || Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP.equals(key) || Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED.equals(key)) {
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
if(downloadService != null) {
|
||||
downloadService.reapplyVolume();
|
||||
}
|
||||
} else if(Constants.PREFERENCES_KEY_START_ON_HEADPHONES.equals(key)) {
|
||||
Intent serviceIntent = new Intent();
|
||||
serviceIntent.setClassName(context.getPackageName(), HeadphoneListenerService.class.getName());
|
||||
|
||||
if(sharedPreferences.getBoolean(key, false)) {
|
||||
context.startService(serviceIntent);
|
||||
} else {
|
||||
context.stopService(serviceIntent);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleBackup();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitPreferences(PreferenceScreen preferenceScreen) {
|
||||
this.setTitle(preferenceScreen.getTitle());
|
||||
|
||||
internalSSID = Util.getSSID(context);
|
||||
if (internalSSID == null) {
|
||||
internalSSID = "";
|
||||
}
|
||||
internalSSIDDisplay = context.getResources().getString(R.string.settings_server_local_network_ssid_hint, internalSSID);
|
||||
|
||||
theme = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_THEME);
|
||||
maxBitrateWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI);
|
||||
maxBitrateMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE);
|
||||
networkTimeout = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT);
|
||||
cacheLocation = (CacheLocationPreference) this.findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION);
|
||||
preloadCountWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI);
|
||||
preloadCountMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE);
|
||||
keepPlayedCount = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_KEEP_PLAYED_CNT);
|
||||
tempLoss = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_TEMP_LOSS);
|
||||
pauseDisconnect = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PAUSE_DISCONNECT);
|
||||
serversCategory = (PreferenceCategory) this.findPreference(Constants.PREFERENCES_KEY_SERVER_KEY);
|
||||
addServerPreference = this.findPreference(Constants.PREFERENCES_KEY_SERVER_ADD);
|
||||
songPressAction = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_SONG_PRESS_ACTION);
|
||||
syncInterval = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_INTERVAL);
|
||||
syncEnabled = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED);
|
||||
syncWifi = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_WIFI);
|
||||
syncNotification = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_NOTIFICATION);
|
||||
syncMostRecent = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT);
|
||||
replayGain = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN);
|
||||
replayGainType = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_TYPE);
|
||||
replayGainBump = this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_BUMP);
|
||||
replayGainUntagged = this.findPreference(Constants.PREFERENCES_KEY_REPLAY_GAIN_UNTAGGED);
|
||||
cacheSize = (EditTextPreference) this.findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE);
|
||||
|
||||
settings = Util.getPreferences(context);
|
||||
serverCount = settings.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1);
|
||||
|
||||
if(cacheSize != null) {
|
||||
this.findPreference("clearCache").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Util.confirmDialog(context, R.string.common_delete, R.string.common_confirm_message_cache, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new LoadingTask<Void>(context, false) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
FileUtil.deleteMusicDirectory(context);
|
||||
FileUtil.deleteSerializedCache(context);
|
||||
FileUtil.deleteArtworkCache(context);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
Util.toast(context, R.string.settings_cache_clear_complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
Util.toast(context, getErrorMessage(error), false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(syncEnabled != null) {
|
||||
this.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
Boolean syncEnabled = (Boolean) newValue;
|
||||
|
||||
Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE);
|
||||
ContentResolver.setSyncAutomatically(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, syncEnabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
syncInterval.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
Integer syncInterval = Integer.parseInt(((String) newValue));
|
||||
|
||||
Account account = new Account(Constants.SYNC_ACCOUNT_NAME, Constants.SYNC_ACCOUNT_TYPE);
|
||||
ContentResolver.addPeriodicSync(account, Constants.SYNC_ACCOUNT_PLAYLIST_AUTHORITY, new Bundle(), 60L * syncInterval);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(serversCategory != null) {
|
||||
addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
serverCount++;
|
||||
int instance = serverCount;
|
||||
serversCategory.addPreference(addServer(serverCount));
|
||||
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount);
|
||||
// Reset set folder ID
|
||||
editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
|
||||
editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, "http://yourhost");
|
||||
editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, getResources().getString(R.string.settings_server_unused));
|
||||
editor.commit();
|
||||
|
||||
ServerSettings ss = new ServerSettings(instance);
|
||||
serverSettings.put(String.valueOf(instance), ss);
|
||||
ss.update();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
serversCategory.setOrderingAsAdded(false);
|
||||
for (int i = 1; i <= serverCount; i++) {
|
||||
serversCategory.addPreference(addServer(i));
|
||||
serverSettings.put(String.valueOf(i), new ServerSettings(i));
|
||||
}
|
||||
}
|
||||
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
private void scheduleBackup() {
|
||||
try {
|
||||
Class managerClass = Class.forName("android.app.backup.BackupManager");
|
||||
Constructor managerConstructor = managerClass.getConstructor(Context.class);
|
||||
Object manager = managerConstructor.newInstance(context);
|
||||
Method m = managerClass.getMethod("dataChanged");
|
||||
m.invoke(manager);
|
||||
} catch(ClassNotFoundException e) {
|
||||
Log.e(TAG, "No backup manager found");
|
||||
} catch(Throwable t) {
|
||||
Log.e(TAG, "Scheduling backup failed " + t);
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (testingConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(theme != null) {
|
||||
theme.setSummary(theme.getEntry());
|
||||
}
|
||||
|
||||
if(cacheSize != null) {
|
||||
maxBitrateWifi.setSummary(maxBitrateWifi.getEntry());
|
||||
maxBitrateMobile.setSummary(maxBitrateMobile.getEntry());
|
||||
networkTimeout.setSummary(networkTimeout.getEntry());
|
||||
cacheLocation.setSummary(cacheLocation.getText());
|
||||
preloadCountWifi.setSummary(preloadCountWifi.getEntry());
|
||||
preloadCountMobile.setSummary(preloadCountMobile.getEntry());
|
||||
|
||||
try {
|
||||
if(megabyteFromat == null) {
|
||||
megabyteFromat = new DecimalFormat(getResources().getString(R.string.util_bytes_format_megabyte));
|
||||
}
|
||||
|
||||
cacheSize.setSummary(megabyteFromat.format((double) Integer.parseInt(cacheSize.getText())).replace(".00", ""));
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to format cache size", e);
|
||||
cacheSize.setSummary(cacheSize.getText());
|
||||
}
|
||||
}
|
||||
|
||||
if(keepPlayedCount != null) {
|
||||
keepPlayedCount.setSummary(keepPlayedCount.getEntry());
|
||||
tempLoss.setSummary(tempLoss.getEntry());
|
||||
pauseDisconnect.setSummary(pauseDisconnect.getEntry());
|
||||
songPressAction.setSummary(songPressAction.getEntry());
|
||||
|
||||
if(replayGain.isChecked()) {
|
||||
replayGainType.setEnabled(true);
|
||||
replayGainBump.setEnabled(true);
|
||||
replayGainUntagged.setEnabled(true);
|
||||
} else {
|
||||
replayGainType.setEnabled(false);
|
||||
replayGainBump.setEnabled(false);
|
||||
replayGainUntagged.setEnabled(false);
|
||||
}
|
||||
replayGainType.setSummary(replayGainType.getEntry());
|
||||
}
|
||||
|
||||
if(syncEnabled != null) {
|
||||
syncInterval.setSummary(syncInterval.getEntry());
|
||||
|
||||
if(syncEnabled.isChecked()) {
|
||||
if(!syncInterval.isEnabled()) {
|
||||
syncInterval.setEnabled(true);
|
||||
syncWifi.setEnabled(true);
|
||||
syncNotification.setEnabled(true);
|
||||
syncMostRecent.setEnabled(true);
|
||||
}
|
||||
} else {
|
||||
if(syncInterval.isEnabled()) {
|
||||
syncInterval.setEnabled(false);
|
||||
syncWifi.setEnabled(false);
|
||||
syncNotification.setEnabled(false);
|
||||
syncMostRecent.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ServerSettings ss : serverSettings.values()) {
|
||||
ss.update();
|
||||
}
|
||||
}
|
||||
public void checkForRemoved() {
|
||||
for (ServerSettings ss : serverSettings.values()) {
|
||||
if(!ss.update()) {
|
||||
serversCategory.removePreference(ss.getScreen());
|
||||
serverCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PreferenceScreen addServer(final int instance) {
|
||||
final PreferenceScreen screen = this.getPreferenceManager().createPreferenceScreen(context);
|
||||
screen.setKey(Constants.PREFERENCES_KEY_SERVER_KEY + instance);
|
||||
screen.setOrder(instance);
|
||||
|
||||
screen.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
SettingsFragment newFragment = new SettingsFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
|
||||
newFragment.setArguments(args);
|
||||
|
||||
replaceFragment(newFragment);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
private PreferenceScreen expandServer(final int instance) {
|
||||
final PreferenceScreen screen = this.getPreferenceManager().createPreferenceScreen(context);
|
||||
screen.setTitle(R.string.settings_server_unused);
|
||||
screen.setKey(Constants.PREFERENCES_KEY_SERVER_KEY + instance);
|
||||
|
||||
final EditTextPreference serverNamePreference = new EditTextPreference(context);
|
||||
serverNamePreference.setKey(Constants.PREFERENCES_KEY_SERVER_NAME + instance);
|
||||
serverNamePreference.setDefaultValue(getResources().getString(R.string.settings_server_unused));
|
||||
serverNamePreference.setTitle(R.string.settings_server_name);
|
||||
serverNamePreference.setDialogTitle(R.string.settings_server_name);
|
||||
|
||||
if (serverNamePreference.getText() == null) {
|
||||
serverNamePreference.setText(getResources().getString(R.string.settings_server_unused));
|
||||
}
|
||||
|
||||
serverNamePreference.setSummary(serverNamePreference.getText());
|
||||
|
||||
final EditTextPreference serverUrlPreference = new EditTextPreference(context);
|
||||
serverUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_URL + instance);
|
||||
serverUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI);
|
||||
serverUrlPreference.setDefaultValue("http://yourhost");
|
||||
serverUrlPreference.setTitle(R.string.settings_server_address);
|
||||
serverUrlPreference.setDialogTitle(R.string.settings_server_address);
|
||||
|
||||
if (serverUrlPreference.getText() == null) {
|
||||
serverUrlPreference.setText("http://yourhost");
|
||||
}
|
||||
|
||||
serverUrlPreference.setSummary(serverUrlPreference.getText());
|
||||
screen.setSummary(serverUrlPreference.getText());
|
||||
|
||||
final EditTextPreference serverLocalNetworkSSIDPreference = new EditTextPreference(context) {
|
||||
@Override
|
||||
protected void onAddEditTextToDialogView(View dialogView, final EditText editText) {
|
||||
super.onAddEditTextToDialogView(dialogView, editText);
|
||||
ViewGroup root = (ViewGroup) ((ViewGroup) dialogView).getChildAt(0);
|
||||
|
||||
Button defaultButton = new Button(getContext());
|
||||
defaultButton.setText(internalSSIDDisplay);
|
||||
defaultButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
editText.setText(internalSSID);
|
||||
}
|
||||
});
|
||||
root.addView(defaultButton);
|
||||
}
|
||||
};
|
||||
serverLocalNetworkSSIDPreference.setKey(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance);
|
||||
serverLocalNetworkSSIDPreference.setTitle(R.string.settings_server_local_network_ssid);
|
||||
serverLocalNetworkSSIDPreference.setDialogTitle(R.string.settings_server_local_network_ssid);
|
||||
|
||||
final EditTextPreference serverInternalUrlPreference = new EditTextPreference(context);
|
||||
serverInternalUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance);
|
||||
serverInternalUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI);
|
||||
serverInternalUrlPreference.setDefaultValue("");
|
||||
serverInternalUrlPreference.setTitle(R.string.settings_server_internal_address);
|
||||
serverInternalUrlPreference.setDialogTitle(R.string.settings_server_internal_address);
|
||||
serverInternalUrlPreference.setSummary(serverInternalUrlPreference.getText());
|
||||
|
||||
final EditTextPreference serverUsernamePreference = new EditTextPreference(context);
|
||||
serverUsernamePreference.setKey(Constants.PREFERENCES_KEY_USERNAME + instance);
|
||||
serverUsernamePreference.setTitle(R.string.settings_server_username);
|
||||
serverUsernamePreference.setDialogTitle(R.string.settings_server_username);
|
||||
|
||||
final EditTextPreference serverPasswordPreference = new EditTextPreference(context);
|
||||
serverPasswordPreference.setKey(Constants.PREFERENCES_KEY_PASSWORD + instance);
|
||||
serverPasswordPreference.getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
serverPasswordPreference.setSummary("***");
|
||||
serverPasswordPreference.setTitle(R.string.settings_server_password);
|
||||
|
||||
final Preference serverOpenBrowser = new Preference(context);
|
||||
serverOpenBrowser.setKey(Constants.PREFERENCES_KEY_OPEN_BROWSER);
|
||||
serverOpenBrowser.setPersistent(false);
|
||||
serverOpenBrowser.setTitle(R.string.settings_server_open_browser);
|
||||
serverOpenBrowser.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
openInBrowser(instance);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Preference serverRemoveServerPreference = new Preference(context);
|
||||
serverRemoveServerPreference.setKey(Constants.PREFERENCES_KEY_SERVER_REMOVE + instance);
|
||||
serverRemoveServerPreference.setPersistent(false);
|
||||
serverRemoveServerPreference.setTitle(R.string.settings_servers_remove);
|
||||
|
||||
serverRemoveServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Util.confirmDialog(context, R.string.common_delete, screen.getTitle().toString(), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Reset values to null so when we ask for them again they are new
|
||||
serverNamePreference.setText(null);
|
||||
serverUrlPreference.setText(null);
|
||||
serverUsernamePreference.setText(null);
|
||||
serverPasswordPreference.setText(null);
|
||||
|
||||
// Don't use Util.getActiveServer since it is 0 if offline
|
||||
int activeServer = Util.getPreferences(context).getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
||||
for (int i = instance; i <= serverCount; i++) {
|
||||
Util.removeInstanceName(context, i, activeServer);
|
||||
}
|
||||
|
||||
serverCount--;
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount);
|
||||
editor.commit();
|
||||
|
||||
removeCurrent();
|
||||
|
||||
SubsonicFragment parentFragment = context.getCurrentFragment();
|
||||
if(parentFragment instanceof SettingsFragment) {
|
||||
SettingsFragment serverSelectionFragment = (SettingsFragment) parentFragment;
|
||||
serverSelectionFragment.checkForRemoved();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Preference serverTestConnectionPreference = new Preference(context);
|
||||
serverTestConnectionPreference.setKey(Constants.PREFERENCES_KEY_TEST_CONNECTION + instance);
|
||||
serverTestConnectionPreference.setPersistent(false);
|
||||
serverTestConnectionPreference.setTitle(R.string.settings_test_connection_title);
|
||||
serverTestConnectionPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
testConnection(instance);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
screen.addPreference(serverNamePreference);
|
||||
screen.addPreference(serverUrlPreference);
|
||||
screen.addPreference(serverInternalUrlPreference);
|
||||
screen.addPreference(serverLocalNetworkSSIDPreference);
|
||||
screen.addPreference(serverUsernamePreference);
|
||||
screen.addPreference(serverPasswordPreference);
|
||||
screen.addPreference(serverTestConnectionPreference);
|
||||
screen.addPreference(serverOpenBrowser);
|
||||
screen.addPreference(serverRemoveServerPreference);
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
private void setHideMedia(boolean hide) {
|
||||
File nomediaDir = new File(FileUtil.getSubsonicDirectory(context), ".nomedia");
|
||||
File musicNoMedia = new File(FileUtil.getMusicDirectory(context), ".nomedia");
|
||||
if (hide && !nomediaDir.exists()) {
|
||||
try {
|
||||
if (!nomediaDir.createNewFile()) {
|
||||
Log.w(TAG, "Failed to create " + nomediaDir);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to create " + nomediaDir, e);
|
||||
}
|
||||
|
||||
try {
|
||||
if(!musicNoMedia.createNewFile()) {
|
||||
Log.w(TAG, "Failed to create " + musicNoMedia);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to create " + musicNoMedia, e);
|
||||
}
|
||||
} else if (!hide && nomediaDir.exists()) {
|
||||
if (!nomediaDir.delete()) {
|
||||
Log.w(TAG, "Failed to delete " + nomediaDir);
|
||||
}
|
||||
if(!musicNoMedia.delete()) {
|
||||
Log.w(TAG, "Failed to delete " + musicNoMedia);
|
||||
}
|
||||
}
|
||||
Util.toast(context, R.string.settings_hide_media_toast, false);
|
||||
}
|
||||
|
||||
private void setMediaButtonsEnabled(boolean enabled) {
|
||||
if (enabled) {
|
||||
Util.registerMediaButtonEventReceiver(context);
|
||||
} else {
|
||||
Util.unregisterMediaButtonEventReceiver(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCacheLocation(String path) {
|
||||
File dir = new File(path);
|
||||
if (!FileUtil.verifyCanWrite(dir)) {
|
||||
Util.toast(context, R.string.settings_cache_location_error, false);
|
||||
|
||||
// Reset it to the default.
|
||||
String defaultPath = FileUtil.getDefaultMusicDirectory(context).getPath();
|
||||
if (!defaultPath.equals(path)) {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultPath);
|
||||
editor.commit();
|
||||
|
||||
if(cacheLocation != null) {
|
||||
cacheLocation.setSummary(defaultPath);
|
||||
cacheLocation.setText(defaultPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear download queue.
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
downloadService.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void testConnection(final int instance) {
|
||||
LoadingTask<Boolean> task = new LoadingTask<Boolean>(context) {
|
||||
private int previousInstance;
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground() throws Throwable {
|
||||
updateProgress(R.string.settings_testing_connection);
|
||||
|
||||
previousInstance = Util.getActiveServer(context);
|
||||
testingConnection = true;
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
try {
|
||||
musicService.setInstance(instance);
|
||||
musicService.ping(context, this);
|
||||
return true;
|
||||
} finally {
|
||||
musicService.setInstance(null);
|
||||
testingConnection = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Boolean licenseValid) {
|
||||
Util.toast(context, R.string.settings_testing_ok);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
super.cancel();
|
||||
Util.setActiveServer(context, previousInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
Log.w(TAG, error.toString(), error);
|
||||
new ErrorDialog(context, getResources().getString(R.string.settings_connection_failure) +
|
||||
" " + getErrorMessage(error), false);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
private void openInBrowser(final int instance) {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
String url = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
|
||||
if(url == null) {
|
||||
new ErrorDialog(context, R.string.settings_invalid_url, false);
|
||||
return;
|
||||
}
|
||||
Uri uriServer = Uri.parse(url);
|
||||
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uriServer);
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
private class ServerSettings {
|
||||
private int instance;
|
||||
private EditTextPreference serverName;
|
||||
private EditTextPreference serverUrl;
|
||||
private EditTextPreference serverLocalNetworkSSID;
|
||||
private EditTextPreference serverInternalUrl;
|
||||
private EditTextPreference username;
|
||||
private PreferenceScreen screen;
|
||||
|
||||
private ServerSettings(int instance) {
|
||||
this.instance = instance;
|
||||
screen = (PreferenceScreen) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_KEY + instance);
|
||||
serverName = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_NAME + instance);
|
||||
serverUrl = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_URL + instance);
|
||||
serverLocalNetworkSSID = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance);
|
||||
serverInternalUrl = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance);
|
||||
username = (EditTextPreference) SettingsFragment.this.findPreference(Constants.PREFERENCES_KEY_USERNAME + instance);
|
||||
|
||||
if(serverName != null) {
|
||||
serverUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
try {
|
||||
String url = (String) value;
|
||||
new URL(url);
|
||||
if (url.contains(" ") || url.contains("@") || url.contains("_")) {
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception x) {
|
||||
new ErrorDialog(context, R.string.settings_invalid_url, false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
serverInternalUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
try {
|
||||
String url = (String) value;
|
||||
// Allow blank internal IP address
|
||||
if ("".equals(url) || url == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
new URL(url);
|
||||
if (url.contains(" ") || url.contains("@") || url.contains("_")) {
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception x) {
|
||||
new ErrorDialog(context, R.string.settings_invalid_url, false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
username.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
String username = (String) value;
|
||||
if (username == null || !username.equals(username.trim())) {
|
||||
new ErrorDialog(context, R.string.settings_invalid_username, false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public PreferenceScreen getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
public boolean update() {
|
||||
SharedPreferences prefs = Util.getPreferences(context);
|
||||
|
||||
if(prefs.contains(Constants.PREFERENCES_KEY_SERVER_NAME + instance)) {
|
||||
if (serverName != null) {
|
||||
serverName.setSummary(serverName.getText());
|
||||
serverUrl.setSummary(serverUrl.getText());
|
||||
serverLocalNetworkSSID.setSummary(serverLocalNetworkSSID.getText());
|
||||
serverInternalUrl.setSummary(serverInternalUrl.getText());
|
||||
username.setSummary(username.getText());
|
||||
|
||||
setTitle(serverName.getText());
|
||||
}
|
||||
|
||||
|
||||
String title = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
|
||||
String summary = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
|
||||
|
||||
if (title != null) {
|
||||
screen.setTitle(title);
|
||||
} else {
|
||||
screen.setTitle(R.string.settings_server_unused);
|
||||
}
|
||||
if (summary != null) {
|
||||
screen.setSummary(summary);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
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 2010 (C) Sindre Mehus
|
||||
*/
|
||||
package github.nvllsvm.audinaut.provider;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.SearchCritera;
|
||||
import github.nvllsvm.audinaut.domain.SearchResult;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* Provides search suggestions based on recent searches.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class AudinautSearchProvider extends ContentProvider {
|
||||
private static final String TAG = AudinautSearchProvider.class.getSimpleName();
|
||||
|
||||
private static final String RESOURCE_PREFIX = "android.resource://github.nvllsvm.audinaut/";
|
||||
private static final String[] COLUMNS = {"_id",
|
||||
SearchManager.SUGGEST_COLUMN_TEXT_1,
|
||||
SearchManager.SUGGEST_COLUMN_TEXT_2,
|
||||
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
|
||||
SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
|
||||
SearchManager.SUGGEST_COLUMN_ICON_1};
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
if(selectionArgs[0].isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String query = selectionArgs[0] + "*";
|
||||
SearchResult searchResult = search(query);
|
||||
return createCursor(selectionArgs[0], searchResult);
|
||||
}
|
||||
|
||||
private SearchResult search(String query) {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
if (musicService == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return musicService.search(new SearchCritera(query, 5, 10, 10), getContext(), null);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor createCursor(String query, SearchResult searchResult) {
|
||||
MatrixCursor cursor = new MatrixCursor(COLUMNS);
|
||||
if (searchResult == null) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// Add all results into one pot
|
||||
List<Object> results = new ArrayList<Object>();
|
||||
results.addAll(searchResult.getArtists());
|
||||
results.addAll(searchResult.getAlbums());
|
||||
results.addAll(searchResult.getSongs());
|
||||
|
||||
// For each, calculate its string distance to the query
|
||||
for(Object obj: results) {
|
||||
if(obj instanceof Artist) {
|
||||
Artist artist = (Artist) obj;
|
||||
artist.setCloseness(Util.getStringDistance(query, artist.getName()));
|
||||
} else {
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) obj;
|
||||
entry.setCloseness(Util.getStringDistance(query, entry.getTitle()));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort based on the closeness paramater
|
||||
Collections.sort(results, new Comparator<Object>() {
|
||||
@Override
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
// Get the closeness of the two objects
|
||||
int left, right;
|
||||
boolean leftArtist = lhs instanceof Artist;
|
||||
boolean rightArtist = rhs instanceof Artist;
|
||||
if (leftArtist) {
|
||||
left = ((Artist) lhs).getCloseness();
|
||||
} else {
|
||||
left = ((MusicDirectory.Entry) lhs).getCloseness();
|
||||
}
|
||||
if (rightArtist) {
|
||||
right = ((Artist) rhs).getCloseness();
|
||||
} else {
|
||||
right = ((MusicDirectory.Entry) rhs).getCloseness();
|
||||
}
|
||||
|
||||
if (left == right) {
|
||||
if(leftArtist && rightArtist) {
|
||||
return 0;
|
||||
} else if(leftArtist) {
|
||||
return -1;
|
||||
} else if(rightArtist) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else if (left > right) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Done sorting, add results to cursor
|
||||
for(Object obj: results) {
|
||||
if(obj instanceof Artist) {
|
||||
Artist artist = (Artist) obj;
|
||||
String icon = RESOURCE_PREFIX + R.drawable.ic_action_artist;
|
||||
cursor.addRow(new Object[]{artist.getId().hashCode(), artist.getName(), null, "ar-" + artist.getId(), artist.getName(), icon});
|
||||
} else {
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) obj;
|
||||
|
||||
if(entry.isDirectory()) {
|
||||
String icon = RESOURCE_PREFIX + R.drawable.ic_action_album;
|
||||
cursor.addRow(new Object[]{entry.getId().hashCode(), entry.getTitle(), entry.getArtist(), entry.getId(), entry.getTitle(), icon});
|
||||
} else {
|
||||
String icon = RESOURCE_PREFIX + R.drawable.ic_action_song;
|
||||
String id;
|
||||
if(Util.isTagBrowsing(getContext())) {
|
||||
id = entry.getAlbumId();
|
||||
} else {
|
||||
id = entry.getParent();
|
||||
}
|
||||
|
||||
String artistDisplay;
|
||||
if(entry.getArtist() == null) {
|
||||
if(entry.getAlbum() != null) {
|
||||
artistDisplay = entry.getAlbumDisplay();
|
||||
} else {
|
||||
artistDisplay = "";
|
||||
}
|
||||
} else if(entry.getAlbum() != null) {
|
||||
artistDisplay = entry.getArtist() + " - " + entry.getAlbumDisplay();
|
||||
} else {
|
||||
artistDisplay = entry.getArtist();
|
||||
}
|
||||
|
||||
cursor.addRow(new Object[]{entry.getId().hashCode(), entry.getTitle(), artistDisplay, "so-" + id, entry.getTitle(), icon});
|
||||
}
|
||||
}
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues contentValues) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String s, String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package github.nvllsvm.audinaut.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
|
||||
public class A2dpIntentReceiver extends BroadcastReceiver {
|
||||
private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse";
|
||||
private String TAG = A2dpIntentReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "GOT INTENT " + intent);
|
||||
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
|
||||
if (downloadService != null){
|
||||
|
||||
Intent avrcpIntent = new Intent(PLAYSTATUS_RESPONSE);
|
||||
|
||||
avrcpIntent.putExtra("duration", (long) downloadService.getPlayerDuration());
|
||||
avrcpIntent.putExtra("position", (long) downloadService.getPlayerPosition());
|
||||
avrcpIntent.putExtra("ListSize", (long) downloadService.getSongs().size());
|
||||
|
||||
switch (downloadService.getPlayerState()){
|
||||
case STARTED:
|
||||
avrcpIntent.putExtra("playing", true);
|
||||
break;
|
||||
case STOPPED:
|
||||
avrcpIntent.putExtra("playing", false);
|
||||
break;
|
||||
case PAUSED:
|
||||
avrcpIntent.putExtra("playing", false);
|
||||
break;
|
||||
case COMPLETED:
|
||||
avrcpIntent.putExtra("playing", false);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
context.sendBroadcast(avrcpIntent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.PlayerState;
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
public class AudioNoisyReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = AudioNoisyReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
// Don't do anything if downloadService is not started
|
||||
if(downloadService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals (intent.getAction ())) {
|
||||
if((downloadService.getPlayerState() == PlayerState.STARTED || downloadService.getPlayerState() == PlayerState.PAUSED_TEMP)) {
|
||||
SharedPreferences prefs = Util.getPreferences(downloadService);
|
||||
int pausePref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_PAUSE_DISCONNECT, "0"));
|
||||
if(pausePref == 0) {
|
||||
downloadService.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import github.nvllsvm.audinaut.service.HeadphoneListenerService;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if(Util.shouldStartOnHeadphones(context)) {
|
||||
Intent serviceIntent = new Intent();
|
||||
serviceIntent.setClassName(context.getPackageName(), HeadphoneListenerService.class.getName());
|
||||
context.startService(serviceIntent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
public class HeadphonePlugReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = HeadphonePlugReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if(Intent.ACTION_HEADSET_PLUG.equals(intent.getAction())) {
|
||||
int headphoneState = intent.getIntExtra("state", -1);
|
||||
if(headphoneState == 1 && Util.shouldStartOnHeadphones(context)) {
|
||||
Intent start = new Intent(context, DownloadService.class);
|
||||
start.setAction(DownloadService.START_PLAY);
|
||||
context.startService(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import github.nvllsvm.audinaut.service.DownloadService;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
|
||||
public class PlayActionReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = PlayActionReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if(intent.hasExtra(Constants.TASKER_EXTRA_BUNDLE)) {
|
||||
Bundle data = intent.getBundleExtra(Constants.TASKER_EXTRA_BUNDLE);
|
||||
Boolean startShuffled = data.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE);
|
||||
|
||||
Intent start = new Intent(context, DownloadService.class);
|
||||
start.setAction(DownloadService.START_PLAY);
|
||||
start.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, startShuffled);
|
||||
start.putExtra(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, data.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR));
|
||||
start.putExtra(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, data.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR));
|
||||
start.putExtra(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, data.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE));
|
||||
start.putExtra(Constants.PREFERENCES_KEY_OFFLINE, data.getInt(Constants.PREFERENCES_KEY_OFFLINE));
|
||||
context.startService(start);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,633 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import github.nvllsvm.audinaut.util.CacheCleaner;
|
||||
import github.daneren2005.serverproxy.BufferFile;
|
||||
|
||||
import org.apache.http.Header;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DownloadFile implements BufferFile {
|
||||
private static final String TAG = DownloadFile.class.getSimpleName();
|
||||
private static final int MAX_FAILURES = 5;
|
||||
private final Context context;
|
||||
private final MusicDirectory.Entry song;
|
||||
private final File partialFile;
|
||||
private final File completeFile;
|
||||
private final File saveFile;
|
||||
|
||||
private final MediaStoreService mediaStoreService;
|
||||
private DownloadTask downloadTask;
|
||||
private boolean save;
|
||||
private boolean failedDownload = false;
|
||||
private int failed = 0;
|
||||
private int bitRate;
|
||||
private boolean isPlaying = false;
|
||||
private boolean saveWhenDone = false;
|
||||
private boolean completeWhenDone = false;
|
||||
private Long contentLength = null;
|
||||
private long currentSpeed = 0;
|
||||
private boolean rateLimit = false;
|
||||
|
||||
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) {
|
||||
this.context = context;
|
||||
this.song = song;
|
||||
this.save = save;
|
||||
saveFile = FileUtil.getSongFile(context, song);
|
||||
bitRate = getActualBitrate();
|
||||
partialFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) +
|
||||
".partial." + FileUtil.getExtension(saveFile.getName()));
|
||||
completeFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) +
|
||||
".complete." + FileUtil.getExtension(saveFile.getName()));
|
||||
mediaStoreService = new MediaStoreService(context);
|
||||
}
|
||||
|
||||
public MusicDirectory.Entry getSong() {
|
||||
return song;
|
||||
}
|
||||
public boolean isSong() {
|
||||
return song.isSong();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective bit rate.
|
||||
*/
|
||||
public int getBitRate() {
|
||||
if(!partialFile.exists()) {
|
||||
bitRate = getActualBitrate();
|
||||
}
|
||||
if (bitRate > 0) {
|
||||
return bitRate;
|
||||
}
|
||||
return song.getBitRate() == null ? 160 : song.getBitRate();
|
||||
}
|
||||
private int getActualBitrate() {
|
||||
int br = Util.getMaxBitrate(context);
|
||||
if(br == 0 && song.getTranscodedSuffix() != null && "mp3".equals(song.getTranscodedSuffix().toLowerCase())) {
|
||||
if(song.getBitRate() != null) {
|
||||
br = Math.min(320, song.getBitRate());
|
||||
} else {
|
||||
br = 320;
|
||||
}
|
||||
} else if(song.getSuffix() != null && (song.getTranscodedSuffix() == null || song.getSuffix().equals(song.getTranscodedSuffix()))) {
|
||||
// If just downsampling, don't try to upsample (ie: 128 kpbs -> 192 kpbs)
|
||||
if(song.getBitRate() != null && (br == 0 || br > song.getBitRate())) {
|
||||
br = song.getBitRate();
|
||||
}
|
||||
}
|
||||
|
||||
return br;
|
||||
}
|
||||
|
||||
public Long getContentLength() {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
public long getCurrentSize() {
|
||||
if(partialFile.exists()) {
|
||||
return partialFile.length();
|
||||
} else {
|
||||
File file = getCompleteFile();
|
||||
if(file.exists()) {
|
||||
return file.length();
|
||||
} else {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEstimatedSize() {
|
||||
if(contentLength != null) {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
File file = getCompleteFile();
|
||||
if(file.exists()) {
|
||||
return file.length();
|
||||
} else if(song.getDuration() == null) {
|
||||
return 0;
|
||||
} else {
|
||||
int br = (getBitRate() * 1000) / 8;
|
||||
int duration = song.getDuration();
|
||||
return br * duration;
|
||||
}
|
||||
}
|
||||
|
||||
public long getBytesPerSecond() {
|
||||
return currentSpeed;
|
||||
}
|
||||
|
||||
public synchronized void download() {
|
||||
rateLimit = false;
|
||||
preDownload();
|
||||
downloadTask.execute();
|
||||
}
|
||||
public synchronized void downloadNow(MusicService musicService) {
|
||||
rateLimit = true;
|
||||
preDownload();
|
||||
downloadTask.setMusicService(musicService);
|
||||
try {
|
||||
downloadTask.doInBackground();
|
||||
} catch(InterruptedException e) {
|
||||
// This should never be reached
|
||||
}
|
||||
}
|
||||
private void preDownload() {
|
||||
FileUtil.createDirectoryForParent(saveFile);
|
||||
failedDownload = false;
|
||||
if(!partialFile.exists()) {
|
||||
bitRate = getActualBitrate();
|
||||
}
|
||||
downloadTask = new DownloadTask(context);
|
||||
}
|
||||
|
||||
public synchronized void cancelDownload() {
|
||||
if (downloadTask != null) {
|
||||
downloadTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
if (saveFile.exists()) {
|
||||
return saveFile;
|
||||
} else if (completeFile.exists()) {
|
||||
return completeFile;
|
||||
} else {
|
||||
return partialFile;
|
||||
}
|
||||
}
|
||||
|
||||
public File getCompleteFile() {
|
||||
if (saveFile.exists()) {
|
||||
return saveFile;
|
||||
}
|
||||
|
||||
if (completeFile.exists()) {
|
||||
return completeFile;
|
||||
}
|
||||
|
||||
return saveFile;
|
||||
}
|
||||
public File getSaveFile() {
|
||||
return saveFile;
|
||||
}
|
||||
|
||||
public File getPartialFile() {
|
||||
return partialFile;
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saveFile.exists();
|
||||
}
|
||||
|
||||
public synchronized boolean isCompleteFileAvailable() {
|
||||
return saveFile.exists() || completeFile.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isWorkDone() {
|
||||
return saveFile.exists() || (completeFile.exists() && !save) || saveWhenDone || completeWhenDone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
setPlaying(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
setPlaying(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onResume() {
|
||||
if(!isWorkDone() && !isFailedMax() && !isDownloading() && !isDownloadCancelled()) {
|
||||
download();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isDownloading() {
|
||||
return downloadTask != null && downloadTask.isRunning();
|
||||
}
|
||||
|
||||
public synchronized boolean isDownloadCancelled() {
|
||||
return downloadTask != null && downloadTask.isCancelled();
|
||||
}
|
||||
|
||||
public boolean shouldSave() {
|
||||
return save;
|
||||
}
|
||||
|
||||
public boolean isFailed() {
|
||||
return failedDownload;
|
||||
}
|
||||
public boolean isFailedMax() {
|
||||
return failed > MAX_FAILURES;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
cancelDownload();
|
||||
|
||||
// Remove from mediaStore BEFORE deleting file since it calls getCompleteFile
|
||||
deleteFromStore();
|
||||
|
||||
// Delete all possible versions of the file
|
||||
File parent = partialFile.getParentFile();
|
||||
Util.delete(partialFile);
|
||||
Util.delete(completeFile);
|
||||
Util.delete(saveFile);
|
||||
FileUtil.deleteEmptyDir(parent);
|
||||
}
|
||||
|
||||
public void unpin() {
|
||||
if (saveFile.exists()) {
|
||||
// Delete old store entry before renaming to pinned file
|
||||
saveFile.renameTo(completeFile);
|
||||
renameInStore(saveFile, completeFile);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean cleanup() {
|
||||
boolean ok = true;
|
||||
if (completeFile.exists() || saveFile.exists()) {
|
||||
ok = Util.delete(partialFile);
|
||||
}
|
||||
if (saveFile.exists()) {
|
||||
ok &= Util.delete(completeFile);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// In support of LRU caching.
|
||||
public void updateModificationDate() {
|
||||
updateModificationDate(saveFile);
|
||||
updateModificationDate(partialFile);
|
||||
updateModificationDate(completeFile);
|
||||
}
|
||||
|
||||
private void updateModificationDate(File file) {
|
||||
if (file.exists()) {
|
||||
boolean ok = file.setLastModified(System.currentTimeMillis());
|
||||
if (!ok) {
|
||||
Log.w(TAG, "Failed to set last-modified date on " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlaying(boolean isPlaying) {
|
||||
try {
|
||||
if(saveWhenDone && !isPlaying) {
|
||||
Util.renameFile(completeFile, saveFile);
|
||||
renameInStore(completeFile, saveFile);
|
||||
saveWhenDone = false;
|
||||
} else if(completeWhenDone && !isPlaying) {
|
||||
if(save) {
|
||||
Util.renameFile(partialFile, saveFile);
|
||||
saveToStore();
|
||||
} else {
|
||||
Util.renameFile(partialFile, completeFile);
|
||||
saveToStore();
|
||||
}
|
||||
completeWhenDone = false;
|
||||
}
|
||||
} catch(IOException ex) {
|
||||
Log.w(TAG, "Failed to rename file " + completeFile + " to " + saveFile, ex);
|
||||
}
|
||||
|
||||
this.isPlaying = isPlaying;
|
||||
}
|
||||
public void renamePartial() {
|
||||
try {
|
||||
Util.renameFile(partialFile, completeFile);
|
||||
saveToStore();
|
||||
} catch(IOException ex) {
|
||||
Log.w(TAG, "Failed to rename file " + partialFile + " to " + completeFile, ex);
|
||||
}
|
||||
}
|
||||
public boolean getPlaying() {
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
private void deleteFromStore() {
|
||||
try {
|
||||
mediaStoreService.deleteFromMediaStore(this);
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to remove from store", e);
|
||||
}
|
||||
}
|
||||
private void saveToStore() {
|
||||
if(!Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_HIDE_MEDIA, false)) {
|
||||
try {
|
||||
mediaStoreService.saveInMediaStore(this);
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to save in media store", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void renameInStore(File start, File end) {
|
||||
try {
|
||||
mediaStoreService.renameInMediaStore(start, end);
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to rename in store", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DownloadFile (" + song + ")";
|
||||
}
|
||||
|
||||
// Don't do this. Causes infinite loop if two instances of same song
|
||||
/*@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DownloadFile downloadFile = (DownloadFile) o;
|
||||
return Util.equals(this.getSong(), downloadFile.getSong());
|
||||
}*/
|
||||
|
||||
private class DownloadTask extends SilentBackgroundTask<Void> {
|
||||
private MusicService musicService;
|
||||
|
||||
public DownloadTask(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground() throws InterruptedException {
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
PowerManager.WakeLock wakeLock = null;
|
||||
WifiManager.WifiLock wifiLock = null;
|
||||
try {
|
||||
|
||||
if (Util.isScreenLitOnDownload(context)) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, toString());
|
||||
wakeLock.acquire();
|
||||
}
|
||||
|
||||
wifiLock = Util.createWifiLock(context, toString());
|
||||
wifiLock.acquire();
|
||||
|
||||
if (saveFile.exists()) {
|
||||
Log.i(TAG, saveFile + " already exists. Skipping.");
|
||||
checkDownloads();
|
||||
return null;
|
||||
}
|
||||
if (completeFile.exists()) {
|
||||
if (save) {
|
||||
if(isPlaying) {
|
||||
saveWhenDone = true;
|
||||
} else {
|
||||
Util.renameFile(completeFile, saveFile);
|
||||
renameInStore(completeFile, saveFile);
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, completeFile + " already exists. Skipping.");
|
||||
}
|
||||
checkDownloads();
|
||||
return null;
|
||||
}
|
||||
|
||||
if(musicService == null) {
|
||||
musicService = MusicServiceFactory.getMusicService(context);
|
||||
}
|
||||
|
||||
// Some devices seem to throw error on partial file which doesn't exist
|
||||
boolean compare;
|
||||
try {
|
||||
compare = (bitRate == 0) || (song.getDuration() == 0) || (partialFile.length() == 0) || (bitRate * song.getDuration() * 1000 / 8) > partialFile.length();
|
||||
} catch(Exception e) {
|
||||
compare = true;
|
||||
}
|
||||
if(compare) {
|
||||
// Attempt partial HTTP GET, appending to the file if it exists.
|
||||
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
|
||||
Header contentLengthHeader = response.getFirstHeader("Content-Length");
|
||||
if(contentLengthHeader != null) {
|
||||
String contentLengthString = contentLengthHeader.getValue();
|
||||
if(contentLengthString != null) {
|
||||
Log.i(TAG, "Content Length: " + contentLengthString);
|
||||
contentLength = Long.parseLong(contentLengthString);
|
||||
}
|
||||
}
|
||||
in = response.getEntity().getContent();
|
||||
boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT;
|
||||
if (partial) {
|
||||
Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes");
|
||||
}
|
||||
|
||||
out = new FileOutputStream(partialFile, partial);
|
||||
long n = copy(in, out);
|
||||
Log.i(TAG, "Downloaded " + n + " bytes to " + partialFile);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
if (isCancelled()) {
|
||||
throw new Exception("Download of '" + song + "' was cancelled");
|
||||
} else if(partialFile.length() == 0) {
|
||||
throw new Exception("Download of '" + song + "' failed. File is 0 bytes long.");
|
||||
}
|
||||
|
||||
downloadAndSaveCoverArt(musicService);
|
||||
}
|
||||
|
||||
if(isPlaying) {
|
||||
completeWhenDone = true;
|
||||
} else {
|
||||
if(save) {
|
||||
Util.renameFile(partialFile, saveFile);
|
||||
} else {
|
||||
Util.renameFile(partialFile, completeFile);
|
||||
}
|
||||
DownloadFile.this.saveToStore();
|
||||
}
|
||||
|
||||
} catch(InterruptedException x) {
|
||||
throw x;
|
||||
} catch(FileNotFoundException x) {
|
||||
Util.delete(completeFile);
|
||||
Util.delete(saveFile);
|
||||
if(!isCancelled()) {
|
||||
failed = MAX_FAILURES + 1;
|
||||
failedDownload = true;
|
||||
Log.w(TAG, "Failed to download '" + song + "'.", x);
|
||||
}
|
||||
} catch(IOException x) {
|
||||
Util.delete(completeFile);
|
||||
Util.delete(saveFile);
|
||||
if(!isCancelled()) {
|
||||
failedDownload = true;
|
||||
Log.w(TAG, "Failed to download '" + song + "'.", x);
|
||||
}
|
||||
} catch (Exception x) {
|
||||
Util.delete(completeFile);
|
||||
Util.delete(saveFile);
|
||||
if (!isCancelled()) {
|
||||
failed++;
|
||||
failedDownload = true;
|
||||
Log.w(TAG, "Failed to download '" + song + "'.", x);
|
||||
}
|
||||
} finally {
|
||||
Util.close(in);
|
||||
Util.close(out);
|
||||
if (wakeLock != null) {
|
||||
wakeLock.release();
|
||||
Log.i(TAG, "Released wake lock " + wakeLock);
|
||||
}
|
||||
if (wifiLock != null) {
|
||||
wifiLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Only run these if not interrupted, ie: cancelled
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
if(downloadService != null && !isCancelled()) {
|
||||
new CacheCleaner(context, downloadService).cleanSpace();
|
||||
checkDownloads();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void checkDownloads() {
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
if(downloadService != null) {
|
||||
downloadService.checkDownloads();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DownloadTask (" + song + ")";
|
||||
}
|
||||
|
||||
public void setMusicService(MusicService musicService) {
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
private void downloadAndSaveCoverArt(MusicService musicService) throws Exception {
|
||||
try {
|
||||
if (song.getCoverArt() != null) {
|
||||
// Check if album art already exists, don't want to needlessly load into memory
|
||||
File albumArtFile = FileUtil.getAlbumArtFile(context, song);
|
||||
if(!albumArtFile.exists()) {
|
||||
musicService.getCoverArt(context, song, 0, null, null);
|
||||
}
|
||||
}
|
||||
} catch (Exception x) {
|
||||
Log.e(TAG, "Failed to get cover art.", x);
|
||||
}
|
||||
}
|
||||
|
||||
private long copy(final InputStream in, OutputStream out) throws IOException, InterruptedException {
|
||||
|
||||
// Start a thread that will close the input stream if the task is
|
||||
// cancelled, thus causing the copy() method to return.
|
||||
new Thread("DownloadFile_copy") {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
Util.sleepQuietly(3000L);
|
||||
if (isCancelled()) {
|
||||
Util.close(in);
|
||||
return;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
byte[] buffer = new byte[1024 * 16];
|
||||
long count = 0;
|
||||
int n;
|
||||
long lastLog = System.currentTimeMillis();
|
||||
long lastCount = 0;
|
||||
|
||||
boolean activeLimit = rateLimit;
|
||||
while (!isCancelled() && (n = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, n);
|
||||
count += n;
|
||||
lastCount += n;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastLog > 3000L) { // Only every so often.
|
||||
Log.i(TAG, "Downloaded " + Util.formatBytes(count) + " of " + song);
|
||||
currentSpeed = lastCount / ((now - lastLog) / 1000L);
|
||||
lastLog = now;
|
||||
lastCount = 0;
|
||||
|
||||
// Re-establish every few seconds whether screen is on or not
|
||||
if(rateLimit) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
if(pm.isScreenOn()) {
|
||||
activeLimit = true;
|
||||
} else {
|
||||
activeLimit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If screen is on and rateLimit is true, stop downloading from exhausting bandwidth
|
||||
if(activeLimit) {
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,430 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.PlayerQueue;
|
||||
import github.nvllsvm.audinaut.domain.PlayerState;
|
||||
import github.nvllsvm.audinaut.util.CacheCleaner;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.Pair;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.SongDBHandler;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
import static github.nvllsvm.audinaut.domain.PlayerState.PREPARING;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class DownloadServiceLifecycleSupport {
|
||||
private static final String TAG = DownloadServiceLifecycleSupport.class.getSimpleName();
|
||||
public static final String FILENAME_DOWNLOADS_SER = "downloadstate2.ser";
|
||||
private static final int DEBOUNCE_TIME = 200;
|
||||
|
||||
private final DownloadService downloadService;
|
||||
private Looper eventLooper;
|
||||
private Handler eventHandler;
|
||||
private BroadcastReceiver ejectEventReceiver;
|
||||
private PhoneStateListener phoneStateListener;
|
||||
private boolean externalStorageAvailable= true;
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
private final AtomicBoolean setup = new AtomicBoolean(false);
|
||||
private long lastPressTime = 0;
|
||||
private SilentBackgroundTask<Void> currentSavePlayQueueTask = null;
|
||||
private Date lastChange = null;
|
||||
|
||||
/**
|
||||
* This receiver manages the intent that could come from other applications.
|
||||
*/
|
||||
private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String action = intent.getAction();
|
||||
Log.i(TAG, "intentReceiver.onReceive: " + action);
|
||||
if (DownloadService.CMD_PLAY.equals(action)) {
|
||||
downloadService.play();
|
||||
} else if (DownloadService.CMD_NEXT.equals(action)) {
|
||||
downloadService.next();
|
||||
} else if (DownloadService.CMD_PREVIOUS.equals(action)) {
|
||||
downloadService.previous();
|
||||
} else if (DownloadService.CMD_TOGGLEPAUSE.equals(action)) {
|
||||
downloadService.togglePlayPause();
|
||||
} else if (DownloadService.CMD_PAUSE.equals(action)) {
|
||||
downloadService.pause();
|
||||
} else if (DownloadService.CMD_STOP.equals(action)) {
|
||||
downloadService.pause();
|
||||
downloadService.seekTo(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public DownloadServiceLifecycleSupport(DownloadService downloadService) {
|
||||
this.downloadService = downloadService;
|
||||
}
|
||||
|
||||
public void onCreate() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
eventLooper = Looper.myLooper();
|
||||
eventHandler = new Handler(eventLooper);
|
||||
|
||||
// Deserialize queue before starting looper
|
||||
try {
|
||||
lock.lock();
|
||||
deserializeDownloadQueueNow();
|
||||
|
||||
// Wait until PREPARING is done to mark lifecycle as ready to receive events
|
||||
while(downloadService.getPlayerState() == PREPARING) {
|
||||
Util.sleepQuietly(50L);
|
||||
}
|
||||
|
||||
setup.set(true);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
Looper.loop();
|
||||
}
|
||||
}, "DownloadServiceLifecycleSupport").start();
|
||||
|
||||
// Stop when SD card is ejected.
|
||||
ejectEventReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
externalStorageAvailable = Intent.ACTION_MEDIA_MOUNTED.equals(intent.getAction());
|
||||
if (!externalStorageAvailable) {
|
||||
Log.i(TAG, "External media is ejecting. Stopping playback.");
|
||||
downloadService.reset();
|
||||
} else {
|
||||
Log.i(TAG, "External media is available.");
|
||||
}
|
||||
}
|
||||
};
|
||||
IntentFilter ejectFilter = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
|
||||
ejectFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
|
||||
ejectFilter.addDataScheme("file");
|
||||
downloadService.registerReceiver(ejectEventReceiver, ejectFilter);
|
||||
|
||||
// React to media buttons.
|
||||
Util.registerMediaButtonEventReceiver(downloadService);
|
||||
|
||||
// Pause temporarily on incoming phone calls.
|
||||
phoneStateListener = new MyPhoneStateListener();
|
||||
|
||||
// Android 6.0 removes requirement for android.Manifest.permission.READ_PHONE_STATE;
|
||||
TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
|
||||
// Register the handler for outside intents.
|
||||
IntentFilter commandFilter = new IntentFilter();
|
||||
commandFilter.addAction(DownloadService.CMD_PLAY);
|
||||
commandFilter.addAction(DownloadService.CMD_TOGGLEPAUSE);
|
||||
commandFilter.addAction(DownloadService.CMD_PAUSE);
|
||||
commandFilter.addAction(DownloadService.CMD_STOP);
|
||||
commandFilter.addAction(DownloadService.CMD_PREVIOUS);
|
||||
commandFilter.addAction(DownloadService.CMD_NEXT);
|
||||
commandFilter.addAction(DownloadService.CANCEL_DOWNLOADS);
|
||||
downloadService.registerReceiver(intentReceiver, commandFilter);
|
||||
|
||||
new CacheCleaner(downloadService, downloadService).clean();
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return setup.get();
|
||||
}
|
||||
|
||||
public void onStart(final Intent intent) {
|
||||
if (intent != null) {
|
||||
final String action = intent.getAction();
|
||||
|
||||
if(eventHandler == null) {
|
||||
Util.sleepQuietly(100L);
|
||||
}
|
||||
if(eventHandler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(!setup.get()) {
|
||||
lock.lock();
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
if(DownloadService.START_PLAY.equals(action)) {
|
||||
int offlinePref = intent.getIntExtra(Constants.PREFERENCES_KEY_OFFLINE, 0);
|
||||
if(offlinePref != 0) {
|
||||
boolean offline = (offlinePref == 2);
|
||||
Util.setOffline(downloadService, offline);
|
||||
if (offline) {
|
||||
downloadService.clearIncomplete();
|
||||
} else {
|
||||
downloadService.checkDownloads();
|
||||
}
|
||||
}
|
||||
|
||||
if(intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, false)) {
|
||||
// Add shuffle parameters
|
||||
SharedPreferences.Editor editor = Util.getPreferences(downloadService).edit();
|
||||
String startYear = intent.getStringExtra(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR);
|
||||
if(startYear != null) {
|
||||
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, startYear);
|
||||
}
|
||||
|
||||
String endYear = intent.getStringExtra(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR);
|
||||
if(endYear != null) {
|
||||
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, endYear);
|
||||
}
|
||||
|
||||
String genre = intent.getStringExtra(Constants.PREFERENCES_KEY_SHUFFLE_GENRE);
|
||||
if(genre != null) {
|
||||
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, genre);
|
||||
}
|
||||
editor.commit();
|
||||
|
||||
downloadService.clear();
|
||||
downloadService.setShufflePlayEnabled(true);
|
||||
} else {
|
||||
downloadService.start();
|
||||
}
|
||||
} else if(DownloadService.CMD_TOGGLEPAUSE.equals(action)) {
|
||||
downloadService.togglePlayPause();
|
||||
} else if(DownloadService.CMD_NEXT.equals(action)) {
|
||||
downloadService.next();
|
||||
} else if(DownloadService.CMD_PREVIOUS.equals(action)) {
|
||||
downloadService.previous();
|
||||
} else if(DownloadService.CANCEL_DOWNLOADS.equals(action)) {
|
||||
downloadService.clearBackground();
|
||||
} else if(intent.getExtras() != null) {
|
||||
final KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
|
||||
if (event != null) {
|
||||
handleKeyEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
serializeDownloadQueue();
|
||||
eventLooper.quit();
|
||||
downloadService.unregisterReceiver(ejectEventReceiver);
|
||||
downloadService.unregisterReceiver(intentReceiver);
|
||||
|
||||
TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
}
|
||||
|
||||
public boolean isExternalStorageAvailable() {
|
||||
return externalStorageAvailable;
|
||||
}
|
||||
|
||||
public void serializeDownloadQueue() {
|
||||
serializeDownloadQueue(true);
|
||||
}
|
||||
public void serializeDownloadQueue(final boolean serializeRemote) {
|
||||
if(!setup.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<DownloadFile> songs = new ArrayList<DownloadFile>(downloadService.getSongs());
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(lock.tryLock()) {
|
||||
try {
|
||||
serializeDownloadQueueNow(songs, serializeRemote);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void serializeDownloadQueueNow(List<DownloadFile> songs, boolean serializeRemote) {
|
||||
final PlayerQueue state = new PlayerQueue();
|
||||
for (DownloadFile downloadFile : songs) {
|
||||
state.songs.add(downloadFile.getSong());
|
||||
}
|
||||
for (DownloadFile downloadFile : downloadService.getToDelete()) {
|
||||
state.toDelete.add(downloadFile.getSong());
|
||||
}
|
||||
state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
state.currentPlayingPosition = downloadService.getPlayerPosition();
|
||||
|
||||
DownloadFile currentPlaying = downloadService.getCurrentPlaying();
|
||||
if(currentPlaying != null) {
|
||||
state.renameCurrent = currentPlaying.isWorkDone() && !currentPlaying.isCompleteFileAvailable();
|
||||
}
|
||||
state.changed = lastChange = new Date();
|
||||
|
||||
Log.i(TAG, "Serialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
|
||||
FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
|
||||
}
|
||||
|
||||
public void post(Runnable runnable) {
|
||||
eventHandler.post(runnable);
|
||||
}
|
||||
|
||||
private void deserializeDownloadQueueNow() {
|
||||
PlayerQueue state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER, PlayerQueue.class);
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "Deserialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
|
||||
|
||||
// Rename first thing before anything else starts
|
||||
if(state.renameCurrent && state.currentPlayingIndex != -1 && state.currentPlayingIndex < state.songs.size()) {
|
||||
DownloadFile currentPlaying = new DownloadFile(downloadService, state.songs.get(state.currentPlayingIndex), false);
|
||||
currentPlaying.renamePartial();
|
||||
}
|
||||
|
||||
downloadService.restore(state.songs, state.toDelete, state.currentPlayingIndex, state.currentPlayingPosition);
|
||||
|
||||
if(state != null) {
|
||||
lastChange = state.changed;
|
||||
}
|
||||
}
|
||||
|
||||
public Date getLastChange() {
|
||||
return lastChange;
|
||||
}
|
||||
|
||||
public void handleKeyEvent(KeyEvent event) {
|
||||
if(event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() > 0) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
downloadService.fastForward();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
downloadService.rewind();
|
||||
break;
|
||||
}
|
||||
} else if(event.getAction() == KeyEvent.ACTION_UP) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
if(lastPressTime < (System.currentTimeMillis() - 500)) {
|
||||
lastPressTime = System.currentTimeMillis();
|
||||
downloadService.togglePlayPause();
|
||||
} else {
|
||||
downloadService.next(false, true);
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
if(lastPressTime < (System.currentTimeMillis() - DEBOUNCE_TIME)) {
|
||||
lastPressTime = System.currentTimeMillis();
|
||||
downloadService.previous();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
if(lastPressTime < (System.currentTimeMillis() - DEBOUNCE_TIME)) {
|
||||
lastPressTime = System.currentTimeMillis();
|
||||
downloadService.next();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_REWIND:
|
||||
downloadService.rewind();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
|
||||
downloadService.fastForward();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
downloadService.stop();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
if(downloadService.getPlayerState() != PlayerState.STARTED) {
|
||||
downloadService.start();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
downloadService.pause();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic taken from packages/apps/Music. Will pause when an incoming
|
||||
* call rings or if a call (incoming or outgoing) is connected.
|
||||
*/
|
||||
private class MyPhoneStateListener extends PhoneStateListener {
|
||||
private boolean resumeAfterCall;
|
||||
|
||||
@Override
|
||||
public void onCallStateChanged(final int state, String incomingNumber) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
if (downloadService.getPlayerState() == PlayerState.STARTED) {
|
||||
resumeAfterCall = true;
|
||||
downloadService.pause(true);
|
||||
}
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
if (resumeAfterCall) {
|
||||
resumeAfterCall = false;
|
||||
if(downloadService.getPlayerState() == PlayerState.PAUSED_TEMP) {
|
||||
downloadService.start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.IBinder;
|
||||
|
||||
import github.nvllsvm.audinaut.receiver.HeadphonePlugReceiver;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* Created by Scott on 4/6/2015.
|
||||
*/
|
||||
public class HeadphoneListenerService extends Service {
|
||||
private HeadphonePlugReceiver receiver;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
receiver = new HeadphonePlugReceiver();
|
||||
registerReceiver(receiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if(!Util.shouldStartOnHeadphones(this)) {
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
try {
|
||||
if(receiver != null) {
|
||||
unregisterReceiver(receiver);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class MediaStoreService {
|
||||
|
||||
private static final String TAG = MediaStoreService.class.getSimpleName();
|
||||
private static final Uri ALBUM_ART_URI = Uri.parse("content://media/external/audio/albumart");
|
||||
|
||||
private final Context context;
|
||||
|
||||
public MediaStoreService(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void saveInMediaStore(DownloadFile downloadFile) {
|
||||
MusicDirectory.Entry song = downloadFile.getSong();
|
||||
File songFile = downloadFile.getCompleteFile();
|
||||
|
||||
// Delete existing row in case the song has been downloaded before.
|
||||
deleteFromMediaStore(downloadFile);
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.MediaColumns.TITLE, song.getTitle());
|
||||
values.put(MediaStore.MediaColumns.DATA, songFile.getAbsolutePath());
|
||||
values.put(MediaStore.Audio.AudioColumns.ARTIST, song.getArtist());
|
||||
values.put(MediaStore.Audio.AudioColumns.ALBUM, song.getAlbum());
|
||||
if (song.getDuration() != null) {
|
||||
values.put(MediaStore.Audio.AudioColumns.DURATION, song.getDuration() * 1000L);
|
||||
}
|
||||
if (song.getTrack() != null) {
|
||||
values.put(MediaStore.Audio.AudioColumns.TRACK, song.getTrack());
|
||||
}
|
||||
if (song.getYear() != null) {
|
||||
values.put(MediaStore.Audio.AudioColumns.YEAR, song.getYear());
|
||||
}
|
||||
if(song.getTranscodedContentType() != null) {
|
||||
values.put(MediaStore.MediaColumns.MIME_TYPE, song.getTranscodedContentType());
|
||||
} else {
|
||||
values.put(MediaStore.MediaColumns.MIME_TYPE, song.getContentType());
|
||||
}
|
||||
values.put(MediaStore.Audio.AudioColumns.IS_MUSIC, 1);
|
||||
|
||||
Uri uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
|
||||
|
||||
// Look up album, and add cover art if found.
|
||||
Cursor cursor = contentResolver.query(uri, new String[]{MediaStore.Audio.AudioColumns.ALBUM_ID}, null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
int albumId = cursor.getInt(0);
|
||||
insertAlbumArt(albumId, downloadFile);
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
public void deleteFromMediaStore(DownloadFile downloadFile) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
MusicDirectory.Entry song = downloadFile.getSong();
|
||||
File file = downloadFile.getCompleteFile();
|
||||
|
||||
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
|
||||
int n = contentResolver.delete(uri,
|
||||
MediaStore.MediaColumns.DATA + "=?",
|
||||
new String[]{file.getAbsolutePath()});
|
||||
if (n > 0) {
|
||||
Log.i(TAG, "Deleting media store row for " + song);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteFromMediaStore(File file) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
|
||||
int n = contentResolver.delete(uri,
|
||||
MediaStore.MediaColumns.DATA + "=?",
|
||||
new String[]{file.getAbsolutePath()});
|
||||
if (n > 0) {
|
||||
Log.i(TAG, "Deleting media store row for " + file);
|
||||
}
|
||||
}
|
||||
|
||||
public void renameInMediaStore(File start, File end) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.MediaColumns.DATA, end.getAbsolutePath());
|
||||
|
||||
int n = contentResolver.update(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
values,
|
||||
MediaStore.MediaColumns.DATA + "=?",
|
||||
new String[]{start.getAbsolutePath()});
|
||||
if (n > 0) {
|
||||
Log.i(TAG, "Rename media store row for " + start + " to " + end);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertAlbumArt(int albumId, DownloadFile downloadFile) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
Cursor cursor = contentResolver.query(Uri.withAppendedPath(ALBUM_ART_URI, String.valueOf(albumId)), null, null, null, null);
|
||||
if (!cursor.moveToFirst()) {
|
||||
|
||||
// No album art found, add it.
|
||||
File albumArtFile = FileUtil.getAlbumArtFile(context, downloadFile.getSong());
|
||||
if (albumArtFile.exists()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.Audio.AlbumColumns.ALBUM_ID, albumId);
|
||||
values.put(MediaStore.MediaColumns.DATA, albumArtFile.getPath());
|
||||
contentResolver.insert(ALBUM_ART_URI, values);
|
||||
Log.i(TAG, "Added album art: " + albumArtFile);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.Genre;
|
||||
import github.nvllsvm.audinaut.domain.Indexes;
|
||||
import github.nvllsvm.audinaut.domain.PlayerQueue;
|
||||
import github.nvllsvm.audinaut.domain.RemoteStatus;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.MusicFolder;
|
||||
import github.nvllsvm.audinaut.domain.Playlist;
|
||||
import github.nvllsvm.audinaut.domain.SearchCritera;
|
||||
import github.nvllsvm.audinaut.domain.SearchResult;
|
||||
import github.nvllsvm.audinaut.domain.User;
|
||||
import github.nvllsvm.audinaut.domain.Version;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public interface MusicService {
|
||||
|
||||
void ping(Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void startRescan(Context context, ProgressListener listener) throws Exception;
|
||||
|
||||
Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void addToPlaylist(String id, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void overwritePlaylist(String id, String name, int toRemove, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getAlbumList(String type, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getAlbumList(String type, String extra, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception;
|
||||
|
||||
Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception;
|
||||
|
||||
HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception;
|
||||
|
||||
String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception;
|
||||
|
||||
List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
MusicDirectory getTopTrackSongs(String artist, int size, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void createUser(User user, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void updateUser(User user, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception;
|
||||
|
||||
void savePlayQueue(List<MusicDirectory.Entry> songs, MusicDirectory.Entry currentPlaying, int position, Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
PlayerQueue getPlayQueue(Context context, ProgressListener progressListener) throws Exception;
|
||||
|
||||
void setInstance(Integer instance) throws Exception;
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service;
|
||||
|
||||
import android.content.Context;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class MusicServiceFactory {
|
||||
|
||||
private static final MusicService REST_MUSIC_SERVICE = new CachedMusicService(new RESTMusicService());
|
||||
private static final MusicService OFFLINE_MUSIC_SERVICE = new OfflineMusicService();
|
||||
|
||||
public static MusicService getMusicService(Context context) {
|
||||
return Util.isOffline(context) ? OFFLINE_MUSIC_SERVICE : REST_MUSIC_SERVICE;
|
||||
}
|
||||
}
|
|
@ -1,638 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Reader;
|
||||
import java.io.FileReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.domain.Genre;
|
||||
import github.nvllsvm.audinaut.domain.Indexes;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
|
||||
import github.nvllsvm.audinaut.domain.PlayerQueue;
|
||||
import github.nvllsvm.audinaut.domain.RemoteStatus;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.MusicFolder;
|
||||
import github.nvllsvm.audinaut.domain.Playlist;
|
||||
import github.nvllsvm.audinaut.domain.SearchCritera;
|
||||
import github.nvllsvm.audinaut.domain.SearchResult;
|
||||
import github.nvllsvm.audinaut.domain.User;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.FileUtil;
|
||||
import github.nvllsvm.audinaut.util.Pair;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.SilentBackgroundTask;
|
||||
import github.nvllsvm.audinaut.util.SongDBHandler;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import java.io.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class OfflineMusicService implements MusicService {
|
||||
private static final String TAG = OfflineMusicService.class.getSimpleName();
|
||||
private static final String ERRORMSG = "Not available in offline mode";
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public void ping(Context context, ProgressListener progressListener) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
for (File file : FileUtil.listFiles(root)) {
|
||||
if (file.isDirectory()) {
|
||||
Artist artist = new Artist();
|
||||
artist.setId(file.getPath());
|
||||
artist.setIndex(file.getName().substring(0, 1));
|
||||
artist.setName(file.getName());
|
||||
artists.add(artist);
|
||||
} else if(!file.getName().equals("albumart.jpg") && !file.getName().equals(".nomedia")) {
|
||||
entries.add(createEntry(context, file));
|
||||
}
|
||||
}
|
||||
|
||||
Indexes indexes = new Indexes(0L, Collections.<Artist>emptyList(), artists, entries);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
return getMusicDirectory(id, artistName, refresh, context, progressListener, false);
|
||||
}
|
||||
private MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener, boolean isPodcast) throws Exception {
|
||||
File dir = new File(id);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
result.setName(dir.getName());
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
for (File file : FileUtil.listMediaFiles(dir)) {
|
||||
String name = getName(file);
|
||||
if (name != null & !names.contains(name)) {
|
||||
names.add(name);
|
||||
result.addChild(createEntry(context, file, name, true, isPodcast));
|
||||
}
|
||||
}
|
||||
result.sortChildren(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CUSTOM_SORT_ENABLED, true));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
private String getName(File file) {
|
||||
String name = file.getName();
|
||||
if (file.isDirectory()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name = name.replace(".complete", "");
|
||||
return FileUtil.getBaseName(name);
|
||||
}
|
||||
|
||||
private Entry createEntry(Context context, File file) {
|
||||
return createEntry(context, file, getName(file));
|
||||
}
|
||||
private Entry createEntry(Context context, File file, String name) {
|
||||
return createEntry(context, file, name, true);
|
||||
}
|
||||
private Entry createEntry(Context context, File file, String name, boolean load) {
|
||||
return createEntry(context, file, name, load, false);
|
||||
}
|
||||
private Entry createEntry(Context context, File file, String name, boolean load, boolean isPodcast) {
|
||||
Entry entry;
|
||||
entry = new Entry();
|
||||
entry.setDirectory(file.isDirectory());
|
||||
entry.setId(file.getPath());
|
||||
entry.setParent(file.getParent());
|
||||
entry.setSize(file.length());
|
||||
String root = FileUtil.getMusicDirectory(context).getPath();
|
||||
if(!file.getParentFile().getParentFile().getPath().equals(root)) {
|
||||
entry.setGrandParent(file.getParentFile().getParent());
|
||||
}
|
||||
entry.setPath(file.getPath().replaceFirst("^" + root + "/" , ""));
|
||||
String title = name;
|
||||
if (file.isFile()) {
|
||||
File artistFolder = file.getParentFile().getParentFile();
|
||||
File albumFolder = file.getParentFile();
|
||||
if(artistFolder.getPath().equals(root)) {
|
||||
entry.setArtist(albumFolder.getName());
|
||||
} else {
|
||||
entry.setArtist(artistFolder.getName());
|
||||
}
|
||||
entry.setAlbum(albumFolder.getName());
|
||||
|
||||
int index = name.indexOf('-');
|
||||
if(index != -1) {
|
||||
try {
|
||||
entry.setTrack(Integer.parseInt(name.substring(0, index)));
|
||||
title = title.substring(index + 1);
|
||||
} catch(Exception e) {
|
||||
// Failed parseInt, just means track filled out
|
||||
}
|
||||
}
|
||||
|
||||
if(load) {
|
||||
entry.loadMetadata(file);
|
||||
}
|
||||
}
|
||||
|
||||
entry.setTitle(title);
|
||||
entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", "")));
|
||||
|
||||
File albumArt = FileUtil.getAlbumArtFile(context, entry);
|
||||
if (albumArt.exists()) {
|
||||
entry.setCoverArt(albumArt.getPath());
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context, Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
|
||||
try {
|
||||
return FileUtil.getAlbumArtBitmap(context, entry, size);
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMusicUrl(Context context, Entry song, int maxBitrate) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRescan(Context context, ProgressListener listener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
List<Entry> albums = new ArrayList<Entry>();
|
||||
List<Entry> songs = new ArrayList<Entry>();
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
int closeness = 0;
|
||||
for (File artistFile : FileUtil.listFiles(root)) {
|
||||
String artistName = artistFile.getName();
|
||||
if (artistFile.isDirectory()) {
|
||||
if((closeness = matchCriteria(criteria, artistName)) > 0) {
|
||||
Artist artist = new Artist();
|
||||
artist.setId(artistFile.getPath());
|
||||
artist.setIndex(artistFile.getName().substring(0, 1));
|
||||
artist.setName(artistName);
|
||||
artist.setCloseness(closeness);
|
||||
artists.add(artist);
|
||||
}
|
||||
|
||||
recursiveAlbumSearch(artistName, artistFile, criteria, context, albums, songs);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(artists, new Comparator<Artist>() {
|
||||
public int compare(Artist lhs, Artist rhs) {
|
||||
if(lhs.getCloseness() == rhs.getCloseness()) {
|
||||
return 0;
|
||||
}
|
||||
else if(lhs.getCloseness() > rhs.getCloseness()) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
Collections.sort(albums, new Comparator<Entry>() {
|
||||
public int compare(Entry lhs, Entry rhs) {
|
||||
if(lhs.getCloseness() == rhs.getCloseness()) {
|
||||
return 0;
|
||||
}
|
||||
else if(lhs.getCloseness() > rhs.getCloseness()) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
Collections.sort(songs, new Comparator<Entry>() {
|
||||
public int compare(Entry lhs, Entry rhs) {
|
||||
if(lhs.getCloseness() == rhs.getCloseness()) {
|
||||
return 0;
|
||||
}
|
||||
else if(lhs.getCloseness() > rhs.getCloseness()) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Respect counts in search criteria
|
||||
int artistCount = Math.min(artists.size(), criteria.getArtistCount());
|
||||
int albumCount = Math.min(albums.size(), criteria.getAlbumCount());
|
||||
int songCount = Math.min(songs.size(), criteria.getSongCount());
|
||||
artists = artists.subList(0, artistCount);
|
||||
albums = albums.subList(0, albumCount);
|
||||
songs = songs.subList(0, songCount);
|
||||
|
||||
return new SearchResult(artists, albums, songs);
|
||||
}
|
||||
|
||||
private void recursiveAlbumSearch(String artistName, File file, SearchCritera criteria, Context context, List<Entry> albums, List<Entry> songs) {
|
||||
int closeness;
|
||||
for(File albumFile : FileUtil.listMediaFiles(file)) {
|
||||
if(albumFile.isDirectory()) {
|
||||
String albumName = getName(albumFile);
|
||||
if((closeness = matchCriteria(criteria, albumName)) > 0) {
|
||||
Entry album = createEntry(context, albumFile, albumName);
|
||||
album.setArtist(artistName);
|
||||
album.setCloseness(closeness);
|
||||
albums.add(album);
|
||||
}
|
||||
|
||||
for(File songFile : FileUtil.listMediaFiles(albumFile)) {
|
||||
String songName = getName(songFile);
|
||||
if(songName == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(songFile.isDirectory()) {
|
||||
recursiveAlbumSearch(artistName, songFile, criteria, context, albums, songs);
|
||||
}
|
||||
else if((closeness = matchCriteria(criteria, songName)) > 0){
|
||||
Entry song = createEntry(context, albumFile, songName);
|
||||
song.setArtist(artistName);
|
||||
song.setAlbum(albumName);
|
||||
song.setCloseness(closeness);
|
||||
songs.add(song);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
String songName = getName(albumFile);
|
||||
if((closeness = matchCriteria(criteria, songName)) > 0) {
|
||||
Entry song = createEntry(context, albumFile, songName);
|
||||
song.setArtist(artistName);
|
||||
song.setAlbum(songName);
|
||||
song.setCloseness(closeness);
|
||||
songs.add(song);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private int matchCriteria(SearchCritera criteria, String name) {
|
||||
if (criteria.getPattern().matcher(name).matches()) {
|
||||
return Util.getStringDistance(
|
||||
criteria.getQuery().toLowerCase(),
|
||||
name.toLowerCase());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
List<Playlist> playlists = new ArrayList<Playlist>();
|
||||
File root = FileUtil.getPlaylistDirectory(context);
|
||||
String lastServer = null;
|
||||
boolean removeServer = true;
|
||||
for (File folder : FileUtil.listFiles(root)) {
|
||||
if(folder.isDirectory()) {
|
||||
String server = folder.getName();
|
||||
SortedSet<File> fileList = FileUtil.listFiles(folder);
|
||||
for(File file: fileList) {
|
||||
if(FileUtil.isPlaylistFile(file)) {
|
||||
String id = file.getName();
|
||||
String filename = FileUtil.getBaseName(id);
|
||||
String name = server + ": " + filename;
|
||||
Playlist playlist = new Playlist(server, name);
|
||||
playlist.setComment(filename);
|
||||
|
||||
Reader reader = null;
|
||||
BufferedReader buffer = null;
|
||||
int songCount = 0;
|
||||
try {
|
||||
reader = new FileReader(file);
|
||||
buffer = new BufferedReader(reader);
|
||||
|
||||
String line = buffer.readLine();
|
||||
while( (line = buffer.readLine()) != null ){
|
||||
// No matter what, end file can't have .complete in it
|
||||
line = line.replace(".complete", "");
|
||||
File entryFile = new File(line);
|
||||
|
||||
// Don't add file to playlist if it doesn't exist as cached or pinned!
|
||||
File checkFile = entryFile;
|
||||
if(!checkFile.exists()) {
|
||||
// If normal file doens't exist, check if .complete version does
|
||||
checkFile = new File(entryFile.getParent(), FileUtil.getBaseName(entryFile.getName())
|
||||
+ ".complete." + FileUtil.getExtension(entryFile.getName()));
|
||||
}
|
||||
|
||||
String entryName = getName(entryFile);
|
||||
if(checkFile.exists() && entryName != null){
|
||||
songCount++;
|
||||
}
|
||||
}
|
||||
|
||||
playlist.setSongCount(Integer.toString(songCount));
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to count songs in playlist", e);
|
||||
} finally {
|
||||
Util.close(buffer);
|
||||
Util.close(reader);
|
||||
}
|
||||
|
||||
if(songCount > 0) {
|
||||
playlists.add(playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!server.equals(lastServer) && fileList.size() > 0) {
|
||||
if(lastServer != null) {
|
||||
removeServer = false;
|
||||
}
|
||||
lastServer = server;
|
||||
}
|
||||
} else {
|
||||
// Delete legacy playlist files
|
||||
try {
|
||||
folder.delete();
|
||||
} catch(Exception e) {
|
||||
Log.w(TAG, "Failed to delete old playlist file: " + folder.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(removeServer) {
|
||||
for(Playlist playlist: playlists) {
|
||||
playlist.setName(playlist.getName().substring(playlist.getId().length() + 2));
|
||||
}
|
||||
}
|
||||
return playlists;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
||||
DownloadService downloadService = DownloadService.getInstance();
|
||||
if (downloadService == null) {
|
||||
return new MusicDirectory();
|
||||
}
|
||||
|
||||
Reader reader = null;
|
||||
BufferedReader buffer = null;
|
||||
try {
|
||||
int firstIndex = name.indexOf(id);
|
||||
if(firstIndex != -1) {
|
||||
name = name.substring(id.length() + 2);
|
||||
}
|
||||
|
||||
File playlistFile = FileUtil.getPlaylistFile(context, id, name);
|
||||
reader = new FileReader(playlistFile);
|
||||
buffer = new BufferedReader(reader);
|
||||
|
||||
MusicDirectory playlist = new MusicDirectory();
|
||||
String line = buffer.readLine();
|
||||
if(!"#EXTM3U".equals(line)) return playlist;
|
||||
|
||||
while( (line = buffer.readLine()) != null ){
|
||||
// No matter what, end file can't have .complete in it
|
||||
line = line.replace(".complete", "");
|
||||
File entryFile = new File(line);
|
||||
|
||||
// Don't add file to playlist if it doesn't exist as cached or pinned!
|
||||
File checkFile = entryFile;
|
||||
if(!checkFile.exists()) {
|
||||
// If normal file doens't exist, check if .complete version does
|
||||
checkFile = new File(entryFile.getParent(), FileUtil.getBaseName(entryFile.getName())
|
||||
+ ".complete." + FileUtil.getExtension(entryFile.getName()));
|
||||
}
|
||||
|
||||
String entryName = getName(entryFile);
|
||||
if(checkFile.exists() && entryName != null){
|
||||
playlist.addChild(createEntry(context, entryFile, entryName, false));
|
||||
}
|
||||
}
|
||||
|
||||
return playlist;
|
||||
} finally {
|
||||
Util.close(buffer);
|
||||
Util.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlaylist(String id, String name, List<Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToPlaylist(String id, List<Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overwritePlaylist(String id, String name, int toRemove, List<Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList(String type, String extra, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getTopTrackSongs(String artist, int size, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception {
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
List<File> children = new LinkedList<File>();
|
||||
listFilesRecursively(root, children);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
|
||||
if (children.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
File file = children.get(random.nextInt(children.size()));
|
||||
result.addChild(createEntry(context, file, getName(file)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCoverArtUrl(Context context, Entry entry) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createUser(User user, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUser(User user, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePlayQueue(List<Entry> songs, Entry currentPlaying, int position, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerQueue getPlayQueue(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInstance(Integer instance) throws Exception{
|
||||
throw new OfflineException(ERRORMSG);
|
||||
}
|
||||
|
||||
private void listFilesRecursively(File parent, List<File> children) {
|
||||
for (File file : FileUtil.listMediaFiles(parent)) {
|
||||
if (file.isFile()) {
|
||||
children.add(file);
|
||||
} else {
|
||||
listFilesRecursively(file, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
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 2010 (C) Sindre Mehus
|
||||
*/
|
||||
package github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.Genre;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Joshua Bahnsen
|
||||
*/
|
||||
public class GenreParser extends AbstractParser {
|
||||
private static final String TAG = GenreParser.class.getSimpleName();
|
||||
|
||||
public GenreParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public List<Genre> parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
List<Genre> result = new ArrayList<Genre>();
|
||||
StringReader sr = null;
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
String xml = null;
|
||||
String line = null;
|
||||
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (xml == null) {
|
||||
xml = line;
|
||||
} else {
|
||||
xml += line;
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
|
||||
// Replace double escaped ampersand (&apos;)
|
||||
xml = xml.replaceAll("(?:&)(amp;|lt;|gt;|#37;|apos;)", "&$1");
|
||||
|
||||
// Replace unescaped ampersand
|
||||
xml = xml.replaceAll("&(?!amp;|lt;|gt;|#37;|apos;)", "&");
|
||||
|
||||
// Replace unescaped percent symbol
|
||||
// No replacements for <> at this time
|
||||
xml = xml.replaceAll("%", "%");
|
||||
|
||||
xml = xml.replaceAll("'", "'");
|
||||
|
||||
sr = new StringReader(xml);
|
||||
} catch (IOException ioe) {
|
||||
Log.e(TAG, "Error parsing Genre XML", ioe);
|
||||
}
|
||||
|
||||
if (sr == null) {
|
||||
Log.w(TAG, "Unable to parse Genre XML, returning empty list");
|
||||
return result;
|
||||
}
|
||||
|
||||
init(sr);
|
||||
|
||||
Genre genre = null;
|
||||
|
||||
int eventType;
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("genre".equals(name)) {
|
||||
genre = new Genre();
|
||||
genre.setSongCount(getInteger("songCount"));
|
||||
genre.setAlbumCount(getInteger("albumCount"));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
} else {
|
||||
genre = null;
|
||||
}
|
||||
} else if (eventType == XmlPullParser.TEXT) {
|
||||
if (genre != null) {
|
||||
String value = getText();
|
||||
if (genre != null) {
|
||||
genre.setName(Html.fromHtml(value).toString());
|
||||
genre.setIndex(value.substring(0, 1));
|
||||
result.add(genre);
|
||||
genre = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
return Genre.GenreComparator.sort(result);
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.domain.Indexes;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import android.util.Log;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class IndexesParser extends MusicDirectoryEntryParser {
|
||||
private static final String TAG = IndexesParser.class.getSimpleName();
|
||||
|
||||
public IndexesParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public Indexes parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
long t0 = System.currentTimeMillis();
|
||||
init(reader);
|
||||
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
List<Artist> shortcuts = new ArrayList<Artist>();
|
||||
List<MusicDirectory.Entry> entries = new ArrayList<MusicDirectory.Entry>();
|
||||
Long lastModified = null;
|
||||
int eventType;
|
||||
String index = "#";
|
||||
String ignoredArticles = null;
|
||||
boolean changed = false;
|
||||
Map<String, Artist> artistList = new HashMap<String, Artist>();
|
||||
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("indexes".equals(name) || "artists".equals(name)) {
|
||||
changed = true;
|
||||
lastModified = getLong("lastModified");
|
||||
ignoredArticles = get("ignoredArticles");
|
||||
} else if ("index".equals(name)) {
|
||||
index = get("name");
|
||||
|
||||
} else if ("artist".equals(name)) {
|
||||
Artist artist = new Artist();
|
||||
artist.setId(get("id"));
|
||||
artist.setName(get("name"));
|
||||
artist.setIndex(index);
|
||||
|
||||
// Combine the id's for the two artists
|
||||
if(artistList.containsKey(artist.getName())) {
|
||||
Artist originalArtist = artistList.get(artist.getName());
|
||||
originalArtist.setId(originalArtist.getId() + ";" + artist.getId());
|
||||
} else {
|
||||
artistList.put(artist.getName(), artist);
|
||||
artists.add(artist);
|
||||
}
|
||||
|
||||
if (artists.size() % 10 == 0) {
|
||||
String msg = getContext().getResources().getString(R.string.parser_artist_count, artists.size());
|
||||
updateProgress(progressListener, msg);
|
||||
}
|
||||
} else if ("shortcut".equals(name)) {
|
||||
Artist shortcut = new Artist();
|
||||
shortcut.setId(get("id"));
|
||||
shortcut.setName(get("name"));
|
||||
shortcut.setIndex("*");
|
||||
shortcuts.add(shortcut);
|
||||
} else if("child".equals(name)) {
|
||||
MusicDirectory.Entry entry = parseEntry("");
|
||||
entries.add(entry);
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
if(ignoredArticles != null) {
|
||||
SharedPreferences.Editor prefs = Util.getPreferences(context).edit();
|
||||
prefs.putString(Constants.CACHE_KEY_IGNORE, ignoredArticles);
|
||||
prefs.commit();
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long t1 = System.currentTimeMillis();
|
||||
Log.d(TAG, "Got " + artists.size() + " artist(s) in " + (t1 - t0) + "ms.");
|
||||
|
||||
String msg = getContext().getResources().getString(R.string.parser_artist_count, artists.size());
|
||||
updateProgress(progressListener, msg);
|
||||
|
||||
return new Indexes(lastModified == null ? 0L : lastModified, shortcuts, artists, entries);
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.util.Constants;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import github.nvllsvm.audinaut.util.Util;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static github.nvllsvm.audinaut.domain.MusicDirectory.*;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class MusicDirectoryParser extends MusicDirectoryEntryParser {
|
||||
|
||||
private static final String TAG = MusicDirectoryParser.class.getSimpleName();
|
||||
|
||||
public MusicDirectoryParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public MusicDirectory parse(String artist, Reader reader, ProgressListener progressListener) throws Exception {
|
||||
init(reader);
|
||||
|
||||
MusicDirectory dir = new MusicDirectory();
|
||||
int eventType;
|
||||
boolean isArtist = false;
|
||||
Map<String, Entry> titleMap = new HashMap<String, Entry>();
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("child".equals(name) || "song".equals(name)) {
|
||||
Entry entry = parseEntry(artist);
|
||||
entry.setGrandParent(dir.getParent());
|
||||
|
||||
// Only check for songs
|
||||
if(!entry.isDirectory()) {
|
||||
// Check if duplicates
|
||||
String disc = (entry.getDiscNumber() != null) ? Integer.toString(entry.getDiscNumber()) : "";
|
||||
String track = (entry.getTrack() != null) ? Integer.toString(entry.getTrack()) : "";
|
||||
String duplicateId = disc + "-" + track + "-" + entry.getTitle();
|
||||
|
||||
Entry duplicate = titleMap.get(duplicateId);
|
||||
if (duplicate != null) {
|
||||
// Check if the first already has been rebased or not
|
||||
if (duplicate.getTitle().equals(entry.getTitle())) {
|
||||
duplicate.rebaseTitleOffPath();
|
||||
}
|
||||
|
||||
// Rebase if this is the second instance of this title found
|
||||
entry.rebaseTitleOffPath();
|
||||
} else {
|
||||
titleMap.put(duplicateId, entry);
|
||||
}
|
||||
}
|
||||
|
||||
dir.addChild(entry);
|
||||
} else if ("directory".equals(name) || "artist".equals(name) || ("album".equals(name) && !isArtist)) {
|
||||
dir.setName(get("name"));
|
||||
dir.setId(get("id"));
|
||||
if(Util.isTagBrowsing(context, instance)) {
|
||||
dir.setParent(get("artistId"));
|
||||
} else {
|
||||
dir.setParent(get("parent"));
|
||||
}
|
||||
isArtist = true;
|
||||
} else if("album".equals(name)) {
|
||||
Entry entry = parseEntry(artist);
|
||||
entry.setDirectory(true);
|
||||
dir.addChild(entry);
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
return dir;
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
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 2015 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.PlayerQueue;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
|
||||
public class PlayQueueParser extends MusicDirectoryEntryParser {
|
||||
private static final String TAG = PlayQueueParser.class.getSimpleName();
|
||||
|
||||
public PlayQueueParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public PlayerQueue parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
init(reader);
|
||||
|
||||
PlayerQueue state = new PlayerQueue();
|
||||
String currentId = null;
|
||||
int eventType;
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if("playQueue".equals(name)) {
|
||||
currentId = get("current");
|
||||
state.currentPlayingPosition = getInteger("position");
|
||||
try {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
state.changed = dateFormat.parse(get("changed"));
|
||||
} catch (ParseException e) {
|
||||
state.changed = null;
|
||||
}
|
||||
} else if ("entry".equals(name)) {
|
||||
MusicDirectory.Entry entry = parseEntry("");
|
||||
// Only add songs
|
||||
state.songs.add(entry);
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
if(currentId != null) {
|
||||
for (MusicDirectory.Entry entry : state.songs) {
|
||||
if (entry.getId().equals(currentId)) {
|
||||
state.currentPlayingIndex = state.songs.indexOf(entry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.currentPlayingIndex = 0;
|
||||
state.currentPlayingPosition = 0;
|
||||
}
|
||||
|
||||
validate();
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
|
||||
public class ScanStatusParser extends AbstractParser {
|
||||
|
||||
public ScanStatusParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public boolean parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
init(reader);
|
||||
|
||||
Boolean started = null;
|
||||
int eventType;
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if("status".equals(name)) {
|
||||
started = getBoolean("started");
|
||||
|
||||
String msg = context.getResources().getString(R.string.parser_scan_count, getInteger("count"));
|
||||
progressListener.updateProgress(msg);
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
return started != null && started;
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
import github.nvllsvm.audinaut.R;
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.domain.SearchResult;
|
||||
import github.nvllsvm.audinaut.domain.Artist;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class SearchResultParser extends MusicDirectoryEntryParser {
|
||||
|
||||
public SearchResultParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public SearchResult parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
init(reader);
|
||||
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
|
||||
int eventType;
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("match".equals(name)) {
|
||||
songs.add(parseEntry(""));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
return new SearchResult(Collections.<Artist>emptyList(), Collections.<MusicDirectory.Entry>emptyList(), songs);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SubsonicRESTException extends Exception {
|
||||
|
||||
private final int code;
|
||||
|
||||
public SubsonicRESTException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
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 2016 (C) Scott Jackson
|
||||
*/
|
||||
package github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.MusicDirectory;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
|
||||
public class TopSongsParser extends MusicDirectoryEntryParser {
|
||||
|
||||
public TopSongsParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
init(reader);
|
||||
|
||||
MusicDirectory dir = new MusicDirectory();
|
||||
int eventType;
|
||||
int trackNumber = 1;
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("song".equals(name)) {
|
||||
MusicDirectory.Entry entry = parseEntry("");
|
||||
entry.setTrack(trackNumber);
|
||||
dir.addChild(entry);
|
||||
|
||||
trackNumber++;
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
return dir;
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
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 2014 (C) Scott Jackson
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.service.parser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import github.nvllsvm.audinaut.domain.MusicFolder;
|
||||
import github.nvllsvm.audinaut.domain.User;
|
||||
import github.nvllsvm.audinaut.domain.User.MusicFolderSetting;
|
||||
import github.nvllsvm.audinaut.domain.User.Setting;
|
||||
import github.nvllsvm.audinaut.service.MusicService;
|
||||
import github.nvllsvm.audinaut.service.MusicServiceFactory;
|
||||
import github.nvllsvm.audinaut.util.ProgressListener;
|
||||
|
||||
public class UserParser extends AbstractParser {
|
||||
private static final String TAG = UserParser.class.getSimpleName();
|
||||
|
||||
public UserParser(Context context, int instance) {
|
||||
super(context, instance);
|
||||
}
|
||||
|
||||
public List<User> parse(Reader reader, ProgressListener progressListener) throws Exception {
|
||||
init(reader);
|
||||
List<User> result = new ArrayList<User>();
|
||||
List<MusicFolder> musicFolders = null;
|
||||
User user = null;
|
||||
int eventType;
|
||||
|
||||
String tagName = null;
|
||||
do {
|
||||
eventType = nextParseEvent();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
tagName = getElementName();
|
||||
if ("user".equals(tagName)) {
|
||||
user = new User();
|
||||
|
||||
user.setUsername(get("username"));
|
||||
user.setEmail(get("email"));
|
||||
for(String role: User.ROLES) {
|
||||
parseSetting(user, role);
|
||||
}
|
||||
|
||||
result.add(user);
|
||||
} else if ("error".equals(tagName)) {
|
||||
handleError();
|
||||
}
|
||||
} else if(eventType == XmlPullParser.TEXT) {
|
||||
if("folder".equals(tagName)) {
|
||||
String id = getText();
|
||||
if(musicFolders == null) {
|
||||
musicFolders = getMusicFolders();
|
||||
}
|
||||
|
||||
if(user != null) {
|
||||
if(user.getMusicFolderSettings() == null) {
|
||||
for (MusicFolder musicFolder : musicFolders) {
|
||||
user.addMusicFolder(musicFolder);
|
||||
}
|
||||
}
|
||||
|
||||
for(Setting musicFolder: user.getMusicFolderSettings()) {
|
||||
if(musicFolder.getName().equals(id)) {
|
||||
musicFolder.setValue(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
|
||||
validate();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<MusicFolder> getMusicFolders() throws Exception{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
return musicService.getMusicFolders(false, context, null);
|
||||
}
|
||||
|
||||
private void parseSetting(User user, String name) {
|
||||
String value = get(name);
|
||||
if(value != null) {
|
||||
user.addSetting(name, "true".equals(value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,553 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package github.nvllsvm.audinaut.service.ssl;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.conn.ConnectTimeoutException;
|
||||
import org.apache.http.conn.scheme.HostNameResolver;
|
||||
import org.apache.http.conn.scheme.LayeredSocketFactory;
|
||||
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Layered socket factory for TLS/SSL connections.
|
||||
* <p>
|
||||
* SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
|
||||
* trusted certificates and to authenticate to the HTTPS server using a private key.
|
||||
* <p>
|
||||
* SSLSocketFactory will enable server authentication when supplied with
|
||||
* a {@link KeyStore trust-store} file containing one or several trusted certificates. The client
|
||||
* secure socket will reject the connection during the SSL session handshake if the target HTTPS
|
||||
* server attempts to authenticate itself with a non-trusted certificate.
|
||||
* <p>
|
||||
* Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
|
||||
* <pre>
|
||||
* keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
|
||||
* </pre>
|
||||
* <p>
|
||||
* In special cases the standard trust verification process can be bypassed by using a custom
|
||||
* {@link TrustStrategy}. This interface is primarily intended for allowing self-signed
|
||||
* certificates to be accepted as trusted without having to add them to the trust-store file.
|
||||
* <p>
|
||||
* The following parameters can be used to customize the behavior of this
|
||||
* class:
|
||||
* <ul>
|
||||
* <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
|
||||
* <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* SSLSocketFactory will enable client authentication when supplied with
|
||||
* a {@link KeyStore key-store} file containing a private key/public certificate
|
||||
* pair. The client secure socket will use the private key to authenticate
|
||||
* itself to the target HTTPS server during the SSL session handshake if
|
||||
* requested to do so by the server.
|
||||
* The target HTTPS server will in its turn verify the certificate presented
|
||||
* by the client in order to establish client's authenticity
|
||||
* <p>
|
||||
* Use the following sequence of actions to generate a key-store file
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>
|
||||
* <p>
|
||||
* Use JDK keytool utility to generate a new key
|
||||
* <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
|
||||
* For simplicity use the same password for the key as that of the key-store
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>
|
||||
* <p>
|
||||
* Issue a certificate signing request (CSR)
|
||||
* <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>
|
||||
* <p>
|
||||
* Send the certificate request to the trusted Certificate Authority for signature.
|
||||
* One may choose to act as her own CA and sign the certificate request using a PKI
|
||||
* tool, such as OpenSSL.
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>
|
||||
* <p>
|
||||
* Import the trusted CA root certificate
|
||||
* <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>
|
||||
* <p>
|
||||
* Import the PKCS#7 file containg the complete certificate chain
|
||||
* <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>
|
||||
* <p>
|
||||
* Verify the content the resultant keystore file
|
||||
* <pre>keytool -list -v -keystore my.keystore</pre>
|
||||
* </p>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SSLSocketFactory implements LayeredSocketFactory {
|
||||
private static final String TAG = SSLSocketFactory.class.getSimpleName();
|
||||
public static final String TLS = "TLS";
|
||||
|
||||
public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
|
||||
= new AllowAllHostnameVerifier();
|
||||
|
||||
public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
|
||||
= new BrowserCompatHostnameVerifier();
|
||||
|
||||
public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
|
||||
= new StrictHostnameVerifier();
|
||||
|
||||
/**
|
||||
* The default factory using the default JVM settings for secure connections.
|
||||
*/
|
||||
private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory();
|
||||
|
||||
/**
|
||||
* Gets the default factory, which uses the default JVM settings for secure
|
||||
* connections.
|
||||
*
|
||||
* @return the default factory
|
||||
*/
|
||||
public static SSLSocketFactory getSocketFactory() {
|
||||
return DEFAULT_FACTORY;
|
||||
}
|
||||
|
||||
private final javax.net.ssl.SSLSocketFactory socketfactory;
|
||||
private final HostNameResolver nameResolver;
|
||||
// TODO: make final
|
||||
private volatile X509HostnameVerifier hostnameVerifier;
|
||||
|
||||
private static SSLContext createSSLContext(
|
||||
String algorithm,
|
||||
final KeyStore keystore,
|
||||
final String keystorePassword,
|
||||
final KeyStore truststore,
|
||||
final SecureRandom random,
|
||||
final TrustStrategy trustStrategy)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
|
||||
if (algorithm == null) {
|
||||
algorithm = TLS;
|
||||
}
|
||||
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
|
||||
KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmfactory.init(keystore, keystorePassword != null ? keystorePassword.toCharArray(): null);
|
||||
KeyManager[] keymanagers = kmfactory.getKeyManagers();
|
||||
TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmfactory.init(keystore);
|
||||
TrustManager[] trustmanagers = tmfactory.getTrustManagers();
|
||||
if (trustmanagers != null && trustStrategy != null) {
|
||||
for (int i = 0; i < trustmanagers.length; i++) {
|
||||
TrustManager tm = trustmanagers[i];
|
||||
if (tm instanceof X509TrustManager) {
|
||||
trustmanagers[i] = new TrustManagerDecorator(
|
||||
(X509TrustManager) tm, trustStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SSLContext sslcontext = SSLContext.getInstance(algorithm);
|
||||
sslcontext.init(keymanagers, trustmanagers, random);
|
||||
return sslcontext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #SSLSocketFactory(String, KeyStore, String, KeyStore, SecureRandom, X509HostnameVerifier)}
|
||||
*/
|
||||
@Deprecated
|
||||
public SSLSocketFactory(
|
||||
final String algorithm,
|
||||
final KeyStore keystore,
|
||||
final String keystorePassword,
|
||||
final KeyStore truststore,
|
||||
final SecureRandom random,
|
||||
final HostNameResolver nameResolver)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(createSSLContext(
|
||||
algorithm, keystore, keystorePassword, truststore, random, null),
|
||||
nameResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public SSLSocketFactory(
|
||||
String algorithm,
|
||||
final KeyStore keystore,
|
||||
final String keystorePassword,
|
||||
final KeyStore truststore,
|
||||
final SecureRandom random,
|
||||
final X509HostnameVerifier hostnameVerifier)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(createSSLContext(
|
||||
algorithm, keystore, keystorePassword, truststore, random, null),
|
||||
hostnameVerifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public SSLSocketFactory(
|
||||
String algorithm,
|
||||
final KeyStore keystore,
|
||||
final String keystorePassword,
|
||||
final KeyStore truststore,
|
||||
final SecureRandom random,
|
||||
final TrustStrategy trustStrategy,
|
||||
final X509HostnameVerifier hostnameVerifier)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(createSSLContext(
|
||||
algorithm, keystore, keystorePassword, truststore, random, trustStrategy),
|
||||
hostnameVerifier);
|
||||
}
|
||||
|
||||
public SSLSocketFactory(
|
||||
final KeyStore keystore,
|
||||
final String keystorePassword,
|
||||
final KeyStore truststore)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(TLS, keystore, keystorePassword, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
public SSLSocketFactory(
|
||||
final KeyStore keystore,
|
||||
final String keystorePassword)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{
|
||||
this(TLS, keystore, keystorePassword, null, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
public SSLSocketFactory(
|
||||
final KeyStore truststore)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(TLS, null, null, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public SSLSocketFactory(
|
||||
final TrustStrategy trustStrategy,
|
||||
final X509HostnameVerifier hostnameVerifier)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(TLS, null, null, null, null, trustStrategy, hostnameVerifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public SSLSocketFactory(
|
||||
final TrustStrategy trustStrategy)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
this(TLS, null, null, null, null, trustStrategy, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
public SSLSocketFactory(final SSLContext sslContext) {
|
||||
this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #SSLSocketFactory(SSLContext)}
|
||||
*/
|
||||
@Deprecated
|
||||
public SSLSocketFactory(
|
||||
final SSLContext sslContext, final HostNameResolver nameResolver) {
|
||||
super();
|
||||
this.socketfactory = sslContext.getSocketFactory();
|
||||
this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
|
||||
this.nameResolver = nameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public SSLSocketFactory(
|
||||
final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
|
||||
super();
|
||||
this.socketfactory = sslContext.getSocketFactory();
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
this.nameResolver = null;
|
||||
}
|
||||
|
||||
private SSLSocketFactory() {
|
||||
super();
|
||||
this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory();
|
||||
this.hostnameVerifier = null;
|
||||
this.nameResolver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param params Optional parameters. Parameters passed to this method will have no effect.
|
||||
* This method will create a unconnected instance of {@link Socket} class
|
||||
* using {@link javax.net.ssl.SSLSocketFactory#createSocket()} method.
|
||||
* @since 4.1
|
||||
*/
|
||||
@SuppressWarnings("cast")
|
||||
public Socket createSocket(final HttpParams params) throws IOException {
|
||||
// the cast makes sure that the factory is working as expected
|
||||
SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket();
|
||||
sslSocket.setEnabledProtocols(getProtocols(sslSocket));
|
||||
sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
|
||||
return sslSocket;
|
||||
}
|
||||
|
||||
@SuppressWarnings("cast")
|
||||
public Socket createSocket() throws IOException {
|
||||
// the cast makes sure that the factory is working as expected
|
||||
SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket();
|
||||
sslSocket.setEnabledProtocols(getProtocols(sslSocket));
|
||||
sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
|
||||
return sslSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public Socket connectSocket(
|
||||
final Socket sock,
|
||||
final InetSocketAddress remoteAddress,
|
||||
final InetSocketAddress localAddress,
|
||||
final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
|
||||
if (remoteAddress == null) {
|
||||
throw new IllegalArgumentException("Remote address may not be null");
|
||||
}
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("HTTP parameters may not be null");
|
||||
}
|
||||
SSLSocket sslsock = (SSLSocket) (sock != null ? sock : createSocket());
|
||||
if (localAddress != null) {
|
||||
// sslsock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
|
||||
sslsock.bind(localAddress);
|
||||
}
|
||||
|
||||
setHostName(sslsock, remoteAddress.getHostName());
|
||||
int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
|
||||
int soTimeout = HttpConnectionParams.getSoTimeout(params);
|
||||
|
||||
try {
|
||||
sslsock.connect(remoteAddress, connTimeout);
|
||||
} catch (SocketTimeoutException ex) {
|
||||
throw new ConnectTimeoutException("Connect to " + remoteAddress.getHostName() + "/"
|
||||
+ remoteAddress.getAddress() + " timed out");
|
||||
}
|
||||
sslsock.setSoTimeout(soTimeout);
|
||||
if (this.hostnameVerifier != null) {
|
||||
try {
|
||||
this.hostnameVerifier.verify(remoteAddress.getHostName(), sslsock);
|
||||
// verifyHostName() didn't blowup - good!
|
||||
} catch (IOException iox) {
|
||||
// close the socket before re-throwing the exception
|
||||
try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
|
||||
throw iox;
|
||||
}
|
||||
}
|
||||
return sslsock;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a socket connection is secure.
|
||||
* This factory creates TLS/SSL socket connections
|
||||
* which, by default, are considered secure.
|
||||
* <br/>
|
||||
* Derived classes may override this method to perform
|
||||
* runtime checks, for example based on the cypher suite.
|
||||
*
|
||||
* @param sock the connected socket
|
||||
*
|
||||
* @return <code>true</code>
|
||||
*
|
||||
* @throws IllegalArgumentException if the argument is invalid
|
||||
*/
|
||||
public boolean isSecure(final Socket sock) throws IllegalArgumentException {
|
||||
if (sock == null) {
|
||||
throw new IllegalArgumentException("Socket may not be null");
|
||||
}
|
||||
// This instanceof check is in line with createSocket() above.
|
||||
if (!(sock instanceof SSLSocket)) {
|
||||
throw new IllegalArgumentException("Socket not created by this factory");
|
||||
}
|
||||
// This check is performed last since it calls the argument object.
|
||||
if (sock.isClosed()) {
|
||||
throw new IllegalArgumentException("Socket is closed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public Socket createLayeredSocket(
|
||||
final Socket socket,
|
||||
final String host,
|
||||
final int port,
|
||||
final boolean autoClose) throws IOException, UnknownHostException {
|
||||
SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
|
||||
socket,
|
||||
host,
|
||||
port,
|
||||
autoClose
|
||||
);
|
||||
sslSocket.setEnabledProtocols(getProtocols(sslSocket));
|
||||
sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
|
||||
if (this.hostnameVerifier != null) {
|
||||
this.hostnameVerifier.verify(host, sslSocket);
|
||||
}
|
||||
// verifyHostName() didn't blowup - good!
|
||||
return sslSocket;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
|
||||
if ( hostnameVerifier == null ) {
|
||||
throw new IllegalArgumentException("Hostname verifier may not be null");
|
||||
}
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
}
|
||||
|
||||
public X509HostnameVerifier getHostnameVerifier() {
|
||||
return this.hostnameVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)}
|
||||
*/
|
||||
@Deprecated
|
||||
public Socket connectSocket(
|
||||
final Socket socket,
|
||||
final String host, int port,
|
||||
final InetAddress localAddress, int localPort,
|
||||
final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
|
||||
InetSocketAddress local = null;
|
||||
if (localAddress != null || localPort > 0) {
|
||||
// we need to bind explicitly
|
||||
if (localPort < 0) {
|
||||
localPort = 0; // indicates "any"
|
||||
}
|
||||
local = new InetSocketAddress(localAddress, localPort);
|
||||
}
|
||||
InetAddress remoteAddress;
|
||||
if (this.nameResolver != null) {
|
||||
remoteAddress = this.nameResolver.resolve(host);
|
||||
} else {
|
||||
remoteAddress = InetAddress.getByName(host);
|
||||
}
|
||||
InetSocketAddress remote = new InetSocketAddress(remoteAddress, port);
|
||||
return connectSocket(socket, remote, local, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #createLayeredSocket(Socket, String, int, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public Socket createSocket(
|
||||
final Socket socket,
|
||||
final String host, int port,
|
||||
boolean autoClose) throws IOException, UnknownHostException {
|
||||
SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(socket, host, port, autoClose);
|
||||
sslSocket.setEnabledProtocols(getProtocols(sslSocket));
|
||||
sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
|
||||
setHostName(sslSocket, host);
|
||||
return sslSocket;
|
||||
}
|
||||
|
||||
private void setHostName(SSLSocket sslsock, String hostname){
|
||||
try {
|
||||
java.lang.reflect.Method setHostnameMethod = sslsock.getClass().getMethod("setHostname", String.class);
|
||||
setHostnameMethod.invoke(sslsock, hostname);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "SNI not useable", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getProtocols(SSLSocket sslSocket) {
|
||||
String[] protocols = sslSocket.getEnabledProtocols();
|
||||
|
||||
// Remove SSLv3 if it is not the only option
|
||||
if(protocols.length > 1) {
|
||||
List<String> protocolList = new ArrayList(Arrays.asList(protocols));
|
||||
protocolList.remove("SSLv3");
|
||||
protocols = protocolList.toArray(new String[protocolList.size()]);
|
||||
}
|
||||
|
||||
return protocols;
|
||||
}
|
||||
|
||||
private String[] getCiphers(SSLSocket sslSocket) {
|
||||
String[] ciphers = sslSocket.getEnabledCipherSuites();
|
||||
|
||||
List<String> enabledCiphers = new ArrayList(Arrays.asList(ciphers));
|
||||
// On Android 5.0 release, Jetty doesn't seem to play nice with these ciphers
|
||||
// Issue seems to have been fixed in M, and now won't work without them. Because Google
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
enabledCiphers.remove("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
|
||||
enabledCiphers.remove("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA");
|
||||
}
|
||||
|
||||
ciphers = enabledCiphers.toArray(new String[enabledCiphers.size()]);
|
||||
return ciphers;
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package github.nvllsvm.audinaut.service.ssl;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
class TrustManagerDecorator implements X509TrustManager {
|
||||
|
||||
private final X509TrustManager trustManager;
|
||||
private final TrustStrategy trustStrategy;
|
||||
|
||||
TrustManagerDecorator(final X509TrustManager trustManager, final TrustStrategy trustStrategy) {
|
||||
super();
|
||||
this.trustManager = trustManager;
|
||||
this.trustStrategy = trustStrategy;
|
||||
}
|
||||
|
||||
public void checkClientTrusted(
|
||||
final X509Certificate[] chain, final String authType) throws CertificateException {
|
||||
this.trustManager.checkClientTrusted(chain, authType);
|
||||
}
|
||||
|
||||
public void checkServerTrusted(
|
||||
final X509Certificate[] chain, final String authType) throws CertificateException {
|
||||
if (!this.trustStrategy.isTrusted(chain, authType)) {
|
||||
this.trustManager.checkServerTrusted(chain, authType);
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return this.trustManager.getAcceptedIssuers();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package github.nvllsvm.audinaut.service.ssl;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* A trust strategy that accepts self-signed certificates as trusted. Verification of all other
|
||||
* certificates is done by the trust manager configured in the SSL context.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class TrustSelfSignedStrategy implements TrustStrategy {
|
||||
|
||||
public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package github.nvllsvm.audinaut.service.ssl;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* A strategy to establish trustworthiness of certificates without consulting the trust manager
|
||||
* configured in the actual SSL context. This interface can be used to override the standard
|
||||
* JSSE certificate verification process.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface TrustStrategy {
|
||||
|
||||
/**
|
||||
* Determines whether the certificate chain can be trusted without consulting the trust manager
|
||||
* configured in the actual SSL context. This method can be used to override the standard JSSE
|
||||
* certificate verification process.
|
||||
* <p>
|
||||
* Please note that, if this method returns <code>false</code>, the trust manager configured
|
||||
* in the actual SSL context can still clear the certificate as trusted.
|
||||
*
|
||||
* @param chain the peer certificate chain
|
||||
* @param authType the authentication type based on the client certificate
|
||||
* @return <code>true</code> if the certificate can be trusted without verification by
|
||||
* the trust manager, <code>false</code> otherwise.
|
||||
* @throws CertificateException thrown if the certificate is not trusted or invalid.
|
||||
*/
|
||||
boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException;
|
||||
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
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 github.nvllsvm.audinaut.service.sync;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
/**
|
||||
* Created by Scott on 8/28/13.
|
||||
*/
|
||||
|
||||
public class AuthenticatorService extends Service {
|
||||
private SubsonicAuthenticator authenticator;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
authenticator = new SubsonicAuthenticator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return authenticator.getIBinder();
|
||||
|
||||
}
|
||||
|
||||
private class SubsonicAuthenticator extends AbstractAccountAuthenticator {
|
||||
public SubsonicAuthenticator(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthTokenLabel(String authTokenType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue