aboutsummaryrefslogtreecommitdiff
path: root/subsonic-android/src/github
diff options
context:
space:
mode:
authorScott Jackson <daneren2005@gmail.com>2012-10-01 20:41:50 -0700
committerScott Jackson <daneren2005@gmail.com>2012-10-01 20:41:50 -0700
commitcb5389c471bbf8b2952abf77db1bdc303f577718 (patch)
tree21d8952defa73bb3cb87a0c79d2a54a40365224e /subsonic-android/src/github
parent56458b12e19310b51d228c601012bf4b65a29f17 (diff)
parent57b20385fd36584efa0ffb3d9ef4761b463287cf (diff)
downloaddsub-cb5389c471bbf8b2952abf77db1bdc303f577718.tar.gz
dsub-cb5389c471bbf8b2952abf77db1bdc303f577718.tar.bz2
dsub-cb5389c471bbf8b2952abf77db1bdc303f577718.zip
Merge star support
Diffstat (limited to 'subsonic-android/src/github')
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java17
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java5
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java8
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/SubsonicTabActivity.java740
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java9
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java10
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java119
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/MusicService.java4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java5
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java22
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java1
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java62
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/AlbumView.java19
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/SongView.java18
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java32
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java27
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java71
17 files changed, 741 insertions, 428 deletions
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
index 1e568c03..61065c18 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
@@ -42,6 +42,7 @@ import android.view.GestureDetector.OnGestureListener;
import android.view.LayoutInflater;
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;
@@ -106,6 +107,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
private Button visualizerButton;
private Button jukeboxButton;
private View toggleListButton;
+ private ImageButton starButton;
private ScheduledExecutorService executorService;
private DownloadFile currentPlaying;
private long currentRevision;
@@ -157,6 +159,19 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
LinearLayout visualizerViewLayout = (LinearLayout) findViewById(R.id.download_visualizer_view_layout);
toggleListButton = findViewById(R.id.download_toggle_list);
+
+ starButton = (ImageButton) findViewById(R.id.download_star);
+ starButton.setVisibility(Util.isOffline(this) ? View.GONE : View.VISIBLE);
+ starButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ DownloadFile currentDownload = getDownloadService().getCurrentPlaying();
+ if (currentDownload != null) {
+ MusicDirectory.Entry currentSong = currentDownload.getSong();
+ toggleStarredInBackground(currentSong, starButton);
+ }
+ }
+ });
View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
@@ -714,11 +729,13 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
albumTextView.setText(song.getAlbum());
artistTextView.setText(song.getArtist());
getImageLoader().loadImage(albumArtImageView, song, true, true);
+ starButton.setImageResource(song.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
} else {
songTitleTextView.setText(null);
albumTextView.setText(null);
artistTextView.setText(null);
getImageLoader().loadImage(albumArtImageView, null, true, false);
+ starButton.setImageResource(android.R.drawable.btn_star_big_off);
}
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java
index 2fab77e7..23b1ba0d 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java
@@ -79,6 +79,7 @@ public class MainActivity extends SubsonicTabActivity {
final View albumsHighestButton = buttons.findViewById(R.id.main_albums_highest);
final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent);
final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent);
+ final View albumsStarredButton = buttons.findViewById(R.id.main_albums_starred);
final View dummyView = findViewById(R.id.main_dummy);
@@ -92,7 +93,7 @@ public class MainActivity extends SubsonicTabActivity {
adapter.addViews(Arrays.asList(serverButton), true);
if (!Util.isOffline(this)) {
adapter.addView(albumsTitle, false);
- adapter.addViews(Arrays.asList(albumsNewestButton, albumsRandomButton, albumsHighestButton, albumsRecentButton, albumsFrequentButton), true);
+ adapter.addViews(Arrays.asList(albumsNewestButton, albumsRandomButton, albumsHighestButton, albumsStarredButton, albumsRecentButton, albumsFrequentButton), true);
}
list.setAdapter(adapter);
registerForContextMenu(dummyView);
@@ -108,6 +109,8 @@ public class MainActivity extends SubsonicTabActivity {
showAlbumList("random");
} else if (view == albumsHighestButton) {
showAlbumList("highest");
+ } else if (view == albumsStarredButton) {
+ showAlbumList("starred");
} else if (view == albumsRecentButton) {
showAlbumList("recent");
} else if (view == albumsFrequentButton) {
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java
index 3d029db3..9a600d59 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java
@@ -314,7 +314,13 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
new LoadTask() {
@Override
protected MusicDirectory load(MusicService service) throws Exception {
- return service.getAlbumList(albumListType, size, offset, SelectAlbumActivity.this, this);
+ MusicDirectory result;
+ if ("starred".equals(albumListType)) {
+ result = service.getStarredList(SelectAlbumActivity.this, this);
+ } else {
+ result = service.getAlbumList(albumListType, size, offset, SelectAlbumActivity.this, this);
+ }
+ return result;
}
@Override
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicTabActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicTabActivity.java
index c7ef57b5..32b6c550 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicTabActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicTabActivity.java
@@ -1,348 +1,392 @@
-/*
- This file is part of Subsonic.
-
- Subsonic is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Subsonic is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
-
- Copyright 2009 (C) Sindre Mehus
- */
-package github.daneren2005.dsub.activity;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.LinkedList;
-import java.util.List;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.TextView;
-import github.daneren2005.dsub.R;
-import com.actionbarsherlock.app.SherlockActivity;
-import github.daneren2005.dsub.domain.MusicDirectory;
-import github.daneren2005.dsub.service.DownloadService;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
-import github.daneren2005.dsub.service.MusicService;
-import github.daneren2005.dsub.service.MusicServiceFactory;
-import github.daneren2005.dsub.util.ImageLoader;
-import github.daneren2005.dsub.util.ModalBackgroundTask;
-import github.daneren2005.dsub.util.Util;
-
-/**
- * @author Sindre Mehus
- */
-public class SubsonicTabActivity extends SherlockActivity {
-
- private static final String TAG = SubsonicTabActivity.class.getSimpleName();
- private static ImageLoader IMAGE_LOADER;
- private String theme;
-
- private boolean destroyed;
- private View homeButton;
- private View musicButton;
- private View playlistButton;
- private View nowPlayingButton;
-
- @Override
- protected void onCreate(Bundle bundle) {
- setUncaughtExceptionHandler();
- applyTheme();
- super.onCreate(bundle);
- startService(new Intent(this, DownloadServiceImpl.class));
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
- }
-
- @Override
- protected void onPostCreate(Bundle bundle) {
- super.onPostCreate(bundle);
-
- homeButton = findViewById(R.id.button_bar_home);
- homeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
- }
- });
-
- musicButton = findViewById(R.id.button_bar_music);
- musicButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
- }
- });
-
- playlistButton = findViewById(R.id.button_bar_playlists);
- playlistButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
- }
- });
-
- nowPlayingButton = findViewById(R.id.button_bar_now_playing);
- nowPlayingButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
- }
- });
-
- if (this instanceof MainActivity) {
- homeButton.setEnabled(false);
- } else if (this instanceof SelectAlbumActivity || this instanceof SelectArtistActivity) {
- musicButton.setEnabled(false);
- } else if (this instanceof SelectPlaylistActivity) {
- playlistButton.setEnabled(false);
- } else if (this instanceof DownloadActivity || this instanceof LyricsActivity) {
- nowPlayingButton.setEnabled(false);
- }
-
- updateButtonVisibility();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Util.registerMediaButtonEventReceiver(this);
-
- // Make sure to update theme
- if (theme != null && !theme.equals(Util.getTheme(this))) {
- restart();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- destroyed = true;
- getImageLoader().clear();
- }
-
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN;
- boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP;
- boolean isVolumeAdjust = isVolumeDown || isVolumeUp;
- boolean isJukebox = getDownloadService() != null && getDownloadService().isJukeboxEnabled();
-
- if (isVolumeAdjust && isJukebox) {
- getDownloadService().adjustJukeboxVolume(isVolumeUp);
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- protected void restart() {
- Intent intent = new Intent(this, this.getClass());
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtras(getIntent());
- Util.startActivityWithoutTransition(this, intent);
- }
-
- @Override
- public void finish() {
- super.finish();
- Util.disablePendingTransition(this);
- }
-
- private void applyTheme() {
- theme = Util.getTheme(this);
- if ("dark".equals(theme)) {
- setTheme(R.style.Theme_DSub_Dark);
- } else if ("light".equals(theme)) {
- setTheme(R.style.Theme_DSub_Light);
- } else if ("dark_fullscreen".equals(theme)) {
- setTheme(R.style.Theme_DSub_Dark_Fullscreen);
- } else if ("light_fullscreen".equals(theme)) {
- setTheme(R.style.Theme_DSub_Light_Fullscreen);
- }else {
- setTheme(R.style.Theme_DSub_Light);
- }
- }
-
- public boolean isDestroyed() {
- return destroyed;
- }
-
- private void updateButtonVisibility() {
- int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
- }
-
- public void setProgressVisible(boolean visible) {
- View view = findViewById(R.id.tab_progress);
- if (view != null) {
- view.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
- }
-
- public void updateProgress(String message) {
- TextView view = (TextView) findViewById(R.id.tab_progress_message);
- if (view != null) {
- view.setText(message);
- }
- }
-
- public DownloadService getDownloadService() {
- // If service is not available, request it to start and wait for it.
- for (int i = 0; i < 5; i++) {
- DownloadService downloadService = DownloadServiceImpl.getInstance();
- if (downloadService != null) {
- return downloadService;
- }
- Log.w(TAG, "DownloadService not running. Attempting to start it.");
- startService(new Intent(this, DownloadServiceImpl.class));
- Util.sleepQuietly(50L);
- }
- return DownloadServiceImpl.getInstance();
- }
-
- protected void warnIfNetworkOrStorageUnavailable() {
- if (!Util.isExternalStoragePresent()) {
- Util.toast(this, R.string.select_album_no_sdcard);
- } else if (!Util.isOffline(this) && !Util.isNetworkConnected(this)) {
- Util.toast(this, R.string.select_album_no_network);
- }
- }
-
- protected synchronized ImageLoader getImageLoader() {
- if (IMAGE_LOADER == null) {
- IMAGE_LOADER = new ImageLoader(this);
- }
- return IMAGE_LOADER;
- }
-
- protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
- downloadRecursively(id, "", true, save, append, autoplay, shuffle, background);
- }
- protected void downloadPlaylist(final String id, final String name, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
- downloadRecursively(id, name, false, save, append, autoplay, shuffle, background);
- }
- protected void downloadRecursively(final String id, final String name, final boolean isDirectory, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
- ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) {
- private static final int MAX_SONGS = 500;
-
- @Override
- protected List<MusicDirectory.Entry> doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
- MusicDirectory root;
- if(isDirectory)
- root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, this);
- else
- root = musicService.getPlaylist(id, name, SubsonicTabActivity.this, this);
- List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>();
- getSongsRecursively(root, songs);
- return songs;
- }
-
- private void getSongsRecursively(MusicDirectory parent, List<MusicDirectory.Entry> songs) throws Exception {
- if (songs.size() > MAX_SONGS) {
- return;
- }
-
- for (MusicDirectory.Entry song : parent.getChildren(false, true)) {
- if (!song.isVideo()) {
- songs.add(song);
- }
- }
- for (MusicDirectory.Entry dir : parent.getChildren(true, false)) {
- MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
- getSongsRecursively(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, this), songs);
- }
- }
-
- @Override
- protected void done(List<MusicDirectory.Entry> songs) {
- DownloadService downloadService = getDownloadService();
- if (!songs.isEmpty() && downloadService != null) {
- if (!append) {
- downloadService.clear();
- }
- warnIfNetworkOrStorageUnavailable();
- if(!background) {
- downloadService.download(songs, save, autoplay, false, shuffle);
- Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
- }
- else {
- downloadService.downloadBackground(songs, save);
- }
- }
- }
- };
-
- task.execute();
- }
-
- private void setUncaughtExceptionHandler() {
- Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
- if (!(handler instanceof SubsonicUncaughtExceptionHandler)) {
- Thread.setDefaultUncaughtExceptionHandler(new SubsonicUncaughtExceptionHandler(this));
- }
- }
-
- /**
- * Logs the stack trace of uncaught exceptions to a file on the SD card.
- */
- private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
-
- private final Thread.UncaughtExceptionHandler defaultHandler;
- private final Context context;
-
- private SubsonicUncaughtExceptionHandler(Context context) {
- this.context = context;
- defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
- }
-
- @Override
- public void uncaughtException(Thread thread, Throwable throwable) {
- File file = null;
- PrintWriter printWriter = null;
- try {
-
- PackageInfo packageInfo = context.getPackageManager().getPackageInfo("github.daneren2005.dsub", 0);
- file = new File(Environment.getExternalStorageDirectory(), "subsonic-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);
- }
-
- }
- }
- }
-}
-
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.activity;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.ImageButton;
+import android.widget.TextView;
+import github.daneren2005.dsub.R;
+import com.actionbarsherlock.app.SherlockActivity;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.service.DownloadService;
+import github.daneren2005.dsub.service.DownloadServiceImpl;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
+import github.daneren2005.dsub.service.OfflineException;
+import github.daneren2005.dsub.service.ServerTooOldException;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.ImageLoader;
+import github.daneren2005.dsub.util.ModalBackgroundTask;
+import github.daneren2005.dsub.util.SilentBackgroundTask;
+import github.daneren2005.dsub.util.Util;
+
+/**
+ * @author Sindre Mehus
+ */
+public class SubsonicTabActivity extends SherlockActivity {
+
+ private static final String TAG = SubsonicTabActivity.class.getSimpleName();
+ private static ImageLoader IMAGE_LOADER;
+ private String theme;
+
+ private boolean destroyed;
+ private View homeButton;
+ private View musicButton;
+ private View playlistButton;
+ private View nowPlayingButton;
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ setUncaughtExceptionHandler();
+ applyTheme();
+ super.onCreate(bundle);
+ startService(new Intent(this, DownloadServiceImpl.class));
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle bundle) {
+ super.onPostCreate(bundle);
+
+ homeButton = findViewById(R.id.button_bar_home);
+ homeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
+ }
+ });
+
+ musicButton = findViewById(R.id.button_bar_music);
+ musicButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
+ }
+ });
+
+ playlistButton = findViewById(R.id.button_bar_playlists);
+ playlistButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
+ }
+ });
+
+ nowPlayingButton = findViewById(R.id.button_bar_now_playing);
+ nowPlayingButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
+ }
+ });
+
+ if (this instanceof MainActivity) {
+ homeButton.setEnabled(false);
+ } else if (this instanceof SelectAlbumActivity || this instanceof SelectArtistActivity) {
+ musicButton.setEnabled(false);
+ } else if (this instanceof SelectPlaylistActivity) {
+ playlistButton.setEnabled(false);
+ } else if (this instanceof DownloadActivity || this instanceof LyricsActivity) {
+ nowPlayingButton.setEnabled(false);
+ }
+
+ updateButtonVisibility();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Util.registerMediaButtonEventReceiver(this);
+
+ // Make sure to update theme
+ if (theme != null && !theme.equals(Util.getTheme(this))) {
+ restart();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ destroyed = true;
+ getImageLoader().clear();
+ }
+
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN;
+ boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP;
+ boolean isVolumeAdjust = isVolumeDown || isVolumeUp;
+ boolean isJukebox = getDownloadService() != null && getDownloadService().isJukeboxEnabled();
+
+ if (isVolumeAdjust && isJukebox) {
+ getDownloadService().adjustJukeboxVolume(isVolumeUp);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ protected void restart() {
+ Intent intent = new Intent(this, this.getClass());
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtras(getIntent());
+ Util.startActivityWithoutTransition(this, intent);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ Util.disablePendingTransition(this);
+ }
+
+ private void applyTheme() {
+ theme = Util.getTheme(this);
+ if ("dark".equals(theme)) {
+ setTheme(R.style.Theme_DSub_Dark);
+ } else if ("light".equals(theme)) {
+ setTheme(R.style.Theme_DSub_Light);
+ } else if ("dark_fullscreen".equals(theme)) {
+ setTheme(R.style.Theme_DSub_Dark_Fullscreen);
+ } else if ("light_fullscreen".equals(theme)) {
+ setTheme(R.style.Theme_DSub_Light_Fullscreen);
+ }else {
+ setTheme(R.style.Theme_DSub_Light);
+ }
+ }
+
+ public boolean isDestroyed() {
+ return destroyed;
+ }
+
+ private void updateButtonVisibility() {
+ int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
+ }
+
+ public void toggleStarredInBackground(final MusicDirectory.Entry entry, final ImageButton button) {
+
+ final boolean starred = !entry.isStarred();
+
+ button.setImageResource(starred ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
+ entry.setStarred(starred);
+
+ // Util.toast(SubsonicTabActivity.this, getResources().getString(R.string.starring_content, entry.getTitle()));
+ new SilentBackgroundTask<Void>(this) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
+ musicService.setStarred(entry.getId(), starred, SubsonicTabActivity.this, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ // Util.toast(SubsonicTabActivity.this, getResources().getString(R.string.starring_content_done, entry.getTitle()));
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ button.setImageResource(!starred ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
+ entry.setStarred(!starred);
+
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = getErrorMessage(error);
+ } else {
+ msg = getResources().getString(R.string.starring_content_error, entry.getTitle()) + " " + getErrorMessage(error);
+ }
+
+ Util.toast(SubsonicTabActivity.this, msg, false);
+ }
+ }.execute();
+ }
+
+ public void setProgressVisible(boolean visible) {
+ View view = findViewById(R.id.tab_progress);
+ if (view != null) {
+ view.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ public void updateProgress(String message) {
+ TextView view = (TextView) findViewById(R.id.tab_progress_message);
+ if (view != null) {
+ view.setText(message);
+ }
+ }
+
+ public DownloadService getDownloadService() {
+ // If service is not available, request it to start and wait for it.
+ for (int i = 0; i < 5; i++) {
+ DownloadService downloadService = DownloadServiceImpl.getInstance();
+ if (downloadService != null) {
+ return downloadService;
+ }
+ Log.w(TAG, "DownloadService not running. Attempting to start it.");
+ startService(new Intent(this, DownloadServiceImpl.class));
+ Util.sleepQuietly(50L);
+ }
+ return DownloadServiceImpl.getInstance();
+ }
+
+ protected void warnIfNetworkOrStorageUnavailable() {
+ if (!Util.isExternalStoragePresent()) {
+ Util.toast(this, R.string.select_album_no_sdcard);
+ } else if (!Util.isOffline(this) && !Util.isNetworkConnected(this)) {
+ Util.toast(this, R.string.select_album_no_network);
+ }
+ }
+
+ protected synchronized ImageLoader getImageLoader() {
+ if (IMAGE_LOADER == null) {
+ IMAGE_LOADER = new ImageLoader(this);
+ }
+ return IMAGE_LOADER;
+ }
+
+ protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
+ downloadRecursively(id, "", true, save, append, autoplay, shuffle, background);
+ }
+ protected void downloadPlaylist(final String id, final String name, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
+ downloadRecursively(id, name, false, save, append, autoplay, shuffle, background);
+ }
+ protected void downloadRecursively(final String id, final String name, final boolean isDirectory, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
+ ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) {
+ private static final int MAX_SONGS = 500;
+
+ @Override
+ protected List<MusicDirectory.Entry> doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
+ MusicDirectory root;
+ if(isDirectory)
+ root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, this);
+ else
+ root = musicService.getPlaylist(id, name, SubsonicTabActivity.this, this);
+ List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>();
+ getSongsRecursively(root, songs);
+ return songs;
+ }
+
+ private void getSongsRecursively(MusicDirectory parent, List<MusicDirectory.Entry> songs) throws Exception {
+ if (songs.size() > MAX_SONGS) {
+ return;
+ }
+
+ for (MusicDirectory.Entry song : parent.getChildren(false, true)) {
+ if (!song.isVideo()) {
+ songs.add(song);
+ }
+ }
+ for (MusicDirectory.Entry dir : parent.getChildren(true, false)) {
+ MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
+ getSongsRecursively(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, this), songs);
+ }
+ }
+
+ @Override
+ protected void done(List<MusicDirectory.Entry> songs) {
+ DownloadService downloadService = getDownloadService();
+ if (!songs.isEmpty() && downloadService != null) {
+ if (!append) {
+ downloadService.clear();
+ }
+ warnIfNetworkOrStorageUnavailable();
+ if(!background) {
+ downloadService.download(songs, save, autoplay, false, shuffle);
+ Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
+ }
+ else {
+ downloadService.downloadBackground(songs, save);
+ }
+ }
+ }
+ };
+
+ task.execute();
+ }
+
+ private void setUncaughtExceptionHandler() {
+ Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
+ if (!(handler instanceof SubsonicUncaughtExceptionHandler)) {
+ Thread.setDefaultUncaughtExceptionHandler(new SubsonicUncaughtExceptionHandler(this));
+ }
+ }
+
+ /**
+ * Logs the stack trace of uncaught exceptions to a file on the SD card.
+ */
+ private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ private final Thread.UncaughtExceptionHandler defaultHandler;
+ private final Context context;
+
+ private SubsonicUncaughtExceptionHandler(Context context) {
+ this.context = context;
+ defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ File file = null;
+ PrintWriter printWriter = null;
+ try {
+
+ PackageInfo packageInfo = context.getPackageManager().getPackageInfo("github.daneren2005.dsub", 0);
+ file = new File(Environment.getExternalStorageDirectory(), "subsonic-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/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java b/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java
index 420ce813..da0d8cbc 100644
--- a/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java
+++ b/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java
@@ -80,6 +80,7 @@ public class MusicDirectory {
private Integer bitRate;
private String path;
private boolean video;
+ private boolean starred;
public String getId() {
return id;
@@ -232,6 +233,14 @@ public class MusicDirectory {
public void setVideo(boolean video) {
this.video = video;
}
+
+ public boolean isStarred() {
+ return starred;
+ }
+
+ public void setStarred(boolean starred) {
+ this.starred = starred;
+ }
@Override
public boolean equals(Object o) {
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
index 094c959f..832b2037 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
@@ -164,6 +164,11 @@ public class CachedMusicService implements MusicService {
}
@Override
+ public MusicDirectory getStarredList(Context context, ProgressListener progressListener) throws Exception {
+ return musicService.getStarredList(context, progressListener);
+ }
+
+ @Override
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
return musicService.getRandomSongs(size, context, progressListener);
}
@@ -222,6 +227,11 @@ public class CachedMusicService implements MusicService {
public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception {
return musicService.setJukeboxGain(gain, context, progressListener);
}
+
+ @Override
+ public void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception {
+ musicService.setStarred(id, starred, context, progressListener);
+ }
private void checkSettingsChanged(Context context) {
String newUrl = Util.getRestUrl(context, null);
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
index 64e71850..7ba54832 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
@@ -18,22 +18,14 @@
*/
package github.daneren2005.dsub.service;
-import android.annotation.TargetApi;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.RemoteControlClient;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.util.Log;
+import static github.daneren2005.dsub.domain.PlayerState.COMPLETED;
+import static github.daneren2005.dsub.domain.PlayerState.DOWNLOADING;
+import static github.daneren2005.dsub.domain.PlayerState.IDLE;
+import static github.daneren2005.dsub.domain.PlayerState.PAUSED;
+import static github.daneren2005.dsub.domain.PlayerState.PREPARED;
+import static github.daneren2005.dsub.domain.PlayerState.PREPARING;
+import static github.daneren2005.dsub.domain.PlayerState.STARTED;
+import static github.daneren2005.dsub.domain.PlayerState.STOPPED;
import github.daneren2005.dsub.audiofx.EqualizerController;
import github.daneren2005.dsub.audiofx.VisualizerController;
import github.daneren2005.dsub.domain.MusicDirectory;
@@ -41,10 +33,12 @@ import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.domain.RepeatMode;
import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver;
import github.daneren2005.dsub.util.CancellableTask;
+import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.LRUCache;
import github.daneren2005.dsub.util.ShufflePlayBuffer;
import github.daneren2005.dsub.util.SimpleServiceBinder;
import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.util.compat.RemoteControlClientHelper;
import java.io.File;
import java.util.ArrayList;
@@ -52,8 +46,17 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import static github.daneren2005.dsub.domain.PlayerState.*;
-import github.daneren2005.dsub.util.*;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.util.Log;
/**
* @author Sindre Mehus
@@ -71,8 +74,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
public static final String CMD_NEXT = "github.daneren2005.dsub.CMD_NEXT";
- private RemoteControlClient mRemoteControlClient;
- private ImageLoader imageLoader;
+ private RemoteControlClientHelper mRemoteControl;
private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
private MediaPlayer mediaPlayer;
@@ -121,12 +123,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
}
- @TargetApi(14)
@Override
public void onCreate() {
super.onCreate();
-
- imageLoader = new ImageLoader(this);
mediaPlayer = new MediaPlayer();
mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
@@ -139,36 +138,15 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
});
- if (Build.VERSION.SDK_INT >= 14) {
-
- Util.requestAudioFocus(this);
- Util.registerMediaButtonEventReceiver(this);
-
- // Use the remote control APIs (if available) to set the playback state
- if (mRemoteControlClient == null) {
- AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- ComponentName mediaButtonReceiverComponent = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
- // audioManager.registerMediaButtonEventReceiver(mediaButtonReceiverComponent);
- // build the PendingIntent for the remote control client
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.setComponent(mediaButtonReceiverComponent);
- PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
- // create and register the remote control client
- mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
- audioManager.registerRemoteControlClient(mRemoteControlClient);
- }
-
- mRemoteControlClient.setPlaybackState(
- RemoteControlClient.PLAYSTATE_STOPPED);
+ Util.requestAudioFocus(this);
+ Util.registerMediaButtonEventReceiver(this);
- mRemoteControlClient.setTransportControlFlags(
- RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
- RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
- RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
- RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
- RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
- RemoteControlClient.FLAG_KEY_MEDIA_STOP);
- }
+ if (mRemoteControl == null) {
+ // Use the remote control APIs (if available) to set the playback state
+ mRemoteControl = RemoteControlClientHelper.createInstance();
+ ComponentName mediaButtonReceiverComponent = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
+ mRemoteControl.register(this, mediaButtonReceiverComponent);
+ }
if (equalizerAvailable) {
equalizerController = new EqualizerController(this, mediaPlayer);
@@ -194,12 +172,13 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
@Override
- public void onStart(Intent intent, int startId) {
- super.onStart(intent, startId);
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
lifecycleSupport.onStart(intent);
+ return START_NOT_STICKY;
}
- @Override
+ @Override
public void onDestroy() {
super.onDestroy();
lifecycleSupport.onDestroy();
@@ -211,6 +190,10 @@ public class DownloadServiceImpl extends Service implements DownloadService {
if (visualizerController != null) {
visualizerController.release();
}
+ if (mRemoteControl != null) {
+ mRemoteControl.unregister(this);
+ mRemoteControl = null;
+ }
instance = null;
}
@@ -447,7 +430,6 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
}
- @TargetApi(14)
synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) {
this.currentPlaying = currentPlaying;
@@ -464,23 +446,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
Util.hidePlayingNotification(this, this, handler);
}
- if (mRemoteControlClient != null) {
- MusicDirectory.Entry currentSong = ((currentPlaying == null) ? null: currentPlaying.getSong());
- // Update the remote controls
- mRemoteControlClient.editMetadata(true)
- .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, currentSong == null ? null : currentSong.getArtist())
- .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, currentSong == null ? null : currentSong.getAlbum())
- .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, currentSong == null ? null : currentSong.getTitle())
- .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (currentSong == null) ? 0 : ((currentSong.getDuration() == null) ? 0 : currentSong.getDuration()))
- .apply();
- if (currentSong == null) {
- mRemoteControlClient.editMetadata(true)
- .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, null)
- .apply();
- } else {
- imageLoader.loadImage(this, mRemoteControlClient, currentSong);
- }
- }
+ MusicDirectory.Entry currentSong = (currentPlaying == null) ? null: currentPlaying.getSong();
+ mRemoteControl.updateMetadata(this, currentSong);
+
}
@Override
@@ -697,7 +665,6 @@ public class DownloadServiceImpl extends Service implements DownloadService {
return playerState;
}
- @TargetApi(14)
synchronized void setPlayerState(PlayerState playerState) {
Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")");
@@ -710,9 +677,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
Util.broadcastPlaybackStatusChange(this, playerState);
this.playerState = playerState;
- if (mRemoteControlClient != null) {
- mRemoteControlClient.setPlaybackState(playerState.getRemoteControlClientPlayState());
- }
+ mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState());
if (show) {
Util.showPlayingNotification(this, this, handler, currentPlaying.getSong());
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
index e8fca8f0..15abbdf8 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
@@ -53,6 +53,8 @@ public interface MusicService {
SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception;
+ MusicDirectory getStarredList(Context context, ProgressListener progressListener) throws Exception;
+
MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception;
List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
@@ -88,4 +90,6 @@ public interface MusicService {
JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception;
JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception;
+
+ void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception;
} \ No newline at end of file
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
index 9c49d550..29992029 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
@@ -308,6 +308,11 @@ public class OfflineMusicService extends RESTMusicService {
public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception {
throw new OfflineException("Jukebox not available in offline mode");
}
+
+ @Override
+ public void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Starring not available in offline mode");
+ }
@Override
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
index f475d9d5..a993df0d 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
@@ -93,6 +93,7 @@ import github.daneren2005.dsub.service.parser.PlaylistsParser;
import github.daneren2005.dsub.service.parser.RandomSongsParser;
import github.daneren2005.dsub.service.parser.SearchResult2Parser;
import github.daneren2005.dsub.service.parser.SearchResultParser;
+import github.daneren2005.dsub.service.parser.StarredListParser;
import github.daneren2005.dsub.service.parser.VersionParser;
import github.daneren2005.dsub.service.ssl.SSLSocketFactory;
import github.daneren2005.dsub.service.ssl.TrustSelfSignedStrategy;
@@ -415,6 +416,16 @@ public class RESTMusicService implements MusicService {
}
@Override
+ public MusicDirectory getStarredList(Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "getStarred", null);
+ try {
+ return new StarredListParser(context).parse(reader, progressListener);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
@@ -602,6 +613,17 @@ public class RESTMusicService implements MusicService {
Util.close(reader);
}
}
+
+ @Override
+ public void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception {
+ checkServerVersion(context, "1.8", "Starring is not supported.");
+ Reader reader = getReader(context, progressListener, starred ? "star" : "unstar", null, "id", id);
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception {
return getReader(context, progressListener, method, requestParams, Collections.<String>emptyList(), Collections.emptyList());
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java
index 3a1826e5..d724f63c 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java
@@ -38,6 +38,7 @@ public class MusicDirectoryEntryParser extends AbstractParser {
entry.setDirectory(getBoolean("isDir"));
entry.setCoverArt(get("coverArt"));
entry.setArtist(get("artist"));
+ entry.setStarred(get("starred") != null);
if (!entry.isDirectory()) {
entry.setAlbum(get("album"));
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java
new file mode 100644
index 00000000..c3c16949
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.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 <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.util.ProgressListener;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.Reader;
+
+/**
+ * @author Kurt Hardin
+ */
+public class StarredListParser extends MusicDirectoryEntryParser {
+
+ public StarredListParser(Context context) {
+ super(context);
+ }
+
+ public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception {
+
+ updateProgress(progressListener, R.string.parser_reading);
+ init(reader);
+
+ MusicDirectory dir = new MusicDirectory();
+ int eventType;
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("album".equals(name) || "song".equals(name)) {
+ dir.addChild(parseEntry());
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ updateProgress(progressListener, R.string.parser_reading_done);
+
+ return dir;
+ }
+} \ No newline at end of file
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/AlbumView.java b/subsonic-android/src/github/daneren2005/dsub/util/AlbumView.java
index 98bd500f..8c799aad 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/AlbumView.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/AlbumView.java
@@ -21,9 +21,11 @@ package github.daneren2005.dsub.util;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SubsonicTabActivity;
import github.daneren2005.dsub.domain.MusicDirectory;
/**
@@ -32,10 +34,13 @@ import github.daneren2005.dsub.domain.MusicDirectory;
* @author Sindre Mehus
*/
public class AlbumView extends LinearLayout {
+
+ private MusicDirectory.Entry album;
private TextView titleView;
private TextView artistView;
private View coverArtView;
+ private ImageButton starButton;
public AlbumView(Context context) {
super(context);
@@ -44,12 +49,26 @@ public class AlbumView extends LinearLayout {
titleView = (TextView) findViewById(R.id.album_title);
artistView = (TextView) findViewById(R.id.album_artist);
coverArtView = findViewById(R.id.album_coverart);
+ starButton = (ImageButton) findViewById(R.id.album_star);
+ starButton.setVisibility(Util.isOffline(getContext()) ? View.GONE : View.VISIBLE);
+ starButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ SubsonicTabActivity activity = (SubsonicTabActivity) getContext();
+ activity.toggleStarredInBackground(album, starButton);
+ }
+ });
}
public void setAlbum(MusicDirectory.Entry album, ImageLoader imageLoader) {
+ this.album = album;
+
titleView.setText(album.getTitle());
artistView.setText(album.getArtist());
artistView.setVisibility(album.getArtist() == null ? View.GONE : View.VISIBLE);
imageLoader.loadImage(coverArtView, album, false, true);
+
+ starButton.setImageResource(album.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
+ starButton.setFocusable(false);
}
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/SongView.java b/subsonic-android/src/github/daneren2005/dsub/util/SongView.java
index 61846c27..1bc2b9c0 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/SongView.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/SongView.java
@@ -25,9 +25,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.Checkable;
import android.widget.CheckedTextView;
+import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SubsonicTabActivity;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.DownloadServiceImpl;
@@ -46,13 +48,15 @@ public class SongView extends LinearLayout implements Checkable {
private static final String TAG = SongView.class.getSimpleName();
private static final WeakHashMap<SongView, ?> INSTANCES = new WeakHashMap<SongView, Object>();
private static Handler handler;
+
+ private MusicDirectory.Entry song;
private CheckedTextView checkedTextView;
private TextView titleTextView;
private TextView artistTextView;
private TextView durationTextView;
private TextView statusTextView;
- private MusicDirectory.Entry song;
+ private ImageButton starButton;
public SongView(Context context) {
super(context);
@@ -63,6 +67,15 @@ public class SongView extends LinearLayout implements Checkable {
artistTextView = (TextView) findViewById(R.id.song_artist);
durationTextView = (TextView) findViewById(R.id.song_duration);
statusTextView = (TextView) findViewById(R.id.song_status);
+ starButton = (ImageButton) findViewById(R.id.song_star);
+ starButton.setVisibility(Util.isOffline(getContext()) ? View.GONE : View.VISIBLE);
+ starButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ SubsonicTabActivity activity = (SubsonicTabActivity) getContext();
+ activity.toggleStarredInBackground(song, starButton);
+ }
+ });
INSTANCES.put(this, null);
int instanceCount = INSTANCES.size();
@@ -96,6 +109,9 @@ public class SongView extends LinearLayout implements Checkable {
artistTextView.setText(artist);
durationTextView.setText(Util.formatDuration(song.getDuration()));
checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE);
+
+ starButton.setImageResource(song.isStarred() ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
+ starButton.setFocusable(false);
update();
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
new file mode 100644
index 00000000..c3f3f70c
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
@@ -0,0 +1,32 @@
+package github.daneren2005.dsub.util.compat;
+
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Log;
+
+public class RemoteControlClientBase extends RemoteControlClientHelper {
+
+ private static final String TAG = RemoteControlClientBase.class.getSimpleName();
+
+ @Override
+ public void register(Context context, ComponentName mediaButtonReceiverComponent) {
+ Log.w(TAG, "RemoteControlClient requires Android API level 14 or higher.");
+ }
+
+ @Override
+ public void unregister(Context context) {
+ Log.w(TAG, "RemoteControlClient requires Android API level 14 or higher.");
+ }
+
+ @Override
+ public void setPlaybackState(int state) {
+ Log.w(TAG, "RemoteControlClient requires Android API level 14 or higher.");
+ }
+
+ @Override
+ public void updateMetadata(Context context, Entry currentSong) {
+ Log.w(TAG, "RemoteControlClient requires Android API level 14 or higher.");
+ }
+
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java
new file mode 100644
index 00000000..ddaa9f43
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java
@@ -0,0 +1,27 @@
+package github.daneren2005.dsub.util.compat;
+
+import github.daneren2005.dsub.domain.MusicDirectory;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+
+public abstract class RemoteControlClientHelper {
+
+ public static RemoteControlClientHelper createInstance() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return new RemoteControlClientBase();
+ } else {
+ return new RemoteControlClientICS();
+ }
+ }
+
+ protected RemoteControlClientHelper() {
+ // Avoid instantiation
+ }
+
+ public abstract void register(final Context context, final ComponentName mediaButtonReceiverComponent);
+ public abstract void unregister(final Context context);
+ public abstract void setPlaybackState(final int state);
+ public abstract void updateMetadata(final Context context, final MusicDirectory.Entry currentSong);
+
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
new file mode 100644
index 00000000..c5b09876
--- /dev/null
+++ b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
@@ -0,0 +1,71 @@
+package github.daneren2005.dsub.util.compat;
+
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.util.ImageLoader;
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.MediaMetadataRetriever;
+import android.media.RemoteControlClient;
+
+@TargetApi(14)
+public class RemoteControlClientICS extends RemoteControlClientHelper {
+
+ private RemoteControlClient mRemoteControl;
+
+ public void register(final Context context, final ComponentName mediaButtonReceiverComponent) {
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ // build the PendingIntent for the remote control client
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(mediaButtonReceiverComponent);
+ PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, mediaButtonIntent, 0);
+
+ // create and register the remote control client
+ mRemoteControl = new RemoteControlClient(mediaPendingIntent);
+ audioManager.registerRemoteControlClient(mRemoteControl);
+
+ mRemoteControl.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
+
+ mRemoteControl.setTransportControlFlags(
+ RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
+ RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
+ RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
+ RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
+ RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
+ RemoteControlClient.FLAG_KEY_MEDIA_STOP);
+ }
+
+ public void unregister(final Context context) {
+ if (mRemoteControl != null) {
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ audioManager.unregisterRemoteControlClient(mRemoteControl);
+ }
+ }
+
+ public void setPlaybackState(final int state) {
+ mRemoteControl.setPlaybackState(state);
+ }
+
+ public void updateMetadata(final Context context, final MusicDirectory.Entry currentSong) {
+ // Update the remote controls
+ mRemoteControl.editMetadata(true)
+ .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist())
+ .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum())
+ .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, (currentSong) == null ? null : currentSong.getTitle())
+ .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (currentSong == null) ?
+ 0 : ((currentSong.getDuration() == null) ? 0 : currentSong.getDuration()))
+ .apply();
+ if (currentSong == null) {
+ mRemoteControl.editMetadata(true)
+ .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, null)
+ .apply();
+ } else {
+ new ImageLoader(context).loadImage(context, mRemoteControl, currentSong);
+ }
+ }
+
+}