Add setting to show lockscreen music controls, fix Jukebox menu item, show album art in Album Track view

This commit is contained in:
Joshua Bahnsen 2013-02-02 11:43:52 -07:00
parent c58d1acf93
commit b747bb28f9
10 changed files with 143 additions and 131 deletions

View File

@ -217,6 +217,8 @@
<string name="settings.network_timeout_120000">120 seconds</string> <string name="settings.network_timeout_120000">120 seconds</string>
<string name="settings.show_notification">Show Notification</string> <string name="settings.show_notification">Show Notification</string>
<string name="settings.show_notification_summary">Show now playing notification in the status bar</string> <string name="settings.show_notification_summary">Show now playing notification in the status bar</string>
<string name="settings.show_lockscreen_controls">Show Lock Screen Controls</string>
<string name="settings.show_lockscreen_controls_summary">Show playback controls on the lock screen</string>
<string name="settings.max_albums">Max Albums</string> <string name="settings.max_albums">Max Albums</string>
<string name="settings.max_albums_5">5</string> <string name="settings.max_albums_5">5</string>
<string name="settings.max_albums_10">10</string> <string name="settings.max_albums_10">10</string>

View File

@ -261,6 +261,12 @@
a:summary="@string/settings.show_notification_summary" a:summary="@string/settings.show_notification_summary"
a:key="showNotification" a:key="showNotification"
a:defaultValue="true"/> a:defaultValue="true"/>
<CheckBoxPreference
a:title="@string/settings.show_lockscreen_controls"
a:summary="@string/settings.show_lockscreen_controls_summary"
a:key="showLockScreen"
a:defaultValue="true"/>
</PreferenceCategory> </PreferenceCategory>

View File

@ -98,7 +98,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
private ImageButton repeatButton; private ImageButton repeatButton;
private MenuItem equalizerMenuItem; private MenuItem equalizerMenuItem;
private MenuItem visualizerMenuItem; private MenuItem visualizerMenuItem;
private MenuItem jukeboxMenuItem;
private View toggleListButton; private View toggleListButton;
private ScheduledExecutorService executorService; private ScheduledExecutorService executorService;
private DownloadFile currentPlaying; private DownloadFile currentPlaying;
@ -276,6 +275,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
onProgressChanged(); onProgressChanged();
} }
}); });
playlistView.setOnTouchListener(gestureListener); playlistView.setOnTouchListener(gestureListener);
registerForContextMenu(playlistView); registerForContextMenu(playlistView);
@ -439,7 +439,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
MenuItem screenOption = menu.findItem(R.id.menu_screen_on_off); MenuItem screenOption = menu.findItem(R.id.menu_screen_on_off);
equalizerMenuItem = menu.findItem(R.id.download_equalizer); equalizerMenuItem = menu.findItem(R.id.download_equalizer);
visualizerMenuItem = menu.findItem(R.id.download_visualizer); visualizerMenuItem = menu.findItem(R.id.download_visualizer);
jukeboxMenuItem = menu.findItem(R.id.download_jukebox);
equalizerMenuItem.setEnabled(equalizerAvailable); equalizerMenuItem.setEnabled(equalizerAvailable);
equalizerMenuItem.setVisible(equalizerAvailable); equalizerMenuItem.setVisible(equalizerAvailable);
@ -536,13 +535,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
boolean active = !visualizerView.isActive(); boolean active = !visualizerView.isActive();
visualizerView.setActive(active); visualizerView.setActive(active);
getDownloadService().setShowVisualization(visualizerView.isActive()); getDownloadService().setShowVisualization(visualizerView.isActive());
//updateButtons();
Util.toast(DownloadActivity.this, active ? R.string.download_visualizer_on : R.string.download_visualizer_off); Util.toast(DownloadActivity.this, active ? R.string.download_visualizer_on : R.string.download_visualizer_off);
return true; return true;
case R.id.download_jukebox: case R.id.download_jukebox:
boolean jukeboxEnabled = !getDownloadService().isJukeboxEnabled(); boolean jukeboxEnabled = !getDownloadService().isJukeboxEnabled();
getDownloadService().setJukeboxEnabled(jukeboxEnabled); getDownloadService().setJukeboxEnabled(jukeboxEnabled);
//updateButtons();
Util.toast(DownloadActivity.this, jukeboxEnabled ? R.string.download_jukebox_on : R.string.download_jukebox_off, false); Util.toast(DownloadActivity.this, jukeboxEnabled ? R.string.download_jukebox_on : R.string.download_jukebox_off, false);
return true; return true;
default: default:
@ -742,9 +739,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
startButton.setVisibility(View.VISIBLE); startButton.setVisibility(View.VISIBLE);
break; break;
} }
if (jukeboxMenuItem != null)
jukeboxMenuItem.setEnabled(getDownloadService().isJukeboxEnabled());
} }
private class SongListAdapter extends ArrayAdapter<DownloadFile> { private class SongListAdapter extends ArrayAdapter<DownloadFile> {

View File

@ -18,9 +18,11 @@
*/ */
package net.sourceforge.subsonic.androidapp.activity; package net.sourceforge.subsonic.androidapp.activity;
import android.app.ActionBar;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@ -606,6 +608,12 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
} }
if (songCount > 0) { if (songCount > 0) {
ActionBar actionBar = getActionBar();
if (actionBar != null) {
getImageLoader().setActionBarArtwork(selectButton, entries.get(0), actionBar);
}
entryList.addFooterView(footer); entryList.addFooterView(footer);
selectButton.setVisibility(View.VISIBLE); selectButton.setVisibility(View.VISIBLE);
playNowButton.setVisibility(View.VISIBLE); playNowButton.setVisibility(View.VISIBLE);

View File

@ -741,7 +741,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
} }
private void setRemoteControl() { private void setRemoteControl() {
if (Util.getMediaButtonsPreference(this)) { if (Util.isLockScreenEnabled(this)) {
AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(_afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); audioManager.requestAudioFocus(_afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

View File

@ -38,12 +38,12 @@ public class MusicDirectoryEntryParser extends AbstractParser {
entry.setDirectory(getBoolean("isDir")); entry.setDirectory(getBoolean("isDir"));
entry.setCoverArt(get("coverArt")); entry.setCoverArt(get("coverArt"));
entry.setArtist(get("artist")); entry.setArtist(get("artist"));
entry.setYear(getInteger("year"));
entry.setStarred(getValueExists("starred")); entry.setStarred(getValueExists("starred"));
if (!entry.isDirectory()) { if (!entry.isDirectory()) {
entry.setAlbum(get("album")); entry.setAlbum(get("album"));
entry.setTrack(getInteger("track")); entry.setTrack(getInteger("track"));
entry.setYear(getInteger("year"));
entry.setGenre(get("genre")); entry.setGenre(get("genre"));
entry.setContentType(get("contentType")); entry.setContentType(get("contentType"));
entry.setSuffix(get("suffix")); entry.setSuffix(get("suffix"));

View File

@ -78,6 +78,7 @@ public final class Constants {
public static final String PREFERENCES_KEY_BUFFER_LENGTH = "bufferLength"; public static final String PREFERENCES_KEY_BUFFER_LENGTH = "bufferLength";
public static final String PREFERENCES_KEY_NETWORK_TIMEOUT = "networkTimeout"; public static final String PREFERENCES_KEY_NETWORK_TIMEOUT = "networkTimeout";
public static final String PREFERENCES_KEY_SHOW_NOTIFICATION = "showNotification"; public static final String PREFERENCES_KEY_SHOW_NOTIFICATION = "showNotification";
public static final String PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS = "showLockScreen";
public static final String PREFERENCES_KEY_MAX_ALBUMS = "maxAlbums"; public static final String PREFERENCES_KEY_MAX_ALBUMS = "maxAlbums";
public static final String PREFERENCES_KEY_MAX_SONGS = "maxSongs"; public static final String PREFERENCES_KEY_MAX_SONGS = "maxSongs";
public static final String PREFERENCES_KEY_MAX_ARTISTS = "maxArtists"; public static final String PREFERENCES_KEY_MAX_ARTISTS = "maxArtists";

View File

@ -1,71 +1,74 @@
/* /*
This file is part of Subsonic. This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Subsonic is distributed in the hope that it will be useful, Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>. along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus Copyright 2010 (C) Sindre Mehus
*/ */
package net.sourceforge.subsonic.androidapp.util; package net.sourceforge.subsonic.androidapp.util;
import java.util.List; import java.util.List;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import net.sourceforge.subsonic.androidapp.activity.SubsonicTabActivity; import net.sourceforge.subsonic.androidapp.activity.SubsonicTabActivity;
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
/** /**
* @author Sindre Mehus * @author Sindre Mehus
*/ */
public class EntryAdapter extends ArrayAdapter<MusicDirectory.Entry> { public class EntryAdapter extends ArrayAdapter<MusicDirectory.Entry> {
private final SubsonicTabActivity activity; private final SubsonicTabActivity activity;
private final ImageLoader imageLoader; private final ImageLoader imageLoader;
private final boolean checkable; private final boolean checkable;
public EntryAdapter(SubsonicTabActivity activity, ImageLoader imageLoader, List<MusicDirectory.Entry> entries, boolean checkable) { public EntryAdapter(SubsonicTabActivity activity, ImageLoader imageLoader, List<MusicDirectory.Entry> entries, boolean checkable) {
super(activity, android.R.layout.simple_list_item_1, entries); super(activity, android.R.layout.simple_list_item_1, entries);
this.activity = activity; this.activity = activity;
this.imageLoader = imageLoader; this.imageLoader = imageLoader;
this.checkable = checkable; this.checkable = checkable;
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
MusicDirectory.Entry entry = getItem(position); MusicDirectory.Entry entry = getItem(position);
if (entry.isDirectory()) { if (entry.isDirectory()) {
AlbumView view; AlbumView view;
// TODO: Reuse AlbumView objects once cover art loading is working.
// if (convertView != null && convertView instanceof AlbumView) { if (convertView != null && convertView instanceof AlbumView) {
// view = (AlbumView) convertView; view = (AlbumView) convertView;
// } else { } else {
view = new AlbumView(activity); view = new AlbumView(activity);
// } }
view.setAlbum(entry, imageLoader);
return view; view.setAlbum(entry, imageLoader);
return view;
} else {
SongView view; } else {
if (convertView != null && convertView instanceof SongView) { SongView view;
view = (SongView) convertView;
} else { if (convertView != null && convertView instanceof SongView) {
view = new SongView(activity); view = (SongView) convertView;
} } else {
view.setSong(entry, checkable); view = new SongView(activity);
return view; }
}
} view.setSong(entry, checkable);
} return view;
}
}
}

View File

@ -18,6 +18,7 @@
*/ */
package net.sourceforge.subsonic.androidapp.util; package net.sourceforge.subsonic.androidapp.util;
import android.app.ActionBar;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -29,6 +30,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable; import android.graphics.drawable.TransitionDrawable;
import android.os.Handler; import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@ -60,6 +62,7 @@ public class ImageLoader implements Runnable {
private final int imageSizeDefault; private final int imageSizeDefault;
private final int imageSizeLarge; private final int imageSizeLarge;
private Drawable largeUnknownImage; private Drawable largeUnknownImage;
private Drawable drawable;
public ImageLoader(Context context) { public ImageLoader(Context context) {
queue = new LinkedBlockingQueue<Task>(500); queue = new LinkedBlockingQueue<Task>(500);
@ -67,7 +70,6 @@ public class ImageLoader implements Runnable {
// Determine the density-dependent image sizes. // Determine the density-dependent image sizes.
imageSizeDefault = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight(); imageSizeDefault = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
DisplayMetrics metrics = context.getResources().getDisplayMetrics(); DisplayMetrics metrics = context.getResources().getDisplayMetrics();
//imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels) * 0.6);
imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels)); imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
for (int i = 0; i < CONCURRENCY; i++) { for (int i = 0; i < CONCURRENCY; i++) {
@ -102,7 +104,46 @@ public class ImageLoader implements Runnable {
} }
queue.offer(new Task(view, entry, size, large, large, crossfade)); 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) { private String getKey(String coverArtId, int size) {
return coverArtId + size; return coverArtId + size;
} }
@ -161,54 +202,6 @@ public class ImageLoader implements Runnable {
} }
} }
private Bitmap createReflection(Bitmap originalImage) {
int width = originalImage.getWidth();
int height = originalImage.getHeight();
// The gap we want between the reflection and the original image
final int reflectionGap = 4;
// This will not scale but will flip on the Y axis
Matrix matrix = new Matrix();
matrix.preScale(1, -1);
// Create a Bitmap with the flip matix applied to it.
// We only want the bottom half of the image
Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false);
// Create a new bitmap with same width but taller to fit reflection
Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height / 2), Bitmap.Config.ARGB_8888);
// Create a new Canvas with the bitmap that's big enough for
// the image plus gap plus reflection
Canvas canvas = new Canvas(bitmapWithReflection);
// Draw in the original image
canvas.drawBitmap(originalImage, 0, 0, null);
// Draw in the gap
Paint defaultPaint = new Paint();
canvas.drawRect(0, height, width, height + reflectionGap, defaultPaint);
// Draw in the reflection
canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
// Create a shader that is a linear gradient that covers the reflection
Paint paint = new Paint();
LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0,
bitmapWithReflection.getHeight() + reflectionGap, 0x70000000, 0xff000000,
Shader.TileMode.CLAMP);
// Set the paint to use this shader (linear gradient)
paint.setShader(shader);
// Draw a rectangle using the paint with our linear gradient
canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint);
return bitmapWithReflection;
}
private class Task { private class Task {
private final View view; private final View view;
private final MusicDirectory.Entry entry; private final MusicDirectory.Entry entry;
@ -227,7 +220,7 @@ public class ImageLoader implements Runnable {
this.crossfade = crossfade; this.crossfade = crossfade;
handler = new Handler(); handler = new Handler();
} }
public void execute() { public void execute() {
try { try {
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext()); MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());

View File

@ -139,6 +139,11 @@ public class Util extends DownloadActivity {
SharedPreferences prefs = getPreferences(context); SharedPreferences prefs = getPreferences(context);
return prefs.getBoolean(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION, false); return prefs.getBoolean(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION, false);
} }
public static boolean isLockScreenEnabled(Context context) {
SharedPreferences prefs = getPreferences(context);
return prefs.getBoolean(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS, false);
}
public static void setActiveServer(Context context, int instance) { public static void setActiveServer(Context context, int instance) {
SharedPreferences prefs = getPreferences(context); SharedPreferences prefs = getPreferences(context);
@ -701,7 +706,7 @@ public class Util extends DownloadActivity {
// Ignored. // Ignored.
} }
} }
private static void startForeground(Service service, int notificationId, Notification notification) { private static void startForeground(Service service, int notificationId, Notification notification) {
// Service.startForeground() was introduced in Android 2.0. // Service.startForeground() was introduced in Android 2.0.
// Use reflection to maintain compatibility with 1.5. // Use reflection to maintain compatibility with 1.5.