mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-16 11:41:16 +01:00
Fix notification sounds causing playback to start, fixed some CacheCleaner issues
This commit is contained in:
parent
6429559026
commit
32024ed1af
@ -1,126 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
package="net.sourceforge.subsonic.androidapp"
|
||||
a:versionCode="56"
|
||||
a:versionName="3.9.9.15" a:installLocation="auto">
|
||||
|
||||
<uses-permission a:name="android.permission.INTERNET"/>
|
||||
<uses-permission a:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission a:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission a:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission a:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission a:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission a:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
|
||||
<uses-sdk a:minSdkVersion="14" a:targetSdkVersion="16"/>
|
||||
|
||||
<supports-screens a:anyDensity="true" a:xlargeScreens="true" a:largeScreens="true" a:normalScreens="true" a:smallScreens="true"/>
|
||||
|
||||
<application a:label="@string/common.appname" a:icon="@drawable/ic_launcher" a:allowBackup="false">
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.MainActivity"
|
||||
a:label="Subsonic"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="standard">
|
||||
<intent-filter>
|
||||
<action a:name="android.intent.action.MAIN"/>
|
||||
<category a:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SelectArtistActivity"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="standard"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SelectAlbumActivity"
|
||||
a:configChanges="orientation|keyboardHidden"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SearchActivity"
|
||||
a:label="@string/search.label"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"
|
||||
/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SelectPlaylistActivity"
|
||||
a:label="@string/playlist.label"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="standard"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.DownloadActivity"
|
||||
a:configChanges="keyboardHidden"
|
||||
a:launchMode="singleTask"
|
||||
a:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SettingsActivity"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.HelpActivity"
|
||||
a:label="@string/help.label"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.LyricsActivity"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.EqualizerActivity"
|
||||
a:label="@string/equalizer.label"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.VoiceQueryReceiverActivity"
|
||||
a:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action a:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<category a:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"
|
||||
a:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action a:name="android.intent.action.SEARCH"/>
|
||||
</intent-filter>
|
||||
<meta-data a:name="android.app.searchable" a:resource="@xml/searchable"/>
|
||||
</activity>
|
||||
|
||||
<service a:name="net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl" a:label="Subsonic Download Service">
|
||||
<intent-filter>
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_TOGGLEPAUSE" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_PLAY" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_PAUSE" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_NEXT" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_PREVIOUS" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_STOP" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver a:name="net.sourceforge.subsonic.androidapp.receiver.MediaButtonIntentReceiver">
|
||||
<intent-filter a:priority="999">
|
||||
<action a:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver a:name="net.sourceforge.subsonic.androidapp.receiver.BluetoothIntentReceiver">
|
||||
<intent-filter>
|
||||
<action a:name="android.bluetooth.a2dp.action.SINK_STATE_CHANGED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver a:name="net.sourceforge.subsonic.androidapp.provider.SubsonicAppWidgetProvider4x1" a:label="Subsonic (4x1)" >
|
||||
<intent-filter>
|
||||
<action a:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data a:name="android.appwidget.provider" a:resource="@xml/appwidget_info_4x1"/>
|
||||
</receiver>
|
||||
|
||||
<provider a:name="net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider"
|
||||
a:authorities="net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider"/>
|
||||
|
||||
<meta-data a:name="android.app.default_searchable"
|
||||
a:value="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
package="net.sourceforge.subsonic.androidapp"
|
||||
a:versionCode="57"
|
||||
a:versionName="3.9.9.16" a:installLocation="auto">
|
||||
|
||||
<uses-permission a:name="android.permission.INTERNET"/>
|
||||
<uses-permission a:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission a:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission a:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission a:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission a:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission a:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
|
||||
<uses-sdk a:minSdkVersion="14" a:targetSdkVersion="17"/>
|
||||
|
||||
<supports-screens a:anyDensity="true" a:xlargeScreens="true" a:largeScreens="true" a:normalScreens="true" a:smallScreens="true"/>
|
||||
|
||||
<application a:label="@string/common.appname" a:icon="@drawable/ic_launcher" a:allowBackup="false">
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.MainActivity"
|
||||
a:label="Subsonic"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="standard">
|
||||
<intent-filter>
|
||||
<action a:name="android.intent.action.MAIN"/>
|
||||
<category a:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SelectArtistActivity"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="standard"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SelectAlbumActivity"
|
||||
a:configChanges="orientation|keyboardHidden"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SearchActivity"
|
||||
a:label="@string/search.label"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"
|
||||
/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SelectPlaylistActivity"
|
||||
a:label="@string/playlist.label"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="standard"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.DownloadActivity"
|
||||
a:configChanges="keyboardHidden"
|
||||
a:launchMode="singleTask"
|
||||
a:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.SettingsActivity"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.HelpActivity"
|
||||
a:label="@string/help.label"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.LyricsActivity"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.EqualizerActivity"
|
||||
a:label="@string/equalizer.label"
|
||||
a:configChanges="orientation|keyboardHidden"
|
||||
a:launchMode="singleTask"/>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.VoiceQueryReceiverActivity"
|
||||
a:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action a:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<category a:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity a:name="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"
|
||||
a:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action a:name="android.intent.action.SEARCH"/>
|
||||
</intent-filter>
|
||||
<meta-data a:name="android.app.searchable" a:resource="@xml/searchable"/>
|
||||
</activity>
|
||||
|
||||
<service a:name="net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl" a:label="Subsonic Download Service">
|
||||
<intent-filter>
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_TOGGLEPAUSE" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_PLAY" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_PAUSE" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_NEXT" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_PREVIOUS" />
|
||||
<action a:name="net.sourceforge.subsonic.androidapp.CMD_STOP" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver a:name="net.sourceforge.subsonic.androidapp.receiver.MediaButtonIntentReceiver">
|
||||
<intent-filter a:priority="999">
|
||||
<action a:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver a:name="net.sourceforge.subsonic.androidapp.receiver.BluetoothIntentReceiver">
|
||||
<intent-filter>
|
||||
<action a:name="android.bluetooth.a2dp.action.SINK_STATE_CHANGED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver a:name="net.sourceforge.subsonic.androidapp.provider.SubsonicAppWidgetProvider4x1" a:label="Subsonic (4x1)" >
|
||||
<intent-filter>
|
||||
<action a:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data a:name="android.appwidget.provider" a:resource="@xml/appwidget_info_4x1"/>
|
||||
</receiver>
|
||||
|
||||
<provider a:name="net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider"
|
||||
a:authorities="net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider"/>
|
||||
|
||||
<meta-data a:name="android.app.default_searchable"
|
||||
a:value="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -1,126 +1,127 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
|
||||
package net.sourceforge.subsonic.androidapp.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||
|
||||
/**
|
||||
* An HTML-based help screen with Back and Done buttons at the bottom.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public final class HelpActivity extends Activity {
|
||||
|
||||
private WebView webView;
|
||||
private Button backButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
|
||||
setContentView(R.layout.help);
|
||||
|
||||
webView = (WebView) findViewById(R.id.help_contents);
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.setWebViewClient(new HelpClient());
|
||||
if (bundle != null) {
|
||||
webView.restoreState(bundle);
|
||||
} else {
|
||||
webView.loadUrl(getResources().getString(R.string.help_url));
|
||||
}
|
||||
|
||||
backButton = (Button) findViewById(R.id.help_back);
|
||||
backButton.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
webView.goBack();
|
||||
}
|
||||
});
|
||||
|
||||
Button doneButton = (Button) findViewById(R.id.help_close);
|
||||
doneButton.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle state) {
|
||||
webView.saveState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private final class HelpClient extends WebViewClient {
|
||||
@Override
|
||||
public void onLoadResource(WebView webView, String url) {
|
||||
setProgressBarIndeterminateVisibility(true);
|
||||
setTitle(getResources().getString(R.string.help_loading));
|
||||
super.onLoadResource(webView, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
String versionName = null;
|
||||
|
||||
try {
|
||||
versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
setTitle(view.getTitle() + " (" + versionName + ")");
|
||||
backButton.setEnabled(view.canGoBack());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
Util.toast(HelpActivity.this, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
|
||||
package net.sourceforge.subsonic.androidapp.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||
|
||||
/**
|
||||
* An HTML-based help screen with Back and Done buttons at the bottom.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public final class HelpActivity extends Activity {
|
||||
private static final String TAG = HelpActivity.class.getSimpleName();
|
||||
private WebView webView;
|
||||
private Button backButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
|
||||
setContentView(R.layout.help);
|
||||
|
||||
webView = (WebView) findViewById(R.id.help_contents);
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.setWebViewClient(new HelpClient());
|
||||
if (bundle != null) {
|
||||
webView.restoreState(bundle);
|
||||
} else {
|
||||
webView.loadUrl(getResources().getString(R.string.help_url));
|
||||
}
|
||||
|
||||
backButton = (Button) findViewById(R.id.help_back);
|
||||
backButton.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
webView.goBack();
|
||||
}
|
||||
});
|
||||
|
||||
Button doneButton = (Button) findViewById(R.id.help_close);
|
||||
doneButton.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle state) {
|
||||
webView.saveState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private final class HelpClient extends WebViewClient {
|
||||
@Override
|
||||
public void onLoadResource(WebView webView, String url) {
|
||||
setProgressBarIndeterminateVisibility(true);
|
||||
setTitle(getResources().getString(R.string.help_loading));
|
||||
super.onLoadResource(webView, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
String versionName = null;
|
||||
|
||||
try {
|
||||
versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
|
||||
setTitle(view.getTitle() + " (" + versionName + ")");
|
||||
backButton.setEnabled(view.canGoBack());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
Util.toast(HelpActivity.this, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,60 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2010 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl;
|
||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class MediaButtonIntentReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Util.getMediaButtonsPreference(context)) {
|
||||
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
|
||||
Log.i(TAG, "Got MEDIA_BUTTON key event: " + event);
|
||||
|
||||
Intent serviceIntent = new Intent(context, DownloadServiceImpl.class);
|
||||
serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
|
||||
context.startService(serviceIntent);
|
||||
|
||||
try {
|
||||
if (isOrderedBroadcast()) {
|
||||
abortBroadcast();
|
||||
}
|
||||
} catch (Exception x) {
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2010 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl;
|
||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class MediaButtonIntentReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Util.getMediaButtonsPreference(context)) {
|
||||
String intentAction = intent.getAction();
|
||||
|
||||
if (!Intent.ACTION_MEDIA_BUTTON.equals(intentAction))
|
||||
return;
|
||||
|
||||
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
|
||||
Log.i(TAG, "Got MEDIA_BUTTON key event: " + event);
|
||||
|
||||
Intent serviceIntent = new Intent(context, DownloadServiceImpl.class);
|
||||
serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
|
||||
context.startService(serviceIntent);
|
||||
|
||||
try {
|
||||
if (isOrderedBroadcast()) {
|
||||
abortBroadcast();
|
||||
}
|
||||
} catch (Exception x) {
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,259 +1,261 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Artist;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Indexes;
|
||||
import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Lyrics;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicFolder;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Playlist;
|
||||
import net.sourceforge.subsonic.androidapp.domain.SearchCritera;
|
||||
import net.sourceforge.subsonic.androidapp.domain.SearchResult;
|
||||
import net.sourceforge.subsonic.androidapp.util.Constants;
|
||||
import net.sourceforge.subsonic.androidapp.util.FileUtil;
|
||||
import net.sourceforge.subsonic.androidapp.util.ProgressListener;
|
||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class OfflineMusicService extends RESTMusicService {
|
||||
|
||||
@Override
|
||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
for (File file : FileUtil.listFiles(root)) {
|
||||
if (file.isDirectory()) {
|
||||
Artist artist = new Artist();
|
||||
artist.setId(file.getPath());
|
||||
artist.setIndex(file.getName().substring(0, 1));
|
||||
artist.setName(file.getName());
|
||||
artists.add(artist);
|
||||
}
|
||||
}
|
||||
return new Indexes(0L, Collections.<Artist>emptyList(), artists);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
File dir = new File(id);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
result.setName(dir.getName());
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
for (File file : FileUtil.listMusicFiles(dir)) {
|
||||
String name = getName(file);
|
||||
if (name != null & !names.contains(name)) {
|
||||
names.add(name);
|
||||
result.addChild(createEntry(context, file, name));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getName(File file) {
|
||||
String name = file.getName();
|
||||
if (file.isDirectory()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name = name.replace(".complete", "");
|
||||
return FileUtil.getBaseName(name);
|
||||
}
|
||||
|
||||
private MusicDirectory.Entry createEntry(Context context, File file, String name) {
|
||||
MusicDirectory.Entry entry = new MusicDirectory.Entry();
|
||||
entry.setDirectory(file.isDirectory());
|
||||
entry.setId(file.getPath());
|
||||
entry.setParent(file.getParent());
|
||||
entry.setSize(file.length());
|
||||
String root = FileUtil.getMusicDirectory(context).getPath();
|
||||
entry.setPath(file.getPath().replaceFirst("^" + root + "/" , ""));
|
||||
if (file.isFile()) {
|
||||
entry.setArtist(file.getParentFile().getParentFile().getName());
|
||||
entry.setAlbum(file.getParentFile().getName());
|
||||
}
|
||||
entry.setTitle(name);
|
||||
entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", "")));
|
||||
|
||||
File albumArt = FileUtil.getAlbumArtFile(context, entry);
|
||||
if (albumArt.exists()) {
|
||||
entry.setCoverArt(albumArt.getPath());
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception {
|
||||
InputStream in = new FileInputStream(entry.getCoverArt());
|
||||
try {
|
||||
byte[] bytes = Util.toByteArray(in);
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
return Bitmap.createScaledBitmap(bitmap, size, size, true);
|
||||
} finally {
|
||||
Util.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void star(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Star not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unstar(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("UnStar not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MusicFolder> getMusicFolders(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Music folders not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Search not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Lyrics not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Scrobbling not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Album lists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVideoUrl(Context context, String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult getStarred(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Starred not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
List<File> children = new LinkedList<File>();
|
||||
listFilesRecursively(root, children);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
|
||||
if (children.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < size; i++) {
|
||||
File file = children.get(random.nextInt(children.size()));
|
||||
result.addChild(createEntry(context, file, getName(file)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void listFilesRecursively(File parent, List<File> children) {
|
||||
for (File file : FileUtil.listMusicFiles(parent)) {
|
||||
if (file.isFile()) {
|
||||
children.add(file);
|
||||
} else {
|
||||
listFilesRecursively(file, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Artist;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Indexes;
|
||||
import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Lyrics;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicFolder;
|
||||
import net.sourceforge.subsonic.androidapp.domain.Playlist;
|
||||
import net.sourceforge.subsonic.androidapp.domain.SearchCritera;
|
||||
import net.sourceforge.subsonic.androidapp.domain.SearchResult;
|
||||
import net.sourceforge.subsonic.androidapp.util.Constants;
|
||||
import net.sourceforge.subsonic.androidapp.util.FileUtil;
|
||||
import net.sourceforge.subsonic.androidapp.util.ProgressListener;
|
||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class OfflineMusicService extends RESTMusicService {
|
||||
|
||||
@Override
|
||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
for (File file : FileUtil.listFiles(root)) {
|
||||
if (file.isDirectory()) {
|
||||
Artist artist = new Artist();
|
||||
artist.setId(file.getPath());
|
||||
artist.setIndex(file.getName().substring(0, 1));
|
||||
artist.setName(file.getName());
|
||||
artists.add(artist);
|
||||
}
|
||||
}
|
||||
return new Indexes(0L, Collections.<Artist>emptyList(), artists);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
File dir = new File(id);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
result.setName(dir.getName());
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
for (File file : FileUtil.listMusicFiles(dir)) {
|
||||
String name = getName(file);
|
||||
if (name != null & !names.contains(name)) {
|
||||
names.add(name);
|
||||
result.addChild(createEntry(context, file, name));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getName(File file) {
|
||||
String name = file.getName();
|
||||
if (file.isDirectory()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name = name.replace(".complete", "");
|
||||
return FileUtil.getBaseName(name);
|
||||
}
|
||||
|
||||
private MusicDirectory.Entry createEntry(Context context, File file, String name) {
|
||||
MusicDirectory.Entry entry = new MusicDirectory.Entry();
|
||||
entry.setDirectory(file.isDirectory());
|
||||
entry.setId(file.getPath());
|
||||
entry.setParent(file.getParent());
|
||||
entry.setSize(file.length());
|
||||
String root = FileUtil.getMusicDirectory(context).getPath();
|
||||
entry.setPath(file.getPath().replaceFirst("^" + root + "/" , ""));
|
||||
if (file.isFile()) {
|
||||
entry.setArtist(file.getParentFile().getParentFile().getName());
|
||||
entry.setAlbum(file.getParentFile().getName());
|
||||
}
|
||||
entry.setTitle(name);
|
||||
entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", "")));
|
||||
|
||||
File albumArt = FileUtil.getAlbumArtFile(context, entry);
|
||||
if (albumArt.exists()) {
|
||||
entry.setCoverArt(albumArt.getPath());
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception {
|
||||
InputStream in = new FileInputStream(entry.getCoverArt());
|
||||
try {
|
||||
byte[] bytes = Util.toByteArray(in);
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
Log.i("getCoverArt", "getCoverArt");
|
||||
return Bitmap.createScaledBitmap(bitmap, size, size, true);
|
||||
} finally {
|
||||
Util.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void star(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Star not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unstar(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("UnStar not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MusicFolder> getMusicFolders(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Music folders not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Search not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Lyrics not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Scrobbling not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Album lists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVideoUrl(Context context, String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult getStarred(Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Starred not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
List<File> children = new LinkedList<File>();
|
||||
listFilesRecursively(root, children);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
|
||||
if (children.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < size; i++) {
|
||||
File file = children.get(random.nextInt(children.size()));
|
||||
result.addChild(createEntry(context, file, getName(file)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void listFilesRecursively(File parent, List<File> children) {
|
||||
for (File file : FileUtil.listMusicFiles(parent)) {
|
||||
if (file.isFile()) {
|
||||
children.add(file);
|
||||
} else {
|
||||
listFilesRecursively(file, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +1,95 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||
|
||||
/**
|
||||
* Used to display albums in a {@code ListView}.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class AlbumView extends LinearLayout {
|
||||
|
||||
private TextView titleView;
|
||||
private TextView artistView;
|
||||
private View coverArtView;
|
||||
private ImageView starImageView;
|
||||
|
||||
public AlbumView(Context context) {
|
||||
super(context);
|
||||
LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true);
|
||||
|
||||
titleView = (TextView) findViewById(R.id.album_title);
|
||||
artistView = (TextView) findViewById(R.id.album_artist);
|
||||
coverArtView = findViewById(R.id.album_coverart);
|
||||
starImageView = (ImageView) findViewById(R.id.album_star);
|
||||
}
|
||||
|
||||
public void setAlbum(final MusicDirectory.Entry album, ImageLoader imageLoader) {
|
||||
titleView.setText(album.getTitle());
|
||||
artistView.setText(album.getArtist());
|
||||
artistView.setVisibility(album.getArtist() == null ? View.GONE : View.VISIBLE);
|
||||
starImageView.setImageDrawable(album.getStarred() ? getResources().getDrawable(R.drawable.star) : getResources().getDrawable(R.drawable.star_hollow));
|
||||
imageLoader.loadImage(coverArtView, album, false, true);
|
||||
|
||||
starImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final boolean isStarred = album.getStarred();
|
||||
final String id = album.getId();
|
||||
|
||||
if (!isStarred) {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star));
|
||||
album.setStarred(true);
|
||||
} else {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow));
|
||||
album.setStarred(false);
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(null);
|
||||
|
||||
try {
|
||||
if (!isStarred) {
|
||||
musicService.star(id, getContext(), null);
|
||||
} else {
|
||||
musicService.unstar(id, getContext(), null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||
|
||||
/**
|
||||
* Used to display albums in a {@code ListView}.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class AlbumView extends LinearLayout {
|
||||
|
||||
private static final String TAG = AlbumView.class.getSimpleName();
|
||||
private TextView titleView;
|
||||
private TextView artistView;
|
||||
private View coverArtView;
|
||||
private ImageView starImageView;
|
||||
|
||||
public AlbumView(Context context) {
|
||||
super(context);
|
||||
LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true);
|
||||
|
||||
titleView = (TextView) findViewById(R.id.album_title);
|
||||
artistView = (TextView) findViewById(R.id.album_artist);
|
||||
coverArtView = findViewById(R.id.album_coverart);
|
||||
starImageView = (ImageView) findViewById(R.id.album_star);
|
||||
}
|
||||
|
||||
public void setAlbum(final MusicDirectory.Entry album, ImageLoader imageLoader) {
|
||||
titleView.setText(album.getTitle());
|
||||
artistView.setText(album.getArtist());
|
||||
artistView.setVisibility(album.getArtist() == null ? View.GONE : View.VISIBLE);
|
||||
starImageView.setImageDrawable(album.getStarred() ? getResources().getDrawable(R.drawable.star) : getResources().getDrawable(R.drawable.star_hollow));
|
||||
imageLoader.loadImage(coverArtView, album, false, true);
|
||||
|
||||
starImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final boolean isStarred = album.getStarred();
|
||||
final String id = album.getId();
|
||||
|
||||
if (!isStarred) {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star));
|
||||
album.setStarred(true);
|
||||
} else {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow));
|
||||
album.setStarred(false);
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(null);
|
||||
|
||||
try {
|
||||
if (!isStarred) {
|
||||
musicService.star(id, getContext(), null);
|
||||
} else {
|
||||
musicService.unstar(id, getContext(), null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -32,31 +35,33 @@ public class CacheCleaner {
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
Log.i(TAG, "Starting cache cleaning.");
|
||||
|
||||
Log.i(TAG, "Starting cache cleaning.");
|
||||
if (downloadService == null) {
|
||||
Log.e(TAG, "DownloadService not set. Aborting cache cleaning.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (downloadService == null) {
|
||||
Log.e(TAG, "DownloadService not set. Aborting cache cleaning.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<File> files = new ArrayList<File>();
|
||||
List<File> dirs = new ArrayList<File>();
|
||||
|
||||
try {
|
||||
findCandidatesForDeletion(FileUtil.getMusicDirectory(context), files, dirs);
|
||||
sortByAscendingModificationTime(files);
|
||||
|
||||
List<File> files = new ArrayList<File>();
|
||||
List<File> dirs = new ArrayList<File>();
|
||||
Set<File> undeletable = findUndeletableFiles();
|
||||
|
||||
findCandidatesForDeletion(FileUtil.getMusicDirectory(context), files, dirs);
|
||||
sortByAscendingModificationTime(files);
|
||||
deleteFiles(files, undeletable);
|
||||
deleteEmptyDirs(dirs, undeletable);
|
||||
Log.i(TAG, "Completed cache cleaning.");
|
||||
|
||||
Set<File> undeletable = findUndeletableFiles();
|
||||
|
||||
deleteFiles(files, undeletable);
|
||||
deleteEmptyDirs(dirs, undeletable);
|
||||
Log.i(TAG, "Completed cache cleaning.");
|
||||
|
||||
} catch (RuntimeException x) {
|
||||
Log.e(TAG, "Error in cache cleaning.", x);
|
||||
}
|
||||
} catch (RuntimeException x) {
|
||||
Log.e(TAG, "Error in cache cleaning.", x);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void deleteEmptyDirs(List<File> dirs, Set<File> undeletable) {
|
||||
@ -68,7 +73,7 @@ public class CacheCleaner {
|
||||
File[] children = dir.listFiles();
|
||||
|
||||
// Delete empty directory and associated album artwork.
|
||||
if (children.length == 0) {
|
||||
if (children != null && children.length == 0) {
|
||||
Util.delete(dir);
|
||||
Util.delete(FileUtil.getAlbumArtFile(dir));
|
||||
}
|
||||
@ -88,25 +93,31 @@ public class CacheCleaner {
|
||||
bytesUsedBySubsonic += file.length();
|
||||
}
|
||||
|
||||
long bytesToDelete = 0;
|
||||
|
||||
// Ensure that file system is not more than 95% full.
|
||||
StatFs stat = new StatFs(files.get(0).getPath());
|
||||
long bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
||||
long bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
||||
long bytesUsedFs = bytesTotalFs - bytesAvailableFs;
|
||||
long minFsAvailability = Math.round(MAX_FILE_SYSTEM_USAGE * (double) bytesTotalFs);
|
||||
try
|
||||
{
|
||||
StatFs stat = new StatFs(files.get(0).getPath());
|
||||
long bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
||||
long bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
||||
long bytesUsedFs = bytesTotalFs - bytesAvailableFs;
|
||||
long minFsAvailability = Math.round(MAX_FILE_SYSTEM_USAGE * (double) bytesTotalFs);
|
||||
|
||||
long bytesToDeleteCacheLimit = Math.max(bytesUsedBySubsonic - cacheSizeBytes, 0L);
|
||||
long bytesToDeleteFsLimit = Math.max(bytesUsedFs - minFsAvailability, 0L);
|
||||
long bytesToDelete = Math.max(bytesToDeleteCacheLimit, bytesToDeleteFsLimit);
|
||||
long bytesToDeleteCacheLimit = Math.max(bytesUsedBySubsonic - cacheSizeBytes, 0L);
|
||||
long bytesToDeleteFsLimit = Math.max(bytesUsedFs - minFsAvailability, 0L);
|
||||
bytesToDelete = Math.max(bytesToDeleteCacheLimit, bytesToDeleteFsLimit);
|
||||
|
||||
Log.i(TAG, "File system : " + Util.formatBytes(bytesAvailableFs) + " of " + Util.formatBytes(bytesTotalFs) + " available");
|
||||
Log.i(TAG, "Cache limit : " + Util.formatBytes(cacheSizeBytes));
|
||||
Log.i(TAG, "Cache size before : " + Util.formatBytes(bytesUsedBySubsonic));
|
||||
Log.i(TAG, "Minimum to delete : " + Util.formatBytes(bytesToDelete));
|
||||
Log.i(TAG, "File system : " + Util.formatBytes(bytesAvailableFs) + " of " + Util.formatBytes(bytesTotalFs) + " available");
|
||||
Log.i(TAG, "Cache limit : " + Util.formatBytes(cacheSizeBytes));
|
||||
Log.i(TAG, "Cache size before : " + Util.formatBytes(bytesUsedBySubsonic));
|
||||
Log.i(TAG, "Minimum to delete : " + Util.formatBytes(bytesToDelete));
|
||||
} catch (Exception x) {
|
||||
//
|
||||
}
|
||||
|
||||
long bytesDeleted = 0L;
|
||||
for (File file : files) {
|
||||
|
||||
if (file.getName().equals(Constants.ALBUM_ART_FILE)) {
|
||||
// Move artwork to new folder.
|
||||
file.renameTo(FileUtil.getAlbumArtFile(file.getParentFile()));
|
||||
|
@ -1,302 +1,303 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class FileUtil {
|
||||
|
||||
private static final String TAG = FileUtil.class.getSimpleName();
|
||||
private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">"};
|
||||
private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">"};
|
||||
private static final List<String> MUSIC_FILE_EXTENSIONS = Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma");
|
||||
private static final File DEFAULT_MUSIC_DIR = createDirectory("music");
|
||||
|
||||
public static File getSongFile(Context context, MusicDirectory.Entry song) {
|
||||
File dir = getAlbumDirectory(context, song);
|
||||
|
||||
StringBuilder fileName = new StringBuilder();
|
||||
Integer track = song.getTrack();
|
||||
if (track != null) {
|
||||
if (track < 10) {
|
||||
fileName.append("0");
|
||||
}
|
||||
fileName.append(track).append("-");
|
||||
}
|
||||
|
||||
fileName.append(fileSystemSafe(song.getTitle())).append(".");
|
||||
|
||||
if (song.getTranscodedSuffix() != null) {
|
||||
fileName.append(song.getTranscodedSuffix());
|
||||
} else {
|
||||
fileName.append(song.getSuffix());
|
||||
}
|
||||
|
||||
return new File(dir, fileName.toString());
|
||||
}
|
||||
|
||||
public static File getAlbumArtFile(Context context, MusicDirectory.Entry entry) {
|
||||
File albumDir = getAlbumDirectory(context, entry);
|
||||
return getAlbumArtFile(albumDir);
|
||||
}
|
||||
|
||||
public static File getAlbumArtFile(File albumDir) {
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
return new File(albumArtDir, Util.md5Hex(albumDir.getPath()) + ".jpeg");
|
||||
}
|
||||
|
||||
public static Bitmap getAlbumArtBitmap(Context context, MusicDirectory.Entry entry, int size) {
|
||||
File albumArtFile = getAlbumArtFile(context, entry);
|
||||
if (albumArtFile.exists()) {
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(albumArtFile.getPath());
|
||||
return bitmap == null ? null : Bitmap.createScaledBitmap(bitmap, size, size, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static File getAlbumArtDirectory() {
|
||||
File albumArtDir = new File(getSubsonicDirectory(), "artwork");
|
||||
ensureDirectoryExistsAndIsReadWritable(albumArtDir);
|
||||
ensureDirectoryExistsAndIsReadWritable(new File(albumArtDir, ".nomedia"));
|
||||
return albumArtDir;
|
||||
}
|
||||
|
||||
private static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) {
|
||||
File dir;
|
||||
if (entry.getPath() != null) {
|
||||
File f = new File(fileSystemSafeDir(entry.getPath()));
|
||||
dir = new File(getMusicDirectory(context).getPath() + "/" + (entry.isDirectory() ? f.getPath() : f.getParent()));
|
||||
} else {
|
||||
String artist = fileSystemSafe(entry.getArtist());
|
||||
String album = fileSystemSafe(entry.getAlbum());
|
||||
dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static void createDirectoryForParent(File file) {
|
||||
File dir = file.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create directory " + dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static File createDirectory(String name) {
|
||||
File dir = new File(getSubsonicDirectory(), name);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create " + name);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static File getSubsonicDirectory() {
|
||||
return new File(Environment.getExternalStorageDirectory(), "subsonic");
|
||||
}
|
||||
|
||||
public static File getDefaultMusicDirectory() {
|
||||
return DEFAULT_MUSIC_DIR;
|
||||
}
|
||||
|
||||
public static File getMusicDirectory(Context context) {
|
||||
String path = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, DEFAULT_MUSIC_DIR.getPath());
|
||||
File dir = new File(path);
|
||||
return ensureDirectoryExistsAndIsReadWritable(dir) ? dir : getDefaultMusicDirectory();
|
||||
}
|
||||
|
||||
public static boolean ensureDirectoryExistsAndIsReadWritable(File dir) {
|
||||
if (dir == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dir.exists()) {
|
||||
if (!dir.isDirectory()) {
|
||||
Log.w(TAG, dir + " exists but is not a directory.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (dir.mkdirs()) {
|
||||
Log.i(TAG, "Created directory " + dir);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create directory " + dir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir.canRead()) {
|
||||
Log.w(TAG, "No read permission for directory " + dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dir.canWrite()) {
|
||||
Log.w(TAG, "No write permission for directory " + dir);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a given filename safe by replacing special characters like slashes ("/" and "\")
|
||||
* with dashes ("-").
|
||||
*
|
||||
* @param filename The filename in question.
|
||||
* @return The filename with special characters replaced by hyphens.
|
||||
*/
|
||||
private static String fileSystemSafe(String filename) {
|
||||
if (filename == null || filename.trim().length() == 0) {
|
||||
return "unnamed";
|
||||
}
|
||||
|
||||
for (String s : FILE_SYSTEM_UNSAFE) {
|
||||
filename = filename.replace(s, "-");
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a given filename safe by replacing special characters like colons (":")
|
||||
* with dashes ("-").
|
||||
*
|
||||
* @param path The path of the directory in question.
|
||||
* @return The the directory name with special characters replaced by hyphens.
|
||||
*/
|
||||
private static String fileSystemSafeDir(String path) {
|
||||
if (path == null || path.trim().length() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (String s : FILE_SYSTEM_UNSAFE_DIR) {
|
||||
path = path.replace(s, "-");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link File#listFiles()}, but returns a sorted set.
|
||||
* Never returns {@code null}, instead a warning is logged, and an empty set is returned.
|
||||
*/
|
||||
public static SortedSet<File> listFiles(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
Log.w(TAG, "Failed to list children for " + dir.getPath());
|
||||
return new TreeSet<File>();
|
||||
}
|
||||
|
||||
return new TreeSet<File>(Arrays.asList(files));
|
||||
}
|
||||
|
||||
public static SortedSet<File> listMusicFiles(File dir) {
|
||||
SortedSet<File> files = listFiles(dir);
|
||||
Iterator<File> iterator = files.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
File file = iterator.next();
|
||||
if (!file.isDirectory() && !isMusicFile(file)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static boolean isMusicFile(File file) {
|
||||
String extension = getExtension(file.getName());
|
||||
return MUSIC_FILE_EXTENSIONS.contains(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension (the substring after the last dot) of the given file. The dot
|
||||
* is not included in the returned extension.
|
||||
*
|
||||
* @param name The filename in question.
|
||||
* @return The extension, or an empty string if no extension is found.
|
||||
*/
|
||||
public static String getExtension(String name) {
|
||||
int index = name.lastIndexOf('.');
|
||||
return index == -1 ? "" : name.substring(index + 1).toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base name (the substring before the last dot) of the given file. The dot
|
||||
* is not included in the returned basename.
|
||||
*
|
||||
* @param name The filename in question.
|
||||
* @return The base name, or an empty string if no basename is found.
|
||||
*/
|
||||
public static String getBaseName(String name) {
|
||||
int index = name.lastIndexOf('.');
|
||||
return index == -1 ? name : name.substring(0, index);
|
||||
}
|
||||
|
||||
public static <T extends Serializable> boolean serialize(Context context, T obj, String fileName) {
|
||||
File file = new File(context.getCacheDir(), fileName);
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
out = new ObjectOutputStream(new FileOutputStream(file));
|
||||
out.writeObject(obj);
|
||||
Log.i(TAG, "Serialized object to " + file);
|
||||
return true;
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to serialize object to " + file);
|
||||
return false;
|
||||
} finally {
|
||||
Util.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Serializable> T deserialize(Context context, String fileName) {
|
||||
File file = new File(context.getCacheDir(), fileName);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ObjectInputStream in = null;
|
||||
try {
|
||||
in = new ObjectInputStream(new FileInputStream(file));
|
||||
T result = (T)in.readObject();
|
||||
Log.i(TAG, "Deserialized object from " + file);
|
||||
return result;
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to deserialize object from " + file, x);
|
||||
return null;
|
||||
} finally {
|
||||
Util.close(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class FileUtil {
|
||||
|
||||
private static final String TAG = FileUtil.class.getSimpleName();
|
||||
private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">"};
|
||||
private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">"};
|
||||
private static final List<String> MUSIC_FILE_EXTENSIONS = Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma");
|
||||
private static final File DEFAULT_MUSIC_DIR = createDirectory("music");
|
||||
|
||||
public static File getSongFile(Context context, MusicDirectory.Entry song) {
|
||||
File dir = getAlbumDirectory(context, song);
|
||||
|
||||
StringBuilder fileName = new StringBuilder();
|
||||
Integer track = song.getTrack();
|
||||
if (track != null) {
|
||||
if (track < 10) {
|
||||
fileName.append("0");
|
||||
}
|
||||
fileName.append(track).append("-");
|
||||
}
|
||||
|
||||
fileName.append(fileSystemSafe(song.getTitle())).append(".");
|
||||
|
||||
if (song.getTranscodedSuffix() != null) {
|
||||
fileName.append(song.getTranscodedSuffix());
|
||||
} else {
|
||||
fileName.append(song.getSuffix());
|
||||
}
|
||||
|
||||
return new File(dir, fileName.toString());
|
||||
}
|
||||
|
||||
public static File getAlbumArtFile(Context context, MusicDirectory.Entry entry) {
|
||||
File albumDir = getAlbumDirectory(context, entry);
|
||||
return getAlbumArtFile(albumDir);
|
||||
}
|
||||
|
||||
public static File getAlbumArtFile(File albumDir) {
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
return new File(albumArtDir, Util.md5Hex(albumDir.getPath()) + ".jpeg");
|
||||
}
|
||||
|
||||
public static Bitmap getAlbumArtBitmap(Context context, MusicDirectory.Entry entry, int size) {
|
||||
File albumArtFile = getAlbumArtFile(context, entry);
|
||||
if (albumArtFile.exists()) {
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(albumArtFile.getPath());
|
||||
Log.i("getAlbumArtBitmap", String.valueOf(size));
|
||||
return bitmap == null ? null : Bitmap.createScaledBitmap(bitmap, size, size, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static File getAlbumArtDirectory() {
|
||||
File albumArtDir = new File(getSubsonicDirectory(), "artwork");
|
||||
ensureDirectoryExistsAndIsReadWritable(albumArtDir);
|
||||
ensureDirectoryExistsAndIsReadWritable(new File(albumArtDir, ".nomedia"));
|
||||
return albumArtDir;
|
||||
}
|
||||
|
||||
private static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) {
|
||||
File dir;
|
||||
if (entry.getPath() != null) {
|
||||
File f = new File(fileSystemSafeDir(entry.getPath()));
|
||||
dir = new File(getMusicDirectory(context).getPath() + "/" + (entry.isDirectory() ? f.getPath() : f.getParent()));
|
||||
} else {
|
||||
String artist = fileSystemSafe(entry.getArtist());
|
||||
String album = fileSystemSafe(entry.getAlbum());
|
||||
dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static void createDirectoryForParent(File file) {
|
||||
File dir = file.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create directory " + dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static File createDirectory(String name) {
|
||||
File dir = new File(getSubsonicDirectory(), name);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create " + name);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static File getSubsonicDirectory() {
|
||||
return new File(Environment.getExternalStorageDirectory(), "subsonic");
|
||||
}
|
||||
|
||||
public static File getDefaultMusicDirectory() {
|
||||
return DEFAULT_MUSIC_DIR;
|
||||
}
|
||||
|
||||
public static File getMusicDirectory(Context context) {
|
||||
String path = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, DEFAULT_MUSIC_DIR.getPath());
|
||||
File dir = new File(path);
|
||||
return ensureDirectoryExistsAndIsReadWritable(dir) ? dir : getDefaultMusicDirectory();
|
||||
}
|
||||
|
||||
public static boolean ensureDirectoryExistsAndIsReadWritable(File dir) {
|
||||
if (dir == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dir.exists()) {
|
||||
if (!dir.isDirectory()) {
|
||||
Log.w(TAG, dir + " exists but is not a directory.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (dir.mkdirs()) {
|
||||
Log.i(TAG, "Created directory " + dir);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create directory " + dir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir.canRead()) {
|
||||
Log.w(TAG, "No read permission for directory " + dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dir.canWrite()) {
|
||||
Log.w(TAG, "No write permission for directory " + dir);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a given filename safe by replacing special characters like slashes ("/" and "\")
|
||||
* with dashes ("-").
|
||||
*
|
||||
* @param filename The filename in question.
|
||||
* @return The filename with special characters replaced by hyphens.
|
||||
*/
|
||||
private static String fileSystemSafe(String filename) {
|
||||
if (filename == null || filename.trim().length() == 0) {
|
||||
return "unnamed";
|
||||
}
|
||||
|
||||
for (String s : FILE_SYSTEM_UNSAFE) {
|
||||
filename = filename.replace(s, "-");
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a given filename safe by replacing special characters like colons (":")
|
||||
* with dashes ("-").
|
||||
*
|
||||
* @param path The path of the directory in question.
|
||||
* @return The the directory name with special characters replaced by hyphens.
|
||||
*/
|
||||
private static String fileSystemSafeDir(String path) {
|
||||
if (path == null || path.trim().length() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (String s : FILE_SYSTEM_UNSAFE_DIR) {
|
||||
path = path.replace(s, "-");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link File#listFiles()}, but returns a sorted set.
|
||||
* Never returns {@code null}, instead a warning is logged, and an empty set is returned.
|
||||
*/
|
||||
public static SortedSet<File> listFiles(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
Log.w(TAG, "Failed to list children for " + dir.getPath());
|
||||
return new TreeSet<File>();
|
||||
}
|
||||
|
||||
return new TreeSet<File>(Arrays.asList(files));
|
||||
}
|
||||
|
||||
public static SortedSet<File> listMusicFiles(File dir) {
|
||||
SortedSet<File> files = listFiles(dir);
|
||||
Iterator<File> iterator = files.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
File file = iterator.next();
|
||||
if (!file.isDirectory() && !isMusicFile(file)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static boolean isMusicFile(File file) {
|
||||
String extension = getExtension(file.getName());
|
||||
return MUSIC_FILE_EXTENSIONS.contains(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension (the substring after the last dot) of the given file. The dot
|
||||
* is not included in the returned extension.
|
||||
*
|
||||
* @param name The filename in question.
|
||||
* @return The extension, or an empty string if no extension is found.
|
||||
*/
|
||||
public static String getExtension(String name) {
|
||||
int index = name.lastIndexOf('.');
|
||||
return index == -1 ? "" : name.substring(index + 1).toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base name (the substring before the last dot) of the given file. The dot
|
||||
* is not included in the returned basename.
|
||||
*
|
||||
* @param name The filename in question.
|
||||
* @return The base name, or an empty string if no basename is found.
|
||||
*/
|
||||
public static String getBaseName(String name) {
|
||||
int index = name.lastIndexOf('.');
|
||||
return index == -1 ? name : name.substring(0, index);
|
||||
}
|
||||
|
||||
public static <T extends Serializable> boolean serialize(Context context, T obj, String fileName) {
|
||||
File file = new File(context.getCacheDir(), fileName);
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
out = new ObjectOutputStream(new FileOutputStream(file));
|
||||
out.writeObject(obj);
|
||||
Log.i(TAG, "Serialized object to " + file);
|
||||
return true;
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to serialize object to " + file);
|
||||
return false;
|
||||
} finally {
|
||||
Util.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Serializable> T deserialize(Context context, String fileName) {
|
||||
File file = new File(context.getCacheDir(), fileName);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ObjectInputStream in = null;
|
||||
try {
|
||||
in = new ObjectInputStream(new FileInputStream(file));
|
||||
T result = (T)in.readObject();
|
||||
Log.i(TAG, "Deserialized object from " + file);
|
||||
return result;
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to deserialize object from " + file, x);
|
||||
return null;
|
||||
} finally {
|
||||
Util.close(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,247 +1,249 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.activity.DownloadActivity;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* Asynchronous loading of images, with caching.
|
||||
* <p/>
|
||||
* There should normally be only one instance of this class.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class ImageLoader implements Runnable {
|
||||
|
||||
private static final String TAG = ImageLoader.class.getSimpleName();
|
||||
private static final int CONCURRENCY = 5;
|
||||
|
||||
private final LRUCache<String, Drawable> cache = new LRUCache<String, Drawable>(100);
|
||||
private final BlockingQueue<Task> queue;
|
||||
private final int imageSizeDefault;
|
||||
private final int imageSizeLarge;
|
||||
private Drawable largeUnknownImage;
|
||||
private Drawable drawable;
|
||||
|
||||
public ImageLoader(Context context) {
|
||||
queue = new LinkedBlockingQueue<Task>(500);
|
||||
|
||||
// Determine the density-dependent image sizes.
|
||||
imageSizeDefault = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
|
||||
|
||||
for (int i = 0; i < CONCURRENCY; i++) {
|
||||
new Thread(this, "ImageLoader").start();
|
||||
}
|
||||
|
||||
createLargeUnknownImage(context);
|
||||
}
|
||||
|
||||
private void createLargeUnknownImage(Context context) {
|
||||
BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.unknown_album_large);
|
||||
Bitmap bitmap = Bitmap.createScaledBitmap(drawable.getBitmap(), imageSizeLarge, imageSizeLarge, true);
|
||||
//bitmap = createReflection(bitmap);
|
||||
largeUnknownImage = Util.createDrawableFromBitmap(context, bitmap);
|
||||
}
|
||||
|
||||
public void loadImage(View view, MusicDirectory.Entry entry, boolean large, boolean crossfade) {
|
||||
if (entry == null || entry.getCoverArt() == null) {
|
||||
setUnknownImage(view, large);
|
||||
return;
|
||||
}
|
||||
|
||||
int size = large ? imageSizeLarge : imageSizeDefault;
|
||||
Drawable drawable = cache.get(getKey(entry.getCoverArt(), size));
|
||||
if (drawable != null) {
|
||||
setImage(view, drawable, large);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!large) {
|
||||
setUnknownImage(view, large);
|
||||
}
|
||||
queue.offer(new Task(view, entry, size, large, large, crossfade));
|
||||
}
|
||||
|
||||
public void setActionBarArtwork(final View view, final MusicDirectory.Entry entry, final ActionBar ab) {
|
||||
if (entry == null || entry.getCoverArt() == null) {
|
||||
ab.setLogo(largeUnknownImage);
|
||||
}
|
||||
|
||||
final int size = imageSizeLarge;
|
||||
drawable = cache.get(getKey(entry.getCoverArt(), size));
|
||||
|
||||
if (drawable != null) {
|
||||
ab.setLogo(drawable);
|
||||
}
|
||||
|
||||
final Handler handler = new Handler(){
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
drawable = (Drawable) msg.obj;
|
||||
ab.setLogo(drawable);
|
||||
}
|
||||
};
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
||||
|
||||
try
|
||||
{
|
||||
Bitmap bitmap = musicService.getCoverArt(view.getContext(), entry, size, true, null);
|
||||
drawable = Util.createDrawableFromBitmap(view.getContext(), bitmap);
|
||||
Message msg = Message.obtain();
|
||||
msg.obj = drawable;
|
||||
handler.sendMessage(msg);
|
||||
cache.put(getKey(entry.getCoverArt(), size), drawable);
|
||||
} catch (Throwable x) {
|
||||
Log.e(TAG, "Failed to download album art.", x);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String getKey(String coverArtId, int size) {
|
||||
return coverArtId + size;
|
||||
}
|
||||
|
||||
private void setImage(View view, Drawable drawable, boolean crossfade) {
|
||||
if (view instanceof TextView) {
|
||||
// Cross-fading is not implemented for TextView since it's not in use. It would be easy to add it, though.
|
||||
TextView textView = (TextView) view;
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
|
||||
} else if (view instanceof ImageView) {
|
||||
ImageView imageView = (ImageView) view;
|
||||
if (crossfade) {
|
||||
|
||||
Drawable existingDrawable = imageView.getDrawable();
|
||||
if (existingDrawable == null) {
|
||||
Bitmap emptyImage = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
existingDrawable = new BitmapDrawable(emptyImage);
|
||||
}
|
||||
|
||||
Drawable[] layers = new Drawable[]{existingDrawable, drawable};
|
||||
|
||||
TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
|
||||
imageView.setImageDrawable(transitionDrawable);
|
||||
transitionDrawable.startTransition(250);
|
||||
} else {
|
||||
imageView.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setUnknownImage(View view, boolean large) {
|
||||
if (large) {
|
||||
setImage(view, largeUnknownImage, false);
|
||||
} else {
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setCompoundDrawablesWithIntrinsicBounds(R.drawable.unknown_album, 0, 0, 0);
|
||||
} else if (view instanceof ImageView) {
|
||||
((ImageView) view).setImageResource(R.drawable.unknown_album);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Task task = queue.take();
|
||||
task.execute();
|
||||
} catch (Throwable x) {
|
||||
Log.e(TAG, "Unexpected exception in ImageLoader.", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Task {
|
||||
private final View view;
|
||||
private final MusicDirectory.Entry entry;
|
||||
private final Handler handler;
|
||||
private final int size;
|
||||
private final boolean reflection;
|
||||
private final boolean saveToFile;
|
||||
private final boolean crossfade;
|
||||
|
||||
public Task(View view, MusicDirectory.Entry entry, int size, boolean reflection, boolean saveToFile, boolean crossfade) {
|
||||
this.view = view;
|
||||
this.entry = entry;
|
||||
this.size = size;
|
||||
this.reflection = reflection;
|
||||
this.saveToFile = saveToFile;
|
||||
this.crossfade = crossfade;
|
||||
handler = new Handler();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
try {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
||||
Bitmap bitmap = musicService.getCoverArt(view.getContext(), entry, size, saveToFile, null);
|
||||
|
||||
if (reflection) {
|
||||
//bitmap = createReflection(bitmap);
|
||||
}
|
||||
|
||||
final Drawable drawable = Util.createDrawableFromBitmap(view.getContext(), bitmap);
|
||||
cache.put(getKey(entry.getCoverArt(), size), drawable);
|
||||
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setImage(view, drawable, crossfade);
|
||||
}
|
||||
});
|
||||
} catch (Throwable x) {
|
||||
Log.e(TAG, "Failed to download album art.", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.activity.DownloadActivity;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* Asynchronous loading of images, with caching.
|
||||
* <p/>
|
||||
* There should normally be only one instance of this class.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class ImageLoader implements Runnable {
|
||||
|
||||
private static final String TAG = ImageLoader.class.getSimpleName();
|
||||
private static final int CONCURRENCY = 5;
|
||||
|
||||
private final LRUCache<String, Drawable> cache = new LRUCache<String, Drawable>(100);
|
||||
private final BlockingQueue<Task> queue;
|
||||
private final int imageSizeDefault;
|
||||
private final int imageSizeLarge;
|
||||
private Drawable largeUnknownImage;
|
||||
private Drawable drawable;
|
||||
|
||||
public ImageLoader(Context context) {
|
||||
queue = new LinkedBlockingQueue<Task>(500);
|
||||
|
||||
// Determine the density-dependent image sizes.
|
||||
imageSizeDefault = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
|
||||
|
||||
for (int i = 0; i < CONCURRENCY; i++) {
|
||||
new Thread(this, "ImageLoader").start();
|
||||
}
|
||||
|
||||
createLargeUnknownImage(context);
|
||||
}
|
||||
|
||||
private void createLargeUnknownImage(Context context) {
|
||||
BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.unknown_album_large);
|
||||
Log.i(TAG, "createLargeUnknownImage");
|
||||
Bitmap bitmap = Bitmap.createScaledBitmap(drawable.getBitmap(), imageSizeLarge, imageSizeLarge, true);
|
||||
|
||||
//bitmap = createReflection(bitmap);
|
||||
largeUnknownImage = Util.createDrawableFromBitmap(context, bitmap);
|
||||
}
|
||||
|
||||
public void loadImage(View view, MusicDirectory.Entry entry, boolean large, boolean crossfade) {
|
||||
if (entry == null || entry.getCoverArt() == null) {
|
||||
setUnknownImage(view, large);
|
||||
return;
|
||||
}
|
||||
|
||||
int size = large ? imageSizeLarge : imageSizeDefault;
|
||||
Drawable drawable = cache.get(getKey(entry.getCoverArt(), size));
|
||||
if (drawable != null) {
|
||||
setImage(view, drawable, large);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!large) {
|
||||
setUnknownImage(view, large);
|
||||
}
|
||||
queue.offer(new Task(view, entry, size, large, large, crossfade));
|
||||
}
|
||||
|
||||
public void setActionBarArtwork(final View view, final MusicDirectory.Entry entry, final ActionBar ab) {
|
||||
if (entry == null || entry.getCoverArt() == null) {
|
||||
ab.setLogo(largeUnknownImage);
|
||||
}
|
||||
|
||||
final int size = imageSizeLarge;
|
||||
drawable = cache.get(getKey(entry.getCoverArt(), size));
|
||||
|
||||
if (drawable != null) {
|
||||
ab.setLogo(drawable);
|
||||
}
|
||||
|
||||
final Handler handler = new Handler(){
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
drawable = (Drawable) msg.obj;
|
||||
ab.setLogo(drawable);
|
||||
}
|
||||
};
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
||||
|
||||
try
|
||||
{
|
||||
Bitmap bitmap = musicService.getCoverArt(view.getContext(), entry, size, true, null);
|
||||
drawable = Util.createDrawableFromBitmap(view.getContext(), bitmap);
|
||||
Message msg = Message.obtain();
|
||||
msg.obj = drawable;
|
||||
handler.sendMessage(msg);
|
||||
cache.put(getKey(entry.getCoverArt(), size), drawable);
|
||||
} catch (Throwable x) {
|
||||
Log.e(TAG, "Failed to download album art.", x);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String getKey(String coverArtId, int size) {
|
||||
return coverArtId + size;
|
||||
}
|
||||
|
||||
private void setImage(View view, Drawable drawable, boolean crossfade) {
|
||||
if (view instanceof TextView) {
|
||||
// Cross-fading is not implemented for TextView since it's not in use. It would be easy to add it, though.
|
||||
TextView textView = (TextView) view;
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
|
||||
} else if (view instanceof ImageView) {
|
||||
ImageView imageView = (ImageView) view;
|
||||
if (crossfade) {
|
||||
|
||||
Drawable existingDrawable = imageView.getDrawable();
|
||||
if (existingDrawable == null) {
|
||||
Bitmap emptyImage = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
existingDrawable = new BitmapDrawable(emptyImage);
|
||||
}
|
||||
|
||||
Drawable[] layers = new Drawable[]{existingDrawable, drawable};
|
||||
|
||||
TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
|
||||
imageView.setImageDrawable(transitionDrawable);
|
||||
transitionDrawable.startTransition(250);
|
||||
} else {
|
||||
imageView.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setUnknownImage(View view, boolean large) {
|
||||
if (large) {
|
||||
setImage(view, largeUnknownImage, false);
|
||||
} else {
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setCompoundDrawablesWithIntrinsicBounds(R.drawable.unknown_album, 0, 0, 0);
|
||||
} else if (view instanceof ImageView) {
|
||||
((ImageView) view).setImageResource(R.drawable.unknown_album);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Task task = queue.take();
|
||||
task.execute();
|
||||
} catch (Throwable x) {
|
||||
Log.e(TAG, "Unexpected exception in ImageLoader.", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Task {
|
||||
private final View view;
|
||||
private final MusicDirectory.Entry entry;
|
||||
private final Handler handler;
|
||||
private final int size;
|
||||
private final boolean reflection;
|
||||
private final boolean saveToFile;
|
||||
private final boolean crossfade;
|
||||
|
||||
public Task(View view, MusicDirectory.Entry entry, int size, boolean reflection, boolean saveToFile, boolean crossfade) {
|
||||
this.view = view;
|
||||
this.entry = entry;
|
||||
this.size = size;
|
||||
this.reflection = reflection;
|
||||
this.saveToFile = saveToFile;
|
||||
this.crossfade = crossfade;
|
||||
handler = new Handler();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
try {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
||||
Bitmap bitmap = musicService.getCoverArt(view.getContext(), entry, size, saveToFile, null);
|
||||
|
||||
if (reflection) {
|
||||
//bitmap = createReflection(bitmap);
|
||||
}
|
||||
|
||||
final Drawable drawable = Util.createDrawableFromBitmap(view.getContext(), bitmap);
|
||||
cache.put(getKey(entry.getCoverArt(), size), drawable);
|
||||
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setImage(view, drawable, crossfade);
|
||||
}
|
||||
});
|
||||
} catch (Throwable x) {
|
||||
Log.e(TAG, "Failed to download album art.", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,224 +1,224 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadService;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadFile;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Used to display songs in a {@code ListView}.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class SongView extends LinearLayout implements Checkable {
|
||||
|
||||
private static final String TAG = SongView.class.getSimpleName();
|
||||
private static final WeakHashMap<SongView, ?> INSTANCES = new WeakHashMap<SongView, Object>();
|
||||
private static Handler handler;
|
||||
|
||||
private CheckedTextView checkedTextView;
|
||||
private ImageView starImageView;
|
||||
private TextView titleTextView;
|
||||
private TextView artistTextView;
|
||||
private TextView durationTextView;
|
||||
private TextView statusTextView;
|
||||
private MusicDirectory.Entry song;
|
||||
|
||||
public SongView(Context context) {
|
||||
super(context);
|
||||
LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true);
|
||||
|
||||
checkedTextView = (CheckedTextView) findViewById(R.id.song_check);
|
||||
starImageView = (ImageView) findViewById(R.id.song_star);
|
||||
titleTextView = (TextView) findViewById(R.id.song_title);
|
||||
artistTextView = (TextView) findViewById(R.id.song_artist);
|
||||
durationTextView = (TextView) findViewById(R.id.song_duration);
|
||||
statusTextView = (TextView) findViewById(R.id.song_status);
|
||||
|
||||
INSTANCES.put(this, null);
|
||||
int instanceCount = INSTANCES.size();
|
||||
|
||||
if (instanceCount > 50) {
|
||||
Log.w(TAG, instanceCount + " live SongView instances");
|
||||
}
|
||||
|
||||
startUpdater();
|
||||
}
|
||||
|
||||
public void setSong(final MusicDirectory.Entry song, boolean checkable) {
|
||||
this.song = song;
|
||||
StringBuilder artist = new StringBuilder(40);
|
||||
|
||||
String bitRate = null;
|
||||
if (song.getBitRate() != null) {
|
||||
bitRate = String.format(getContext().getString(R.string.song_details_kbps), song.getBitRate());
|
||||
}
|
||||
|
||||
String fileFormat = null;
|
||||
if (song.getTranscodedSuffix() != null && !song.getTranscodedSuffix().equals(song.getSuffix())) {
|
||||
fileFormat = String.format("%s > %s", song.getSuffix(), song.getTranscodedSuffix());
|
||||
} else {
|
||||
fileFormat = song.getSuffix();
|
||||
}
|
||||
|
||||
artist.append(song.getArtist()).append(" (")
|
||||
.append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat))
|
||||
.append(")");
|
||||
|
||||
titleTextView.setText(song.getTitle());
|
||||
artistTextView.setText(artist);
|
||||
durationTextView.setText(Util.formatDuration(song.getDuration()));
|
||||
starImageView.setImageDrawable(song.getStarred() ? getResources().getDrawable(R.drawable.star) : getResources().getDrawable(R.drawable.star_hollow));
|
||||
checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE);
|
||||
|
||||
starImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final boolean isStarred = song.getStarred();
|
||||
final String id = song.getId();
|
||||
|
||||
if (!isStarred) {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star));
|
||||
song.setStarred(true);
|
||||
} else {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow));
|
||||
song.setStarred(false);
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(null);
|
||||
|
||||
try {
|
||||
if (!isStarred) {
|
||||
musicService.star(id, getContext(), null);
|
||||
} else {
|
||||
musicService.unstar(id, getContext(), null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
if (downloadService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadFile downloadFile = downloadService.forSong(song);
|
||||
File completeFile = downloadFile.getCompleteFile();
|
||||
File partialFile = downloadFile.getPartialFile();
|
||||
|
||||
int leftImage = 0;
|
||||
int rightImage = 0;
|
||||
|
||||
if (completeFile.exists()) {
|
||||
leftImage = downloadFile.isSaved() ? R.drawable.ic_stat_saved : R.drawable.ic_stat_downloaded;
|
||||
}
|
||||
|
||||
if (downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && partialFile.exists()) {
|
||||
statusTextView.setText(Util.formatLocalizedBytes(partialFile.length(), getContext()));
|
||||
rightImage = R.drawable.ic_stat_downloading;
|
||||
} else {
|
||||
statusTextView.setText(null);
|
||||
}
|
||||
statusTextView.setCompoundDrawablesWithIntrinsicBounds(leftImage, 0, rightImage, 0);
|
||||
|
||||
if (!song.getStarred()) {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow));
|
||||
} else {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star));
|
||||
}
|
||||
|
||||
boolean playing = downloadService.getCurrentPlaying() == downloadFile;
|
||||
if (playing) {
|
||||
titleTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stat_play, 0, 0, 0);
|
||||
} else {
|
||||
titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void startUpdater() {
|
||||
if (handler != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler = new Handler();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateAll();
|
||||
handler.postDelayed(this, 1000L);
|
||||
}
|
||||
};
|
||||
handler.postDelayed(runnable, 1000L);
|
||||
}
|
||||
|
||||
private static void updateAll() {
|
||||
try {
|
||||
for (SongView view : INSTANCES.keySet()) {
|
||||
if (view.isShown()) {
|
||||
view.update();
|
||||
}
|
||||
}
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Error when updating song views.", x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean b) {
|
||||
checkedTextView.setChecked(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return checkedTextView.isChecked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle() {
|
||||
checkedTextView.toggle();
|
||||
}
|
||||
}
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package net.sourceforge.subsonic.androidapp.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import net.sourceforge.subsonic.androidapp.R;
|
||||
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadService;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl;
|
||||
import net.sourceforge.subsonic.androidapp.service.DownloadFile;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Used to display songs in a {@code ListView}.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class SongView extends LinearLayout implements Checkable {
|
||||
|
||||
private static final String TAG = SongView.class.getSimpleName();
|
||||
private static final WeakHashMap<SongView, ?> INSTANCES = new WeakHashMap<SongView, Object>();
|
||||
private static Handler handler;
|
||||
|
||||
private CheckedTextView checkedTextView;
|
||||
private ImageView starImageView;
|
||||
private TextView titleTextView;
|
||||
private TextView artistTextView;
|
||||
private TextView durationTextView;
|
||||
private TextView statusTextView;
|
||||
private MusicDirectory.Entry song;
|
||||
|
||||
public SongView(Context context) {
|
||||
super(context);
|
||||
LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true);
|
||||
|
||||
checkedTextView = (CheckedTextView) findViewById(R.id.song_check);
|
||||
starImageView = (ImageView) findViewById(R.id.song_star);
|
||||
titleTextView = (TextView) findViewById(R.id.song_title);
|
||||
artistTextView = (TextView) findViewById(R.id.song_artist);
|
||||
durationTextView = (TextView) findViewById(R.id.song_duration);
|
||||
statusTextView = (TextView) findViewById(R.id.song_status);
|
||||
|
||||
INSTANCES.put(this, null);
|
||||
int instanceCount = INSTANCES.size();
|
||||
|
||||
if (instanceCount > 50) {
|
||||
Log.w(TAG, instanceCount + " live SongView instances");
|
||||
}
|
||||
|
||||
startUpdater();
|
||||
}
|
||||
|
||||
public void setSong(final MusicDirectory.Entry song, boolean checkable) {
|
||||
this.song = song;
|
||||
StringBuilder artist = new StringBuilder(40);
|
||||
|
||||
String bitRate = null;
|
||||
if (song.getBitRate() != null) {
|
||||
bitRate = String.format(getContext().getString(R.string.song_details_kbps), song.getBitRate());
|
||||
}
|
||||
|
||||
String fileFormat = null;
|
||||
if (song.getTranscodedSuffix() != null && !song.getTranscodedSuffix().equals(song.getSuffix())) {
|
||||
fileFormat = String.format("%s > %s", song.getSuffix(), song.getTranscodedSuffix());
|
||||
} else {
|
||||
fileFormat = song.getSuffix();
|
||||
}
|
||||
|
||||
artist.append(song.getArtist()).append(" (")
|
||||
.append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat))
|
||||
.append(")");
|
||||
|
||||
titleTextView.setText(song.getTitle());
|
||||
artistTextView.setText(artist);
|
||||
durationTextView.setText(Util.formatDuration(song.getDuration()));
|
||||
starImageView.setImageDrawable(song.getStarred() ? getResources().getDrawable(R.drawable.star) : getResources().getDrawable(R.drawable.star_hollow));
|
||||
checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE);
|
||||
|
||||
starImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final boolean isStarred = song.getStarred();
|
||||
final String id = song.getId();
|
||||
|
||||
if (!isStarred) {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star));
|
||||
song.setStarred(true);
|
||||
} else {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow));
|
||||
song.setStarred(false);
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(null);
|
||||
|
||||
try {
|
||||
if (!isStarred) {
|
||||
musicService.star(id, getContext(), null);
|
||||
} else {
|
||||
musicService.unstar(id, getContext(), null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
if (downloadService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadFile downloadFile = downloadService.forSong(song);
|
||||
File completeFile = downloadFile.getCompleteFile();
|
||||
File partialFile = downloadFile.getPartialFile();
|
||||
|
||||
int leftImage = 0;
|
||||
int rightImage = 0;
|
||||
|
||||
if (completeFile.exists()) {
|
||||
leftImage = downloadFile.isSaved() ? R.drawable.ic_stat_saved : R.drawable.ic_stat_downloaded;
|
||||
}
|
||||
|
||||
if (downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && partialFile.exists()) {
|
||||
statusTextView.setText(Util.formatLocalizedBytes(partialFile.length(), getContext()));
|
||||
rightImage = R.drawable.ic_stat_downloading;
|
||||
} else {
|
||||
statusTextView.setText(null);
|
||||
}
|
||||
statusTextView.setCompoundDrawablesWithIntrinsicBounds(leftImage, 0, rightImage, 0);
|
||||
|
||||
if (!song.getStarred()) {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star_hollow));
|
||||
} else {
|
||||
starImageView.setImageDrawable(getResources().getDrawable(R.drawable.star));
|
||||
}
|
||||
|
||||
boolean playing = downloadService.getCurrentPlaying() == downloadFile;
|
||||
if (playing) {
|
||||
titleTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_stat_play, 0, 0, 0);
|
||||
} else {
|
||||
titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void startUpdater() {
|
||||
if (handler != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler = new Handler();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateAll();
|
||||
handler.postDelayed(this, 1000L);
|
||||
}
|
||||
};
|
||||
handler.postDelayed(runnable, 1000L);
|
||||
}
|
||||
|
||||
private static void updateAll() {
|
||||
try {
|
||||
for (SongView view : INSTANCES.keySet()) {
|
||||
if (view.isShown()) {
|
||||
view.update();
|
||||
}
|
||||
}
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Error when updating song views.", x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean b) {
|
||||
checkedTextView.setChecked(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return checkedTextView.isChecked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle() {
|
||||
checkedTextView.toggle();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user