aboutsummaryrefslogtreecommitdiff
path: root/subsonic-android/patches/jukebox-patch.txt
diff options
context:
space:
mode:
Diffstat (limited to 'subsonic-android/patches/jukebox-patch.txt')
-rw-r--r--subsonic-android/patches/jukebox-patch.txt1234
1 files changed, 1234 insertions, 0 deletions
diff --git a/subsonic-android/patches/jukebox-patch.txt b/subsonic-android/patches/jukebox-patch.txt
new file mode 100644
index 00000000..5bc92000
--- /dev/null
+++ b/subsonic-android/patches/jukebox-patch.txt
@@ -0,0 +1,1234 @@
+Index: AndroidManifest.xml
+===================================================================
+--- AndroidManifest.xml (revision 2441)
++++ AndroidManifest.xml (working copy)
+@@ -114,7 +114,8 @@
+ a:authorities="net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider"/>
+
+ <meta-data a:name="android.app.default_searchable"
+- a:value="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"/>
++ a:value="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"/>
++ <activity a:name="net.sourceforge.subsonic.androidapp.activity.JukeboxActivity" a:launchMode="singleTask" a:configChanges="keyboardHidden"></activity>
+
+ </application>
+
+Index: res/drawable/menu_jukebox.png
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+
+Property changes on: res/drawable/menu_jukebox.png
+___________________________________________________________________
+Added: svn:mime-type
+ + application/octet-stream
+
+Index: res/drawable-hdpi-v4/menu_jukebox.png
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+
+Property changes on: res/drawable-hdpi-v4/menu_jukebox.png
+___________________________________________________________________
+Added: svn:mime-type
+ + application/octet-stream
+
+Index: res/layout/button_bar.xml
+===================================================================
+--- res/layout/button_bar.xml (revision 2441)
++++ res/layout/button_bar.xml (working copy)
+@@ -47,6 +47,14 @@
+ a:layout_weight="1"
+ a:layout_width="0dp"
+ a:layout_height="wrap_content"/>
++
++ <ImageButton a:id="@+id/button_bar_jukebox"
++ a:src="@drawable/menu_jukebox"
++ a:contentDescription="@string/button_bar.jukebox"
++ a:background="@drawable/menubar_button"
++ a:layout_weight="1"
++ a:layout_width="0dp"
++ a:layout_height="wrap_content"/>
+
+ <ImageButton a:id="@+id/button_bar_now_playing"
+ a:src="@drawable/menu_now_playing"
+Index: res/layout/jukebox.xml
+===================================================================
+--- res/layout/jukebox.xml (revision 0)
++++ res/layout/jukebox.xml (revision 0)
+@@ -0,0 +1,72 @@
++<?xml version="1.0" encoding="utf-8"?>
++<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
++ a:layout_width="fill_parent"
++ a:layout_height="fill_parent"
++ a:orientation="vertical" >
++
++ <include layout="@layout/tab_progress"/>
++
++ <ListView
++ a:id="@+id/jukebox_list"
++ a:layout_width="match_parent"
++ a:layout_height="0dp"
++ a:layout_weight="1" >
++ </ListView>
++
++ <LinearLayout
++ a:layout_width="fill_parent"
++ a:layout_height="wrap_content"
++ a:layout_marginTop="0dip"
++ a:background="@color/mediaControlBackground"
++ a:gravity="center"
++ a:orientation="horizontal"
++ a:paddingBottom="0dip"
++ a:paddingTop="0dip" >
++
++ <ImageButton
++ a:id="@+id/jukebox_shuffle"
++ a:layout_width="wrap_content"
++ a:layout_height="fill_parent"
++ a:background="@android:color/transparent"
++ a:src="@drawable/media_shuffle" />
++
++ <ImageButton
++ a:id="@+id/jukebox_previous"
++ a:layout_width="wrap_content"
++ a:layout_height="wrap_content"
++ a:background="@android:color/transparent"
++ a:padding="0dip"
++ a:src="@drawable/media_backward" />
++
++ <ImageButton
++ a:id="@+id/jukebox_stop"
++ a:layout_width="60dip"
++ a:layout_height="60dip"
++ a:padding="0dip"
++ a:background="@drawable/media_stop" />
++
++ <ImageButton
++ a:id="@+id/jukebox_start"
++ a:layout_width="60dip"
++ a:layout_height="60dip"
++ a:padding="0dip"
++ a:background="@drawable/media_start" />
++
++ <ImageButton
++ a:id="@+id/jukebox_next"
++ a:layout_width="wrap_content"
++ a:layout_height="wrap_content"
++ a:background="@android:color/transparent"
++ a:padding="0dip"
++ a:src="@drawable/media_forward" />
++ </LinearLayout>
++
++ <SeekBar
++ a:id="@+id/jukebox_seek"
++ a:background="@color/mediaControlBackground"
++ a:layout_width="match_parent"
++ a:layout_height="wrap_content" />
++
++ <include layout="@layout/button_bar" />
++
++</LinearLayout>
+\ No newline at end of file
+Index: res/menu/jukebox_context.xml
+===================================================================
+--- res/menu/jukebox_context.xml (revision 0)
++++ res/menu/jukebox_context.xml (revision 0)
+@@ -0,0 +1,8 @@
++<?xml version="1.0" encoding="utf-8"?>
++<menu xmlns:android="http://schemas.android.com/apk/res/android" >
++ <item android:id="@+id/jukebox_clear" android:title="@string/jukebox_clear"></item>
++ <item android:id="@+id/jukebox_remove" android:title="@string/jukebox_remove"></item>
++
++
++
++</menu>
+\ No newline at end of file
+Index: res/menu/select_album_context.xml
+===================================================================
+--- res/menu/select_album_context.xml (revision 2441)
++++ res/menu/select_album_context.xml (working copy)
+@@ -15,5 +15,6 @@
+ a:id="@+id/album_menu_pin"
+ a:title="@string/common.pin"
+ />
++ <item a:id="@+id/album_menu_jukebox_add" a:title="@string/jukebox_add"></item>
+
+ </menu>
+Index: res/menu/select_artist_context.xml
+===================================================================
+--- res/menu/select_artist_context.xml (revision 2441)
++++ res/menu/select_artist_context.xml (working copy)
+@@ -15,5 +15,6 @@
+ a:id="@+id/artist_menu_pin"
+ a:title="@string/common.pin"
+ />
++ <item a:id="@+id/artist_menu_jukebox_add" a:title="@string/jukebox_add"></item>
+
+ </menu>
+Index: res/menu/select_song_context.xml
+===================================================================
+--- res/menu/select_song_context.xml (revision 2441)
++++ res/menu/select_song_context.xml (working copy)
+@@ -15,5 +15,10 @@
+ a:id="@+id/song_menu_play_last"
+ a:title="@string/common.play_last"
+ />
++
++ <item
++ a:id="@+id/song_menu_jukebox_add"
++ a:title="@string/jukebox_add"
++ />
+
+ </menu>
+Index: res/values/strings.xml
+===================================================================
+--- res/values/strings.xml (revision 2441)
++++ res/values/strings.xml (working copy)
+@@ -226,6 +226,14 @@
+ <plurals name="select_album_donate_dialog_n_trial_days_left">
+ <item quantity="one">One day left of trial period</item>
+ <item quantity="other">%d days left of trial period</item>
+- </plurals>
++ </plurals>
++ <string name="button_bar.jukebox">Jukebox</string>
++ <string name="jukebox_start">Start</string>
++ <string name="jukebox_stop">Stop</string>
++ <string name="jukebox_next">Next</string>
++ <string name="jukebox_previous">Previous</string>
++ <string name="jukebox_clear">Clear</string>
++ <string name="jukebox_remove">Remove</string>
++ <string name="jukebox_add">Add to Jukebox</string>
+
+ </resources>
+Index: src/net/sourceforge/subsonic/androidapp/activity/JukeboxActivity.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/activity/JukeboxActivity.java (revision 0)
++++ src/net/sourceforge/subsonic/androidapp/activity/JukeboxActivity.java (revision 0)
+@@ -0,0 +1,235 @@
++/*
++ This file is part of Subsonic.
++
++ Subsonic is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ Subsonic is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
++
++ Copyright 2009 (C) Sindre Mehus
++ */
++
++package net.sourceforge.subsonic.androidapp.activity;
++
++import java.util.List;
++
++import net.sourceforge.subsonic.androidapp.R;
++import net.sourceforge.subsonic.androidapp.domain.Jukebox;
++import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
++import net.sourceforge.subsonic.androidapp.domain.MusicDirectory.Entry;
++import net.sourceforge.subsonic.androidapp.service.MusicService;
++import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
++import net.sourceforge.subsonic.androidapp.util.BackgroundTask;
++import net.sourceforge.subsonic.androidapp.util.JukeboxSongView;
++import net.sourceforge.subsonic.androidapp.util.ProgressListener;
++import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask;
++import net.sourceforge.subsonic.androidapp.util.Util;
++import android.os.Bundle;
++import android.view.ContextMenu;
++import android.view.MenuInflater;
++import android.view.MenuItem;
++import android.view.View;
++import android.view.View.OnClickListener;
++import android.view.ViewGroup;
++import android.widget.AdapterView;
++import android.widget.AdapterView.OnItemClickListener;
++import android.widget.ArrayAdapter;
++import android.widget.ImageButton;
++import android.widget.ListView;
++import android.widget.SeekBar;
++import android.widget.SeekBar.OnSeekBarChangeListener;
++
++/**
++ * @author meld0
++ *
++ */
++public class JukeboxActivity extends SubsonicTabActivity implements ProgressListener, OnClickListener, OnSeekBarChangeListener, OnItemClickListener{
++
++ private ImageButton play, stop, next, prev, shuffle;
++ private ListView entryList;
++ private SeekBar gain;
++ private Jukebox jukebox;
++
++ @Override
++ public void onCreate(Bundle savedInstanceState){
++ super.onCreate(savedInstanceState);
++ setContentView(R.layout.jukebox);
++
++ jukebox = new Jukebox();
++
++ play = (ImageButton) findViewById(R.id.jukebox_start);
++ stop = (ImageButton) findViewById(R.id.jukebox_stop);
++ prev = (ImageButton) findViewById(R.id.jukebox_previous);
++ next = (ImageButton) findViewById(R.id.jukebox_next);
++ shuffle = (ImageButton) findViewById(R.id.jukebox_shuffle);
++ entryList = (ListView) findViewById(R.id.jukebox_list);
++
++ gain = (SeekBar) findViewById(R.id.jukebox_seek);
++ gain.setMax(100);
++
++ play.setOnClickListener(this);
++ stop.setOnClickListener(this);
++ next.setOnClickListener(this);
++ prev.setOnClickListener(this);
++ shuffle.setOnClickListener(this);
++ gain.setOnSeekBarChangeListener(this);
++ entryList.setOnItemClickListener(this);
++
++ registerForContextMenu(entryList);
++ load();
++ }
++
++ @Override
++ public void updateProgress(int messageId){
++
++ }
++
++ private void load(){
++ load(Jukebox.GET);
++ }
++
++ private void load(String action){
++ load(action, "");
++ }
++
++ private void load(final String action, final String additional){
++ BackgroundTask<Boolean> task = new TabActivityBackgroundTask<Boolean>(this){
++
++ protected Boolean doInBackground() throws Throwable{
++ boolean update = false;
++ if (!Util.isOffline(JukeboxActivity.this)) {
++ MusicService musicService = MusicServiceFactory.getMusicService(JukeboxActivity.this);
++ jukebox.lastAction = action;
++ jukebox = musicService.getJukebox(jukebox, JukeboxActivity.this, this, additional);
++ update = true;
++ }
++ return update;
++ }
++
++ protected void done(Boolean result){
++ if (jukebox.isSuccess()) {
++ if (jukebox.lastAction.equals(Jukebox.SET_GAIN)) {
++ jukebox.setGain((int) (Double.valueOf(additional.split("=")[1]) * 100));
++ gain.setProgress(jukebox.getGain());
++ } else if (jukebox.lastAction.equals(Jukebox.REMOVE_ITEM)) ((SongListAdapter) entryList.getAdapter()).remove((Entry) entryList.getAdapter().getItem(
++ Integer.valueOf(additional.split("=")[1])));
++ else if (jukebox.lastAction.equals(Jukebox.CLEAR_PLAYLIST)) ((SongListAdapter) entryList.getAdapter()).clear();
++ else if (jukebox.lastAction.equals(Jukebox.SKIP_TO_INDEX)) jukebox.setCurrentIndex(Integer.valueOf(additional.split("=")[1]));
++
++ if (jukebox.isNewPlaylist()) entryList.setAdapter(new SongListAdapter(jukebox.getChildren()));
++ }
++ }
++ };
++ task.execute();
++ }
++
++ @Override
++ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo){
++ super.onCreateContextMenu(menu, view, menuInfo);
++ MenuInflater inflater = getMenuInflater();
++ inflater.inflate(R.menu.jukebox_context, menu);
++ }
++
++ @Override
++ public boolean onContextItemSelected(MenuItem menuItem){
++ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
++ switch (menuItem.getItemId()) {
++ case R.id.jukebox_remove:
++ load(Jukebox.REMOVE_ITEM, "index=" + info.position);
++ break;
++ case R.id.jukebox_clear:
++ load(Jukebox.CLEAR_PLAYLIST);
++ break;
++ default:
++ return super.onContextItemSelected(menuItem);
++ }
++ return true;
++ }
++
++ @Override
++ protected void onResume(){
++ super.onResume();
++ load();
++ }
++
++ @Override
++ public void onClick(View arg0){
++ if (arg0.equals(this.play)) {
++ load(Jukebox.START_PLAYBACK);
++ } else if (arg0.equals(this.stop)) {
++ load(Jukebox.STOP_PLAYBACK);
++ } else if (arg0.equals(this.prev)) {
++ loadW();
++ load(Jukebox.SKIP_TO_INDEX, "index=" + (jukebox.getCurrentIndex() - 1)); // TODO // MAYBE // BETTER // ?
++ } else if (arg0.equals(this.next)) {
++ loadW();
++ load(Jukebox.SKIP_TO_INDEX, "index=" + (jukebox.getCurrentIndex() + 1));
++ } else if (arg0.equals(this.shuffle)) {
++ load(Jukebox.SHUFFLE_PLAYLIST);
++ load();
++ }
++ }
++
++ @Override
++ public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2){
++ }
++
++ @Override
++ public void onStartTrackingTouch(SeekBar arg0){
++ }
++
++ @Override
++ public void onStopTrackingTouch(SeekBar arg0){
++ load(Jukebox.SET_GAIN, "gain=" + ((double) arg0.getProgress() / 100));
++ }
++
++ private class SongListAdapter extends ArrayAdapter<MusicDirectory.Entry>{
++
++ public SongListAdapter(List<MusicDirectory.Entry> entries){
++ super(JukeboxActivity.this, android.R.layout.simple_list_item_1, entries);
++ }
++
++ @Override
++ public View getView(int position, View convertView, ViewGroup parent){
++ JukeboxSongView view;
++ if (convertView != null && convertView instanceof JukeboxSongView) {
++ view = (JukeboxSongView) convertView;
++ } else {
++ view = new JukeboxSongView(JukeboxActivity.this);
++ }
++ MusicDirectory.Entry entry = getItem(position);
++
++ if (position == jukebox.getCurrentIndex()) {
++ view.setSong(entry, false, true);
++ } else view.setSong(entry, false, false);
++ return view;
++ }
++ }
++
++ @Override
++ public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3){
++ load(Jukebox.SKIP_TO_INDEX, "index=" + arg3);
++ }
++
++ public void loadW(){
++ if (!Util.isOffline(JukeboxActivity.this)) {
++ MusicService musicService = MusicServiceFactory.getMusicService(JukeboxActivity.this);
++ jukebox.lastAction = Jukebox.GET;
++ try {
++ jukebox = musicService.getJukebox(jukebox, JukeboxActivity.this, this, "");
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++ }
++ }
++
++}
+Index: src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java (working copy)
+@@ -252,6 +252,9 @@
+ case R.id.album_menu_pin:
+ downloadRecursively(entry.getId(), true, true, false);
+ break;
++ case R.id.album_menu_jukebox_add:
++ addToJukebox(entry.getId());
++ break;
+ case R.id.song_menu_play_now:
+ getDownloadService().download(songs, false, true, true);
+ break;
+@@ -261,6 +264,8 @@
+ case R.id.song_menu_play_last:
+ getDownloadService().download(songs, false, false, false);
+ break;
++ case R.id.song_menu_jukebox_add:
++ addToJukebox(entry);
+ default:
+ return super.onContextItemSelected(menuItem);
+ }
+Index: src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java (working copy)
+@@ -210,6 +210,9 @@
+ case R.id.artist_menu_pin:
+ downloadRecursively(artist.getId(), true, true, false);
+ break;
++ case R.id.artist_menu_jukebox_add:
++ addToJukebox(artist.getId());
++ break;
+ default:
+ return super.onContextItemSelected(menuItem);
+ }
+Index: src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java (working copy)
+@@ -23,6 +23,16 @@
+ import java.util.LinkedList;
+ import java.util.List;
+
++import net.sourceforge.subsonic.androidapp.R;
++import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
++import net.sourceforge.subsonic.androidapp.service.DownloadService;
++import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl;
++import net.sourceforge.subsonic.androidapp.service.MusicService;
++import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
++import net.sourceforge.subsonic.androidapp.util.Constants;
++import net.sourceforge.subsonic.androidapp.util.ImageLoader;
++import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask;
++import net.sourceforge.subsonic.androidapp.util.Util;
+ import android.app.Activity;
+ import android.content.Context;
+ import android.content.Intent;
+@@ -39,16 +49,6 @@
+ import android.view.View;
+ import android.view.Window;
+ import android.widget.TextView;
+-import net.sourceforge.subsonic.androidapp.R;
+-import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
+-import net.sourceforge.subsonic.androidapp.service.DownloadService;
+-import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl;
+-import net.sourceforge.subsonic.androidapp.service.MusicService;
+-import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
+-import net.sourceforge.subsonic.androidapp.util.Constants;
+-import net.sourceforge.subsonic.androidapp.util.ImageLoader;
+-import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask;
+-import net.sourceforge.subsonic.androidapp.util.Util;
+
+ /**
+ * @author Sindre Mehus
+@@ -63,6 +63,7 @@
+ private View musicButton;
+ private View searchButton;
+ private View playlistButton;
++ private View jukeboxButton;
+ private View nowPlayingButton;
+
+ @Override
+@@ -118,6 +119,14 @@
+ Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
+ }
+ });
++
++ jukeboxButton = findViewById(R.id.button_bar_jukebox);
++ jukeboxButton.setOnClickListener(new View.OnClickListener() {
++ @Override
++ public void onClick(View view) {
++ Util.startActivityWithoutTransition(SubsonicTabActivity.this, JukeboxActivity.class);
++ }
++ });
+
+ nowPlayingButton = findViewById(R.id.button_bar_now_playing);
+ nowPlayingButton.setOnClickListener(new View.OnClickListener() {
+@@ -225,6 +234,22 @@
+ int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
+ searchButton.setVisibility(visibility);
+ playlistButton.setVisibility(visibility);
++ jukeboxButton.setVisibility(visibility);
++ /*
++ * The following block would check for valid Permissions
++ * but it is called too often -> network traffic/ problems ?
++ *
++ MusicService musicService = MusicServiceFactory.getMusicService(this);
++ try {
++ User user = musicService.getUser(this, null);
++ if(!user.isJukeboxControl()){
++ jukeboxButton.setVisibility(View.GONE);
++ }
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++ */
+ }
+
+ public void setProgressVisible(boolean visible) {
+@@ -363,5 +388,67 @@
+ }
+ }
+ }
++
++ protected void addToJukebox(String id) {
++
++ List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
++ MusicService musicService = MusicServiceFactory.getMusicService(this);
++ MusicDirectory root;
++
++ try {
++ root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, null);
++ collectEntries(root, entries);
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++
++
++ StringBuilder build = new StringBuilder();
++ build.append("action=add");
++ for(MusicDirectory.Entry entry2: entries){
++ build.append("&id=");
++ build.append(entry2.getId());
++ }
++ try {
++ musicService.getJukebox(null, this, null, build.toString());
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++
++ }
++
++ protected void addToJukebox(MusicDirectory.Entry entry) {
++ try {
++ MusicService musicService = MusicServiceFactory.getMusicService(this);
++ musicService.getJukebox(null, this, null, "action=add&id="+entry.getId());
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++ }
++
++ private List<MusicDirectory.Entry> collectEntries(MusicDirectory parent, List<MusicDirectory.Entry> songs){
++ 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);
++ try {
++ collectEntries(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, null), songs);
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++ }
++
++ return songs;
++ }
++
++
++
+ }
+
+Index: src/net/sourceforge/subsonic/androidapp/domain/Jukebox.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/domain/Jukebox.java (revision 0)
++++ src/net/sourceforge/subsonic/androidapp/domain/Jukebox.java (revision 0)
+@@ -0,0 +1,113 @@
++/*
++ This file is part of Subsonic.
++
++ Subsonic is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ Subsonic is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
++
++ Copyright 2009 (C) Sindre Mehus
++ */
++
++package net.sourceforge.subsonic.androidapp.domain;
++
++import java.io.Serializable;
++import java.util.List;
++
++/**
++ * @author meld0
++ *
++ */
++public class Jukebox extends MusicDirectory implements Serializable{
++
++ public String lastAction;
++
++ //get, start, stop, skip, add, clear, remove, shuffle, setGain
++ public static final String GET = "get";
++ public static final String START_PLAYBACK = "start";
++ public static final String STOP_PLAYBACK = "stop";
++ public static final String SKIP_TO_INDEX = "skip";
++ public static final String ADD_TO_PLAYLIST = "add";
++ public static final String CLEAR_PLAYLIST = "clear";
++ public static final String REMOVE_ITEM = "remove";
++ public static final String SHUFFLE_PLAYLIST = "shuffle";
++ public static final String SET_GAIN = "setGain";
++
++ public Jukebox() {
++ super();
++ }
++
++ public boolean isPlaying() {
++ return playing;
++ }
++
++ public void setPlaying(boolean playing) {
++ this.playing = playing;
++ }
++
++ public int getGain() {
++ return gain;
++ }
++
++ public void setGain(int gain) {
++ this.gain = gain;
++ }
++
++ public int getCurrentIndex() {
++ return currentIndex;
++ }
++
++ public void setCurrentIndex(int currentIndex) {
++ this.currentIndex = currentIndex;
++ }
++
++ public boolean isAvailable() {
++ return available;
++ }
++
++ public boolean isSuccess() {
++ return success;
++ }
++
++ public void setAvailable(boolean available) {
++ this.available = available;
++ }
++
++ public void setSuccess(boolean success) {
++ this.success = success;
++ }
++
++ public boolean isNewPlaylist() {
++ return newPlaylist;
++ }
++
++ public void setNewPlaylist(boolean newPlaylist) {
++ this.newPlaylist = newPlaylist;
++ }
++
++ private boolean playing = false;
++ private int gain = 0;
++ private int currentIndex = 0;
++ private boolean available = false;
++ private boolean success = false;
++ private boolean newPlaylist = false;
++
++ public void addChildren(List<Entry> children){
++ super.addChildren(children);
++ }
++
++ public void reset(){
++ this.available = false;
++ this.success = false;
++ this.newPlaylist = false;
++ }
++
++}
+Index: src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java (working copy)
+@@ -59,6 +59,13 @@
+ }
+ return result;
+ }
++
++ protected void addChildren(List<Entry> children){
++ if(!children.isEmpty()) {
++ this.children.clear();
++ this.children.addAll(children);
++ }
++ }
+
+ public static class Entry implements Serializable {
+ private String id;
+Index: src/net/sourceforge/subsonic/androidapp/domain/User.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/domain/User.java (revision 0)
++++ src/net/sourceforge/subsonic/androidapp/domain/User.java (revision 0)
+@@ -0,0 +1,43 @@
++/*
++ This file is part of Subsonic.
++
++ Subsonic is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ Subsonic is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
++
++ Copyright 2009 (C) Sindre Mehus
++ */
++
++package net.sourceforge.subsonic.androidapp.domain;
++
++import java.io.Serializable;
++
++/**
++ * @author meld0
++ *
++ */
++public class User implements Serializable{
++
++ //TODO implement missing user attributes
++
++ private boolean jukeboxControl;
++
++ public boolean isJukeboxControl() {
++ return jukeboxControl;
++ }
++
++ public void setJukeboxControl(boolean jukeboxControl) {
++ this.jukeboxControl = jukeboxControl;
++ }
++
++
++}
+Index: src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java (working copy)
+@@ -21,9 +21,11 @@
+ import android.content.Context;
+ import android.graphics.Bitmap;
+ import net.sourceforge.subsonic.androidapp.domain.Indexes;
++import net.sourceforge.subsonic.androidapp.domain.Jukebox;
+ import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
+ import net.sourceforge.subsonic.androidapp.domain.MusicFolder;
+ import net.sourceforge.subsonic.androidapp.domain.Playlist;
++import net.sourceforge.subsonic.androidapp.domain.User;
+ import net.sourceforge.subsonic.androidapp.domain.Version;
+ import net.sourceforge.subsonic.androidapp.domain.SearchResult;
+ import net.sourceforge.subsonic.androidapp.domain.SearchCritera;
+@@ -199,4 +201,17 @@
+ restUrl = newUrl;
+ }
+ }
++
++ @Override
++ public Jukebox getJukebox(Jukebox jukebox, Context context,
++ ProgressListener progressListener, String action) throws Exception {
++ return musicService.getJukebox(jukebox, context, progressListener, action);
++ }
++
++ @Override
++ public User getUser(Context context, ProgressListener progressListener)
++ throws Exception {
++ return musicService.getUser(context, progressListener);
++ }
++
+ }
+Index: src/net/sourceforge/subsonic/androidapp/service/MusicService.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/service/MusicService.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/service/MusicService.java (working copy)
+@@ -21,9 +21,11 @@
+ import android.content.Context;
+ import android.graphics.Bitmap;
+ import net.sourceforge.subsonic.androidapp.domain.Indexes;
++import net.sourceforge.subsonic.androidapp.domain.Jukebox;
+ import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
+ import net.sourceforge.subsonic.androidapp.domain.MusicFolder;
+ import net.sourceforge.subsonic.androidapp.domain.Playlist;
++import net.sourceforge.subsonic.androidapp.domain.User;
+ import net.sourceforge.subsonic.androidapp.domain.Version;
+ import net.sourceforge.subsonic.androidapp.domain.SearchResult;
+ import net.sourceforge.subsonic.androidapp.domain.SearchCritera;
+@@ -74,4 +76,8 @@
+ Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception;
+
+ String getVideoUrl(Context context, String id);
++
++ Jukebox getJukebox(Jukebox jukebox, Context context, ProgressListener progressListener , String action) throws Exception;
++
++ User getUser(Context context, ProgressListener progressListener) throws Exception;
+ }
+\ No newline at end of file
+Index: src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java (working copy)
+@@ -28,6 +28,7 @@
+ import android.util.Log;
+ import net.sourceforge.subsonic.androidapp.R;
+ import net.sourceforge.subsonic.androidapp.domain.Indexes;
++import net.sourceforge.subsonic.androidapp.domain.Jukebox;
+ import net.sourceforge.subsonic.androidapp.domain.Lyrics;
+ import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
+ import net.sourceforge.subsonic.androidapp.domain.MusicFolder;
+@@ -35,10 +36,12 @@
+ import net.sourceforge.subsonic.androidapp.domain.SearchCritera;
+ import net.sourceforge.subsonic.androidapp.domain.SearchResult;
+ import net.sourceforge.subsonic.androidapp.domain.ServerInfo;
++import net.sourceforge.subsonic.androidapp.domain.User;
+ import net.sourceforge.subsonic.androidapp.domain.Version;
+ import net.sourceforge.subsonic.androidapp.service.parser.AlbumListParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.ErrorParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.IndexesParser;
++import net.sourceforge.subsonic.androidapp.service.parser.JukeboxParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.LicenseParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.LyricsParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.MusicDirectoryParser;
+@@ -48,6 +51,7 @@
+ import net.sourceforge.subsonic.androidapp.service.parser.RandomSongsParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.SearchResult2Parser;
+ import net.sourceforge.subsonic.androidapp.service.parser.SearchResultParser;
++import net.sourceforge.subsonic.androidapp.service.parser.UserParser;
+ import net.sourceforge.subsonic.androidapp.service.parser.VersionParser;
+ import net.sourceforge.subsonic.androidapp.service.ssl.SSLSocketFactory;
+ import net.sourceforge.subsonic.androidapp.service.ssl.TrustSelfSignedStrategy;
+@@ -195,6 +199,53 @@
+ Util.close(reader);
+ }
+ }
++
++ public Jukebox getJukebox(Jukebox jukebox, Context context, ProgressListener progressListener, String action) throws Exception {
++
++ List<String> parameterNames = new ArrayList<String>();
++ List<Object> parameterValues = new ArrayList<Object>();
++ if(jukebox != null){
++ parameterNames.add("action");
++ parameterValues.add(jukebox.lastAction);
++ }
++ if(!action.isEmpty()){
++ String[] names_values = action.split("&");
++ for(String name_value: names_values){
++ String[] pair = name_value.split("=");
++ parameterNames.add(pair[0]);
++ parameterValues.add(pair[1]);
++ }
++ }
++
++ Reader reader = getReader(context, progressListener, "jukeboxControl", null, parameterNames, parameterValues);
++
++ try {
++ return new JukeboxParser(context).parse(jukebox, reader, progressListener);
++ } finally {
++ Util.close(reader);
++ }
++ }
++
++ public User getUser(Context context, ProgressListener progressListener) throws Exception {
++
++ SharedPreferences prefs = Util.getPreferences(context);
++ int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
++ String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
++
++ List<String> parameterNames = new ArrayList<String>();
++ List<Object> parameterValues = new ArrayList<Object>();
++ parameterNames.add("username");
++ parameterValues.add(username);
++
++ Reader reader = getReader(context, progressListener, "getUser", null, parameterNames, parameterValues);
++
++ try {
++ return new UserParser(context).parse(reader, progressListener);
++ } finally {
++ Util.close(reader);
++ }
++ }
++
+
+ @Override
+ public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+Index: src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java (revision 2441)
++++ src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java (working copy)
+@@ -97,6 +97,11 @@
+ String s = get(name);
+ return s == null ? null : Long.valueOf(s);
+ }
++
++ protected Double getDouble(String name) {
++ String s = get(name);
++ return s == null ? null : Double.valueOf(s);
++ }
+
+ protected void init(Reader reader) throws Exception {
+ parser = Xml.newPullParser();
+Index: src/net/sourceforge/subsonic/androidapp/service/parser/JukeboxParser.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/service/parser/JukeboxParser.java (revision 0)
++++ src/net/sourceforge/subsonic/androidapp/service/parser/JukeboxParser.java (revision 0)
+@@ -0,0 +1,86 @@
++/*
++ This file is part of Subsonic.
++
++ Subsonic is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ Subsonic is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
++
++ Copyright 2009 (C) Sindre Mehus
++ */
++
++package net.sourceforge.subsonic.androidapp.service.parser;
++
++import java.io.Reader;
++
++import net.sourceforge.subsonic.androidapp.R;
++import net.sourceforge.subsonic.androidapp.domain.Jukebox;
++import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
++import net.sourceforge.subsonic.androidapp.util.ProgressListener;
++
++import org.xmlpull.v1.XmlPullParser;
++
++import android.content.Context;
++
++/**
++ * @author meld0
++ *
++ */
++public class JukeboxParser extends MusicDirectoryEntryParser {
++
++ public JukeboxParser(Context context) {
++ super(context);
++ }
++
++ public Jukebox parse(Jukebox jukebox, Reader reader,
++ ProgressListener progressListener) throws Exception {
++ updateProgress(progressListener, R.string.parser_reading);
++
++ if (jukebox == null)
++ jukebox = new Jukebox();
++ jukebox.reset();
++ init(reader);
++ jukebox.setAvailable(true);
++
++ MusicDirectory dir = new MusicDirectory();
++
++ int eventType;
++ do {
++ eventType = nextParseEvent();
++ if (eventType == XmlPullParser.START_TAG) {
++ String name = getElementName();
++ if ("entry".equals(name)) {
++ dir.addChild(parseEntry());
++ jukebox.setNewPlaylist(true);
++ } else if ("jukeboxPlaylist".equals(name)) {
++ jukebox.setPlaying(getBoolean("playing"));
++ jukebox.setGain((int) (getDouble("gain") * 100));
++ jukebox.setCurrentIndex(getInteger("currentIndex"));
++ } else if ("subsonic-response".equals(name)) {
++ if (get("status").equals("ok"))
++ jukebox.setSuccess(true);
++ } else if ("error".equals(name)) {
++ handleError();
++ }
++ }
++ } while (eventType != XmlPullParser.END_DOCUMENT);
++
++ if(jukebox.isNewPlaylist()) {
++ jukebox.addChildren(dir.getChildren());
++ }
++
++ validate();
++ updateProgress(progressListener, R.string.parser_reading_done);
++
++ return jukebox;
++ }
++
++}
+Index: src/net/sourceforge/subsonic/androidapp/service/parser/UserParser.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/service/parser/UserParser.java (revision 0)
++++ src/net/sourceforge/subsonic/androidapp/service/parser/UserParser.java (revision 0)
+@@ -0,0 +1,67 @@
++/*
++ This file is part of Subsonic.
++
++ Subsonic is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ Subsonic is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
++
++ Copyright 2009 (C) Sindre Mehus
++ */
++
++package net.sourceforge.subsonic.androidapp.service.parser;
++
++import java.io.Reader;
++
++import net.sourceforge.subsonic.androidapp.R;
++import net.sourceforge.subsonic.androidapp.domain.User;
++import net.sourceforge.subsonic.androidapp.util.ProgressListener;
++
++import org.xmlpull.v1.XmlPullParser;
++
++import android.content.Context;
++
++/**
++ * @author meld0
++ *
++ */
++public class UserParser extends AbstractParser{
++
++ public UserParser(Context context) {
++ super(context);
++ }
++
++ public User parse(Reader reader, ProgressListener progressListener) throws Exception {
++ updateProgress(progressListener, R.string.parser_reading);
++ init(reader);
++
++ User user = new User();
++ int eventType;
++ do {
++ eventType = nextParseEvent();
++ if (eventType == XmlPullParser.START_TAG) {
++ String name = getElementName();
++ if ("user".equals(name)) {
++ user.setJukeboxControl(getBoolean("jukeboxRole"));
++ } else if ("error".equals(name)) {
++ handleError();
++ }
++ }
++ } while (eventType != XmlPullParser.END_DOCUMENT);
++
++ validate();
++ updateProgress(progressListener, R.string.parser_reading_done);
++
++ return user;
++ }
++
++
++}
+Index: src/net/sourceforge/subsonic/androidapp/util/JukeboxSongView.java
+===================================================================
+--- src/net/sourceforge/subsonic/androidapp/util/JukeboxSongView.java (revision 0)
++++ src/net/sourceforge/subsonic/androidapp/util/JukeboxSongView.java (revision 0)
+@@ -0,0 +1,88 @@
++/*
++ This file is part of Subsonic.
++
++ Subsonic is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ Subsonic is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
++
++ Copyright 2009 (C) Sindre Mehus
++ */
++
++package net.sourceforge.subsonic.androidapp.util;
++
++import net.sourceforge.subsonic.androidapp.R;
++import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
++import android.content.Context;
++import android.view.LayoutInflater;
++import android.view.View;
++import android.widget.CheckedTextView;
++import android.widget.LinearLayout;
++import android.widget.TextView;
++
++/**
++ * @author meld0
++ *
++ */
++public class JukeboxSongView extends LinearLayout{
++
++ private CheckedTextView checkedTextView;
++ private TextView titleTextView;
++ private TextView artistTextView;
++ private TextView durationTextView;
++ private TextView statusTextView;
++ private MusicDirectory.Entry song;
++
++ public JukeboxSongView(Context context) {
++ super(context);
++ LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true);
++
++ checkedTextView = (CheckedTextView) findViewById(R.id.song_check);
++ titleTextView = (TextView) findViewById(R.id.song_title);
++ artistTextView = (TextView) findViewById(R.id.song_artist);
++ durationTextView = (TextView) findViewById(R.id.song_duration);
++ statusTextView = (TextView) findViewById(R.id.song_status);
++
++ }
++
++ public void setSong(MusicDirectory.Entry song, boolean checkable, boolean playing) {
++ this.song = song;
++ StringBuilder artist = new StringBuilder(40);
++
++ String bitRate = null;
++ if (song.getBitRate() != null) {
++ bitRate = String.format(getContext().getString(R.string.song_details_kbps), song.getBitRate());
++ }
++
++ String fileFormat = null;
++ if (song.getTranscodedSuffix() != null && !song.getTranscodedSuffix().equals(song.getSuffix())) {
++ fileFormat = String.format("%s > %s", song.getSuffix(), song.getTranscodedSuffix());
++ } else {
++ fileFormat = song.getSuffix();
++ }
++
++ artist.append(song.getArtist()).append(" (")
++ .append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat))
++ .append(")");
++
++ titleTextView.setText(song.getTitle());
++ artistTextView.setText(artist);
++ durationTextView.setText(Util.formatDuration(song.getDuration()));
++ checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE);
++
++ if(playing){
++ titleTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.stat_notify_playing, 0, 0, 0);
++ }else{
++ titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
++ }
++ }
++
++}