aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--subsonic-android/AndroidManifest.xml4
-rw-r--r--subsonic-android/res/layout/save_playlist.xml9
-rw-r--r--subsonic-android/res/menu/select_video_context.xml5
-rw-r--r--subsonic-android/res/raw/changelog.xml13
-rw-r--r--subsonic-android/res/values/arrays.xml11
-rw-r--r--subsonic-android/res/values/strings.xml11
-rw-r--r--subsonic-android/res/xml/settings.xml11
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java74
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java3
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java10
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java170
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java9
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadService.java4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java45
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/MusicService.java4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java9
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java33
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/Constants.java1
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/ShufflePlayBuffer.java11
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/Util.java2130
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java5
25 files changed, 1391 insertions, 1201 deletions
diff --git a/.gitignore b/.gitignore
index 553414a3..443eb753 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
-subsonic-android/ant.properties
+subsonic-android/ant.properties
subsonic-android/.classpath
subsonic-android/.project
subsonic-android/bin/*
subsonic-android/gen/*
subsonic-android/private/*
-subsonic-android/nbandroid/*
+subsonic-android/nbandroid/*
+subsonic-android/.idea
+subsonic-android/subsonic-android.iml
diff --git a/subsonic-android/AndroidManifest.xml b/subsonic-android/AndroidManifest.xml
index 76677dbe..bdfbb422 100644
--- a/subsonic-android/AndroidManifest.xml
+++ b/subsonic-android/AndroidManifest.xml
@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="github.daneren2005.dsub"
android:installLocation="internalOnly"
- android:versionCode="52"
- android:versionName="4.0.3">
+ android:versionCode="54"
+ android:versionName="4.0.5">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
diff --git a/subsonic-android/res/layout/save_playlist.xml b/subsonic-android/res/layout/save_playlist.xml
index a0272f37..43f1827a 100644
--- a/subsonic-android/res/layout/save_playlist.xml
+++ b/subsonic-android/res/layout/save_playlist.xml
@@ -13,5 +13,14 @@
android:inputType="text"
android:singleLine="true"/>
+ <CheckBox
+ android:id="@+id/save_playlist_overwrite"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/playlist.overwrite"
+ android:layout_marginLeft="4dp"
+ android:checked="false"
+ android:visibility="gone"/>
+
</LinearLayout>
diff --git a/subsonic-android/res/menu/select_video_context.xml b/subsonic-android/res/menu/select_video_context.xml
index 7a45d34a..5926f8a5 100644
--- a/subsonic-android/res/menu/select_video_context.xml
+++ b/subsonic-android/res/menu/select_video_context.xml
@@ -11,11 +11,6 @@
<item
android:id="@+id/song_menu_play_external"
android:title="@string/common.play_external"/>
-
- <item
- android:id="@+id/song_menu_webview"
- android:title="@string/common.webview"
- />
<item
android:id="@+id/song_menu_download"
diff --git a/subsonic-android/res/raw/changelog.xml b/subsonic-android/res/raw/changelog.xml
index 14f59866..95f71228 100644
--- a/subsonic-android/res/raw/changelog.xml
+++ b/subsonic-android/res/raw/changelog.xml
@@ -1,5 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
+ <release version="4.0.5" versioncode="54" releasedate="6/7/2013">
+ <change>Fix album art on old Subsonic/MusicCabinet servers</change>
+ </release>
+ <release version="4.0.4" versioncode="53" releasedate="6/6/2013">
+ <change>Added Genre parsing (thanks archrival)</change>
+ <change>Changed Genre to combo selection on 4.8+ servers</change>
+ <change>Added video choice similar to Subsonic (Raw is the same as MX but you can choose which player to use)</change>
+ <change>Added 4x2, 4x3, 4x4 widgets (thanks archrival)</change>
+ <change>Add option to create new playlist when adding song to playlists</change>
+ <change>Added option to overwrite existing playlist on 4.7+ servers</change>
+ <change>Fix when removing the current server</change>
+ <change>Fix edge case in new sort</change>
+ </release>
<release version="4.0.3" versioncode="52" releasedate="5/31/2013">
<change>Sort by disc number if specified in tags</change>
<change>Show starred artists in starred list</change>
diff --git a/subsonic-android/res/values/arrays.xml b/subsonic-android/res/values/arrays.xml
index 80cc4fad..daca9b8b 100644
--- a/subsonic-android/res/values/arrays.xml
+++ b/subsonic-android/res/values/arrays.xml
@@ -137,4 +137,15 @@
<item>@string/settings.temp_loss_nothing</item>
</string-array>
+ <string-array name="videoPlayerValues">
+ <item>raw</item>
+ <item>transcode</item>
+ <item>flash</item>
+ </string-array>
+ <string-array name="videoPlayerNames">
+ <item>@string/settings.video_raw</item>
+ <item>@string/settings.video_transcode</item>
+ <item>@string/settings.video_flash</item>
+ </string-array>
+
</resources> \ No newline at end of file
diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml
index 5635a74e..a3f8f540 100644
--- a/subsonic-android/res/values/strings.xml
+++ b/subsonic-android/res/values/strings.xml
@@ -18,9 +18,8 @@
<string name="common.name">Name</string>
<string name="common.comment">Comment</string>
<string name="common.public">Public</string>
- <string name="common.webview">Play Web View (Flash)</string>
- <string name="common.play_external">Play External Player</string>
- <string name="common.stream_external">Stream External Player</string>
+ <string name="common.play_external">Play Video</string>
+ <string name="common.stream_external">Stream Video</string>
<string name="common.confirm">Confirm</string>
<string name="button_bar.home">Home</string>
@@ -75,6 +74,7 @@
<string name="playlist.update_info">Update Information</string>
<string name="playlist.updated_info">Updated playlist information for %s</string>
<string name="playlist.updated_info_error">Failed to update playlist information for %s</string>
+ <string name="playlist.overwrite">Overwrite existing playlist</string>
<string name="help.label">Help</string>
<string name="help.title">Welcome to DSub!</string>
@@ -287,6 +287,11 @@
<string name="settings.chat_refresh">Chat Refresh Rate (Secs)</string>
<string name="settings.chat_enabled">Chat Enabled</string>
<string name="settings.chat_enabled_summary">Whether or not to display the chat tab. Restart app after changing.</string>
+ <string name="settings.video_title">Video</string>
+ <string name="settings.video_player">Video Player</string>
+ <string name="settings.video_raw">Raw (Requires Subsonic 4.8+)</string>
+ <string name="settings.video_transcode">Direct Transcode (Requires video -> mp4 or similar setup on Server)</string>
+ <string name="settings.video_flash">Flash (Requires Plugin)</string>
<string name="shuffle.startYear">Start Year:</string>
<string name="shuffle.endYear">End Year:</string>
diff --git a/subsonic-android/res/xml/settings.xml b/subsonic-android/res/xml/settings.xml
index 17a51621..d3618ffa 100644
--- a/subsonic-android/res/xml/settings.xml
+++ b/subsonic-android/res/xml/settings.xml
@@ -25,6 +25,17 @@
</PreferenceCategory>
+ <PreferenceCategory
+ android:title="@string/settings.video_title">
+
+ <ListPreference
+ android:title="@string/settings.video_player"
+ android:key="videoPlayer"
+ android:defaultValue="raw"
+ android:entryValues="@array/videoPlayerValues"
+ android:entries="@array/videoPlayerNames"/>
+ </PreferenceCategory>
+
<PreferenceCategory
android:title="@string/settings.network_title">
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
index 3448867a..bfdc9eb9 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
@@ -86,52 +86,6 @@ public class DownloadActivity extends SubsonicActivity {
return false;
}
}
-
- @Override
- public Dialog onCreateDialog(int id) {
- if (id == DownloadFragment.DIALOG_SAVE_PLAYLIST) {
- AlertDialog.Builder builder;
-
- LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- final View layout = inflater.inflate(R.layout.save_playlist, null);
- playlistNameView = (EditText) layout.findViewById(R.id.save_playlist_name);
-
- builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.download_playlist_title);
- builder.setMessage(R.string.download_playlist_name);
- builder.setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- savePlaylistInBackground(String.valueOf(playlistNameView.getText()));
- }
- });
- builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
- builder.setView(layout);
- builder.setCancelable(true);
-
- return builder.create();
- } else {
- return super.onCreateDialog(id);
- }
- }
-
- @Override
- public void onPrepareDialog(int id, Dialog dialog) {
- if (id == DownloadFragment.DIALOG_SAVE_PLAYLIST) {
- String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null;
- if (playlistName != null) {
- playlistNameView.setText(playlistName);
- } else {
- DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
- playlistNameView.setText(dateFormat.format(new Date()));
- }
- }
- }
@Override
public void onBackPressed() {
@@ -139,32 +93,4 @@ public class DownloadActivity extends SubsonicActivity {
super.onBackPressed();
}
}
-
- private void savePlaylistInBackground(final String playlistName) {
- Util.toast(this, getResources().getString(R.string.download_playlist_saving, playlistName));
- getDownloadService().setSuggestedPlaylistName(playlistName);
- new SilentBackgroundTask<Void>(DownloadActivity.this) {
- @Override
- protected Void doInBackground() throws Throwable {
- List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
- for (DownloadFile downloadFile : getDownloadService().getSongs()) {
- entries.add(downloadFile.getSong());
- }
- MusicService musicService = MusicServiceFactory.getMusicService(DownloadActivity.this);
- musicService.createPlaylist(null, playlistName, entries, DownloadActivity.this, null);
- return null;
- }
-
- @Override
- protected void done(Void result) {
- Util.toast(DownloadActivity.this, R.string.download_playlist_done);
- }
-
- @Override
- protected void error(Throwable error) {
- String msg = getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
- Util.toast(DownloadActivity.this, msg);
- }
- }.execute();
- }
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java
index 88e487f1..e40ecab8 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java
@@ -69,6 +69,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
private Preference addServerPreference;
private PreferenceCategory serversCategory;
private EditTextPreference chatRefreshRate;
+ private ListPreference videoPlayer;
private int serverCount = 3;
private SharedPreferences settings;
@@ -95,6 +96,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
addServerPreference = (Preference) findPreference(Constants.PREFERENCES_KEY_SERVER_ADD);
serversCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_SERVER_KEY);
chatRefreshRate = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH);
+ videoPlayer = (ListPreference) findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER);
settings = Util.getPreferences(this);
serverCount = settings.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 3);
@@ -191,6 +193,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
tempLoss.setSummary(tempLoss.getEntry());
bufferLength.setSummary(bufferLength.getText() + " seconds");
chatRefreshRate.setSummary(chatRefreshRate.getText());
+ videoPlayer.setSummary(videoPlayer.getEntry());
for (ServerSettings ss : serverSettings.values()) {
ss.update();
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java b/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java
index 10e9ef22..bb49378a 100644
--- a/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java
+++ b/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java
@@ -82,6 +82,10 @@ public class MusicDirectory {
return result;
}
+ public int getChildrenSize() {
+ return children.size();
+ }
+
public void sortChildren() {
EntryComparator.sort(children);
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java
index 9113cd74..9bcb4a05 100644
--- a/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java
+++ b/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java
@@ -606,7 +606,11 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe
}.execute();
return true;
case R.id.menu_save_playlist:
- context.showDialog(DIALOG_SAVE_PLAYLIST);
+ List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
+ for (DownloadFile downloadFile : getDownloadService().getSongs()) {
+ entries.add(downloadFile.getSong());
+ }
+ createNewPlaylist(entries, true);
return true;
case R.id.menu_star:
toggleStarred(song.getSong());
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
index c4da3070..a38fa5fd 100644
--- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
+++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
@@ -241,11 +241,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
replaceFragment(fragment, R.id.select_album_layout);
} else if (entry.isVideo()) {
- if(entryExists(entry)) {
- playExternalPlayer(entry);
- } else {
- streamExternalPlayer(entry);
- }
+ playVideo(entry);
}
}
}
@@ -499,7 +495,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter
warnIfNetworkOrStorageUnavailable();
getDownloadService().download(songs, save, autoplay, playNext, shuffle);
if (playlistName != null) {
- getDownloadService().setSuggestedPlaylistName(playlistName);
+ getDownloadService().setSuggestedPlaylistName(playlistName, playlistId);
+ } else {
+ getDownloadService().setSuggestedPlaylistName(null, null);
}
if (autoplay) {
Util.startActivityWithoutTransition(context, DownloadActivity.class);
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java
index 2fcdc00f..77479424 100644
--- a/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java
+++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java
@@ -34,8 +34,8 @@ import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.AdapterView;
import android.widget.Button;
+import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragment;
@@ -66,8 +66,10 @@ import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.LoadingTask;
import github.daneren2005.dsub.util.Util;
import java.io.File;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
@@ -253,9 +255,6 @@ public class SubsonicFragment extends SherlockFragment {
case R.id.song_menu_star:
toggleStarred(entry);
break;
- case R.id.song_menu_webview:
- playWebView(entry);
- break;
case R.id.song_menu_play_external:
playExternalPlayer(entry);
break;
@@ -388,11 +387,9 @@ public class SubsonicFragment extends SherlockFragment {
final String oldStartYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "");
final String oldEndYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "");
final String oldGenre = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "");
-
- Version version = Util.getServerRestVersion(context);
- Version genreVersion = new Version("1.9.0");
+
boolean _useCombo = false;
- if(version.compareTo(genreVersion) >= 0) {
+ if(Util.checkServerVersion(context, "1.9.0")) {
genreBox.setVisibility(View.GONE);
genreCombo.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
@@ -627,6 +624,7 @@ public class SubsonicFragment extends SherlockFragment {
@Override
protected void done(final List<Playlist> playlists) {
List<String> names = new ArrayList<String>();
+ names.add("Create New");
for(Playlist playlist: playlists) {
names.add(playlist.getName());
}
@@ -635,7 +633,12 @@ public class SubsonicFragment extends SherlockFragment {
builder.setTitle("Add to Playlist")
.setItems(names.toArray(new CharSequence[names.size()]), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
- addToPlaylist(playlists.get(which), songs);
+
+ if(which > 0) {
+ addToPlaylist(playlists.get(which - 1), songs);
+ } else {
+ createNewPlaylist(songs, false);
+ }
}
});
AlertDialog dialog = builder.create();
@@ -683,6 +686,107 @@ public class SubsonicFragment extends SherlockFragment {
}
}.execute();
}
+
+ protected void createNewPlaylist(final List<MusicDirectory.Entry> songs, boolean getSuggestion) {
+ View layout = context.getLayoutInflater().inflate(R.layout.save_playlist, null);
+ final EditText playlistNameView = (EditText) layout.findViewById(R.id.save_playlist_name);
+ final CheckBox overwriteCheckBox = (CheckBox) layout.findViewById(R.id.save_playlist_overwrite);
+ if(getSuggestion) {
+ String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null;
+ if (playlistName != null) {
+ playlistNameView.setText(playlistName);
+ try {
+ if(Util.checkServerVersion(context, "1.8.0") && Integer.parseInt(getDownloadService().getSuggestedPlaylistId()) != -1) {
+ overwriteCheckBox.setChecked(true);
+ overwriteCheckBox.setVisibility(View.VISIBLE);
+ }
+ } catch(Exception e) {
+ Log.d(TAG, "Playlist id isn't a integer, probably MusicCabinet");
+ }
+ } else {
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ playlistNameView.setText(dateFormat.format(new Date()));
+ }
+ } else {
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ playlistNameView.setText(dateFormat.format(new Date()));
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.download_playlist_title)
+ .setMessage(R.string.download_playlist_name)
+ .setView(layout)
+ .setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ if(overwriteCheckBox.isChecked()) {
+ overwritePlaylist(songs, String.valueOf(playlistNameView.getText()), getDownloadService().getSuggestedPlaylistId());
+ } else {
+ createNewPlaylist(songs, String.valueOf(playlistNameView.getText()));
+ }
+ }
+ })
+ .setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ })
+ .setCancelable(true);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+ private void createNewPlaylist(final List<MusicDirectory.Entry> songs, final String name) {
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.createPlaylist(null, name, songs, context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ Util.toast(context, R.string.download_playlist_done);
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ String msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+ private void overwritePlaylist(final List<MusicDirectory.Entry> songs, final String name, final String id) {
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ MusicDirectory playlist = musicService.getPlaylist(id, name, context, null);
+ List<MusicDirectory.Entry> toDelete = playlist.getChildren();
+ musicService.overwritePlaylist(id, name, toDelete.size(), songs, context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ Util.toast(context, R.string.download_playlist_done);
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = getErrorMessage(error);
+ } else {
+ msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
+ }
+
+ Util.toast(context, msg, false);
+ }
+ }.execute();
+ }
public void displaySongInfo(final MusicDirectory.Entry song) {
Integer bitrate = null;
@@ -747,6 +851,19 @@ public class SubsonicFragment extends SherlockFragment {
.setMessage(msg)
.show();
}
+
+ protected void playVideo(MusicDirectory.Entry entry) {
+ String videoPlayerType = Util.getVideoPlayerType(context);
+ if(entryExists(entry)) {
+ playExternalPlayer(entry);
+ } else {
+ if("flash".equals(videoPlayerType)) {
+ playWebView(entry);
+ } else {
+ streamExternalPlayer(entry, "raw".equals(videoPlayerType) ? "raw" : entry.getTranscodedSuffix());
+ }
+ }
+ }
protected void playWebView(MusicDirectory.Entry entry) {
int maxBitrate = Util.getMaxVideoBitrate(context);
@@ -773,17 +890,32 @@ public class SubsonicFragment extends SherlockFragment {
}
}
protected void streamExternalPlayer(MusicDirectory.Entry entry) {
- int maxBitrate = Util.getMaxVideoBitrate(context);
+ String videoPlayerType = Util.getVideoPlayerType(context);
+ streamExternalPlayer(entry, "raw".equals(videoPlayerType) ? "raw" : entry.getTranscodedSuffix());
+ }
+ protected void streamExternalPlayer(MusicDirectory.Entry entry, String format) {
+ try {
+ int maxBitrate = Util.getMaxVideoBitrate(context);
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoStreamUrl(maxBitrate, context, entry.getId())), "video/*");
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoStreamUrl(format, maxBitrate, context, entry.getId())), "video/*");
+ intent.putExtra("title", entry.getTitle());
- List<ResolveInfo> intents = context.getPackageManager()
- .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if(intents != null && intents.size() > 0) {
- startActivity(intent);
- } else {
- Util.toast(context, R.string.download_no_streaming_player);
+ List<ResolveInfo> intents = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if(intents != null && intents.size() > 0) {
+ startActivity(intent);
+ } else {
+ Util.toast(context, R.string.download_no_streaming_player);
+ }
+ } catch(Exception error) {
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = error.getMessage();
+ } else {
+ msg = context.getResources().getString(R.string.download_no_streaming_player) + " " + error.getMessage();
+ }
+
+ Util.toast(context, msg, false);
}
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
index 49542045..218949c4 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java
@@ -170,6 +170,11 @@ public class CachedMusicService implements MusicService {
}
@Override
+ public void overwritePlaylist(String id, String name, int toRemove, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
+ musicService.overwritePlaylist(id, name, toRemove, toAdd, context, progressListener);
+ }
+
+ @Override
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
musicService.updatePlaylist(id, name, comment, pub, context, progressListener);
}
@@ -225,8 +230,8 @@ public class CachedMusicService implements MusicService {
}
@Override
- public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
- return musicService.getVideoStreamUrl(maxBitrate, context, id);
+ public String getVideoStreamUrl(String format, int maxBitrate, Context context, String id) throws Exception {
+ return musicService.getVideoStreamUrl(format, maxBitrate, context, id);
}
@Override
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadService.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadService.java
index 43e24096..328cc962 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadService.java
@@ -107,10 +107,12 @@ public interface DownloadService {
long getDownloadListUpdateRevision();
- void setSuggestedPlaylistName(String name);
+ void setSuggestedPlaylistName(String name, String id);
String getSuggestedPlaylistName();
+ String getSuggestedPlaylistId();
+
boolean getEqualizerAvailable();
boolean getVisualizerAvailable();
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
index f659a434..664754b1 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
@@ -110,6 +110,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
private long revision;
private static DownloadService instance;
private String suggestedPlaylistName;
+ private String suggestedPlaylistId;
private PowerManager.WakeLock wakeLock;
private boolean keepScreenOn;
private int cachedPosition = 0;
@@ -647,7 +648,18 @@ public class DownloadServiceImpl extends Service implements DownloadService {
setNextPlaying();
}
}
-
+ private synchronized void playNext() {
+ if(nextPlaying != null && nextPlayerState == PlayerState.PREPARED) {
+ if(!nextSetup) {
+ playNext(true);
+ } else {
+ nextSetup = false;
+ playNext(false);
+ }
+ } else {
+ onSongCompleted();
+ }
+ }
private synchronized void playNext(boolean start) {
// Swap the media players since nextMediaPlayer is ready to play
if(start) {
@@ -931,8 +943,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
@Override
- public void setSuggestedPlaylistName(String name) {
+ public void setSuggestedPlaylistName(String name, String id) {
this.suggestedPlaylistName = name;
+ this.suggestedPlaylistId = id;
}
@Override
@@ -941,6 +954,11 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
@Override
+ public String getSuggestedPlaylistId() {
+ return suggestedPlaylistId;
+ }
+
+ @Override
public boolean getEqualizerAvailable() {
return equalizerAvailable;
}
@@ -1128,19 +1146,23 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial) {
+ final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile);
int pos = cachedPosition;
reset();
- downloadFile.setPlaying(false);
- doPlay(downloadFile, pos, true);
- downloadFile.setPlaying(true);
+ if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) {
+ playNext();
+ } else {
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ }
return true;
}
});
- final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
@@ -1154,16 +1176,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
int pos = cachedPosition;
Log.i(TAG, "Ending position " + pos + " of " + duration);
if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) {
- if(nextPlaying != null && nextPlayerState == PlayerState.PREPARED) {
- if(!nextSetup) {
- playNext(true);
- } else {
- nextSetup = false;
- playNext(false);
- }
- } else {
- onSongCompleted();
- }
+ playNext();
return;
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
index c99551b4..046610f4 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java
@@ -70,6 +70,8 @@ public interface MusicService {
void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception;
+ void overwritePlaylist(String id, String name, int toRemove, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception;
+
void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception;
Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception;
@@ -90,7 +92,7 @@ public interface MusicService {
String getVideoUrl(int maxBitrate, Context context, String id);
- String getVideoStreamUrl(int Bitrate, Context context, String id);
+ String getVideoStreamUrl(String format, int Bitrate, Context context, String id) throws Exception;
JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception;
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
index c2a85488..606eab88 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java
@@ -392,6 +392,11 @@ public class OfflineMusicService extends RESTMusicService {
}
@Override
+ public void overwritePlaylist(String id, String name, int toRemove, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Overwriting playlist not available in offline mode");
+ }
+
+ @Override
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
throw new OfflineException("Updating playlist not available in offline mode");
}
@@ -443,7 +448,7 @@ public class OfflineMusicService extends RESTMusicService {
}
@Override
- public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
+ public String getVideoStreamUrl(String format, int maxBitrate, Context context, String id) throws Exception {
return null;
}
@@ -530,4 +535,4 @@ public class OfflineMusicService extends RESTMusicService {
}
}
}
-} \ No newline at end of file
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
index 27a51145..31554ceb 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java
@@ -444,6 +444,31 @@ public class RESTMusicService implements MusicService {
}
@Override
+ public void overwritePlaylist(String id, String name, int toRemove, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
+ checkServerVersion(context, "1.8", "Updating playlists is not supported.");
+ List<String> names = new ArrayList<String>();
+ List<Object> values = new ArrayList<Object>();
+ names.add("playlistId");
+ values.add(id);
+ names.add("name");
+ values.add(name);
+ for(MusicDirectory.Entry song: toAdd) {
+ names.add("songIdToAdd");
+ values.add(song.getId());
+ }
+ for(int i = 0; i < toRemove; i++) {
+ names.add("songIndexToRemove");
+ values.add(i);
+ }
+ Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values);
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.8", "Updating playlists is not supported.");
Reader reader = getReader(context, progressListener, "updatePlaylist", null, Arrays.asList("playlistId", "name", "comment", "public"), Arrays.<Object>asList(id, name, comment, pub));
@@ -669,10 +694,14 @@ public class RESTMusicService implements MusicService {
}
@Override
- public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
+ public String getVideoStreamUrl(String format, int maxBitrate, Context context, String id) throws Exception {
StringBuilder builder = new StringBuilder(Util.getRestUrl(context, "stream"));
builder.append("&id=").append(id);
- builder.append("&maxBitRate=").append(maxBitrate);
+ if(!"raw".equals(format)) {
+ checkServerVersion(context, "1.9", "Video streaming not supported.");
+ builder.append("&maxBitRate=").append(maxBitrate);
+ }
+ builder.append("&format=").append(format);
String url = rewriteUrlWithRedirect(context, builder.toString());
Log.i(TAG, "Using video URL: " + url);
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java
index 038518c6..17b09805 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java
@@ -71,9 +71,7 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser {
updateProgress(progressListener, R.string.parser_reading_done);
// Only apply sorting on server version 4.7 and greater, where disc is supported
- Version version = Util.getServerRestVersion(context);
- Version discVersion = new Version("1.8.0");
- if(version.compareTo(discVersion) >= 0) {
+ if(Util.checkServerVersion(context, "1.8.0")) {
dir.sortChildren();
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java
index af5f7e08..d6aa4541 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java
@@ -107,6 +107,7 @@ public final class Constants {
public static final String PREFERENCES_KEY_SHUFFLE_MODE = "shuffleMode";
public static final String PREFERENCES_KEY_CHAT_REFRESH = "chatRefreshRate";
public static final String PREFERENCES_KEY_CHAT_ENABLED = "chatEnabled";
+ public static final String PREFERENCES_KEY_VIDEO_PLAYER = "videoPlayer";
public static final String CACHE_KEY_IGNORE = "ignoreArticles";
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/ShufflePlayBuffer.java b/subsonic-android/src/github/daneren2005/dsub/util/ShufflePlayBuffer.java
index 3bc022ac..195fe913 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/ShufflePlayBuffer.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/ShufflePlayBuffer.java
@@ -43,6 +43,7 @@ public class ShufflePlayBuffer {
private final ScheduledExecutorService executorService;
private final List<MusicDirectory.Entry> buffer = new ArrayList<MusicDirectory.Entry>();
+ private int lastCount = -1;
private Context context;
private int currentServer;
private String currentFolder = "";
@@ -57,8 +58,8 @@ public class ShufflePlayBuffer {
Runnable runnable = new Runnable() {
@Override
public void run() {
- refill();
- }
+ refill();
+ }
};
executorService.scheduleWithFixedDelay(runnable, 1, 10, TimeUnit.SECONDS);
}
@@ -85,7 +86,7 @@ public class ShufflePlayBuffer {
// Check if active server has changed.
clearBufferIfnecessary();
- if (buffer.size() > REFILL_THRESHOLD || (!Util.isNetworkConnected(context) && !Util.isOffline(context))) {
+ if (buffer.size() > REFILL_THRESHOLD || (!Util.isNetworkConnected(context) && !Util.isOffline(context)) || lastCount == 0) {
return;
}
@@ -97,7 +98,8 @@ public class ShufflePlayBuffer {
synchronized (buffer) {
buffer.addAll(songs.getChildren());
- Log.i(TAG, "Refilled shuffle play buffer with " + songs.getChildren().size() + " songs.");
+ Log.i(TAG, "Refilled shuffle play buffer with " + songs.getChildrenSize() + " songs.");
+ lastCount = songs.getChildrenSize();
}
} catch (Exception x) {
Log.w(TAG, "Failed to refill shuffle play buffer.", x);
@@ -112,6 +114,7 @@ public class ShufflePlayBuffer {
|| (genre != null && !genre.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "")))
|| (startYear != null && !startYear.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "")))
|| (endYear != null && !endYear.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "")))) {
+ lastCount = -1;
currentServer = Util.getActiveServer(context);
currentFolder = Util.getSelectedMusicFolderId(context);
genre = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "");
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java
index 9d785212..18b30d49 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java
@@ -1,1056 +1,1074 @@
-/*
- 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.util;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiManager;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.view.KeyEvent;
-import android.widget.LinearLayout;
-import android.widget.RemoteViews;
-import android.widget.TextView;
-import android.widget.Toast;
-import github.daneren2005.dsub.R;
-import github.daneren2005.dsub.activity.MainActivity;
-import github.daneren2005.dsub.domain.MusicDirectory;
-import github.daneren2005.dsub.domain.PlayerState;
-import github.daneren2005.dsub.domain.RepeatMode;
-import github.daneren2005.dsub.domain.Version;
-import github.daneren2005.dsub.provider.DSubWidgetProvider;
-import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver;
-import github.daneren2005.dsub.service.DownloadService;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
-import org.apache.http.HttpEntity;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.security.MessageDigest;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * @author Sindre Mehus
- * @version $Id$
- */
-public final class Util {
- private static final String TAG = Util.class.getSimpleName();
-
- private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB");
- private static final DecimalFormat MEGA_BYTE_FORMAT = new DecimalFormat("0.00 MB");
- private static final DecimalFormat KILO_BYTE_FORMAT = new DecimalFormat("0 KB");
-
- private static DecimalFormat GIGA_BYTE_LOCALIZED_FORMAT = null;
- private static DecimalFormat MEGA_BYTE_LOCALIZED_FORMAT = null;
- private static DecimalFormat KILO_BYTE_LOCALIZED_FORMAT = null;
- private static DecimalFormat BYTE_LOCALIZED_FORMAT = null;
-
- public static final String EVENT_META_CHANGED = "github.daneren2005.dsub.EVENT_META_CHANGED";
- public static final String EVENT_PLAYSTATE_CHANGED = "github.daneren2005.dsub.EVENT_PLAYSTATE_CHANGED";
-
- public static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
- public static final String AVRCP_METADATA_CHANGED = "com.android.music.metachanged";
-
- private static boolean hasFocus = false;
- private static boolean pauseFocus = false;
- private static boolean lowerFocus = false;
-
- private static final Map<Integer, Version> SERVER_REST_VERSIONS = new ConcurrentHashMap<Integer, Version>();
-
- // Used by hexEncode()
- private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
-
- private final static Pair<Integer, Integer> NOTIFICATION_TEXT_COLORS = new Pair<Integer, Integer>();
- private static Toast toast;
-
- private Util() {
- }
-
- public static boolean isOffline(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getBoolean(Constants.PREFERENCES_KEY_OFFLINE, false);
- }
-
- public static void setOffline(Context context, boolean offline) {
- SharedPreferences prefs = getPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, offline);
- editor.commit();
- }
-
- public static boolean isScreenLitOnDownload(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getBoolean(Constants.PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD, false);
- }
-
- public static RepeatMode getRepeatMode(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return RepeatMode.valueOf(prefs.getString(Constants.PREFERENCES_KEY_REPEAT_MODE, RepeatMode.OFF.name()));
- }
-
- public static void setRepeatMode(Context context, RepeatMode repeatMode) {
- SharedPreferences prefs = getPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name());
- editor.commit();
- }
-
- public static boolean isScrobblingEnabled(Context context) {
- /*if (isOffline(context)) {
- return false;
- }*/
- SharedPreferences prefs = getPreferences(context);
- return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false);
- }
-
- public static void setActiveServer(Context context, int instance) {
- SharedPreferences prefs = getPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
- editor.commit();
- }
-
- public static int getActiveServer(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
- }
-
- public static int getServerCount(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1);
- }
-
- public static void removeInstanceName(Context context, int instance, int activeInstance) {
- SharedPreferences prefs = getPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
-
- int newInstance = instance + 1;
-
- String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null);
- String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
- String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
- String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
- String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
-
- editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + instance, server);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl);
- editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName);
- editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password);
-
- editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
- editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
- editor.commit();
-
- if (instance == activeInstance) {
- Util.setActiveServer(context, 0);
- }
-
- if (newInstance == activeInstance) {
- Util.setActiveServer(context, instance);
- }
- }
-
- public static String getServerName(Context context, int instance) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
- }
-
- public static String getUserName(Context context, int instance) {
- if (instance == 0) {
- return context.getResources().getString(R.string.main_offline);
- }
- SharedPreferences prefs = getPreferences(context);
- return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
- }
-
- public static void setServerRestVersion(Context context, Version version) {
- SERVER_REST_VERSIONS.put(getActiveServer(context), version);
- }
-
- public static Version getServerRestVersion(Context context) {
- return SERVER_REST_VERSIONS.get(getActiveServer(context));
- }
-
- public static void setSelectedMusicFolderId(Context context, String musicFolderId) {
- int instance = getActiveServer(context);
- SharedPreferences prefs = getPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId);
- editor.commit();
- }
-
- public static String getSelectedMusicFolderId(Context context) {
- SharedPreferences prefs = getPreferences(context);
- int instance = getActiveServer(context);
- return prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
- }
-
- public static String getTheme(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getString(Constants.PREFERENCES_KEY_THEME, null);
- }
-
- public static int getMaxBitrate(Context context) {
- ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
- if (networkInfo == null) {
- return 0;
- }
-
- boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
- SharedPreferences prefs = getPreferences(context);
- return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE, "0"));
- }
-
- public static int getMaxVideoBitrate(Context context) {
- ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
- if (networkInfo == null) {
- return 0;
- }
-
- boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
- SharedPreferences prefs = getPreferences(context);
- return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE, "0"));
- }
-
- public static int getPreloadCount(Context context) {
- ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
- if (networkInfo == null) {
- return 3;
- }
-
- SharedPreferences prefs = getPreferences(context);
- boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
- int preloadCount = Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI : Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE, "-1"));
- return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount;
- }
-
- public static int getCacheSizeMB(Context context) {
- SharedPreferences prefs = getPreferences(context);
- int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1"));
- return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize;
- }
-
- public static String getRestUrl(Context context, String method) {
- StringBuilder builder = new StringBuilder();
-
- SharedPreferences prefs = getPreferences(context);
-
- int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
- String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
- String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
- String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
-
- // Slightly obfuscate password
- password = "enc:" + Util.utf8HexEncode(password);
-
- builder.append(serverUrl);
- if (builder.charAt(builder.length() - 1) != '/') {
- builder.append("/");
- }
- builder.append("rest/").append(method).append(".view");
- builder.append("?u=").append(username);
- builder.append("&p=").append(password);
- builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION);
- builder.append("&c=").append(Constants.REST_CLIENT_ID);
-
- return builder.toString();
- }
-
- public static SharedPreferences getPreferences(Context context) {
- return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0);
- }
-
- public static String getContentType(HttpEntity entity) {
- if (entity == null || entity.getContentType() == null) {
- return null;
- }
- return entity.getContentType().getValue();
- }
-
- public static int getRemainingTrialDays(Context context) {
- SharedPreferences prefs = getPreferences(context);
- long installTime = prefs.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L);
-
- if (installTime == 0L) {
- installTime = System.currentTimeMillis();
- SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(Constants.PREFERENCES_KEY_INSTALL_TIME, installTime);
- editor.commit();
- }
-
- long now = System.currentTimeMillis();
- long millisPerDay = 24L * 60L * 60L * 1000L;
- int daysSinceInstall = (int) ((now - installTime) / millisPerDay);
- return Math.max(0, Constants.FREE_TRIAL_DAYS - daysSinceInstall);
- }
-
- /**
- * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
- * <p/>
- * This method buffers the input internally, so there is no need to use a
- * <code>BufferedInputStream</code>.
- *
- * @param input the <code>InputStream</code> to read from
- * @return the requested byte array
- * @throws NullPointerException if the input is null
- * @throws IOException if an I/O error occurs
- */
- public static byte[] toByteArray(InputStream input) throws IOException {
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- copy(input, output);
- return output.toByteArray();
- }
-
- public static long copy(InputStream input, OutputStream output)
- throws IOException {
- byte[] buffer = new byte[1024 * 4];
- long count = 0;
- int n;
- while (-1 != (n = input.read(buffer))) {
- output.write(buffer, 0, n);
- count += n;
- }
- return count;
- }
-
- public static void atomicCopy(File from, File to) throws IOException {
- FileInputStream in = null;
- FileOutputStream out = null;
- File tmp = null;
- try {
- tmp = new File(to.getPath() + ".tmp");
- in = new FileInputStream(from);
- out = new FileOutputStream(tmp);
- in.getChannel().transferTo(0, from.length(), out.getChannel());
- out.close();
- if (!tmp.renameTo(to)) {
- throw new IOException("Failed to rename " + tmp + " to " + to);
- }
- Log.i(TAG, "Copied " + from + " to " + to);
- } catch (IOException x) {
- close(out);
- delete(to);
- throw x;
- } finally {
- close(in);
- close(out);
- delete(tmp);
- }
- }
- public static void renameFile(File from, File to) throws IOException {
- if(from.renameTo(to)) {
- Log.i(TAG, "Renamed " + from + " to " + to);
- } else {
- atomicCopy(from, to);
- }
- }
-
- public static void close(Closeable closeable) {
- try {
- if (closeable != null) {
- closeable.close();
- }
- } catch (Throwable x) {
- // Ignored
- }
- }
-
- public static boolean delete(File file) {
- if (file != null && file.exists()) {
- if (!file.delete()) {
- Log.w(TAG, "Failed to delete file " + file);
- return false;
- }
- Log.i(TAG, "Deleted file " + file);
- }
- return true;
- }
- public static boolean recursiveDelete(File dir) {
- if (dir != null && dir.exists()) {
- for(File file: dir.listFiles()) {
- if(file.isDirectory()) {
- if(!recursiveDelete(file)) {
- return false;
- }
- } else if(file.exists()) {
- if(!file.delete()) {
- return false;
- }
- }
- }
- return dir.delete();
- }
- return false;
- }
-
- public static void toast(Context context, int messageId) {
- toast(context, messageId, true);
- }
-
- public static void toast(Context context, int messageId, boolean shortDuration) {
- toast(context, context.getString(messageId), shortDuration);
- }
-
- public static void toast(Context context, String message) {
- toast(context, message, true);
- }
-
- public static void toast(Context context, String message, boolean shortDuration) {
- if (toast == null) {
- toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
- toast.setGravity(Gravity.CENTER, 0, 0);
- } else {
- toast.setText(message);
- toast.setDuration(shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
- }
- toast.show();
- }
-
- /**
- * Converts a byte-count to a formatted string suitable for display to the user.
- * For instance:
- * <ul>
- * <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
- * <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
- * <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
- * </ul>
- * This method assumes that 1 KB is 1024 bytes.
- * To get a localized string, please use formatLocalizedBytes instead.
- *
- * @param byteCount The number of bytes.
- * @return The formatted string.
- */
- public static synchronized String formatBytes(long byteCount) {
-
- // More than 1 GB?
- if (byteCount >= 1024 * 1024 * 1024) {
- NumberFormat gigaByteFormat = GIGA_BYTE_FORMAT;
- return gigaByteFormat.format((double) byteCount / (1024 * 1024 * 1024));
- }
-
- // More than 1 MB?
- if (byteCount >= 1024 * 1024) {
- NumberFormat megaByteFormat = MEGA_BYTE_FORMAT;
- return megaByteFormat.format((double) byteCount / (1024 * 1024));
- }
-
- // More than 1 KB?
- if (byteCount >= 1024) {
- NumberFormat kiloByteFormat = KILO_BYTE_FORMAT;
- return kiloByteFormat.format((double) byteCount / 1024);
- }
-
- return byteCount + " B";
- }
-
- /**
- * Converts a byte-count to a formatted string suitable for display to the user.
- * For instance:
- * <ul>
- * <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
- * <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
- * <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
- * </ul>
- * This method assumes that 1 KB is 1024 bytes.
- * This version of the method returns a localized string.
- *
- * @param byteCount The number of bytes.
- * @return The formatted string.
- */
- public static synchronized String formatLocalizedBytes(long byteCount, Context context) {
-
- // More than 1 GB?
- if (byteCount >= 1024 * 1024 * 1024) {
- if (GIGA_BYTE_LOCALIZED_FORMAT == null) {
- GIGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_gigabyte));
- }
-
- return GIGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024 * 1024));
- }
-
- // More than 1 MB?
- if (byteCount >= 1024 * 1024) {
- if (MEGA_BYTE_LOCALIZED_FORMAT == null) {
- MEGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_megabyte));
- }
-
- return MEGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024));
- }
-
- // More than 1 KB?
- if (byteCount >= 1024) {
- if (KILO_BYTE_LOCALIZED_FORMAT == null) {
- KILO_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_kilobyte));
- }
-
- return KILO_BYTE_LOCALIZED_FORMAT.format((double) byteCount / 1024);
- }
-
- if (BYTE_LOCALIZED_FORMAT == null) {
- BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_byte));
- }
-
- return BYTE_LOCALIZED_FORMAT.format((double) byteCount);
- }
-
- public static String formatDuration(Integer seconds) {
- if (seconds == null) {
- return null;
- }
-
- int hours = seconds / 3600;
- int minutes = (seconds / 60) % 60;
- int secs = seconds % 60;
-
- StringBuilder builder = new StringBuilder(7);
- if(hours > 0) {
- builder.append(hours).append(":");
- if(minutes < 10) {
- builder.append("0");
- }
- }
- builder.append(minutes).append(":");
- if (secs < 10) {
- builder.append("0");
- }
- builder.append(secs);
- return builder.toString();
- }
-
- public static boolean equals(Object object1, Object object2) {
- if (object1 == object2) {
- return true;
- }
- if (object1 == null || object2 == null) {
- return false;
- }
- return object1.equals(object2);
-
- }
-
- /**
- * Encodes the given string by using the hexadecimal representation of its UTF-8 bytes.
- *
- * @param s The string to encode.
- * @return The encoded string.
- */
- public static String utf8HexEncode(String s) {
- if (s == null) {
- return null;
- }
- byte[] utf8;
- try {
- utf8 = s.getBytes(Constants.UTF_8);
- } catch (UnsupportedEncodingException x) {
- throw new RuntimeException(x);
- }
- return hexEncode(utf8);
- }
-
- /**
- * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
- * The returned array will be double the length of the passed array, as it takes two characters to represent any
- * given byte.
- *
- * @param data Bytes to convert to hexadecimal characters.
- * @return A string containing hexadecimal characters.
- */
- public static String hexEncode(byte[] data) {
- int length = data.length;
- char[] out = new char[length << 1];
- // two characters form the hex value.
- for (int i = 0, j = 0; i < length; i++) {
- out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
- out[j++] = HEX_DIGITS[0x0F & data[i]];
- }
- return new String(out);
- }
-
- /**
- * Calculates the MD5 digest and returns the value as a 32 character hex string.
- *
- * @param s Data to digest.
- * @return MD5 digest as a hex string.
- */
- public static String md5Hex(String s) {
- if (s == null) {
- return null;
- }
-
- try {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- return hexEncode(md5.digest(s.getBytes(Constants.UTF_8)));
- } catch (Exception x) {
- throw new RuntimeException(x.getMessage(), x);
- }
- }
-
- public static boolean isNullOrWhiteSpace(String string) {
- return string == null || string.isEmpty() || string.trim().isEmpty();
- }
-
- public static boolean isNetworkConnected(Context context) {
- ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
- boolean connected = networkInfo != null && networkInfo.isConnected();
-
- boolean wifiConnected = connected && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
- boolean wifiRequired = isWifiRequiredForDownload(context);
-
- return connected && (!wifiRequired || wifiConnected);
- }
-
- public static boolean isExternalStoragePresent() {
- return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
- }
-
- private static boolean isWifiRequiredForDownload(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getBoolean(Constants.PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD, false);
- }
-
- public static void info(Context context, int titleId, int messageId) {
- showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId);
- }
- public static void info(Context context, int titleId, String message) {
- showDialog(context, android.R.drawable.ic_dialog_info, titleId, message);
- }
-
- private static void showDialog(Context context, int icon, int titleId, int messageId) {
- new AlertDialog.Builder(context)
- .setIcon(icon)
- .setTitle(titleId)
- .setMessage(messageId)
- .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int i) {
- dialog.dismiss();
- }
- })
- .show();
- }
- private static void showDialog(Context context, int icon, int titleId, String message) {
- new AlertDialog.Builder(context)
- .setIcon(icon)
- .setTitle(titleId)
- .setMessage(message)
- .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int i) {
- dialog.dismiss();
- }
- })
- .show();
- }
-
- public static void showPlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler, MusicDirectory.Entry song) {
- // Set the icon, scrolling text and timestamp
- final Notification notification = new Notification(R.drawable.stat_notify_playing, song.getTitle(), System.currentTimeMillis());
- notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
-
- boolean playing = downloadService.getPlayerState() == PlayerState.STARTED;
- if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){
- RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded);
- setupViews(expandedContentView,context,song, playing);
- notification.bigContentView = expandedContentView;
- }
-
- RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification);
- setupViews(smallContentView, context, song, playing);
- notification.contentView = smallContentView;
-
- Intent notificationIntent = new Intent(context, MainActivity.class);
- notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
- notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- notification.contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification);
- }
- });
-
- // Update widget
- DSubWidgetProvider.notifyInstances(context, downloadService, true);
- }
-
- private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean playing){
-
- // Use the same text for the ticker and the expanded notification
- String title = song.getTitle();
- String arist = song.getArtist();
- String album = song.getAlbum();
-
- // Set the album art.
- try {
- int size = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
- Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size);
- if (bitmap == null) {
- // set default album art
- rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
- } else {
- rv.setImageViewBitmap(R.id.notification_image, bitmap);
- }
- } catch (Exception x) {
- Log.w(TAG, "Failed to get notification cover art", x);
- rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
- }
-
- // set the text for the notifications
- rv.setTextViewText(R.id.notification_title, title);
- rv.setTextViewText(R.id.notification_artist, arist);
- rv.setTextViewText(R.id.notification_album, album);
-
- Pair<Integer, Integer> colors = getNotificationTextColors(context);
- if (colors.getFirst() != null) {
- rv.setTextColor(R.id.notification_title, colors.getFirst());
- }
- if (colors.getSecond() != null) {
- rv.setTextColor(R.id.notification_artist, colors.getSecond());
- }
-
- if(!playing) {
- rv.setImageViewResource(R.id.control_pause, R.drawable.notification_play);
- rv.setImageViewResource(R.id.control_previous, R.drawable.notification_stop);
- }
-
- // Create actions for media buttons
- PendingIntent pendingIntent;
- if(playing) {
- Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS");
- prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
- prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
- pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
- rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
- } else {
- Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP");
- prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
- prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP));
- pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
- rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
- }
-
- Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE");
- pauseIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
- pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
- pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0);
- rv.setOnClickPendingIntent(R.id.control_pause, pendingIntent);
-
- Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT");
- nextIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
- nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT));
- pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0);
- rv.setOnClickPendingIntent(R.id.control_next, pendingIntent);
- }
-
- public static void hidePlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler) {
- // Remove notification and remove the service from the foreground
- handler.post(new Runnable() {
- @Override
- public void run() {
- downloadService.stopForeground(true);
- }
- });
-
- // Update widget
- DSubWidgetProvider.notifyInstances(context, downloadService, false);
- }
-
- public static void sleepQuietly(long millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException x) {
- Log.w(TAG, "Interrupted from sleep.", x);
- }
- }
-
- public static void startActivityWithoutTransition(Activity currentActivity, Class<? extends Activity> newActivitiy) {
- startActivityWithoutTransition(currentActivity, new Intent(currentActivity, newActivitiy));
- }
-
- public static void startActivityWithoutTransition(Activity currentActivity, Intent intent) {
- currentActivity.startActivity(intent);
- disablePendingTransition(currentActivity);
- }
-
- public static void disablePendingTransition(Activity activity) {
-
- // Activity.overridePendingTransition() was introduced in Android 2.0. Use reflection to maintain
- // compatibility with 1.5.
- try {
- Method method = Activity.class.getMethod("overridePendingTransition", int.class, int.class);
- method.invoke(activity, 0, 0);
- } catch (Throwable x) {
- // Ignored
- }
- }
-
- public static Drawable createDrawableFromBitmap(Context context, Bitmap bitmap) {
- // BitmapDrawable(Resources, Bitmap) was introduced in Android 1.6. Use reflection to maintain
- // compatibility with 1.5.
- try {
- Constructor<BitmapDrawable> constructor = BitmapDrawable.class.getConstructor(Resources.class, Bitmap.class);
- return constructor.newInstance(context.getResources(), bitmap);
- } catch (Throwable x) {
- return new BitmapDrawable(bitmap);
- }
- }
-
- public static void registerMediaButtonEventReceiver(Context context) {
-
- // Only do it if enabled in the settings.
- SharedPreferences prefs = getPreferences(context);
- boolean enabled = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true);
-
- if (enabled) {
-
- // AudioManager.registerMediaButtonEventReceiver() was introduced in Android 2.2.
- // Use reflection to maintain compatibility with 1.5.
- try {
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
- Method method = AudioManager.class.getMethod("registerMediaButtonEventReceiver", ComponentName.class);
- method.invoke(audioManager, componentName);
- } catch (Throwable x) {
- // Ignored.
- }
- }
- }
-
- public static void unregisterMediaButtonEventReceiver(Context context) {
- // AudioManager.unregisterMediaButtonEventReceiver() was introduced in Android 2.2.
- // Use reflection to maintain compatibility with 1.5.
- try {
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
- Method method = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver", ComponentName.class);
- method.invoke(audioManager, componentName);
- } catch (Throwable x) {
- // Ignored.
- }
- }
-
- @TargetApi(8)
- public static void requestAudioFocus(final Context context) {
- if (Build.VERSION.SDK_INT >= 8 && !hasFocus) {
- final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- hasFocus = true;
- audioManager.requestAudioFocus(new OnAudioFocusChangeListener() {
- public void onAudioFocusChange(int focusChange) {
- DownloadServiceImpl downloadService = (DownloadServiceImpl)context;
- if((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isJukeboxEnabled()) {
- if(downloadService.getPlayerState() == PlayerState.STARTED) {
- SharedPreferences prefs = getPreferences(context);
- int lossPref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1"));
- if(lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) {
- lowerFocus = true;
- downloadService.setVolume(0.1f);
- } else if(lossPref == 0 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)) {
- pauseFocus = true;
- downloadService.pause();
- }
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- if(pauseFocus) {
- pauseFocus = false;
- downloadService.start();
- } else if(lowerFocus) {
- lowerFocus = false;
- downloadService.setVolume(1.0f);
- }
- } else if(focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled()) {
- hasFocus = false;
- downloadService.pause();
- audioManager.abandonAudioFocus(this);
- }
- }
- }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
- }
- }
-
- /**
- * <p>Broadcasts the given song info as the new song being played.</p>
- */
- public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) {
- DownloadService downloadService = (DownloadServiceImpl)context;
- Intent intent = new Intent(EVENT_META_CHANGED);
- Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED);
-
- if (song != null) {
- intent.putExtra("title", song.getTitle());
- intent.putExtra("artist", song.getArtist());
- intent.putExtra("album", song.getAlbum());
-
- File albumArtFile = FileUtil.getAlbumArtFile(context, song);
- intent.putExtra("coverart", albumArtFile.getAbsolutePath());
-
- avrcpIntent.putExtra("playing", true);
- avrcpIntent.putExtra("track", song.getTitle());
- avrcpIntent.putExtra("artist", song.getArtist());
- avrcpIntent.putExtra("album", song.getAlbum());
- avrcpIntent.putExtra("ListSize",(long) downloadService.getSongs().size());
- avrcpIntent.putExtra("id", (long) downloadService.getCurrentPlayingIndex()+1);
- avrcpIntent.putExtra("duration", (long) downloadService.getPlayerDuration());
- avrcpIntent.putExtra("position", (long) downloadService.getPlayerPosition());
- avrcpIntent.putExtra("coverart", albumArtFile.getAbsolutePath());
- } else {
- intent.putExtra("title", "");
- intent.putExtra("artist", "");
- intent.putExtra("album", "");
- intent.putExtra("coverart", "");
-
- avrcpIntent.putExtra("playing", false);
- avrcpIntent.putExtra("track", "");
- avrcpIntent.putExtra("artist", "");
- avrcpIntent.putExtra("album", "");
- avrcpIntent.putExtra("ListSize",(long)0);
- avrcpIntent.putExtra("id", (long) 0);
- avrcpIntent.putExtra("duration", (long )0);
- avrcpIntent.putExtra("position", (long) 0);
- avrcpIntent.putExtra("coverart", "");
- }
-
- context.sendBroadcast(intent);
- context.sendBroadcast(avrcpIntent);
- }
-
- /**
- * <p>Broadcasts the given player state as the one being set.</p>
- */
- public static void broadcastPlaybackStatusChange(Context context, PlayerState state) {
- Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED);
- Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED);
-
- switch (state) {
- case STARTED:
- intent.putExtra("state", "play");
- avrcpIntent.putExtra("playing", true);
- break;
- case STOPPED:
- intent.putExtra("state", "stop");
- avrcpIntent.putExtra("playing", false);
- break;
- case PAUSED:
- intent.putExtra("state", "pause");
- avrcpIntent.putExtra("playing", false);
- break;
- case COMPLETED:
- intent.putExtra("state", "complete");
- avrcpIntent.putExtra("playing", false);
- break;
- default:
- return; // No need to broadcast.
- }
-
- context.sendBroadcast(intent);
- context.sendBroadcast(avrcpIntent);
- }
-
- /**
- * Resolves the default text color for notifications.
- *
- * Based on http://stackoverflow.com/questions/4867338/custom-notification-layouts-and-text-colors/7320604#7320604
- */
- private static Pair<Integer, Integer> getNotificationTextColors(Context context) {
- if (NOTIFICATION_TEXT_COLORS.getFirst() == null && NOTIFICATION_TEXT_COLORS.getSecond() == null) {
- try {
- Notification notification = new Notification();
- String title = "title";
- String content = "content";
- notification.setLatestEventInfo(context, title, content, null);
- LinearLayout group = new LinearLayout(context);
- ViewGroup event = (ViewGroup) notification.contentView.apply(context, group);
- findNotificationTextColors(event, title, content);
- group.removeAllViews();
- } catch (Exception x) {
- Log.w(TAG, "Failed to resolve notification text colors.", x);
- }
- }
- return NOTIFICATION_TEXT_COLORS;
- }
-
- private static void findNotificationTextColors(ViewGroup group, String title, String content) {
- for (int i = 0; i < group.getChildCount(); i++) {
- if (group.getChildAt(i) instanceof TextView) {
- TextView textView = (TextView) group.getChildAt(i);
- String text = textView.getText().toString();
- if (title.equals(text)) {
- NOTIFICATION_TEXT_COLORS.setFirst(textView.getTextColors().getDefaultColor());
- }
- else if (content.equals(text)) {
- NOTIFICATION_TEXT_COLORS.setSecond(textView.getTextColors().getDefaultColor());
- }
- }
- else if (group.getChildAt(i) instanceof ViewGroup)
- findNotificationTextColors((ViewGroup) group.getChildAt(i), title, content);
- }
- }
-
- public static WifiManager.WifiLock createWifiLock(Context context, String tag) {
- WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- int lockType = WifiManager.WIFI_MODE_FULL;
- if (Build.VERSION.SDK_INT >= 12) {
- lockType = 3;
- }
- return wm.createWifiLock(lockType, tag);
- }
-}
+/*
+ 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.util;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+import android.widget.Toast;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.MainActivity;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.PlayerState;
+import github.daneren2005.dsub.domain.RepeatMode;
+import github.daneren2005.dsub.domain.Version;
+import github.daneren2005.dsub.provider.DSubWidgetProvider;
+import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver;
+import github.daneren2005.dsub.service.DownloadService;
+import github.daneren2005.dsub.service.DownloadServiceImpl;
+import org.apache.http.HttpEntity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.MessageDigest;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Sindre Mehus
+ * @version $Id$
+ */
+public final class Util {
+ private static final String TAG = Util.class.getSimpleName();
+
+ private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB");
+ private static final DecimalFormat MEGA_BYTE_FORMAT = new DecimalFormat("0.00 MB");
+ private static final DecimalFormat KILO_BYTE_FORMAT = new DecimalFormat("0 KB");
+
+ private static DecimalFormat GIGA_BYTE_LOCALIZED_FORMAT = null;
+ private static DecimalFormat MEGA_BYTE_LOCALIZED_FORMAT = null;
+ private static DecimalFormat KILO_BYTE_LOCALIZED_FORMAT = null;
+ private static DecimalFormat BYTE_LOCALIZED_FORMAT = null;
+
+ public static final String EVENT_META_CHANGED = "github.daneren2005.dsub.EVENT_META_CHANGED";
+ public static final String EVENT_PLAYSTATE_CHANGED = "github.daneren2005.dsub.EVENT_PLAYSTATE_CHANGED";
+
+ public static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
+ public static final String AVRCP_METADATA_CHANGED = "com.android.music.metachanged";
+
+ private static boolean hasFocus = false;
+ private static boolean pauseFocus = false;
+ private static boolean lowerFocus = false;
+
+ private static final Map<Integer, Version> SERVER_REST_VERSIONS = new ConcurrentHashMap<Integer, Version>();
+
+ // Used by hexEncode()
+ private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ private final static Pair<Integer, Integer> NOTIFICATION_TEXT_COLORS = new Pair<Integer, Integer>();
+ private static Toast toast;
+
+ private Util() {
+ }
+
+ public static boolean isOffline(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getBoolean(Constants.PREFERENCES_KEY_OFFLINE, false);
+ }
+
+ public static void setOffline(Context context, boolean offline) {
+ SharedPreferences prefs = getPreferences(context);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, offline);
+ editor.commit();
+ }
+
+ public static boolean isScreenLitOnDownload(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getBoolean(Constants.PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD, false);
+ }
+
+ public static RepeatMode getRepeatMode(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return RepeatMode.valueOf(prefs.getString(Constants.PREFERENCES_KEY_REPEAT_MODE, RepeatMode.OFF.name()));
+ }
+
+ public static void setRepeatMode(Context context, RepeatMode repeatMode) {
+ SharedPreferences prefs = getPreferences(context);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name());
+ editor.commit();
+ }
+
+ public static boolean isScrobblingEnabled(Context context) {
+ /*if (isOffline(context)) {
+ return false;
+ }*/
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false);
+ }
+
+ public static void setActiveServer(Context context, int instance) {
+ SharedPreferences prefs = getPreferences(context);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance);
+ editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
+ editor.commit();
+ }
+
+ public static int getActiveServer(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
+ }
+
+ public static boolean checkServerVersion(Context context, String requiredVersion) {
+ Version version = Util.getServerRestVersion(context);
+ Version required = new Version(requiredVersion);
+ if(version != null && version.compareTo(required) >= 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static int getServerCount(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1);
+ }
+
+ public static void removeInstanceName(Context context, int instance, int activeInstance) {
+ SharedPreferences prefs = getPreferences(context);
+ SharedPreferences.Editor editor = prefs.edit();
+
+ int newInstance = instance + 1;
+
+ String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null);
+ String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
+ String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
+ String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
+ String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
+
+ editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + instance, server);
+ editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName);
+ editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl);
+ editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName);
+ editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password);
+
+ editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null);
+ editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null);
+ editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null);
+ editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null);
+ editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null);
+ editor.commit();
+
+ if (instance == activeInstance) {
+ if(instance != 1) {
+ Util.setActiveServer(context, 1);
+ } else {
+ Util.setOffline(context, true);
+ }
+ } else if (newInstance == activeInstance) {
+ Util.setActiveServer(context, instance);
+ }
+ }
+
+ public static String getServerName(Context context, int instance) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null);
+ }
+
+ public static String getUserName(Context context, int instance) {
+ if (instance == 0) {
+ return context.getResources().getString(R.string.main_offline);
+ }
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
+ }
+
+ public static void setServerRestVersion(Context context, Version version) {
+ SERVER_REST_VERSIONS.put(getActiveServer(context), version);
+ }
+
+ public static Version getServerRestVersion(Context context) {
+ return SERVER_REST_VERSIONS.get(getActiveServer(context));
+ }
+
+ public static void setSelectedMusicFolderId(Context context, String musicFolderId) {
+ int instance = getActiveServer(context);
+ SharedPreferences prefs = getPreferences(context);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId);
+ editor.commit();
+ }
+
+ public static String getSelectedMusicFolderId(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ int instance = getActiveServer(context);
+ return prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null);
+ }
+
+ public static String getTheme(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getString(Constants.PREFERENCES_KEY_THEME, null);
+ }
+
+ public static int getMaxBitrate(Context context) {
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ if (networkInfo == null) {
+ return 0;
+ }
+
+ boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
+ SharedPreferences prefs = getPreferences(context);
+ return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE, "0"));
+ }
+
+ public static int getMaxVideoBitrate(Context context) {
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ if (networkInfo == null) {
+ return 0;
+ }
+
+ boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
+ SharedPreferences prefs = getPreferences(context);
+ return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE, "0"));
+ }
+
+ public static int getPreloadCount(Context context) {
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ if (networkInfo == null) {
+ return 3;
+ }
+
+ SharedPreferences prefs = getPreferences(context);
+ boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
+ int preloadCount = Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI : Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE, "-1"));
+ return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount;
+ }
+
+ public static int getCacheSizeMB(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1"));
+ return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize;
+ }
+
+ public static String getRestUrl(Context context, String method) {
+ StringBuilder builder = new StringBuilder();
+
+ SharedPreferences prefs = getPreferences(context);
+
+ int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
+ String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
+ String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
+ String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
+
+ // Slightly obfuscate password
+ password = "enc:" + Util.utf8HexEncode(password);
+
+ builder.append(serverUrl);
+ if (builder.charAt(builder.length() - 1) != '/') {
+ builder.append("/");
+ }
+ builder.append("rest/").append(method).append(".view");
+ builder.append("?u=").append(username);
+ builder.append("&p=").append(password);
+ builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION);
+ builder.append("&c=").append(Constants.REST_CLIENT_ID);
+
+ return builder.toString();
+ }
+
+ public static String getVideoPlayerType(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getString(Constants.PREFERENCES_KEY_VIDEO_PLAYER, "raw");
+ }
+
+ public static SharedPreferences getPreferences(Context context) {
+ return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0);
+ }
+
+ public static String getContentType(HttpEntity entity) {
+ if (entity == null || entity.getContentType() == null) {
+ return null;
+ }
+ return entity.getContentType().getValue();
+ }
+
+ public static int getRemainingTrialDays(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ long installTime = prefs.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L);
+
+ if (installTime == 0L) {
+ installTime = System.currentTimeMillis();
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(Constants.PREFERENCES_KEY_INSTALL_TIME, installTime);
+ editor.commit();
+ }
+
+ long now = System.currentTimeMillis();
+ long millisPerDay = 24L * 60L * 60L * 1000L;
+ int daysSinceInstall = (int) ((now - installTime) / millisPerDay);
+ return Math.max(0, Constants.FREE_TRIAL_DAYS - daysSinceInstall);
+ }
+
+ /**
+ * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
+ * <p/>
+ * This method buffers the input internally, so there is no need to use a
+ * <code>BufferedInputStream</code>.
+ *
+ * @param input the <code>InputStream</code> to read from
+ * @return the requested byte array
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ */
+ public static byte[] toByteArray(InputStream input) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy(input, output);
+ return output.toByteArray();
+ }
+
+ public static long copy(InputStream input, OutputStream output)
+ throws IOException {
+ byte[] buffer = new byte[1024 * 4];
+ long count = 0;
+ int n;
+ while (-1 != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ }
+
+ public static void atomicCopy(File from, File to) throws IOException {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ File tmp = null;
+ try {
+ tmp = new File(to.getPath() + ".tmp");
+ in = new FileInputStream(from);
+ out = new FileOutputStream(tmp);
+ in.getChannel().transferTo(0, from.length(), out.getChannel());
+ out.close();
+ if (!tmp.renameTo(to)) {
+ throw new IOException("Failed to rename " + tmp + " to " + to);
+ }
+ Log.i(TAG, "Copied " + from + " to " + to);
+ } catch (IOException x) {
+ close(out);
+ delete(to);
+ throw x;
+ } finally {
+ close(in);
+ close(out);
+ delete(tmp);
+ }
+ }
+ public static void renameFile(File from, File to) throws IOException {
+ if(from.renameTo(to)) {
+ Log.i(TAG, "Renamed " + from + " to " + to);
+ } else {
+ atomicCopy(from, to);
+ }
+ }
+
+ public static void close(Closeable closeable) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (Throwable x) {
+ // Ignored
+ }
+ }
+
+ public static boolean delete(File file) {
+ if (file != null && file.exists()) {
+ if (!file.delete()) {
+ Log.w(TAG, "Failed to delete file " + file);
+ return false;
+ }
+ Log.i(TAG, "Deleted file " + file);
+ }
+ return true;
+ }
+ public static boolean recursiveDelete(File dir) {
+ if (dir != null && dir.exists()) {
+ for(File file: dir.listFiles()) {
+ if(file.isDirectory()) {
+ if(!recursiveDelete(file)) {
+ return false;
+ }
+ } else if(file.exists()) {
+ if(!file.delete()) {
+ return false;
+ }
+ }
+ }
+ return dir.delete();
+ }
+ return false;
+ }
+
+ public static void toast(Context context, int messageId) {
+ toast(context, messageId, true);
+ }
+
+ public static void toast(Context context, int messageId, boolean shortDuration) {
+ toast(context, context.getString(messageId), shortDuration);
+ }
+
+ public static void toast(Context context, String message) {
+ toast(context, message, true);
+ }
+
+ public static void toast(Context context, String message, boolean shortDuration) {
+ if (toast == null) {
+ toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER, 0, 0);
+ } else {
+ toast.setText(message);
+ toast.setDuration(shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG);
+ }
+ toast.show();
+ }
+
+ /**
+ * Converts a byte-count to a formatted string suitable for display to the user.
+ * For instance:
+ * <ul>
+ * <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
+ * <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
+ * <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
+ * </ul>
+ * This method assumes that 1 KB is 1024 bytes.
+ * To get a localized string, please use formatLocalizedBytes instead.
+ *
+ * @param byteCount The number of bytes.
+ * @return The formatted string.
+ */
+ public static synchronized String formatBytes(long byteCount) {
+
+ // More than 1 GB?
+ if (byteCount >= 1024 * 1024 * 1024) {
+ NumberFormat gigaByteFormat = GIGA_BYTE_FORMAT;
+ return gigaByteFormat.format((double) byteCount / (1024 * 1024 * 1024));
+ }
+
+ // More than 1 MB?
+ if (byteCount >= 1024 * 1024) {
+ NumberFormat megaByteFormat = MEGA_BYTE_FORMAT;
+ return megaByteFormat.format((double) byteCount / (1024 * 1024));
+ }
+
+ // More than 1 KB?
+ if (byteCount >= 1024) {
+ NumberFormat kiloByteFormat = KILO_BYTE_FORMAT;
+ return kiloByteFormat.format((double) byteCount / 1024);
+ }
+
+ return byteCount + " B";
+ }
+
+ /**
+ * Converts a byte-count to a formatted string suitable for display to the user.
+ * For instance:
+ * <ul>
+ * <li><code>format(918)</code> returns <em>"918 B"</em>.</li>
+ * <li><code>format(98765)</code> returns <em>"96 KB"</em>.</li>
+ * <li><code>format(1238476)</code> returns <em>"1.2 MB"</em>.</li>
+ * </ul>
+ * This method assumes that 1 KB is 1024 bytes.
+ * This version of the method returns a localized string.
+ *
+ * @param byteCount The number of bytes.
+ * @return The formatted string.
+ */
+ public static synchronized String formatLocalizedBytes(long byteCount, Context context) {
+
+ // More than 1 GB?
+ if (byteCount >= 1024 * 1024 * 1024) {
+ if (GIGA_BYTE_LOCALIZED_FORMAT == null) {
+ GIGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_gigabyte));
+ }
+
+ return GIGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024 * 1024));
+ }
+
+ // More than 1 MB?
+ if (byteCount >= 1024 * 1024) {
+ if (MEGA_BYTE_LOCALIZED_FORMAT == null) {
+ MEGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_megabyte));
+ }
+
+ return MEGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024));
+ }
+
+ // More than 1 KB?
+ if (byteCount >= 1024) {
+ if (KILO_BYTE_LOCALIZED_FORMAT == null) {
+ KILO_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_kilobyte));
+ }
+
+ return KILO_BYTE_LOCALIZED_FORMAT.format((double) byteCount / 1024);
+ }
+
+ if (BYTE_LOCALIZED_FORMAT == null) {
+ BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_byte));
+ }
+
+ return BYTE_LOCALIZED_FORMAT.format((double) byteCount);
+ }
+
+ public static String formatDuration(Integer seconds) {
+ if (seconds == null) {
+ return null;
+ }
+
+ int hours = seconds / 3600;
+ int minutes = (seconds / 60) % 60;
+ int secs = seconds % 60;
+
+ StringBuilder builder = new StringBuilder(7);
+ if(hours > 0) {
+ builder.append(hours).append(":");
+ if(minutes < 10) {
+ builder.append("0");
+ }
+ }
+ builder.append(minutes).append(":");
+ if (secs < 10) {
+ builder.append("0");
+ }
+ builder.append(secs);
+ return builder.toString();
+ }
+
+ public static boolean equals(Object object1, Object object2) {
+ if (object1 == object2) {
+ return true;
+ }
+ if (object1 == null || object2 == null) {
+ return false;
+ }
+ return object1.equals(object2);
+
+ }
+
+ /**
+ * Encodes the given string by using the hexadecimal representation of its UTF-8 bytes.
+ *
+ * @param s The string to encode.
+ * @return The encoded string.
+ */
+ public static String utf8HexEncode(String s) {
+ if (s == null) {
+ return null;
+ }
+ byte[] utf8;
+ try {
+ utf8 = s.getBytes(Constants.UTF_8);
+ } catch (UnsupportedEncodingException x) {
+ throw new RuntimeException(x);
+ }
+ return hexEncode(utf8);
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data Bytes to convert to hexadecimal characters.
+ * @return A string containing hexadecimal characters.
+ */
+ public static String hexEncode(byte[] data) {
+ int length = data.length;
+ char[] out = new char[length << 1];
+ // two characters form the hex value.
+ for (int i = 0, j = 0; i < length; i++) {
+ out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
+ out[j++] = HEX_DIGITS[0x0F & data[i]];
+ }
+ return new String(out);
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hex string.
+ *
+ * @param s Data to digest.
+ * @return MD5 digest as a hex string.
+ */
+ public static String md5Hex(String s) {
+ if (s == null) {
+ return null;
+ }
+
+ try {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ return hexEncode(md5.digest(s.getBytes(Constants.UTF_8)));
+ } catch (Exception x) {
+ throw new RuntimeException(x.getMessage(), x);
+ }
+ }
+
+ public static boolean isNullOrWhiteSpace(String string) {
+ return string == null || string.isEmpty() || string.trim().isEmpty();
+ }
+
+ public static boolean isNetworkConnected(Context context) {
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ boolean connected = networkInfo != null && networkInfo.isConnected();
+
+ boolean wifiConnected = connected && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
+ boolean wifiRequired = isWifiRequiredForDownload(context);
+
+ return connected && (!wifiRequired || wifiConnected);
+ }
+
+ public static boolean isExternalStoragePresent() {
+ return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
+ }
+
+ private static boolean isWifiRequiredForDownload(Context context) {
+ SharedPreferences prefs = getPreferences(context);
+ return prefs.getBoolean(Constants.PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD, false);
+ }
+
+ public static void info(Context context, int titleId, int messageId) {
+ showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId);
+ }
+ public static void info(Context context, int titleId, String message) {
+ showDialog(context, android.R.drawable.ic_dialog_info, titleId, message);
+ }
+
+ private static void showDialog(Context context, int icon, int titleId, int messageId) {
+ new AlertDialog.Builder(context)
+ .setIcon(icon)
+ .setTitle(titleId)
+ .setMessage(messageId)
+ .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i) {
+ dialog.dismiss();
+ }
+ })
+ .show();
+ }
+ private static void showDialog(Context context, int icon, int titleId, String message) {
+ new AlertDialog.Builder(context)
+ .setIcon(icon)
+ .setTitle(titleId)
+ .setMessage(message)
+ .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i) {
+ dialog.dismiss();
+ }
+ })
+ .show();
+ }
+
+ public static void showPlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler, MusicDirectory.Entry song) {
+ // Set the icon, scrolling text and timestamp
+ final Notification notification = new Notification(R.drawable.stat_notify_playing, song.getTitle(), System.currentTimeMillis());
+ notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
+
+ boolean playing = downloadService.getPlayerState() == PlayerState.STARTED;
+ if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){
+ RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded);
+ setupViews(expandedContentView,context,song, playing);
+ notification.bigContentView = expandedContentView;
+ }
+
+ RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification);
+ setupViews(smallContentView, context, song, playing);
+ notification.contentView = smallContentView;
+
+ Intent notificationIntent = new Intent(context, MainActivity.class);
+ notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
+ notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notification.contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification);
+ }
+ });
+
+ // Update widget
+ DSubWidgetProvider.notifyInstances(context, downloadService, true);
+ }
+
+ private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean playing){
+
+ // Use the same text for the ticker and the expanded notification
+ String title = song.getTitle();
+ String arist = song.getArtist();
+ String album = song.getAlbum();
+
+ // Set the album art.
+ try {
+ int size = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
+ Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size);
+ if (bitmap == null) {
+ // set default album art
+ rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
+ } else {
+ rv.setImageViewBitmap(R.id.notification_image, bitmap);
+ }
+ } catch (Exception x) {
+ Log.w(TAG, "Failed to get notification cover art", x);
+ rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
+ }
+
+ // set the text for the notifications
+ rv.setTextViewText(R.id.notification_title, title);
+ rv.setTextViewText(R.id.notification_artist, arist);
+ rv.setTextViewText(R.id.notification_album, album);
+
+ Pair<Integer, Integer> colors = getNotificationTextColors(context);
+ if (colors.getFirst() != null) {
+ rv.setTextColor(R.id.notification_title, colors.getFirst());
+ }
+ if (colors.getSecond() != null) {
+ rv.setTextColor(R.id.notification_artist, colors.getSecond());
+ }
+
+ if(!playing) {
+ rv.setImageViewResource(R.id.control_pause, R.drawable.notification_play);
+ rv.setImageViewResource(R.id.control_previous, R.drawable.notification_stop);
+ }
+
+ // Create actions for media buttons
+ PendingIntent pendingIntent;
+ if(playing) {
+ Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS");
+ prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
+ pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
+ rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
+ } else {
+ Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP");
+ prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP));
+ pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
+ rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
+ }
+
+ Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE");
+ pauseIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+ pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0);
+ rv.setOnClickPendingIntent(R.id.control_pause, pendingIntent);
+
+ Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT");
+ nextIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT));
+ pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0);
+ rv.setOnClickPendingIntent(R.id.control_next, pendingIntent);
+ }
+
+ public static void hidePlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler) {
+ // Remove notification and remove the service from the foreground
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ downloadService.stopForeground(true);
+ }
+ });
+
+ // Update widget
+ DSubWidgetProvider.notifyInstances(context, downloadService, false);
+ }
+
+ public static void sleepQuietly(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException x) {
+ Log.w(TAG, "Interrupted from sleep.", x);
+ }
+ }
+
+ public static void startActivityWithoutTransition(Activity currentActivity, Class<? extends Activity> newActivitiy) {
+ startActivityWithoutTransition(currentActivity, new Intent(currentActivity, newActivitiy));
+ }
+
+ public static void startActivityWithoutTransition(Activity currentActivity, Intent intent) {
+ currentActivity.startActivity(intent);
+ disablePendingTransition(currentActivity);
+ }
+
+ public static void disablePendingTransition(Activity activity) {
+
+ // Activity.overridePendingTransition() was introduced in Android 2.0. Use reflection to maintain
+ // compatibility with 1.5.
+ try {
+ Method method = Activity.class.getMethod("overridePendingTransition", int.class, int.class);
+ method.invoke(activity, 0, 0);
+ } catch (Throwable x) {
+ // Ignored
+ }
+ }
+
+ public static Drawable createDrawableFromBitmap(Context context, Bitmap bitmap) {
+ // BitmapDrawable(Resources, Bitmap) was introduced in Android 1.6. Use reflection to maintain
+ // compatibility with 1.5.
+ try {
+ Constructor<BitmapDrawable> constructor = BitmapDrawable.class.getConstructor(Resources.class, Bitmap.class);
+ return constructor.newInstance(context.getResources(), bitmap);
+ } catch (Throwable x) {
+ return new BitmapDrawable(bitmap);
+ }
+ }
+
+ public static void registerMediaButtonEventReceiver(Context context) {
+
+ // Only do it if enabled in the settings.
+ SharedPreferences prefs = getPreferences(context);
+ boolean enabled = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true);
+
+ if (enabled) {
+
+ // AudioManager.registerMediaButtonEventReceiver() was introduced in Android 2.2.
+ // Use reflection to maintain compatibility with 1.5.
+ try {
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
+ Method method = AudioManager.class.getMethod("registerMediaButtonEventReceiver", ComponentName.class);
+ method.invoke(audioManager, componentName);
+ } catch (Throwable x) {
+ // Ignored.
+ }
+ }
+ }
+
+ public static void unregisterMediaButtonEventReceiver(Context context) {
+ // AudioManager.unregisterMediaButtonEventReceiver() was introduced in Android 2.2.
+ // Use reflection to maintain compatibility with 1.5.
+ try {
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName());
+ Method method = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver", ComponentName.class);
+ method.invoke(audioManager, componentName);
+ } catch (Throwable x) {
+ // Ignored.
+ }
+ }
+
+ @TargetApi(8)
+ public static void requestAudioFocus(final Context context) {
+ if (Build.VERSION.SDK_INT >= 8 && !hasFocus) {
+ final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ hasFocus = true;
+ audioManager.requestAudioFocus(new OnAudioFocusChangeListener() {
+ public void onAudioFocusChange(int focusChange) {
+ DownloadServiceImpl downloadService = (DownloadServiceImpl)context;
+ if((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isJukeboxEnabled()) {
+ if(downloadService.getPlayerState() == PlayerState.STARTED) {
+ SharedPreferences prefs = getPreferences(context);
+ int lossPref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1"));
+ if(lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) {
+ lowerFocus = true;
+ downloadService.setVolume(0.1f);
+ } else if(lossPref == 0 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)) {
+ pauseFocus = true;
+ downloadService.pause();
+ }
+ }
+ } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ if(pauseFocus) {
+ pauseFocus = false;
+ downloadService.start();
+ } else if(lowerFocus) {
+ lowerFocus = false;
+ downloadService.setVolume(1.0f);
+ }
+ } else if(focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled()) {
+ hasFocus = false;
+ downloadService.pause();
+ audioManager.abandonAudioFocus(this);
+ }
+ }
+ }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ }
+ }
+
+ /**
+ * <p>Broadcasts the given song info as the new song being played.</p>
+ */
+ public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) {
+ DownloadService downloadService = (DownloadServiceImpl)context;
+ Intent intent = new Intent(EVENT_META_CHANGED);
+ Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED);
+
+ if (song != null) {
+ intent.putExtra("title", song.getTitle());
+ intent.putExtra("artist", song.getArtist());
+ intent.putExtra("album", song.getAlbum());
+
+ File albumArtFile = FileUtil.getAlbumArtFile(context, song);
+ intent.putExtra("coverart", albumArtFile.getAbsolutePath());
+
+ avrcpIntent.putExtra("playing", true);
+ avrcpIntent.putExtra("track", song.getTitle());
+ avrcpIntent.putExtra("artist", song.getArtist());
+ avrcpIntent.putExtra("album", song.getAlbum());
+ avrcpIntent.putExtra("ListSize",(long) downloadService.getSongs().size());
+ avrcpIntent.putExtra("id", (long) downloadService.getCurrentPlayingIndex()+1);
+ avrcpIntent.putExtra("duration", (long) downloadService.getPlayerDuration());
+ avrcpIntent.putExtra("position", (long) downloadService.getPlayerPosition());
+ avrcpIntent.putExtra("coverart", albumArtFile.getAbsolutePath());
+ } else {
+ intent.putExtra("title", "");
+ intent.putExtra("artist", "");
+ intent.putExtra("album", "");
+ intent.putExtra("coverart", "");
+
+ avrcpIntent.putExtra("playing", false);
+ avrcpIntent.putExtra("track", "");
+ avrcpIntent.putExtra("artist", "");
+ avrcpIntent.putExtra("album", "");
+ avrcpIntent.putExtra("ListSize",(long)0);
+ avrcpIntent.putExtra("id", (long) 0);
+ avrcpIntent.putExtra("duration", (long )0);
+ avrcpIntent.putExtra("position", (long) 0);
+ avrcpIntent.putExtra("coverart", "");
+ }
+
+ context.sendBroadcast(intent);
+ context.sendBroadcast(avrcpIntent);
+ }
+
+ /**
+ * <p>Broadcasts the given player state as the one being set.</p>
+ */
+ public static void broadcastPlaybackStatusChange(Context context, PlayerState state) {
+ Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED);
+ Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED);
+
+ switch (state) {
+ case STARTED:
+ intent.putExtra("state", "play");
+ avrcpIntent.putExtra("playing", true);
+ break;
+ case STOPPED:
+ intent.putExtra("state", "stop");
+ avrcpIntent.putExtra("playing", false);
+ break;
+ case PAUSED:
+ intent.putExtra("state", "pause");
+ avrcpIntent.putExtra("playing", false);
+ break;
+ case COMPLETED:
+ intent.putExtra("state", "complete");
+ avrcpIntent.putExtra("playing", false);
+ break;
+ default:
+ return; // No need to broadcast.
+ }
+
+ context.sendBroadcast(intent);
+ context.sendBroadcast(avrcpIntent);
+ }
+
+ /**
+ * Resolves the default text color for notifications.
+ *
+ * Based on http://stackoverflow.com/questions/4867338/custom-notification-layouts-and-text-colors/7320604#7320604
+ */
+ private static Pair<Integer, Integer> getNotificationTextColors(Context context) {
+ if (NOTIFICATION_TEXT_COLORS.getFirst() == null && NOTIFICATION_TEXT_COLORS.getSecond() == null) {
+ try {
+ Notification notification = new Notification();
+ String title = "title";
+ String content = "content";
+ notification.setLatestEventInfo(context, title, content, null);
+ LinearLayout group = new LinearLayout(context);
+ ViewGroup event = (ViewGroup) notification.contentView.apply(context, group);
+ findNotificationTextColors(event, title, content);
+ group.removeAllViews();
+ } catch (Exception x) {
+ Log.w(TAG, "Failed to resolve notification text colors.", x);
+ }
+ }
+ return NOTIFICATION_TEXT_COLORS;
+ }
+
+ private static void findNotificationTextColors(ViewGroup group, String title, String content) {
+ for (int i = 0; i < group.getChildCount(); i++) {
+ if (group.getChildAt(i) instanceof TextView) {
+ TextView textView = (TextView) group.getChildAt(i);
+ String text = textView.getText().toString();
+ if (title.equals(text)) {
+ NOTIFICATION_TEXT_COLORS.setFirst(textView.getTextColors().getDefaultColor());
+ }
+ else if (content.equals(text)) {
+ NOTIFICATION_TEXT_COLORS.setSecond(textView.getTextColors().getDefaultColor());
+ }
+ }
+ else if (group.getChildAt(i) instanceof ViewGroup)
+ findNotificationTextColors((ViewGroup) group.getChildAt(i), title, content);
+ }
+ }
+
+ public static WifiManager.WifiLock createWifiLock(Context context, String tag) {
+ WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ int lockType = WifiManager.WIFI_MODE_FULL;
+ if (Build.VERSION.SDK_INT >= 12) {
+ lockType = 3;
+ }
+ return wm.createWifiLock(lockType, tag);
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java b/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java
index 1a4544e0..ff7393c6 100644
--- a/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java
+++ b/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java
@@ -19,6 +19,7 @@
package github.daneren2005.dsub.view;
import android.content.Context;
+import android.util.Log;
import java.util.List;
import android.view.View;
@@ -31,7 +32,7 @@ import github.daneren2005.dsub.util.ImageLoader;
* @author Sindre Mehus
*/
public class EntryAdapter extends ArrayAdapter<MusicDirectory.Entry> {
-
+ private final static String TAG = EntryAdapter.class.getSimpleName();
private final Context activity;
private final ImageLoader imageLoader;
private final boolean checkable;
@@ -54,7 +55,7 @@ public class EntryAdapter extends ArrayAdapter<MusicDirectory.Entry> {
MusicDirectory.Entry entry = getItem(position);
if (entry.isDirectory()) {
- if(entry.getParent() != null) {
+ if(entry.getArtist() != null || entry.getParent() != null) {
AlbumView view;
view = new AlbumView(activity);
view.setAlbum(entry, imageLoader);