Initial release

This commit is contained in:
Andrew Rabert 2016-12-18 12:41:30 -05:00
parent 91b0bd5e73
commit 48c9a75a7f
832 changed files with 39387 additions and 1 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
.classpath
.project
bin/*
gen/*
private/*
nbandroid/*
.idea
subsonic-android.iml
releases/
proguard_logs/
/gen/
/out/
.gradle/*
/build/
local.properties
*Thumbs.db

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "ServerProxy"]
path = ServerProxy
url = https://github.com/daneren2005/ServerProxy.git

19
Audinaut.iml Normal file
View File

@ -0,0 +1,19 @@
<?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>

View File

@ -1,2 +1,19 @@
# Audinaut
A Libresonic client for Android.
<img src="/app/src/main/res/drawable/audinaut.png" width="200" hspace="10" vspace="10"></br>
A FOSS Libresonic client for Android.
## Building
```
git submodule update --init
gradle assemble
```
## SDK Project Dependencies
Under sdk -> extras:</br>
android -> support -> v7 -> appcompat</br>
android -> support -> v7 -> mediarouter</br>
## 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>

1
ServerProxy Submodule

@ -0,0 +1 @@
Subproject commit a4d957353db2634906e0d5099d7a078a111bfab9

2
app/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
*.iml

63
app/build.gradle Normal file
View File

@ -0,0 +1,63 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
useLibrary 'org.apache.http.legacy'
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
}
}
packagingOptions {
exclude 'META-INF/beans.xml'
}
lintOptions {
checkReleaseBuilds false
warning 'InvalidPackage'
}
signingConfigs {
debug {
storeFile file('../debug.keystore')
}
}
}
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'
}

BIN
app/libs/kryo-2.21-all.jar Normal file

Binary file not shown.

62
app/proguard.cfg Normal file
View File

@ -0,0 +1,62 @@
-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

View File

@ -0,0 +1,13 @@
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);
}
}

View File

@ -0,0 +1,34 @@
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));
}
}

View File

@ -0,0 +1,68 @@
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);
}
}

View File

@ -0,0 +1,296 @@
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;
}
}

View File

@ -0,0 +1,26 @@
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)

View File

@ -0,0 +1 @@
#Sat Oct 01 15:10:08 EDT 2016

View File

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="github.nvllsvm.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.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.software.leanback" android:required="false"/>
<supports-screens android:anyDensity="true" android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
<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="github.nvllsvm.audinaut.activity.SettingsActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTask"/>
<activity android:name="github.nvllsvm.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">
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<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">
<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="org.fourthline.cling.android.AndroidUpnpServiceImpl"/>
<service android:name="github.nvllsvm.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>
<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>
<receiver android:name="github.nvllsvm.audinaut.receiver.MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</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="github.nvllsvm.audinaut.provider.AudinautWidget4x1"
android:label="@string/widget.4x1">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<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">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<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">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<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">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget4x4"/>
</receiver>
<receiver
android:name="github.nvllsvm.audinaut.receiver.PlayActionReceiver">
<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"/>
<meta-data android:name="android.app.default_searchable"
android:value="github.nvllsvm.audinaut.activity.QueryReceiverActivity"/>
</application>
</manifest>

View File

@ -0,0 +1,245 @@
/*
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();
}
}

View File

@ -0,0 +1,85 @@
/*
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);
}
}
}

View File

@ -0,0 +1,58 @@
/*
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

View File

@ -0,0 +1,929 @@
/*
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);
}
}
}
}

View File

@ -0,0 +1,62 @@
/*
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.MediaStore;
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 voice search queries and forwards to the SearchFragment.
*
* http://android-developers.blogspot.com/2010/09/supporting-new-music-voice-action.html
*
* @author Sindre Mehus
*/
public class VoiceQueryReceiverActivity extends Activity {
private static final String TAG = VoiceQueryReceiverActivity.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String query = getIntent().getStringExtra(SearchManager.QUERY);
if (query != null) {
Intent intent = new Intent(VoiceQueryReceiverActivity.this, SubsonicFragmentActivity.class);
intent.putExtra(Constants.INTENT_EXTRA_NAME_QUERY, query);
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
intent.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS));
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Util.startActivityWithoutTransition(VoiceQueryReceiverActivity.this, intent);
}
finish();
Util.disablePendingTransition(this);
}
}

View File

@ -0,0 +1,44 @@
/*
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());
}
}
}

View File

@ -0,0 +1,162 @@
/*
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);
}
}

View File

@ -0,0 +1,48 @@
/*
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;
}
}

View File

@ -0,0 +1,62 @@
/*
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;
}
}

View File

@ -0,0 +1,74 @@
/*
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);
}
}
}

View File

@ -0,0 +1,156 @@
/*
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);
}
}
}

View File

@ -0,0 +1,152 @@
/*
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();
}
}

View File

@ -0,0 +1,150 @@
/*
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);
}
}
}

View File

@ -0,0 +1,54 @@
/*
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());
}
}

View File

@ -0,0 +1,96 @@
/*
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);
}
}
}

View File

@ -0,0 +1,72 @@
/*
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;
}
}
}

View File

@ -0,0 +1,140 @@
/*
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);
}
}

View File

@ -0,0 +1,516 @@
/*
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);
}
}

View File

@ -0,0 +1,121 @@
/*
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);
}
}

View File

@ -0,0 +1,69 @@
/*
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;
}
}

View File

@ -0,0 +1,198 @@
/*
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);
}
}
}
}

View File

@ -0,0 +1,77 @@
/*
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;
}
}
}

View File

@ -0,0 +1,138 @@
/*
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);
}
}
}

View File

@ -0,0 +1,69 @@
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;
}
}
}

View File

@ -0,0 +1,87 @@
/*
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);
}
}

View File

@ -0,0 +1,628 @@
/*
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");
}
}
}
}

View File

@ -0,0 +1,80 @@
/*
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);
}
}
}

View File

@ -0,0 +1,30 @@
/*
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;
}

View File

@ -0,0 +1,47 @@
/*
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;
}
}

View File

@ -0,0 +1,187 @@
/*
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;
}
}
}

View File

@ -0,0 +1,63 @@
/*
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;
}
}

View File

@ -0,0 +1,28 @@
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();
}

View File

@ -0,0 +1,93 @@
/*
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;
}
}

View File

@ -0,0 +1,62 @@
/*
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.util.List;
/**
* The result of a search. Contains matching artists, albums and songs.
*
* @author Sindre Mehus
*/
public class SearchResult implements Serializable {
private final List<Artist> artists;
private final List<MusicDirectory.Entry> albums;
private final List<MusicDirectory.Entry> songs;
public SearchResult(List<Artist> artists, List<MusicDirectory.Entry> albums, List<MusicDirectory.Entry> songs) {
this.artists = artists;
this.albums = albums;
this.songs = songs;
}
public List<Artist> getArtists() {
return artists;
}
public List<MusicDirectory.Entry> getAlbums() {
return albums;
}
public List<MusicDirectory.Entry> getSongs() {
return songs;
}
public boolean hasArtists() {
return !artists.isEmpty();
}
public boolean hasAlbums() {
return !albums.isEmpty();
}
public boolean hasSongs() {
return !songs.isEmpty();
}
}

View File

@ -0,0 +1,146 @@
/*
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;
}
}
}

View File

@ -0,0 +1,187 @@
/*
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;
}
}

View File

@ -0,0 +1,190 @@
/*
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();
}
}
}

View File

@ -0,0 +1,459 @@
/*
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));
}
}

View File

@ -0,0 +1,357 @@
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

View File

@ -0,0 +1,334 @@
/*
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);
}

View File

@ -0,0 +1,291 @@
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);
}
}
}

View File

@ -0,0 +1,253 @@
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();
}
}
}

View File

@ -0,0 +1,840 @@
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));
}
}

View File

@ -0,0 +1,77 @@
/*
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;
}
}

View File

@ -0,0 +1,341 @@
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();
}
}

View File

@ -0,0 +1,219 @@
/*
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;
}
}
}

View File

@ -0,0 +1,88 @@
/*
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;
}
}

View File

@ -0,0 +1,806 @@
/*
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

View File

@ -0,0 +1,209 @@
/*
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;
}
}

View File

@ -0,0 +1,28 @@
/*
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 github.nvllsvm.audinaut.R;
public class AudinautWidget4x1 extends AudinautWidgetProvider {
@Override
protected int getLayout() {
return R.layout.appwidget4x1;
}
}

View File

@ -0,0 +1,28 @@
/*
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 github.nvllsvm.audinaut.R;
public class AudinautWidget4x2 extends AudinautWidgetProvider {
@Override
protected int getLayout() {
return R.layout.appwidget4x2;
}
}

View File

@ -0,0 +1,28 @@
/*
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 github.nvllsvm.audinaut.R;
public class AudinautWidget4x3 extends AudinautWidgetProvider {
@Override
protected int getLayout() {
return R.layout.appwidget4x3;
}
}

View File

@ -0,0 +1,28 @@
/*
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 github.nvllsvm.audinaut.R;
public class AudinautWidget4x4 extends AudinautWidgetProvider {
@Override
protected int getLayout() {
return R.layout.appwidget4x4;
}
}

View File

@ -0,0 +1,305 @@
/*
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.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import github.nvllsvm.audinaut.R;
import github.nvllsvm.audinaut.activity.SubsonicActivity;
import github.nvllsvm.audinaut.activity.SubsonicFragmentActivity;
import github.nvllsvm.audinaut.domain.MusicDirectory;
import github.nvllsvm.audinaut.domain.PlayerQueue;
import github.nvllsvm.audinaut.service.DownloadService;
import github.nvllsvm.audinaut.service.DownloadServiceLifecycleSupport;
import github.nvllsvm.audinaut.util.Constants;
import github.nvllsvm.audinaut.util.FileUtil;
import github.nvllsvm.audinaut.util.ImageLoader;
import github.nvllsvm.audinaut.util.Util;
/**
* Simple widget to show currently playing album art along
* with play/pause and next track buttons.
* <p/>
* Based on source code from the stock Android Music app.
*
* @author Sindre Mehus
*/
public class AudinautWidgetProvider extends AppWidgetProvider {
private static final String TAG = AudinautWidgetProvider.class.getSimpleName();
private static AudinautWidget4x1 instance4x1;
private static AudinautWidget4x2 instance4x2;
private static AudinautWidget4x3 instance4x3;
private static AudinautWidget4x4 instance4x4;
public static synchronized void notifyInstances(Context context, DownloadService service, boolean playing) {
if(instance4x1 == null) {
instance4x1 = new AudinautWidget4x1();
}
if(instance4x2 == null) {
instance4x2 = new AudinautWidget4x2();
}
if(instance4x3 == null) {
instance4x3 = new AudinautWidget4x3();
}
if(instance4x4 == null) {
instance4x4 = new AudinautWidget4x4();
}
instance4x1.notifyChange(context, service, playing);
instance4x2.notifyChange(context, service, playing);
instance4x3.notifyChange(context, service, playing);
instance4x4.notifyChange(context, service, playing);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
defaultAppWidget(context, appWidgetIds);
}
@Override
public void onEnabled(Context context) {
notifyInstances(context, DownloadService.getInstance(), false);
}
protected int getLayout() {
return 0;
}
/**
* Initialize given widgets to default state, where we launch Subsonic on default click
* and hide actions if service not running.
*/
private void defaultAppWidget(Context context, int[] appWidgetIds) {
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), getLayout());
views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text));
if(getLayout() == R.layout.appwidget4x2) {
views.setTextViewText(R.id.album, "");
}
linkButtons(context, views, false);
performUpdate(context, null, appWidgetIds, false);
}
private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) {
// Update specific list of appWidgetIds if given, otherwise default to all
final AppWidgetManager manager = AppWidgetManager.getInstance(context);
if (appWidgetIds != null) {
manager.updateAppWidget(appWidgetIds, views);
} else {
manager.updateAppWidget(new ComponentName(context, this.getClass()), views);
}
}
/**
* Handle a change notification coming over from {@link DownloadService}
*/
public void notifyChange(Context context, DownloadService service, boolean playing) {
if (hasInstances(context)) {
performUpdate(context, service, null, playing);
}
}
/**
* Check against {@link AppWidgetManager} if there are any instances of this widget.
*/
private boolean hasInstances(Context context) {
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, getClass()));
return (appWidgetIds.length > 0);
}
/**
* Update all active widget instances by pushing changes
*/
private void performUpdate(Context context, DownloadService service, int[] appWidgetIds, boolean playing) {
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), getLayout());
if(playing) {
views.setViewVisibility(R.id.widget_root, View.VISIBLE);
} else {
// Hide widget
SharedPreferences prefs = Util.getPreferences(context);
if(prefs.getBoolean(Constants.PREFERENCES_KEY_HIDE_WIDGET, false)) {
views.setViewVisibility(R.id.widget_root, View.GONE);
}
}
// Get Entry from current playing DownloadFile
MusicDirectory.Entry currentPlaying = null;
if(service == null) {
// Deserialize from playling list to setup
try {
PlayerQueue state = FileUtil.deserialize(context, DownloadServiceLifecycleSupport.FILENAME_DOWNLOADS_SER, PlayerQueue.class);
if (state != null && state.currentPlayingIndex != -1) {
currentPlaying = state.songs.get(state.currentPlayingIndex);
}
} catch(Exception e) {
Log.e(TAG, "Failed to grab current playing", e);
}
} else {
currentPlaying = service.getCurrentPlaying() == null ? null : service.getCurrentPlaying().getSong();
}
String title = currentPlaying == null ? null : currentPlaying.getTitle();
CharSequence artist = currentPlaying == null ? null : currentPlaying.getArtist();
CharSequence album = currentPlaying == null ? null : currentPlaying.getAlbum();
CharSequence errorState = null;
// Show error message?
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_SHARED) ||
status.equals(Environment.MEDIA_UNMOUNTED)) {
errorState = res.getText(R.string.widget_sdcard_busy);
} else if (status.equals(Environment.MEDIA_REMOVED)) {
errorState = res.getText(R.string.widget_sdcard_missing);
} else if (currentPlaying == null) {
errorState = res.getText(R.string.widget_initial_text);
}
if (errorState != null) {
// Show error state to user
views.setTextViewText(R.id.title,null);
views.setTextViewText(R.id.artist, errorState);
views.setTextViewText(R.id.album, "");
if(getLayout() != R.layout.appwidget4x1) {
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_default);
}
} else {
// No error, so show normal titles
views.setTextViewText(R.id.title, title);
views.setTextViewText(R.id.artist, artist);
if(getLayout() != R.layout.appwidget4x1) {
views.setTextViewText(R.id.album, album);
}
}
// Set correct drawable for pause state
if (playing) {
views.setImageViewResource(R.id.control_play, R.drawable.media_pause_dark);
} else {
views.setImageViewResource(R.id.control_play, R.drawable.media_start_dark);
}
// Set the cover art
try {
boolean large = false;
if(getLayout() != R.layout.appwidget4x1 && getLayout() != R.layout.appwidget4x2) {
large = true;
}
ImageLoader imageLoader = SubsonicActivity.getStaticImageLoader(context);
Bitmap bitmap = imageLoader == null ? null : imageLoader.getCachedImage(context, currentPlaying, large);
if (bitmap == null) {
// Set default cover art
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_unknown);
} else {
bitmap = getRoundedCornerBitmap(bitmap);
views.setImageViewBitmap(R.id.appwidget_coverart, bitmap);
}
} catch (Exception x) {
Log.e(TAG, "Failed to load cover art", x);
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_unknown);
}
// Link actions buttons to intents
linkButtons(context, views, currentPlaying != null);
pushUpdate(context, appWidgetIds, views);
}
/**
* Round the corners of a bitmap for the cover art image
*/
private static Bitmap getRoundedCornerBitmap(Bitmap bitmap) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final float roundPx = 10;
// Add extra width to the rect so the right side wont be rounded.
final Rect rect = new Rect(0, 0, bitmap.getWidth() + (int) roundPx, bitmap.getHeight());
final RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
/**
* Link up various button actions using {@link PendingIntent}.
*
* @param playerActive @param playerActive True if player is active in background. Launch {@link github.nvllsvm.audinaut.activity.SubsonicFragmentActivity}.
*/
private void linkButtons(Context context, RemoteViews views, boolean playerActive) {
Intent intent = new Intent(context, SubsonicFragmentActivity.class);
intent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent);
views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent);
// Emulate media button clicks.
intent = new Intent("Audinaut.PLAY_PAUSE");
intent.setComponent(new ComponentName(context, DownloadService.class));
intent.setAction(DownloadService.CMD_TOGGLEPAUSE);
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
intent = new Intent("Audinaut.NEXT"); // Use a unique action name to ensure a different PendingIntent to be created.
intent.setComponent(new ComponentName(context, DownloadService.class));
intent.setAction(DownloadService.CMD_NEXT);
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
intent = new Intent("Audinaut.PREVIOUS"); // Use a unique action name to ensure a different PendingIntent to be created.
intent.setComponent(new ComponentName(context, DownloadService.class));
intent.setAction(DownloadService.CMD_PREVIOUS);
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
}
}

View File

@ -0,0 +1,61 @@
/*
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.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
/**
* Created by Scott on 8/28/13.
*/
public class MostRecentStubProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return "";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

View File

@ -0,0 +1,61 @@
/*
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.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
/**
* Created by Scott on 8/28/13.
*/
public class PlaylistStubProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return "";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

View File

@ -0,0 +1,47 @@
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);
}
}
}

View File

@ -0,0 +1,51 @@
/*
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();
}
}
}
}
}

View File

@ -0,0 +1,34 @@
/*
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);
}
}
}

View File

@ -0,0 +1,40 @@
/*
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);
}
}
}
}

View File

@ -0,0 +1,57 @@
/*
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.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
import github.nvllsvm.audinaut.service.DownloadService;
/**
* @author Sindre Mehus
*/
public class MediaButtonIntentReceiver extends BroadcastReceiver {
private static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if(DownloadService.getInstance() == null && (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_STOP ||
event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
Log.w(TAG, "Ignore keycode event because downloadService is off");
return;
}
Log.i(TAG, "Got MEDIA_BUTTON key event: " + event);
Intent serviceIntent = new Intent(context, DownloadService.class);
serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
context.startService(serviceIntent);
if (isOrderedBroadcast()) {
try {
abortBroadcast();
} catch (Exception x) {
// Ignored.
}
}
}
}

View File

@ -0,0 +1,46 @@
/*
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

View File

@ -0,0 +1,633 @@
/*
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

View File

@ -0,0 +1,430 @@
/*
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;
}
}
});
}
}
}

View File

@ -0,0 +1,66 @@
/*
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
}
}
}

View File

@ -0,0 +1,151 @@
/*
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();
}
}

View File

@ -0,0 +1,123 @@
/*
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;
}

View File

@ -0,0 +1,36 @@
/*
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;
}
}

View File

@ -0,0 +1,32 @@
/*
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;
/**
* Thrown by service methods that are not available in offline mode.
*
* @author Sindre Mehus
* @version $Id$
*/
public class OfflineException extends Exception {
public OfflineException(String message) {
super(message);
}
}

View File

@ -0,0 +1,638 @@
/*
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

View File

@ -0,0 +1,159 @@
/*
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.IOException;
import java.io.Reader;
import org.xmlpull.v1.XmlPullParser;
import android.content.Context;
import android.util.Log;
import android.util.Xml;
import github.nvllsvm.audinaut.R;
import github.nvllsvm.audinaut.domain.Version;
import github.nvllsvm.audinaut.util.ProgressListener;
import github.nvllsvm.audinaut.util.Util;
/**
* @author Sindre Mehus
*/
public abstract class AbstractParser {
private static final String TAG = AbstractParser.class.getSimpleName();
private static final String SUBSONIC_RESPONSE = "subsonic-response";
private static final String SUBSONIC = "subsonic";
protected final Context context;
protected final int instance;
private XmlPullParser parser;
private boolean rootElementFound;
public AbstractParser(Context context, int instance) {
this.context = context;
this.instance = instance;
}
protected Context getContext() {
return context;
}
protected void handleError() throws Exception {
int code = getInteger("code");
String message;
switch (code) {
case 0:
message = context.getResources().getString(R.string.parser_server_error, get("message"));
break;
case 20:
message = context.getResources().getString(R.string.parser_upgrade_client);
break;
case 30:
message = context.getResources().getString(R.string.parser_upgrade_server);
break;
case 40:
message = context.getResources().getString(R.string.parser_not_authenticated);
break;
case 41:
Util.setBlockTokenUse(context, instance, true);
// Throw IOException so RESTMusicService knows to retry
throw new IOException();
case 50:
message = context.getResources().getString(R.string.parser_not_authorized);
break;
default:
message = get("message");
break;
}
throw new SubsonicRESTException(code, message);
}
protected void updateProgress(ProgressListener progressListener, int messageId) {
if (progressListener != null) {
progressListener.updateProgress(messageId);
}
}
protected void updateProgress(ProgressListener progressListener, String message) {
if (progressListener != null) {
progressListener.updateProgress(message);
}
}
protected String getText() {
return parser.getText();
}
protected String get(String name) {
return parser.getAttributeValue(null, name);
}
protected boolean getBoolean(String name) {
return "true".equals(get(name));
}
protected Integer getInteger(String name) {
String s = get(name);
try {
return (s == null || "".equals(s)) ? null : Integer.valueOf(s);
} catch(Exception e) {
Log.w(TAG, "Failed to parse " + s + " into integer");
return null;
}
}
protected Long getLong(String name) {
String s = get(name);
return s == null ? null : Long.valueOf(s);
}
protected Float getFloat(String name) {
String s = get(name);
return s == null ? null : Float.valueOf(s);
}
protected void init(Reader reader) throws Exception {
parser = Xml.newPullParser();
parser.setInput(reader);
rootElementFound = false;
}
protected int nextParseEvent() throws Exception {
try {
return parser.next();
} catch(Exception e) {
throw e;
}
}
protected String getElementName() {
String name = parser.getName();
if (SUBSONIC_RESPONSE.equals(name)) {
rootElementFound = true;
String version = get("version");
}
return name;
}
protected void validate() throws Exception {
if (!rootElementFound) {
throw new Exception(context.getResources().getString(R.string.background_task_parse_error));
}
}
}

View File

@ -0,0 +1,66 @@
/*
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.util.ProgressListener;
import org.xmlpull.v1.XmlPullParser;
import java.io.Reader;
/**
* @author Sindre Mehus
*/
public class EntryListParser extends MusicDirectoryEntryParser {
public EntryListParser(Context context, int instance) {
super(context, instance);
}
public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception {
init(reader);
MusicDirectory dir = new MusicDirectory();
int eventType;
do {
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG) {
String name = getElementName();
if ("album".equals(name)) {
MusicDirectory.Entry entry = parseEntry("");
if(get("isDir") == null) {
entry.setDirectory(true);
}
dir.addChild(entry);
} else if ("song".equals(name)) {
MusicDirectory.Entry entry = parseEntry("");
dir.addChild(entry);
} else if ("error".equals(name)) {
handleError();
}
}
} while (eventType != XmlPullParser.END_DOCUMENT);
validate();
return dir;
}
}

View File

@ -0,0 +1,49 @@
/*
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 org.xmlpull.v1.XmlPullParser;
import java.io.Reader;
/**
* @author Sindre Mehus
*/
public class ErrorParser extends AbstractParser {
public ErrorParser(Context context, int instance) {
super(context, instance);
}
public void parse(Reader reader) throws Exception {
init(reader);
int eventType;
do {
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG && "error".equals(getElementName())) {
handleError();
}
} while (eventType != XmlPullParser.END_DOCUMENT);
validate();
}
}

View File

@ -0,0 +1,122 @@
/*
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 (&amp;apos;)
xml = xml.replaceAll("(?:&amp;)(amp;|lt;|gt;|#37;|apos;)", "&$1");
// Replace unescaped ampersand
xml = xml.replaceAll("&(?!amp;|lt;|gt;|#37;|apos;)", "&amp;");
// Replace unescaped percent symbol
// No replacements for <> at this time
xml = xml.replaceAll("%", "&#37;");
xml = xml.replaceAll("'", "&apos;");
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);
}
}

View File

@ -0,0 +1,129 @@
/*
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);
}
}

Some files were not shown because too many files have changed in this diff Show More