diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4838c7c
--- /dev/null
+++ b/.gitignore
@@ -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
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..91dd332
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "ServerProxy"]
+ path = ServerProxy
+ url = https://github.com/daneren2005/ServerProxy.git
diff --git a/Audinaut.iml b/Audinaut.iml
new file mode 100644
index 0000000..561e0d8
--- /dev/null
+++ b/Audinaut.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 54999e6..ff1c333 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,19 @@
# Audinaut
-A Libresonic client for Android.
+
+A FOSS Libresonic client for Android.
+
+## Building
+```
+git submodule update --init
+gradle assemble
+```
+
+## SDK Project Dependencies
+Under sdk -> extras:
+android -> support -> v7 -> appcompat
+android -> support -> v7 -> mediarouter
+
+## SDK Library Dependencies
+android -> support -> v4 -> android-support-v4.jar
+android -> support -> v7 -> appcompat -> libs android-support-v7-appcompat.jar
+android -> support -> v7 -> mediarouter -> libs -> android-support-v7-mediarouter.jar
diff --git a/ServerProxy b/ServerProxy
new file mode 160000
index 0000000..a4d9573
--- /dev/null
+++ b/ServerProxy
@@ -0,0 +1 @@
+Subproject commit a4d957353db2634906e0d5099d7a078a111bfab9
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..e8fa30f
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+*.iml
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..dcd8a59
--- /dev/null
+++ b/app/build.gradle
@@ -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'
+}
diff --git a/app/libs/kryo-2.21-all.jar b/app/libs/kryo-2.21-all.jar
new file mode 100644
index 0000000..83f8b0f
Binary files /dev/null and b/app/libs/kryo-2.21-all.jar differ
diff --git a/app/proguard.cfg b/app/proguard.cfg
new file mode 100644
index 0000000..a18ae91
--- /dev/null
+++ b/app/proguard.cfg
@@ -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 ;
+}
+
+-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
\ No newline at end of file
diff --git a/app/src/androidTest/java/github/nvllsvm/audinaut/ApplicationTest.java b/app/src/androidTest/java/github/nvllsvm/audinaut/ApplicationTest.java
new file mode 100644
index 0000000..e5d0567
--- /dev/null
+++ b/app/src/androidTest/java/github/nvllsvm/audinaut/ApplicationTest.java
@@ -0,0 +1,13 @@
+package github.nvllsvm.audinaut;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/github/nvllsvm/audinaut/activity/SubsonicFragmentActivityTest.java b/app/src/androidTest/java/github/nvllsvm/audinaut/activity/SubsonicFragmentActivityTest.java
new file mode 100644
index 0000000..0458300
--- /dev/null
+++ b/app/src/androidTest/java/github/nvllsvm/audinaut/activity/SubsonicFragmentActivityTest.java
@@ -0,0 +1,34 @@
+package github.nvllsvm.audinaut.activity;
+
+import github.nvllsvm.audinaut.R;
+import android.test.ActivityInstrumentationTestCase2;
+
+public class SubsonicFragmentActivityTest extends
+ ActivityInstrumentationTestCase2 {
+
+ 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));
+ }
+}
diff --git a/app/src/androidTest/java/github/nvllsvm/audinaut/domain/GenreComparatorTest.java b/app/src/androidTest/java/github/nvllsvm/audinaut/domain/GenreComparatorTest.java
new file mode 100644
index 0000000..df36d9e
--- /dev/null
+++ b/app/src/androidTest/java/github/nvllsvm/audinaut/domain/GenreComparatorTest.java
@@ -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 genres = new ArrayList();
+ genres.add(g1);
+ genres.add(g2);
+
+ List 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 genres = new ArrayList();
+ genres.add(g1);
+ genres.add(g2);
+
+ List 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 genres = new ArrayList();
+ genres.add(g1);
+ genres.add(g2);
+ genres.add(g3);
+
+ List sortedGenre = Genre.GenreComparator.sort(genres);
+ assertEquals(sortedGenre.get(0), g2);
+ assertEquals(sortedGenre.get(1), g3);
+ assertEquals(sortedGenre.get(2), g1);
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/github/nvllsvm/audinaut/service/DownloadServiceTest.java b/app/src/androidTest/java/github/nvllsvm/audinaut/service/DownloadServiceTest.java
new file mode 100644
index 0000000..9366f2b
--- /dev/null
+++ b/app/src/androidTest/java/github/nvllsvm/audinaut/service/DownloadServiceTest.java
@@ -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 {
+
+ 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 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 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 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 musicEntries = new LinkedList();
+
+ for (int i = 0; i < size; i++) {
+ musicEntries.add(musicEntry);
+ }
+
+ return musicEntries;
+
+ }
+
+}
diff --git a/app/src/audinaut-stacktrace.txt b/app/src/audinaut-stacktrace.txt
new file mode 100644
index 0000000..57a6a54
--- /dev/null
+++ b/app/src/audinaut-stacktrace.txt
@@ -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)
diff --git a/app/src/main/.gradle/3.1/taskArtifacts/cache.properties b/app/src/main/.gradle/3.1/taskArtifacts/cache.properties
new file mode 100644
index 0000000..9e3a6ad
--- /dev/null
+++ b/app/src/main/.gradle/3.1/taskArtifacts/cache.properties
@@ -0,0 +1 @@
+#Sat Oct 01 15:10:08 EDT 2016
diff --git a/app/src/main/.gradle/3.1/taskArtifacts/cache.properties.lock b/app/src/main/.gradle/3.1/taskArtifacts/cache.properties.lock
new file mode 100644
index 0000000..405bc39
Binary files /dev/null and b/app/src/main/.gradle/3.1/taskArtifacts/cache.properties.lock differ
diff --git a/app/src/main/.gradle/3.1/taskArtifacts/fileSnapshots.bin b/app/src/main/.gradle/3.1/taskArtifacts/fileSnapshots.bin
new file mode 100644
index 0000000..bb89b57
Binary files /dev/null and b/app/src/main/.gradle/3.1/taskArtifacts/fileSnapshots.bin differ
diff --git a/app/src/main/.gradle/3.1/taskArtifacts/taskArtifacts.bin b/app/src/main/.gradle/3.1/taskArtifacts/taskArtifacts.bin
new file mode 100644
index 0000000..60fe4e4
Binary files /dev/null and b/app/src/main/.gradle/3.1/taskArtifacts/taskArtifacts.bin differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3b66ddb
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/github/nvllsvm/audinaut/activity/EditPlayActionActivity.java b/app/src/main/java/github/nvllsvm/audinaut/activity/EditPlayActionActivity.java
new file mode 100644
index 0000000..47b6ffb
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/activity/EditPlayActionActivity.java
@@ -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 .
+ 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>(context, true) {
+ @Override
+ protected List doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ return musicService.getGenres(false, context, this);
+ }
+
+ @Override
+ protected void done(final List genres) {
+ List names = new ArrayList();
+ 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 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 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();
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/activity/QueryReceiverActivity.java b/app/src/main/java/github/nvllsvm/audinaut/activity/QueryReceiverActivity.java
new file mode 100644
index 0000000..52a8f19
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/activity/QueryReceiverActivity.java
@@ -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 .
+
+ 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);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/activity/SettingsActivity.java b/app/src/main/java/github/nvllsvm/audinaut/activity/SettingsActivity.java
new file mode 100644
index 0000000..06a314b
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/activity/SettingsActivity.java
@@ -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 .
+
+ 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);
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/activity/SubsonicActivity.java b/app/src/main/java/github/nvllsvm/audinaut/activity/SubsonicActivity.java
new file mode 100644
index 0000000..3789292
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/activity/SubsonicActivity.java
@@ -0,0 +1,1055 @@
+/*
+ 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 .
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.nvllsvm.audinaut.activity;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.support.design.widget.NavigationView;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import github.nvllsvm.audinaut.R;
+import github.nvllsvm.audinaut.fragments.SubsonicFragment;
+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.DrawableTint;
+import github.nvllsvm.audinaut.util.ImageLoader;
+import github.nvllsvm.audinaut.util.SilentBackgroundTask;
+import github.nvllsvm.audinaut.util.ThemeUtil;
+import github.nvllsvm.audinaut.util.Util;
+import github.nvllsvm.audinaut.view.UpdateView;
+import github.nvllsvm.audinaut.util.UserUtil;
+
+import static android.Manifest.*;
+
+public class SubsonicActivity extends AppCompatActivity implements OnItemSelectedListener {
+ private static final String TAG = SubsonicActivity.class.getSimpleName();
+ private static ImageLoader IMAGE_LOADER;
+ protected static String theme;
+ protected static boolean fullScreen;
+ protected static boolean actionbarColored;
+ private static final int MENU_GROUP_SERVER = 10;
+ private static final int MENU_ITEM_SERVER_BASE = 100;
+ private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
+
+ private final List afterServiceAvailable = new ArrayList<>();
+ private boolean drawerIdle = true;
+ private boolean destroyed = false;
+ private boolean finished = false;
+ protected List backStack = new ArrayList();
+ protected SubsonicFragment currentFragment;
+ protected View primaryContainer;
+ protected View secondaryContainer;
+ protected boolean tv = false;
+ protected boolean touchscreen = true;
+ protected Handler handler = new Handler();
+ Spinner actionBarSpinner;
+ ArrayAdapter spinnerAdapter;
+ ViewGroup rootView;
+ DrawerLayout drawer;
+ ActionBarDrawerToggle drawerToggle;
+ NavigationView drawerList;
+ View drawerHeader;
+ ImageView drawerHeaderToggle;
+ TextView drawerServerName;
+ TextView drawerUserName;
+ int lastSelectedPosition = 0;
+ boolean showingTabs = true;
+ boolean drawerOpen = false;
+ SharedPreferences.OnSharedPreferenceChangeListener preferencesListener;
+
+ static {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO);
+ }
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+ // tv = true;
+ }
+ PackageManager pm = getPackageManager();
+ if(!pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
+ touchscreen = false;
+ }
+
+ setUncaughtExceptionHandler();
+ applyTheme();
+ applyFullscreen();
+ super.onCreate(bundle);
+ startService(new Intent(this, DownloadService.class));
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ if(getIntent().hasExtra(Constants.FRAGMENT_POSITION)) {
+ lastSelectedPosition = getIntent().getIntExtra(Constants.FRAGMENT_POSITION, 0);
+ }
+
+ if(preferencesListener == null) {
+ Util.getPreferences(this).registerOnSharedPreferenceChangeListener(preferencesListener);
+ }
+
+ if (ContextCompat.checkSelfPermission(this, permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{ permission.WRITE_EXTERNAL_STORAGE }, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ switch (requestCode) {
+ case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: {
+ // If request is cancelled, the result arrays are empty.
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+
+ } else {
+ Util.toast(this, R.string.permission_external_storage_failed);
+ finish();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ if(spinnerAdapter == null) {
+ createCustomActionBarView();
+ }
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+
+ if(Util.shouldStartOnHeadphones(this)) {
+ Intent serviceIntent = new Intent();
+ serviceIntent.setClassName(this.getPackageName(), HeadphoneListenerService.class.getName());
+ this.startService(serviceIntent);
+ }
+ }
+
+ protected void createCustomActionBarView() {
+ actionBarSpinner = (Spinner) getLayoutInflater().inflate(R.layout.actionbar_spinner, null);
+ if((this instanceof SubsonicFragmentActivity || this instanceof SettingsActivity) && (Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true) || ThemeUtil.getThemeRes(this) != R.style.Theme_Audinaut_Light_No_Color)) {
+ actionBarSpinner.setBackgroundDrawable(DrawableTint.getTintedDrawableFromColor(this, R.drawable.abc_spinner_mtrl_am_alpha, android.R.color.white));
+ }
+ spinnerAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item);
+ spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ actionBarSpinner.setOnItemSelectedListener(this);
+ actionBarSpinner.setAdapter(spinnerAdapter);
+
+ getSupportActionBar().setCustomView(actionBarSpinner);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Util.registerMediaButtonEventReceiver(this);
+
+ // Make sure to update theme
+ SharedPreferences prefs = Util.getPreferences(this);
+ if (theme != null && !theme.equals(ThemeUtil.getTheme(this)) || fullScreen != prefs.getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false) || actionbarColored != prefs.getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
+ restart();
+ overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
+ DrawableTint.wipeTintCache();
+ }
+
+ populateTabs();
+ getImageLoader().onUIVisible();
+ UpdateView.addActiveActivity();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ UpdateView.removeActiveActivity();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ destroyed = true;
+ Util.getPreferences(this).unregisterOnSharedPreferenceChangeListener(preferencesListener);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ Util.disablePendingTransition(this);
+ }
+
+ @Override
+ public void setContentView(int viewId) {
+ if(isTv()) {
+ super.setContentView(R.layout.static_drawer_activity);
+ } else {
+ super.setContentView(R.layout.abstract_activity);
+ }
+ rootView = (ViewGroup) findViewById(R.id.content_frame);
+
+ if(viewId != 0) {
+ LayoutInflater layoutInflater = getLayoutInflater();
+ layoutInflater.inflate(viewId, rootView);
+ }
+
+ drawerList = (NavigationView) findViewById(R.id.left_drawer);
+ drawerList.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(final MenuItem menuItem) {
+ if(showingTabs) {
+ // Settings are on a different selectable track
+ if (menuItem.getItemId() != R.id.drawer_settings && menuItem.getItemId() != R.id.drawer_offline) {
+ menuItem.setChecked(true);
+ lastSelectedPosition = menuItem.getItemId();
+ }
+
+ switch (menuItem.getItemId()) {
+ case R.id.drawer_library:
+ drawerItemSelected("Artist");
+ return true;
+ case R.id.drawer_playlists:
+ drawerItemSelected("Playlist");
+ return true;
+ case R.id.drawer_downloading:
+ drawerItemSelected("Download");
+ return true;
+ case R.id.drawer_offline:
+ toggleOffline();
+ return true;
+ case R.id.drawer_settings:
+ startActivity(new Intent(SubsonicActivity.this, SettingsActivity.class));
+ drawer.closeDrawers();
+ return true;
+ }
+ } else {
+ int activeServer = menuItem.getItemId() - MENU_ITEM_SERVER_BASE;
+ SubsonicActivity.this.setActiveServer(activeServer);
+ populateTabs();
+ return true;
+ }
+
+ return false;
+ }
+ });
+
+ drawerHeader = drawerList.inflateHeaderView(R.layout.drawer_header);
+ drawerHeader.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if(showingTabs) {
+ populateServers();
+ } else {
+ populateTabs();
+ }
+ }
+ });
+
+ drawerHeaderToggle = (ImageView) drawerHeader.findViewById(R.id.header_select_image);
+ drawerServerName = (TextView) drawerHeader.findViewById(R.id.header_server_name);
+ drawerUserName = (TextView) drawerHeader.findViewById(R.id.header_user_name);
+
+ updateDrawerHeader();
+
+ if(!isTv()) {
+ drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+
+ // Pass in toolbar if it exists
+ Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
+ drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.common_appname, R.string.common_appname) {
+ @Override
+ public void onDrawerClosed(View view) {
+ drawerIdle = true;
+ drawerOpen = false;
+
+ if(!showingTabs) {
+ populateTabs();
+ }
+ }
+
+ @Override
+ public void onDrawerOpened(View view) {
+ DownloadService downloadService = getDownloadService();
+ boolean downloadingVisible = downloadService != null && !downloadService.getBackgroundDownloads().isEmpty();
+ if(lastSelectedPosition == R.id.drawer_downloading) {
+ downloadingVisible = true;
+ }
+ setDrawerItemVisible(R.id.drawer_downloading, downloadingVisible);
+
+ drawerIdle = true;
+ drawerOpen = true;
+ }
+
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ super.onDrawerSlide(drawerView, slideOffset);
+ drawerIdle = false;
+ }
+ };
+ drawer.setDrawerListener(drawerToggle);
+ drawerToggle.setDrawerIndicatorEnabled(true);
+
+ drawer.setOnTouchListener(new View.OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ if (drawerIdle && currentFragment != null && currentFragment.getGestureDetector() != null) {
+ return currentFragment.getGestureDetector().onTouchEvent(event);
+ } else {
+ return false;
+ }
+ }
+ });
+ }
+
+ // Check whether this is a tablet or not
+ secondaryContainer = findViewById(R.id.fragment_second_container);
+ if(secondaryContainer != null) {
+ primaryContainer = findViewById(R.id.fragment_container);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ super.onSaveInstanceState(savedInstanceState);
+ String[] ids = new String[backStack.size() + 1];
+ ids[0] = currentFragment.getTag();
+ int i = 1;
+ for(SubsonicFragment frag: backStack) {
+ ids[i] = frag.getTag();
+ i++;
+ }
+ savedInstanceState.putStringArray(Constants.MAIN_BACK_STACK, ids);
+ savedInstanceState.putInt(Constants.MAIN_BACK_STACK_SIZE, backStack.size() + 1);
+ savedInstanceState.putInt(Constants.FRAGMENT_POSITION, lastSelectedPosition);
+ }
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ int size = savedInstanceState.getInt(Constants.MAIN_BACK_STACK_SIZE);
+ String[] ids = savedInstanceState.getStringArray(Constants.MAIN_BACK_STACK);
+ FragmentManager fm = getSupportFragmentManager();
+ currentFragment = (SubsonicFragment)fm.findFragmentByTag(ids[0]);
+ currentFragment.setPrimaryFragment(true);
+ currentFragment.setSupportTag(ids[0]);
+ supportInvalidateOptionsMenu();
+ FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+ for(int i = 1; i < size; i++) {
+ SubsonicFragment frag = (SubsonicFragment)fm.findFragmentByTag(ids[i]);
+ frag.setSupportTag(ids[i]);
+ if(secondaryContainer != null) {
+ frag.setPrimaryFragment(false, true);
+ }
+ trans.hide(frag);
+ backStack.add(frag);
+ }
+ trans.commit();
+
+ // Current fragment is hidden in secondaryContainer
+ if(secondaryContainer == null && !currentFragment.isVisible()) {
+ trans = getSupportFragmentManager().beginTransaction();
+ trans.remove(currentFragment);
+ trans.commit();
+ getSupportFragmentManager().executePendingTransactions();
+
+ trans = getSupportFragmentManager().beginTransaction();
+ trans.add(R.id.fragment_container, currentFragment, ids[0]);
+ trans.commit();
+ }
+ // Current fragment needs to be moved over to secondaryContainer
+ else if(secondaryContainer != null && secondaryContainer.findViewById(currentFragment.getRootId()) == null && backStack.size() > 0) {
+ trans = getSupportFragmentManager().beginTransaction();
+ trans.remove(currentFragment);
+ trans.show(backStack.get(backStack.size() - 1));
+ trans.commit();
+ getSupportFragmentManager().executePendingTransactions();
+
+ trans = getSupportFragmentManager().beginTransaction();
+ trans.add(R.id.fragment_second_container, currentFragment, ids[0]);
+ trans.commit();
+
+ secondaryContainer.setVisibility(View.VISIBLE);
+ }
+
+ lastSelectedPosition = savedInstanceState.getInt(Constants.FRAGMENT_POSITION);
+ if(lastSelectedPosition != 0) {
+ MenuItem item = drawerList.getMenu().findItem(lastSelectedPosition);
+ if(item != null) {
+ item.setChecked(true);
+ }
+ }
+ recreateSpinner();
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ SubsonicFragment currentFragment = getCurrentFragment();
+ if(currentFragment != null) {
+ try {
+ SubsonicFragment fragment = getCurrentFragment();
+ fragment.setContext(this);
+ fragment.onCreateOptionsMenu(menu, menuInflater);
+
+ if(isTouchscreen()) {
+ menu.setGroupVisible(R.id.not_touchscreen, false);
+ }
+ } catch(Exception e) {
+ Log.w(TAG, "Error on creating options menu", e);
+ }
+ }
+ return true;
+ }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(drawerToggle != null && drawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ } else if(item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+
+ return getCurrentFragment().onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN;
+ boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP;
+ boolean isVolumeAdjust = isVolumeDown || isVolumeUp;
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ if(title != null && getSupportActionBar() != null && !title.equals(getSupportActionBar().getTitle())) {
+ getSupportActionBar().setTitle(title);
+ recreateSpinner();
+ }
+ }
+ public void setSubtitle(CharSequence title) {
+ getSupportActionBar().setSubtitle(title);
+ }
+
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ int top = spinnerAdapter.getCount() - 1;
+ if(position < top) {
+ for(int i = top; i > position && i >= 0; i--) {
+ removeCurrent();
+ }
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+
+ }
+
+ private void populateTabs() {
+ drawerList.getMenu().clear();
+ drawerList.inflateMenu(R.menu.drawer_navigation);
+
+ SharedPreferences prefs = Util.getPreferences(this);
+ boolean sharedEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_SHARED_ENABLED, true) && !Util.isOffline(this);
+
+ MenuItem offlineMenuItem = drawerList.getMenu().findItem(R.id.drawer_offline);
+ if(Util.isOffline(this)) {
+ setDrawerItemVisible(R.id.drawer_library, false);
+
+ if(lastSelectedPosition == 0 || lastSelectedPosition == R.id.drawer_library) {
+ String newFragment = Util.openToTab(this);
+ if(newFragment == null || "Library".equals(newFragment)) {
+ newFragment = "Artist";
+ }
+
+ lastSelectedPosition = getDrawerItemId(newFragment);
+ drawerItemSelected(newFragment);
+ }
+
+ offlineMenuItem.setTitle(R.string.main_online);
+ } else {
+ offlineMenuItem.setTitle(R.string.main_offline);
+ }
+
+ if(lastSelectedPosition != 0) {
+ MenuItem item = drawerList.getMenu().findItem(lastSelectedPosition);
+ if(item != null) {
+ item.setChecked(true);
+ }
+ }
+ drawerHeaderToggle.setImageResource(R.drawable.main_select_server_dark);
+
+ showingTabs = true;
+ }
+ private void populateServers() {
+ drawerList.getMenu().clear();
+
+ int serverCount = Util.getServerCount(this);
+ int activeServer = Util.getActiveServer(this);
+ for(int i = 1; i <= serverCount; i++) {
+ MenuItem item = drawerList.getMenu().add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_BASE + i, MENU_ITEM_SERVER_BASE + i, Util.getServerName(this, i));
+ if(activeServer == i) {
+ item.setChecked(true);
+ }
+ }
+ drawerList.getMenu().setGroupCheckable(MENU_GROUP_SERVER, true, true);
+ drawerHeaderToggle.setImageResource(R.drawable.main_select_tabs_dark);
+
+ showingTabs = false;
+ }
+ private void setDrawerItemVisible(int id, boolean visible) {
+ MenuItem item = drawerList.getMenu().findItem(id);
+ if(item != null) {
+ item.setVisible(visible);
+ }
+ }
+
+ protected void drawerItemSelected(String fragmentType) {
+ if(currentFragment != null) {
+ currentFragment.stopActionMode();
+ }
+ startFragmentActivity(fragmentType);
+ }
+
+ public void startFragmentActivity(String fragmentType) {
+ Intent intent = new Intent();
+ intent.setClass(SubsonicActivity.this, SubsonicFragmentActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if(!"".equals(fragmentType)) {
+ intent.putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, fragmentType);
+ }
+ if(lastSelectedPosition != 0) {
+ intent.putExtra(Constants.FRAGMENT_POSITION, lastSelectedPosition);
+ }
+ startActivity(intent);
+ finish();
+ }
+
+ protected void exit() {
+ if(((Object) this).getClass() != SubsonicFragmentActivity.class) {
+ Intent intent = new Intent(this, SubsonicFragmentActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true);
+ Util.startActivityWithoutTransition(this, intent);
+ } else {
+ finished = true;
+ this.stopService(new Intent(this, DownloadService.class));
+ this.finish();
+ }
+ }
+
+ public boolean onBackPressedSupport() {
+ if(drawerOpen) {
+ drawer.closeDrawers();
+ return false;
+ } else if(backStack.size() > 0) {
+ removeCurrent();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if(onBackPressedSupport()) {
+ super.onBackPressed();
+ }
+ }
+
+ public SubsonicFragment getCurrentFragment() {
+ return this.currentFragment;
+ }
+
+ public void replaceFragment(SubsonicFragment fragment, int tag) {
+ replaceFragment(fragment, tag, false);
+ }
+ public void replaceFragment(SubsonicFragment fragment, int tag, boolean replaceCurrent) {
+ SubsonicFragment oldFragment = currentFragment;
+ if(currentFragment != null) {
+ currentFragment.setPrimaryFragment(false, secondaryContainer != null);
+ }
+ backStack.add(currentFragment);
+
+ currentFragment = fragment;
+ currentFragment.setPrimaryFragment(true);
+ supportInvalidateOptionsMenu();
+
+ if(secondaryContainer == null || oldFragment.isAlwaysFullscreen() || currentFragment.isAlwaysStartFullscreen()) {
+ 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(oldFragment);
+ trans.add(R.id.fragment_container, fragment, tag + "");
+ trans.commit();
+ } else {
+ // Make sure secondary container is visible now
+ secondaryContainer.setVisibility(View.VISIBLE);
+
+ FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+
+ // Check to see if you need to put on top of old left or not
+ if(backStack.size() > 1) {
+ // Move old right to left if there is a backstack already
+ SubsonicFragment newLeftFragment = backStack.get(backStack.size() - 1);
+ if(replaceCurrent) {
+ // trans.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
+ }
+ trans.remove(newLeftFragment);
+
+ // Only move right to left if replaceCurrent is false
+ if(!replaceCurrent) {
+ SubsonicFragment oldLeftFragment = backStack.get(backStack.size() - 2);
+ oldLeftFragment.setSecondaryFragment(false);
+ // trans.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
+ trans.hide(oldLeftFragment);
+
+ // Make sure remove is finished before adding
+ trans.commit();
+ getSupportFragmentManager().executePendingTransactions();
+
+ 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.add(R.id.fragment_container, newLeftFragment, newLeftFragment.getSupportTag() + "");
+ } else {
+ backStack.remove(backStack.size() - 1);
+ }
+ }
+
+ // Add fragment to the right container
+ trans.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
+ trans.add(R.id.fragment_second_container, fragment, tag + "");
+
+ // Commit it all
+ trans.commit();
+
+ oldFragment.setIsOnlyVisible(false);
+ currentFragment.setIsOnlyVisible(false);
+ }
+ recreateSpinner();
+ }
+ public void removeCurrent() {
+ // Don't try to remove current if there is no backstack to remove from
+ if(backStack.isEmpty()) {
+ return;
+ }
+
+ if(currentFragment != null) {
+ currentFragment.setPrimaryFragment(false);
+ }
+ SubsonicFragment oldFragment = currentFragment;
+
+ currentFragment = backStack.remove(backStack.size() - 1);
+ currentFragment.setPrimaryFragment(true, false);
+ supportInvalidateOptionsMenu();
+
+ if(secondaryContainer == null || currentFragment.isAlwaysFullscreen() || oldFragment.isAlwaysStartFullscreen()) {
+ 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(oldFragment);
+ trans.show(currentFragment);
+ trans.commit();
+ } else {
+ FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+
+ // Remove old right fragment
+ trans.setCustomAnimations(R.anim.enter_from_left, R.anim.exit_to_right, R.anim.enter_from_right, R.anim.exit_to_left);
+ trans.remove(oldFragment);
+
+ // Only switch places if there is a backstack, otherwise primary container is correct
+ if(backStack.size() > 0 && !backStack.get(backStack.size() - 1).isAlwaysFullscreen() && !currentFragment.isAlwaysStartFullscreen()) {
+ trans.setCustomAnimations(0, 0, 0, 0);
+ // Add current left fragment to right side
+ trans.remove(currentFragment);
+
+ // Make sure remove is finished before adding
+ trans.commit();
+ getSupportFragmentManager().executePendingTransactions();
+
+ 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.add(R.id.fragment_second_container, currentFragment, currentFragment.getSupportTag() + "");
+
+ SubsonicFragment newLeftFragment = backStack.get(backStack.size() - 1);
+ newLeftFragment.setSecondaryFragment(true);
+ trans.show(newLeftFragment);
+ } else {
+ secondaryContainer.startAnimation(AnimationUtils.loadAnimation(this, R.anim.exit_to_right));
+ secondaryContainer.setVisibility(View.GONE);
+
+ currentFragment.setIsOnlyVisible(true);
+ }
+
+ trans.commit();
+ }
+ recreateSpinner();
+ }
+ public void replaceExistingFragment(SubsonicFragment fragment, int tag) {
+ FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+ trans.remove(currentFragment);
+ trans.add(R.id.fragment_container, fragment, tag + "");
+ trans.commit();
+
+ currentFragment = fragment;
+ currentFragment.setPrimaryFragment(true);
+ supportInvalidateOptionsMenu();
+ }
+
+ public void invalidate() {
+ if(currentFragment != null) {
+ while(backStack.size() > 0) {
+ removeCurrent();
+ }
+
+ currentFragment.invalidate();
+ populateTabs();
+ }
+
+ supportInvalidateOptionsMenu();
+ }
+
+ protected void recreateSpinner() {
+ if(currentFragment == null || currentFragment.getTitle() == null) {
+ return;
+ }
+ if(spinnerAdapter == null || getSupportActionBar().getCustomView() == null) {
+ createCustomActionBarView();
+ }
+
+ if(backStack.size() > 0) {
+ createCustomActionBarView();
+ spinnerAdapter.clear();
+ for(int i = 0; i < backStack.size(); i++) {
+ CharSequence title = backStack.get(i).getTitle();
+ if(title != null) {
+ spinnerAdapter.add(title);
+ } else {
+ spinnerAdapter.add("null");
+ }
+ }
+ if(currentFragment.getTitle() != null) {
+ spinnerAdapter.add(currentFragment.getTitle());
+ } else {
+ spinnerAdapter.add("null");
+ }
+ spinnerAdapter.notifyDataSetChanged();
+ actionBarSpinner.setSelection(spinnerAdapter.getCount() - 1);
+ if(!isTv()) {
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ getSupportActionBar().setDisplayShowCustomEnabled(true);
+ }
+
+ if(drawerToggle.isDrawerIndicatorEnabled()) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ drawerToggle.setDrawerIndicatorEnabled(false);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ } else if(!isTv()) {
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
+ getSupportActionBar().setTitle(currentFragment.getTitle());
+ getSupportActionBar().setDisplayShowCustomEnabled(false);
+ drawerToggle.setDrawerIndicatorEnabled(true);
+ }
+ }
+
+ protected void restart() {
+ restart(true);
+ }
+ protected void restart(boolean resumePosition) {
+ Intent intent = new Intent(this, this.getClass());
+ intent.putExtras(getIntent());
+ if(resumePosition) {
+ intent.putExtra(Constants.FRAGMENT_POSITION, lastSelectedPosition);
+ } else {
+ String fragmentType = Util.openToTab(this);
+ intent.putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, fragmentType);
+ intent.putExtra(Constants.FRAGMENT_POSITION, getDrawerItemId(fragmentType));
+ }
+ finish();
+ Util.startActivityWithoutTransition(this, intent);
+ }
+
+ private void applyTheme() {
+ theme = ThemeUtil.getTheme(this);
+
+ if(theme != null && theme.indexOf("fullscreen") != -1) {
+ theme = theme.substring(0, theme.indexOf("_fullscreen"));
+ ThemeUtil.setTheme(this, theme);
+ }
+
+ ThemeUtil.applyTheme(this, theme);
+ actionbarColored = Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true);
+ }
+ private void applyFullscreen() {
+ fullScreen = Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false);
+ if(fullScreen || isTv()) {
+ // Hide additional elements on higher Android versions
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+ getWindow().getDecorView().setSystemUiVisibility(flags);
+ } else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+ }
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+ }
+
+ public boolean isDestroyedCompat() {
+ return destroyed;
+ }
+
+ public synchronized ImageLoader getImageLoader() {
+ if (IMAGE_LOADER == null) {
+ IMAGE_LOADER = new ImageLoader(this);
+ }
+ return IMAGE_LOADER;
+ }
+ public synchronized static ImageLoader getStaticImageLoader(Context context) {
+ if (IMAGE_LOADER == null) {
+ IMAGE_LOADER = new ImageLoader(context);
+ }
+ return IMAGE_LOADER;
+ }
+
+ public DownloadService getDownloadService() {
+ if(finished) {
+ return null;
+ }
+
+ // If service is not available, request it to start and wait for it.
+ for (int i = 0; i < 5; i++) {
+ DownloadService downloadService = DownloadService.getInstance();
+ if (downloadService != null) {
+ break;
+ }
+ Log.w(TAG, "DownloadService not running. Attempting to start it.");
+ startService(new Intent(this, DownloadService.class));
+ Util.sleepQuietly(50L);
+ }
+
+ final DownloadService downloadService = DownloadService.getInstance();
+ if(downloadService != null && afterServiceAvailable.size() > 0) {
+ for(Runnable runnable: afterServiceAvailable) {
+ handler.post(runnable);
+ }
+ afterServiceAvailable.clear();
+ }
+ return downloadService;
+ }
+ public void runWhenServiceAvailable(Runnable runnable) {
+ if(getDownloadService() != null) {
+ runnable.run();
+ } else {
+ afterServiceAvailable.add(runnable);
+ checkIfServiceAvailable();
+ }
+ }
+ private void checkIfServiceAvailable() {
+ if(getDownloadService() == null) {
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ checkIfServiceAvailable();
+ }
+ }, 50);
+ } else if(afterServiceAvailable.size() > 0) {
+ for(Runnable runnable: afterServiceAvailable) {
+ handler.post(runnable);
+ }
+ afterServiceAvailable.clear();
+ }
+ }
+
+ public static String getThemeName() {
+ return theme;
+ }
+
+ public boolean isTv() {
+ return tv;
+ }
+ public boolean isTouchscreen() {
+ return touchscreen;
+ }
+
+ public void openNowPlaying() {
+
+ }
+ public void closeNowPlaying() {
+
+ }
+
+ public void setActiveServer(int instance) {
+ if (Util.getActiveServer(this) != instance) {
+ final DownloadService service = getDownloadService();
+ if (service != null) {
+ new SilentBackgroundTask(this) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ service.clearIncomplete();
+ return null;
+ }
+ }.execute();
+
+ }
+ Util.setActiveServer(this, instance);
+ invalidate();
+ UserUtil.refreshCurrentUser(this, false, true);
+ updateDrawerHeader();
+ }
+ }
+ public void updateDrawerHeader() {
+ if(Util.isOffline(this)) {
+ drawerServerName.setText(R.string.select_album_offline);
+ drawerUserName.setText("");
+ drawerHeader.setClickable(false);
+ drawerHeaderToggle.setVisibility(View.GONE);
+ } else {
+ drawerServerName.setText(Util.getServerName(this));
+ drawerUserName.setText(UserUtil.getCurrentUsername(this));
+ drawerHeader.setClickable(true);
+ drawerHeaderToggle.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void toggleOffline() {
+ boolean isOffline = Util.isOffline(this);
+ Util.setOffline(this, !isOffline);
+ invalidate();
+ DownloadService service = getDownloadService();
+ if (service != null) {
+ service.setOnline(isOffline);
+ }
+
+ UserUtil.seedCurrentUser(this);
+ this.updateDrawerHeader();
+ drawer.closeDrawers();
+ }
+
+ public int getDrawerItemId(String fragmentType) {
+ if(fragmentType == null) {
+ return R.id.drawer_library;
+ }
+
+ switch(fragmentType) {
+ case "Artist":
+ return R.id.drawer_library;
+ case "Playlist":
+ return R.id.drawer_playlists;
+ default:
+ return R.id.drawer_library;
+ }
+ }
+
+ private void setUncaughtExceptionHandler() {
+ Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
+ if (!(handler instanceof SubsonicActivity.SubsonicUncaughtExceptionHandler)) {
+ Thread.setDefaultUncaughtExceptionHandler(new SubsonicActivity.SubsonicUncaughtExceptionHandler(this));
+ }
+ }
+
+ /**
+ * Logs the stack trace of uncaught exceptions to a file on the SD card.
+ */
+ private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ private final Thread.UncaughtExceptionHandler defaultHandler;
+ private final Context context;
+
+ private SubsonicUncaughtExceptionHandler(Context context) {
+ this.context = context;
+ defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ File file = null;
+ PrintWriter printWriter = null;
+ try {
+
+ PackageInfo packageInfo = context.getPackageManager().getPackageInfo("github.nvllsvm.audinaut", 0);
+ file = new File(Environment.getExternalStorageDirectory(), "audinaut-stacktrace.txt");
+ printWriter = new PrintWriter(file);
+ printWriter.println("Android API level: " + Build.VERSION.SDK);
+ printWriter.println("Subsonic version name: " + packageInfo.versionName);
+ printWriter.println("Subsonic version code: " + packageInfo.versionCode);
+ printWriter.println();
+ throwable.printStackTrace(printWriter);
+ Log.i(TAG, "Stack trace written to " + file);
+ } catch (Throwable x) {
+ Log.e(TAG, "Failed to write stack trace to " + file, x);
+ } finally {
+ Util.close(printWriter);
+ if (defaultHandler != null) {
+ defaultHandler.uncaughtException(thread, throwable);
+ }
+
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/activity/SubsonicFragmentActivity.java b/app/src/main/java/github/nvllsvm/audinaut/activity/SubsonicFragmentActivity.java
new file mode 100644
index 0000000..7dca8a4
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/activity/SubsonicFragmentActivity.java
@@ -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 .
+
+ 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(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(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(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(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(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(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(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(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(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(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 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);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/activity/VoiceQueryReceiverActivity.java b/app/src/main/java/github/nvllsvm/audinaut/activity/VoiceQueryReceiverActivity.java
new file mode 100644
index 0000000..4f22542
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/activity/VoiceQueryReceiverActivity.java
@@ -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 .
+
+ 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);
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/AlphabeticalAlbumAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/AlphabeticalAlbumAdapter.java
new file mode 100644
index 0000000..282562e
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/AlphabeticalAlbumAdapter.java
@@ -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 .
+ 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 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());
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/ArtistAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/ArtistAdapter.java
new file mode 100644
index 0000000..ef934b3
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/ArtistAdapter.java
@@ -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 .
+ 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 implements FastScroller.BubbleTextGetter {
+ public static int VIEW_TYPE_SONG = 3;
+ public static int VIEW_TYPE_ARTIST = 4;
+
+ private List musicFolders;
+ private OnMusicFolderChanged onMusicFolderChanged;
+
+ public ArtistAdapter(Context context, List artists, OnItemClickedListener listener) {
+ this(context, artists, null, listener, null);
+ }
+
+ public ArtistAdapter(Context context, List artists, List 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);
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/BasicListAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/BasicListAdapter.java
new file mode 100644
index 0000000..c6f7c3b
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/BasicListAdapter.java
@@ -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 .
+ 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 {
+ public static int VIEW_TYPE_LINE = 1;
+
+ public BasicListAdapter(Context context, List 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;
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/DetailsAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/DetailsAdapter.java
new file mode 100644
index 0000000..f3ef084
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/DetailsAdapter.java
@@ -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 .
+ 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 {
+ private List headers;
+ private List details;
+
+ public DetailsAdapter(Context context, int layout, List headers, List 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;
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/DownloadFileAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/DownloadFileAdapter.java
new file mode 100644
index 0000000..e9b7b49
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/DownloadFileAdapter.java
@@ -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 .
+ 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 implements FastScroller.BubbleTextGetter {
+ public static int VIEW_TYPE_DOWNLOAD_FILE = 1;
+
+ public DownloadFileAdapter(Context context, List 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);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/EntryGridAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/EntryGridAdapter.java
new file mode 100644
index 0000000..88d9a03
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/EntryGridAdapter.java
@@ -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 .
+ 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 {
+ 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 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);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/EntryInfiniteGridAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/EntryInfiniteGridAdapter.java
new file mode 100644
index 0000000..dfd1a4b
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/EntryInfiniteGridAdapter.java
@@ -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 .
+ 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 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(context) {
+ private List 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 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 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();
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/ExpandableSectionAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/ExpandableSectionAdapter.java
new file mode 100644
index 0000000..6fdf3d4
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/ExpandableSectionAdapter.java
@@ -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 .
+ 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 extends SectionAdapter {
+ 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 sectionsDefaultVisible;
+ protected List> sectionsExtras;
+ protected int expandToggleRes;
+ protected int collapseToggleRes;
+
+ protected ExpandableSectionAdapter() {}
+ public ExpandableSectionAdapter(Context context, List section) {
+ List> sections = new ArrayList<>();
+ sections.add(section);
+
+ init(context, Arrays.asList("Section"), sections, Arrays.asList((Integer) null));
+ }
+ public ExpandableSectionAdapter(Context context, List headers, List> sections) {
+ init(context, headers, sections, null);
+ }
+ public ExpandableSectionAdapter(Context context, List headers, List> sections, List sectionsDefaultVisible) {
+ init(context, headers, sections, sectionsDefaultVisible);
+ }
+ protected void init(Context context, List headers, List> fullSections, List 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 fullSection: fullSections) {
+ List 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 visibleSelection = sections.get(sectionIndex);
+ List 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 visibleSelection = sections.get(sectionIndex);
+ List 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);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/GenreAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/GenreAdapter.java
new file mode 100644
index 0000000..aa97469
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/GenreAdapter.java
@@ -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 .
+ 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 implements FastScroller.BubbleTextGetter{
+ public static int VIEW_TYPE_GENRE = 1;
+
+ public GenreAdapter(Context context, List 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());
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/MainAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/MainAdapter.java
new file mode 100644
index 0000000..5a8bca6
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/MainAdapter.java
@@ -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 .
+ 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 {
+ public static final int VIEW_TYPE_ALBUM_LIST = 1;
+ public static final int VIEW_TYPE_ALBUM_COUNT_LIST = 2;
+
+ public MainAdapter(Context context, List headers, List> 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);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/PlaylistAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/PlaylistAdapter.java
new file mode 100644
index 0000000..9ac61dc
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/PlaylistAdapter.java
@@ -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 .
+ 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 implements FastScroller.BubbleTextGetter {
+ public static int VIEW_TYPE_PLAYLIST = 1;
+
+ private ImageLoader imageLoader;
+ private boolean largeCell;
+
+ public PlaylistAdapter(Context context, List playlists, ImageLoader imageLoader, boolean largeCell, OnItemClickedListener listener) {
+ super(context, playlists);
+ this.imageLoader = imageLoader;
+ this.largeCell = largeCell;
+ this.onItemClickedListener = listener;
+ }
+ public PlaylistAdapter(Context context, List headers, List> 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;
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/SearchAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/SearchAdapter.java
new file mode 100644
index 0000000..1e7376c
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/SearchAdapter.java
@@ -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 .
+ 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 {
+ 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> sections = new ArrayList<>();
+ List headers = new ArrayList<>();
+ List defaultVisible = new ArrayList<>();
+ Resources res = context.getResources();
+ if(!searchResult.getArtists().isEmpty()) {
+ sections.add((List) (List>) searchResult.getArtists());
+ headers.add(res.getString(R.string.search_artists));
+ defaultVisible.add(MAX_ARTISTS);
+ }
+ if(!searchResult.getAlbums().isEmpty()) {
+ sections.add((List) (List>) searchResult.getAlbums());
+ headers.add(res.getString(R.string.search_albums));
+ defaultVisible.add(MAX_ALBUMS);
+ }
+ if(!searchResult.getSongs().isEmpty()) {
+ sections.add((List) (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);
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/SectionAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/SectionAdapter.java
new file mode 100644
index 0000000..238a9aa
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/SectionAdapter.java
@@ -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 .
+ 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 extends RecyclerView.Adapter> {
+ private static String TAG = SectionAdapter.class.getSimpleName();
+ public static int VIEW_TYPE_HEADER = 0;
+ public static String[] ignoredArticles;
+
+ protected Context context;
+ protected List headers;
+ protected List> sections;
+ protected boolean singleSectionHeader;
+ protected OnItemClickedListener onItemClickedListener;
+ protected List selected = new ArrayList<>();
+ protected List selectedViews = new ArrayList<>();
+ protected ActionMode currentActionMode;
+ protected boolean checkable = false;
+
+ protected SectionAdapter() {}
+ public SectionAdapter(Context context, List section) {
+ this(context, section, false);
+ }
+ public SectionAdapter(Context context, List 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 headers, List> sections) {
+ this(context, headers, sections, true);
+ }
+ public SectionAdapter(Context context, List headers, List> sections, boolean singleSectionHeader){
+ this.context = context;
+ this.headers = headers;
+ this.sections = sections;
+ this.singleSectionHeader = singleSectionHeader;
+ }
+
+ public void replaceExistingData(List section) {
+ this.sections = new ArrayList<>();
+ this.sections.add(section);
+ notifyDataSetChanged();
+ }
+ public void replaceExistingData(List headers, List> 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 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 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 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 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 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 section: sections) {
+ subPosition += section.size() + 1;
+
+ int position = section.indexOf(item);
+ if(position != -1) {
+ return position + subPosition;
+ }
+ }
+
+ return -1;
+ }
+
+ public void setOnItemClickedListener(OnItemClickedListener onItemClickedListener) {
+ this.onItemClickedListener = onItemClickedListener;
+ }
+
+ public void addSelected(T item) {
+ selected.add(item);
+ }
+ public List getSelected() {
+ List 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 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 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 holder) {
+ final UpdateView 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 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 {
+ void onItemClicked(UpdateView updateView, T item);
+ void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView updateView, T item);
+ boolean onContextItemSelected(MenuItem menuItem, UpdateView updateView, T item);
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/adapter/SettingsAdapter.java b/app/src/main/java/github/nvllsvm/audinaut/adapter/SettingsAdapter.java
new file mode 100644
index 0000000..308b662
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/adapter/SettingsAdapter.java
@@ -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 .
+ 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 {
+ 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 headers, List> settingSections, ImageLoader imageLoader, boolean editable, OnItemClickedListener onItemClickedListener) {
+ super(context, headers, settingSections, imageLoader != null);
+ this.user = user;
+ this.imageLoader = imageLoader;
+ this.editable = editable;
+ this.onItemClickedListener = onItemClickedListener;
+
+ for(List 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 onItemClickedListener) {
+ return getSettingsAdapter(context, user, imageLoader, true, onItemClickedListener);
+ }
+ public static SettingsAdapter getSettingsAdapter(Context context, User user, ImageLoader imageLoader, boolean isEditable, OnItemClickedListener onItemClickedListener) {
+ List headers = new ArrayList<>();
+ List> 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);
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/audiofx/AudioEffectsController.java b/app/src/main/java/github/nvllsvm/audinaut/audiofx/AudioEffectsController.java
new file mode 100644
index 0000000..a852f93
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/audiofx/AudioEffectsController.java
@@ -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 .
+
+ 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;
+ }
+}
+
diff --git a/app/src/main/java/github/nvllsvm/audinaut/audiofx/EqualizerController.java b/app/src/main/java/github/nvllsvm/audinaut/audiofx/EqualizerController.java
new file mode 100644
index 0000000..59915a4
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/audiofx/EqualizerController.java
@@ -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 .
+
+ 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);
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/github/nvllsvm/audinaut/audiofx/LoudnessEnhancerController.java b/app/src/main/java/github/nvllsvm/audinaut/audiofx/LoudnessEnhancerController.java
new file mode 100644
index 0000000..75bfbd4
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/audiofx/LoudnessEnhancerController.java
@@ -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 .
+
+ 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;
+ }
+ }
+
+}
+
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/Artist.java b/app/src/main/java/github/nvllsvm/audinaut/domain/Artist.java
new file mode 100644
index 0000000..a183d6b
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/Artist.java
@@ -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 .
+
+ 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 {
+ 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 artists, String[] ignoredArticles) {
+ try {
+ Collections.sort(artists, new ArtistComparator(ignoredArticles));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to sort artists", e);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/Genre.java b/app/src/main/java/github/nvllsvm/audinaut/domain/Genre.java
new file mode 100644
index 0000000..77255f0
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/Genre.java
@@ -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 {
+ @Override
+ public int compare(Genre genre1, Genre genre2) {
+ return genre1.getName().compareToIgnoreCase(genre2.getName());
+ }
+
+ public static List sort(List genres) {
+ Collections.sort(genres, new GenreComparator());
+ return genres;
+ }
+
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/Indexes.java b/app/src/main/java/github/nvllsvm/audinaut/domain/Indexes.java
new file mode 100644
index 0000000..0de26c2
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/Indexes.java
@@ -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 .
+
+ 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 shortcuts;
+ private List artists;
+ private List entries;
+
+ public Indexes() {
+
+ }
+ public Indexes(long lastModified, List shortcuts, List artists) {
+ this.lastModified = lastModified;
+ this.shortcuts = shortcuts;
+ this.artists = artists;
+ this.entries = new ArrayList();
+ }
+ public Indexes(long lastModified, List shortcuts, List artists, List entries) {
+ this.lastModified = lastModified;
+ this.shortcuts = shortcuts;
+ this.artists = artists;
+ this.entries = entries;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public List getShortcuts() {
+ return shortcuts;
+ }
+
+ public List getArtists() {
+ return artists;
+ }
+
+ public void setArtists(List artists) {
+ this.shortcuts = new ArrayList();
+ this.artists.clear();
+ this.artists.addAll(artists);
+ }
+
+ public List 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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/MusicDirectory.java b/app/src/main/java/github/nvllsvm/audinaut/domain/MusicDirectory.java
new file mode 100644
index 0000000..28f7308
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/MusicDirectory.java
@@ -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 .
+
+ 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 children;
+
+ public MusicDirectory() {
+ children = new ArrayList();
+ }
+ public MusicDirectory(List 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 children) {
+ this.children.addAll(children);
+ }
+
+ public void replaceChildren(List children) {
+ this.children = children;
+ }
+
+ public synchronized List getChildren() {
+ return getChildren(true, true);
+ }
+
+ public synchronized List getChildren(boolean includeDirs, boolean includeFiles) {
+ if (includeDirs && includeFiles) {
+ return children;
+ }
+
+ List result = new ArrayList(children.size());
+ for (Entry child : children) {
+ if (child != null && child.isDirectory() && includeDirs || !child.isDirectory() && includeFiles) {
+ result.add(child);
+ }
+ }
+ return result;
+ }
+ public synchronized List getSongs() {
+ List result = new ArrayList();
+ 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 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 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 {
+ 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 entries) {
+ sort(entries, true);
+ }
+ public static void sort(List entries, boolean byYear) {
+ try {
+ Collections.sort(entries, new EntryComparator(byYear));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to sort MusicDirectory");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/MusicFolder.java b/app/src/main/java/github/nvllsvm/audinaut/domain/MusicFolder.java
new file mode 100644
index 0000000..5906e68
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/MusicFolder.java
@@ -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 .
+
+ 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 {
+ 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 musicFolders) {
+ try {
+ Collections.sort(musicFolders, new MusicFolderComparator());
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to sort music folders", e);
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/PlayerQueue.java b/app/src/main/java/github/nvllsvm/audinaut/domain/PlayerQueue.java
new file mode 100644
index 0000000..6232203
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/PlayerQueue.java
@@ -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 .
+ 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 songs = new ArrayList();
+ public List toDelete = new ArrayList();
+ public int currentPlayingIndex;
+ public int currentPlayingPosition;
+ public boolean renameCurrent = false;
+ public Date changed = null;
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/PlayerState.java b/app/src/main/java/github/nvllsvm/audinaut/domain/PlayerState.java
new file mode 100644
index 0000000..fe90e89
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/PlayerState.java
@@ -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 .
+
+ 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;
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/Playlist.java b/app/src/main/java/github/nvllsvm/audinaut/domain/Playlist.java
new file mode 100644
index 0000000..c79021a
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/Playlist.java
@@ -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 .
+
+ 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 {
+ @Override
+ public int compare(Playlist playlist1, Playlist playlist2) {
+ return playlist1.getName().compareToIgnoreCase(playlist2.getName());
+ }
+
+ public static List sort(List playlists) {
+ Collections.sort(playlists, new PlaylistComparator());
+ return playlists;
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/RemoteStatus.java b/app/src/main/java/github/nvllsvm/audinaut/domain/RemoteStatus.java
new file mode 100644
index 0000000..eba6a10
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/RemoteStatus.java
@@ -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 .
+
+ 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;
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/RepeatMode.java b/app/src/main/java/github/nvllsvm/audinaut/domain/RepeatMode.java
new file mode 100644
index 0000000..57f1ec8
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/RepeatMode.java
@@ -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();
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/SearchCritera.java b/app/src/main/java/github/nvllsvm/audinaut/domain/SearchCritera.java
new file mode 100644
index 0000000..631a7c5
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/SearchCritera.java
@@ -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 .
+
+ 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;
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/SearchResult.java b/app/src/main/java/github/nvllsvm/audinaut/domain/SearchResult.java
new file mode 100644
index 0000000..bd15043
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/SearchResult.java
@@ -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 .
+
+ 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 artists;
+ private final List albums;
+ private final List songs;
+
+ public SearchResult(List artists, List albums, List songs) {
+ this.artists = artists;
+ this.albums = albums;
+ this.songs = songs;
+ }
+
+ public List getArtists() {
+ return artists;
+ }
+
+ public List getAlbums() {
+ return albums;
+ }
+
+ public List getSongs() {
+ return songs;
+ }
+
+ public boolean hasArtists() {
+ return !artists.isEmpty();
+ }
+ public boolean hasAlbums() {
+ return !albums.isEmpty();
+ }
+ public boolean hasSongs() {
+ return !songs.isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/User.java b/app/src/main/java/github/nvllsvm/audinaut/domain/User.java
new file mode 100644
index 0000000..4a5e88b
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/User.java
@@ -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 .
+ 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 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 settings = new ArrayList();
+ private List 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 getSettings() {
+ return settings;
+ }
+ public void setSettings(List 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 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;
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/domain/Version.java b/app/src/main/java/github/nvllsvm/audinaut/domain/Version.java
new file mode 100644
index 0000000..a069dfd
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/domain/Version.java
@@ -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 .
+
+ 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, 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;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/github/nvllsvm/audinaut/fragments/DownloadFragment.java b/app/src/main/java/github/nvllsvm/audinaut/fragments/DownloadFragment.java
new file mode 100644
index 0000000..1247575
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/fragments/DownloadFragment.java
@@ -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 .
+ 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 implements SectionAdapter.OnItemClickedListener {
+ 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 objs) {
+ return new DownloadFileAdapter(context, objs, this);
+ }
+
+ @Override
+ public List getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
+ DownloadService downloadService = getDownloadService();
+ if(downloadService == null) {
+ return new ArrayList();
+ }
+
+ List songList = new ArrayList();
+ songList.addAll(downloadService.getBackgroundDownloads());
+ currentRevision = downloadService.getDownloadListUpdateRevision();
+ return songList;
+ }
+
+ @Override
+ public int getTitleResource() {
+ return R.string.button_bar_downloading;
+ }
+
+ @Override
+ public void onItemClicked(UpdateView updateView, DownloadFile item) {
+
+ }
+
+ @Override
+ public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView 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 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(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 downloadFileList = downloadService.getBackgroundDownloads();
+ objects.clear();
+ objects.addAll(downloadFileList);
+ adapter.notifyDataSetChanged();
+
+ currentRevision = downloadService.getDownloadListUpdateRevision();
+ }
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/fragments/EqualizerFragment.java b/app/src/main/java/github/nvllsvm/audinaut/fragments/EqualizerFragment.java
new file mode 100644
index 0000000..c2c477c
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/fragments/EqualizerFragment.java
@@ -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 .
+
+ 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 bars = new HashMap();
+ 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 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));
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/fragments/MainFragment.java b/app/src/main/java/github/nvllsvm/audinaut/fragments/MainFragment.java
new file mode 100644
index 0000000..e8d1325
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/fragments/MainFragment.java
@@ -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 {
+ 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> sections = new ArrayList<>();
+ List headers = new ArrayList<>();
+
+ List 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 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(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 headers = new ArrayList<>();
+ List 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(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(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 progs = new ArrayList();
+ 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 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 updateView, Integer item) {}
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem, UpdateView updateView, Integer item) {
+ return false;
+ }
+}
diff --git a/app/src/main/java/github/nvllsvm/audinaut/fragments/NowPlayingFragment.java b/app/src/main/java/github/nvllsvm/audinaut/fragments/NowPlayingFragment.java
new file mode 100644
index 0000000..f7c40f0
--- /dev/null
+++ b/app/src/main/java/github/nvllsvm/audinaut/fragments/NowPlayingFragment.java
@@ -0,0 +1,1109 @@
+/*
+ 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 .
+ Copyright 2014 (C) Scott Jackson
+*/
+package github.nvllsvm.audinaut.fragments;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import android.annotation.TargetApi;
+import android.support.v7.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.Log;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.ViewFlipper;
+import github.nvllsvm.audinaut.R;
+import github.nvllsvm.audinaut.activity.SubsonicFragmentActivity;
+import github.nvllsvm.audinaut.adapter.SectionAdapter;
+import github.nvllsvm.audinaut.audiofx.EqualizerController;
+import github.nvllsvm.audinaut.domain.PlayerState;
+import github.nvllsvm.audinaut.domain.RepeatMode;
+import github.nvllsvm.audinaut.service.DownloadFile;
+import github.nvllsvm.audinaut.service.DownloadService;
+import github.nvllsvm.audinaut.service.DownloadService.OnSongChangedListener;
+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.SilentBackgroundTask;
+import github.nvllsvm.audinaut.adapter.DownloadFileAdapter;
+import github.nvllsvm.audinaut.view.FadeOutAnimation;
+import github.nvllsvm.audinaut.view.FastScroller;
+import github.nvllsvm.audinaut.view.UpdateView;
+import github.nvllsvm.audinaut.util.Util;
+
+import static github.nvllsvm.audinaut.domain.MusicDirectory.Entry;
+import static github.nvllsvm.audinaut.domain.PlayerState.*;
+import github.nvllsvm.audinaut.util.*;
+import github.nvllsvm.audinaut.view.AutoRepeatButton;
+import java.util.ArrayList;
+import java.util.concurrent.ScheduledFuture;
+
+public class NowPlayingFragment extends SubsonicFragment implements OnGestureListener, SectionAdapter.OnItemClickedListener, OnSongChangedListener {
+ private static final String TAG = NowPlayingFragment.class.getSimpleName();
+ private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 10;
+
+ private static final int ACTION_PREVIOUS = 1;
+ private static final int ACTION_NEXT = 2;
+ private static final int ACTION_REWIND = 3;
+ private static final int ACTION_FORWARD = 4;
+
+ private ViewFlipper playlistFlipper;
+ private TextView emptyTextView;
+ private TextView songTitleTextView;
+ private ImageView albumArtImageView;
+ private RecyclerView playlistView;
+ private TextView positionTextView;
+ private TextView durationTextView;
+ private TextView statusTextView;
+ private SeekBar progressBar;
+ private AutoRepeatButton previousButton;
+ private AutoRepeatButton nextButton;
+ private AutoRepeatButton rewindButton;
+ private AutoRepeatButton fastforwardButton;
+ private View pauseButton;
+ private View stopButton;
+ private View startButton;
+ private ImageButton repeatButton;
+ private View toggleListButton;
+
+ private ScheduledExecutorService executorService;
+ private DownloadFile currentPlaying;
+ private int swipeDistance;
+ private int swipeVelocity;
+ private ScheduledFuture> hideControlsFuture;
+ private List songList;
+ private DownloadFileAdapter songListAdapter;
+ private boolean seekInProgress = false;
+ private boolean startFlipped = false;
+ private boolean scrollWhenLoaded = false;
+ private int lastY = 0;
+ private int currentPlayingSize = 0;
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if(savedInstanceState != null) {
+ if(savedInstanceState.getInt(Constants.FRAGMENT_DOWNLOAD_FLIPPER) == 1) {
+ startFlipped = true;
+ }
+ }
+ primaryFragment = false;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(Constants.FRAGMENT_DOWNLOAD_FLIPPER, playlistFlipper.getDisplayedChild());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ rootView = inflater.inflate(R.layout.download, container, false);
+ setTitle(R.string.button_bar_now_playing);
+
+ WindowManager w = context.getWindowManager();
+ Display d = w.getDefaultDisplay();
+ swipeDistance = (d.getWidth() + d.getHeight()) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100;
+ swipeVelocity = (d.getWidth() + d.getHeight()) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100;
+ gestureScanner = new GestureDetector(this);
+
+ playlistFlipper = (ViewFlipper)rootView.findViewById(R.id.download_playlist_flipper);
+ emptyTextView = (TextView)rootView.findViewById(R.id.download_empty);
+ songTitleTextView = (TextView)rootView.findViewById(R.id.download_song_title);
+ albumArtImageView = (ImageView)rootView.findViewById(R.id.download_album_art_image);
+ positionTextView = (TextView)rootView.findViewById(R.id.download_position);
+ durationTextView = (TextView)rootView.findViewById(R.id.download_duration);
+ statusTextView = (TextView)rootView.findViewById(R.id.download_status);
+ progressBar = (SeekBar)rootView.findViewById(R.id.download_progress_bar);
+ previousButton = (AutoRepeatButton)rootView.findViewById(R.id.download_previous);
+ nextButton = (AutoRepeatButton)rootView.findViewById(R.id.download_next);
+ rewindButton = (AutoRepeatButton) rootView.findViewById(R.id.download_rewind);
+ fastforwardButton = (AutoRepeatButton) rootView.findViewById(R.id.download_fastforward);
+ pauseButton =rootView.findViewById(R.id.download_pause);
+ stopButton =rootView.findViewById(R.id.download_stop);
+ startButton =rootView.findViewById(R.id.download_start);
+ repeatButton = (ImageButton)rootView.findViewById(R.id.download_repeat);
+ toggleListButton =rootView.findViewById(R.id.download_toggle_list);
+
+ playlistView = (RecyclerView)rootView.findViewById(R.id.download_list);
+ FastScroller fastScroller = (FastScroller) rootView.findViewById(R.id.download_fast_scroller);
+ fastScroller.attachRecyclerView(playlistView);
+ setupLayoutManager(playlistView, false);
+ ItemTouchHelper touchHelper = new ItemTouchHelper(new DownloadFileItemHelperCallback(this, true));
+ touchHelper.attachToRecyclerView(playlistView);
+
+ View.OnTouchListener touchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent me) {
+ return gestureScanner.onTouchEvent(me);
+ }
+ };
+ pauseButton.setOnTouchListener(touchListener);
+ stopButton.setOnTouchListener(touchListener);
+ startButton.setOnTouchListener(touchListener);
+ emptyTextView.setOnTouchListener(touchListener);
+ albumArtImageView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent me) {
+ if (me.getAction() == MotionEvent.ACTION_DOWN) {
+ lastY = (int) me.getRawY();
+ }
+ return gestureScanner.onTouchEvent(me);
+ }
+ });
+
+ previousButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ warnIfStorageUnavailable();
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().previous();
+ return null;
+ }
+ }.execute();
+ setControlsVisible(true);
+ }
+ });
+ previousButton.setOnRepeatListener(new Runnable() {
+ public void run() {
+ changeProgress(true);
+ }
+ });
+
+ nextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ warnIfStorageUnavailable();
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Boolean doInBackground() throws Throwable {
+ getDownloadService().next();
+ return true;
+ }
+ }.execute();
+ setControlsVisible(true);
+ }
+ });
+ nextButton.setOnRepeatListener(new Runnable() {
+ public void run() {
+ changeProgress(false);
+ }
+ });
+
+ rewindButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ changeProgress(true);
+ }
+ });
+ rewindButton.setOnRepeatListener(new Runnable() {
+ public void run() {
+ changeProgress(true);
+ }
+ });
+
+ fastforwardButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ changeProgress(false);
+ }
+ });
+ fastforwardButton.setOnRepeatListener(new Runnable() {
+ public void run() {
+ changeProgress(false);
+ }
+ });
+
+
+ pauseButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().pause();
+ return null;
+ }
+ }.execute();
+ }
+ });
+
+ stopButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().reset();
+ return null;
+ }
+ }.execute();
+ }
+ });
+
+ startButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ warnIfStorageUnavailable();
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ start();
+ return null;
+ }
+ }.execute();
+ }
+ });
+
+ repeatButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ RepeatMode repeatMode = getDownloadService().getRepeatMode().next();
+ getDownloadService().setRepeatMode(repeatMode);
+ switch (repeatMode) {
+ case OFF:
+ Util.toast(context, R.string.download_repeat_off);
+ break;
+ case ALL:
+ Util.toast(context, R.string.download_repeat_all);
+ break;
+ case SINGLE:
+ Util.toast(context, R.string.download_repeat_single);
+ break;
+ default:
+ break;
+ }
+ updateRepeatButton();
+ setControlsVisible(true);
+ }
+ });
+
+ toggleListButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ toggleFullscreenAlbumArt();
+ setControlsVisible(true);
+ }
+ });
+
+ View overlay = rootView.findViewById(R.id.download_overlay_buttons);
+ final int overlayHeight = overlay != null ? overlay.getHeight() : -1;
+ albumArtImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (overlayHeight == -1 || lastY < (view.getBottom() - overlayHeight)) {
+ toggleFullscreenAlbumArt();
+ setControlsVisible(true);
+ }
+ }
+ });
+
+ progressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onStopTrackingTouch(final SeekBar seekBar) {
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().seekTo(progressBar.getProgress());
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ seekInProgress = false;
+ }
+ }.execute();
+ }
+
+ @Override
+ public void onStartTrackingTouch(final SeekBar seekBar) {
+ seekInProgress = true;
+ }
+
+ @Override
+ public void onProgressChanged(final SeekBar seekBar, final int position, final boolean fromUser) {
+ if (fromUser) {
+ positionTextView.setText(Util.formatDuration(position / 1000));
+ setControlsVisible(true);
+ }
+ }
+ });
+
+ return rootView;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
+ DownloadService downloadService = getDownloadService();
+ if(Util.isOffline(context)) {
+ menuInflater.inflate(R.menu.nowplaying_offline, menu);
+ } else {
+ menuInflater.inflate(R.menu.nowplaying, menu);
+ }
+ if(downloadService != null && downloadService.isRemovePlayed()) {
+ menu.findItem(R.id.menu_remove_played).setChecked(true);
+ }
+
+ boolean equalizerAvailable = downloadService != null && downloadService.getEqualizerAvailable();
+ if(equalizerAvailable) {
+ SharedPreferences prefs = Util.getPreferences(context);
+ boolean equalizerOn = prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false);
+ if (equalizerOn && downloadService != null) {
+ if(downloadService.getEqualizerController() != null && downloadService.getEqualizerController().isEnabled()) {
+ menu.findItem(R.id.menu_equalizer).setChecked(true);
+ }
+ }
+ } else {
+ menu.removeItem(R.id.menu_equalizer);
+ }
+
+ if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, false)) {
+ menu.findItem(R.id.menu_batch_mode).setChecked(true);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ if(menuItemSelected(menuItem.getItemId(), null)) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(menuItem);
+ }
+
+ @Override
+ public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView updateView, DownloadFile downloadFile) {
+ if(Util.isOffline(context)) {
+ menuInflater.inflate(R.menu.nowplaying_context_offline, menu);
+ } else {
+ menuInflater.inflate(R.menu.nowplaying_context, menu);
+ }
+
+ if (downloadFile.getSong().getParent() == null) {
+ menu.findItem(R.id.menu_show_album).setVisible(false);
+ menu.findItem(R.id.menu_show_artist).setVisible(false);
+ }
+
+ MenuUtil.hideMenuItems(context, menu, updateView);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem, UpdateView updateView, DownloadFile downloadFile) {
+ if(onContextItemSelected(menuItem, downloadFile.getSong())) {
+ return true;
+ }
+
+ return menuItemSelected(menuItem.getItemId(), downloadFile);
+ }
+
+ private boolean menuItemSelected(int menuItemId, final DownloadFile song) {
+ List songs;
+ switch (menuItemId) {
+ case R.id.menu_show_album: case R.id.menu_show_artist:
+ Entry entry = song.getSong();
+
+ Intent intent = new Intent(context, SubsonicFragmentActivity.class);
+ intent.putExtra(Constants.INTENT_EXTRA_VIEW_ALBUM, true);
+ String albumId;
+ String albumName;
+ if(menuItemId == R.id.menu_show_album) {
+ if(Util.isTagBrowsing(context)) {
+ albumId = entry.getAlbumId();
+ } else {
+ albumId = entry.getParent();
+ }
+ albumName = entry.getAlbum();
+ } else {
+ if(Util.isTagBrowsing(context)) {
+ albumId = entry.getArtistId();
+ } else {
+ albumId = entry.getGrandParent();
+ if(albumId == null) {
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getParent());
+ }
+ }
+ albumName = entry.getArtist();
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_ARTIST, true);
+ }
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, albumId);
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, albumName);
+ intent.putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, "Artist");
+
+ if(Util.isOffline(context)) {
+ try {
+ // This should only be successful if this is a online song in offline mode
+ Integer.parseInt(entry.getParent());
+ String root = FileUtil.getMusicDirectory(context).getPath();
+ String id = root + "/" + entry.getPath();
+ id = id.substring(0, id.lastIndexOf("/"));
+ if(menuItemId == R.id.menu_show_album) {
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, id);
+ }
+ id = id.substring(0, id.lastIndexOf("/"));
+ if(menuItemId != R.id.menu_show_album) {
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, id);
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, entry.getArtist());
+ intent.removeExtra(Constants.INTENT_EXTRA_NAME_CHILD_ID);
+ }
+ } catch(Exception e) {
+ // Do nothing, entry.getParent() is fine
+ }
+ }
+
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Util.startActivityWithoutTransition(context, intent);
+ return true;
+ 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(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().setShufflePlayEnabled(false);
+ getDownloadService().clear();
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ context.closeNowPlaying();
+ }
+ }.execute();
+ }
+ });
+ return true;
+ case R.id.menu_remove_played:
+ if (getDownloadService().isRemovePlayed()) {
+ getDownloadService().setRemovePlayed(false);
+ } else {
+ getDownloadService().setRemovePlayed(true);
+ }
+ context.supportInvalidateOptionsMenu();
+ return true;
+ case R.id.menu_shuffle:
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().shuffle();
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ Util.toast(context, R.string.download_menu_shuffle_notification);
+ }
+ }.execute();
+ return true;
+ case R.id.menu_save_playlist:
+ List entries = new LinkedList();
+ for (DownloadFile downloadFile : getDownloadService().getSongs()) {
+ entries.add(downloadFile.getSong());
+ }
+ createNewPlaylist(entries, true);
+ return true;
+ case R.id.menu_info:
+ displaySongInfo(song.getSong());
+ return true;
+ case R.id.menu_equalizer: {
+ DownloadService downloadService = getDownloadService();
+ if (downloadService != null) {
+ EqualizerController controller = downloadService.getEqualizerController();
+ if(controller != null) {
+ SubsonicFragment fragment = new EqualizerFragment();
+ replaceFragment(fragment);
+ setControlsVisible(true);
+
+ return true;
+ }
+ }
+
+ // Any failed condition will get here
+ Util.toast(context, "Failed to start equalizer. Try restarting.");
+ return true;
+ }case R.id.menu_batch_mode:
+ if(Util.isBatchMode(context)) {
+ Util.setBatchMode(context, false);
+ songListAdapter.notifyDataSetChanged();
+ } else {
+ Util.setBatchMode(context, true);
+ songListAdapter.notifyDataSetChanged();
+ }
+ context.supportInvalidateOptionsMenu();
+
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if(this.primaryFragment) {
+ onResumeHandlers();
+ } else {
+ update();
+ }
+ }
+ private void onResumeHandlers() {
+ executorService = Executors.newSingleThreadScheduledExecutor();
+ setControlsVisible(true);
+
+ final DownloadService downloadService = getDownloadService();
+ if (downloadService == null || downloadService.getCurrentPlaying() == null || startFlipped) {
+ playlistFlipper.setDisplayedChild(1);
+ startFlipped = false;
+ }
+
+ updateButtons();
+
+ if(currentPlaying == null && downloadService != null && currentPlaying == downloadService.getCurrentPlaying()) {
+ getImageLoader().loadImage(albumArtImageView, (Entry) null, true, false);
+ }
+
+ context.runWhenServiceAvailable(new Runnable() {
+ @Override
+ public void run() {
+ if (primaryFragment) {
+ DownloadService downloadService = getDownloadService();
+ downloadService.addOnSongChangedListener(NowPlayingFragment.this, true);
+ }
+ updateRepeatButton();
+ updateTitle();
+ }
+ });
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ onPauseHandlers();
+ }
+ private void onPauseHandlers() {
+ if(executorService != null) {
+ DownloadService downloadService = getDownloadService();
+ if (downloadService != null) {
+ downloadService.removeOnSongChangeListener(this);
+ }
+ playlistFlipper.setDisplayedChild(0);
+ }
+ }
+
+ @Override
+ public void setPrimaryFragment(boolean primary) {
+ super.setPrimaryFragment(primary);
+ if(rootView != null) {
+ if(primary) {
+ onResumeHandlers();
+ } else {
+ onPauseHandlers();
+ }
+ }
+ }
+
+ @Override
+ public void setTitle(int title) {
+ this.title = context.getResources().getString(title);
+ if(this.primaryFragment) {
+ context.setTitle(this.title);
+ }
+ }
+ @Override
+ public void setSubtitle(CharSequence title) {
+ this.subtitle = title;
+ if(this.primaryFragment) {
+ context.setSubtitle(title);
+ }
+ }
+
+ @Override
+ public SectionAdapter getCurrentAdapter() {
+ return songListAdapter;
+ }
+
+ private void scheduleHideControls() {
+ if (hideControlsFuture != null) {
+ hideControlsFuture.cancel(false);
+ }
+
+ final Handler handler = new Handler();
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ setControlsVisible(false);
+ }
+ });
+ }
+ };
+ hideControlsFuture = executorService.schedule(runnable, 3000L, TimeUnit.MILLISECONDS);
+ }
+
+ private void setControlsVisible(boolean visible) {
+ DownloadService downloadService = getDownloadService();
+ try {
+ long duration = 1700L;
+ FadeOutAnimation.createAndStart(rootView.findViewById(R.id.download_overlay_buttons), !visible, duration);
+
+ if (visible) {
+ scheduleHideControls();
+ }
+ } catch(Exception e) {
+
+ }
+ }
+
+ private void updateButtons() {
+ if(context == null) {
+ return;
+ }
+ }
+
+ // Scroll to current playing/downloading.
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void scrollToCurrent() {
+ if (getDownloadService() == null || songListAdapter == null) {
+ scrollWhenLoaded = true;
+ return;
+ }
+
+ // Try to get position of current playing/downloading
+ int position = songListAdapter.getItemPosition(currentPlaying);
+ if(position == -1) {
+ DownloadFile currentDownloading = getDownloadService().getCurrentDownloading();
+ position = songListAdapter.getItemPosition(currentDownloading);
+ }
+
+ // If found, scroll to it
+ if(position != -1) {
+ // RecyclerView.scrollToPosition just puts it on the screen (ie: bottom if scrolled below it)
+ LinearLayoutManager layoutManager = (LinearLayoutManager) playlistView.getLayoutManager();
+ layoutManager.scrollToPositionWithOffset(position, 0);
+ }
+ }
+
+ private void update() {
+ if(startFlipped) {
+ startFlipped = false;
+ scrollToCurrent();
+ }
+ }
+
+ private int getMinutes(int progress) {
+ if(progress < 30) {
+ return progress + 1;
+ } else if(progress < 49) {
+ return (progress - 30) * 5 + getMinutes(29);
+ } else if(progress < 57) {
+ return (progress - 48) * 30 + getMinutes(48);
+ } else if(progress < 81) {
+ return (progress - 56) * 60 + getMinutes(56);
+ } else {
+ return (progress - 80) * 150 + getMinutes(80);
+ }
+ }
+
+ private void toggleFullscreenAlbumArt() {
+ if (playlistFlipper.getDisplayedChild() == 1) {
+ playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(context, R.anim.push_down_in));
+ playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(context, R.anim.push_down_out));
+ playlistFlipper.setDisplayedChild(0);
+ } else {
+ scrollToCurrent();
+ playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(context, R.anim.push_up_in));
+ playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(context, R.anim.push_up_out));
+ playlistFlipper.setDisplayedChild(1);
+
+ UpdateView.triggerUpdate();
+ }
+ }
+
+ private void start() {
+ DownloadService service = getDownloadService();
+ PlayerState state = service.getPlayerState();
+ if (state == PAUSED || state == COMPLETED || state == STOPPED) {
+ service.start();
+ } else if (state == STOPPED || state == IDLE) {
+ warnIfStorageUnavailable();
+ int current = service.getCurrentPlayingIndex();
+ // TODO: Use play() method.
+ if (current == -1) {
+ service.play(0);
+ } else {
+ service.play(current);
+ }
+ }
+ }
+
+ private void changeProgress(final boolean rewind) {
+ final DownloadService downloadService = getDownloadService();
+ if(downloadService == null) {
+ return;
+ }
+
+ new SilentBackgroundTask(context) {
+ int seekTo;
+
+ @Override
+ protected Void doInBackground() throws Throwable {
+ if(rewind) {
+ seekTo = downloadService.rewind();
+ } else {
+ seekTo = downloadService.fastForward();
+ }
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ progressBar.setProgress(seekTo);
+ }
+ }.execute();
+ }
+
+ @Override
+ public boolean onDown(MotionEvent me) {
+ setControlsVisible(true);
+ return false;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ final DownloadService downloadService = getDownloadService();
+ if (downloadService == null || e1 == null || e2 == null) {
+ return false;
+ }
+
+ // Right to Left swipe
+ int action = 0;
+ if (e1.getX() - e2.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) {
+ action = ACTION_NEXT;
+ }
+ // Left to Right swipe
+ else if (e2.getX() - e1.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) {
+ action = ACTION_PREVIOUS;
+ }
+ // Top to Bottom swipe
+ else if (e2.getY() - e1.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
+ action = ACTION_FORWARD;
+ }
+ // Bottom to Top swipe
+ else if (e1.getY() - e2.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
+ action = ACTION_REWIND;
+ }
+
+ if(action > 0) {
+ final int performAction = action;
+ warnIfStorageUnavailable();
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ switch(performAction) {
+ case ACTION_NEXT:
+ downloadService.next();
+ break;
+ case ACTION_PREVIOUS:
+ downloadService.previous();
+ break;
+ case ACTION_FORWARD:
+ downloadService.seekTo(downloadService.getPlayerPosition() + DownloadService.FAST_FORWARD);
+ break;
+ case ACTION_REWIND:
+ downloadService.seekTo(downloadService.getPlayerPosition() - DownloadService.REWIND);
+ break;
+ }
+ return null;
+ }
+ }.execute();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onItemClicked(UpdateView updateView, final DownloadFile item) {
+ warnIfStorageUnavailable();
+ new SilentBackgroundTask(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ getDownloadService().play(item);
+ return null;
+ }
+ }.execute();
+ }
+
+ @Override
+ public void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex) {
+ this.currentPlaying = currentPlaying;
+ setupSubtitle(currentPlayingIndex);
+
+ if (currentPlaying != null && !currentPlaying.isSong()) {
+ previousButton.setVisibility(View.GONE);
+ nextButton.setVisibility(View.GONE);
+
+ rewindButton.setVisibility(View.VISIBLE);
+ fastforwardButton.setVisibility(View.VISIBLE);
+ } else {
+ previousButton.setVisibility(View.VISIBLE);
+ nextButton.setVisibility(View.VISIBLE);
+
+ rewindButton.setVisibility(View.GONE);
+ fastforwardButton.setVisibility(View.GONE);
+ }
+ updateTitle();
+ }
+
+ private void setupSubtitle(int currentPlayingIndex) {
+ if (currentPlaying != null) {
+ Entry song = currentPlaying.getSong();
+ songTitleTextView.setText(song.getTitle());
+ getImageLoader().loadImage(albumArtImageView, song, true, true);
+
+ DownloadService downloadService = getDownloadService();
+ if(downloadService.isShufflePlayEnabled()) {
+ setSubtitle(context.getResources().getString(R.string.download_playerstate_playing_shuffle));
+ } else {
+ setSubtitle(context.getResources().getString(R.string.download_playing_out_of, currentPlayingIndex + 1, currentPlayingSize));
+ }
+ } else {
+ songTitleTextView.setText(null);
+ getImageLoader().loadImage(albumArtImageView, (Entry) null, true, false);
+ setSubtitle(null);
+ }
+ }
+
+ @Override
+ public void onSongsChanged(List songs, DownloadFile currentPlaying, int currentPlayingIndex) {
+ currentPlayingSize = songs.size();
+
+ DownloadService downloadService = getDownloadService();
+ if(downloadService.isShufflePlayEnabled()) {
+ emptyTextView.setText(R.string.download_shuffle_loading);
+ }
+ else {
+ emptyTextView.setText(R.string.download_empty);
+ }
+
+ if(songListAdapter == null) {
+ songList = new ArrayList<>();
+ songList.addAll(songs);
+ playlistView.setAdapter(songListAdapter = new DownloadFileAdapter(context, songList, NowPlayingFragment.this));
+ } else {
+ songList.clear();
+ songList.addAll(songs);
+ songListAdapter.notifyDataSetChanged();
+ }
+
+ emptyTextView.setVisibility(songs.isEmpty() ? View.VISIBLE : View.GONE);
+
+ if(scrollWhenLoaded) {
+ scrollToCurrent();
+ scrollWhenLoaded = false;
+ }
+
+ if(this.currentPlaying != currentPlaying) {
+ onSongChanged(currentPlaying, currentPlayingIndex);
+ onMetadataUpdate(currentPlaying != null ? currentPlaying.getSong() : null, DownloadService.METADATA_UPDATED_ALL);
+ } else {
+ setupSubtitle(currentPlayingIndex);
+ }
+
+ toggleListButton.setVisibility(View.VISIBLE);
+ repeatButton.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onSongProgress(DownloadFile currentPlaying, int millisPlayed, Integer duration, boolean isSeekable) {
+ if (currentPlaying != null) {
+ int millisTotal = duration == null ? 0 : duration;
+
+ positionTextView.setText(Util.formatDuration(millisPlayed / 1000));
+ if(millisTotal > 0) {
+ durationTextView.setText(Util.formatDuration(millisTotal / 1000));
+ } else {
+ durationTextView.setText("-:--");
+ }
+ progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
+ if(!seekInProgress) {
+ progressBar.setProgress(millisPlayed);
+ }
+ progressBar.setEnabled(isSeekable);
+ } else {
+ positionTextView.setText("0:00");
+ durationTextView.setText("-:--");
+ progressBar.setProgress(0);
+ progressBar.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onStateUpdate(DownloadFile downloadFile, PlayerState playerState) {
+ switch (playerState) {
+ case DOWNLOADING:
+ if(currentPlaying != null) {
+ if(Util.isWifiRequiredForDownload(context)) {
+ statusTextView.setText(context.getResources().getString(R.string.download_playerstate_mobile_disabled));
+ } else {
+ long bytes = currentPlaying.getPartialFile().length();
+ statusTextView.setText(context.getResources().getString(R.string.download_playerstate_downloading, Util.formatLocalizedBytes(bytes, context)));
+ }
+ }
+ break;
+ case PREPARING:
+ statusTextView.setText(R.string.download_playerstate_buffering);
+ break;
+ default:
+ if(currentPlaying != null) {
+ Entry entry = currentPlaying.getSong();
+ if(entry.getAlbum() != null) {
+ String artist = "";
+ if (entry.getArtist() != null) {
+ artist = currentPlaying.getSong().getArtist() + " - ";
+ }
+ statusTextView.setText(artist + entry.getAlbum());
+ } else {
+ statusTextView.setText(null);
+ }
+ } else {
+ statusTextView.setText(null);
+ }
+ break;
+ }
+
+ switch (playerState) {
+ case STARTED:
+ pauseButton.setVisibility(View.VISIBLE);
+ stopButton.setVisibility(View.INVISIBLE);
+ startButton.setVisibility(View.INVISIBLE);
+ break;
+ case DOWNLOADING:
+ case PREPARING:
+ pauseButton.setVisibility(View.INVISIBLE);
+ stopButton.setVisibility(View.VISIBLE);
+ startButton.setVisibility(View.INVISIBLE);
+ break;
+ default:
+ pauseButton.setVisibility(View.INVISIBLE);
+ stopButton.setVisibility(View.INVISIBLE);
+ startButton.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ @Override
+ public void onMetadataUpdate(Entry song, int fieldChange) {
+ if(song != null && albumArtImageView != null && fieldChange == DownloadService.METADATA_UPDATED_COVER_ART) {
+ getImageLoader().loadImage(albumArtImageView, song, true, true);
+ }
+ }
+
+ public void updateRepeatButton() {
+ DownloadService downloadService = getDownloadService();
+ switch (downloadService.getRepeatMode()) {
+ case OFF:
+ repeatButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.media_button_repeat_off));
+ break;
+ case ALL:
+ repeatButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.media_button_repeat_all));
+ break;
+ case SINGLE:
+ repeatButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.media_button_repeat_single));
+ break;
+ default:
+ break;
+ }
+ }
+ private void updateTitle() {
+ DownloadService downloadService = getDownloadService();
+
+ String title = context.getResources().getString(R.string.button_bar_now_playing);
+
+ setTitle(title);
+ }
+
+ @Override
+ protected List