Add a new style for FullPlaybackActivity

This commit is contained in:
Christopher Eby 2011-08-27 15:24:41 -05:00
parent 6e3c8e7580
commit 0303cfb08f
9 changed files with 324 additions and 89 deletions

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<org.kreed.vanilla.CoverView
android:id="@+id/cover_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent" />
<LinearLayout
android:id="@+id/top_group"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_gravity="top|left"
android:layout_margin="0dip"
android:padding="4dip"
android:background="#a000"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:singleLine="true"
android:textStyle="bold" />
<TextView
android:id="@+id/artist"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:singleLine="true" />
<TextView
android:id="@+id/album"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:singleLine="true" />
<LinearLayout
android:id="@+id/controls_top"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<SeekBar
android:id="@+id/seek_bar"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:paddingTop="5px"
android:paddingBottom="5px"
android:paddingLeft="15px"
android:paddingRight="15px"
android:layout_weight="1" />
<TextView
android:id="@+id/seek_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="15px"
android:text="0:00 / 0:00" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/controls_bottom"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_gravity="bottom|left"
android:layout_margin="0dip"
android:background="#a000"
android:orientation="horizontal">
<!-- TODO: maybe add library button here? -->
<org.kreed.vanilla.ControlButton
android:id="@+id/previous"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="1"
android:src="@drawable/previous" />
<org.kreed.vanilla.ControlButton
android:id="@+id/play_pause"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="1"
android:src="@drawable/play" />
<org.kreed.vanilla.ControlButton
android:id="@+id/next"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="1"
android:src="@drawable/next" />
</LinearLayout>
</merge>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -36,9 +36,17 @@ THE SOFTWARE.
<item>Open Mini Player</item>
<item>Skip To Next Song</item>
</string-array>
<string-array name="display_mode_entries">
<item>Info In Overlapping Box (default)</item>
<item>Info Below Cover Art</item>
<item>Info In Immobile Layer</item>
</string-array>
<string-array name="entry_values">
<!-- Note: even if this was an integer-array, these would be converted
to strings anyway.
http://code.google.com/p/android/issues/detail?id=2096 -->
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>
</resources>

View File

@ -112,6 +112,12 @@ THE SOFTWARE.
<string name="notification_action_title">Notification Action</string>
<string name="notification_action_summary">What to do when the notification is pressed</string>
<string name="pref_playback_view">Playback View</string>
<string name="display_mode_title">Display Mode</string>
<string name="display_mode_summary">The appearance and position of the song art and info.</string>
<string name="disable_cover_art_title">Disable Cover Art</string>
<string name="disable_cover_art_summary">Do not show cover art anywhere in the application</string>
<string name="pref_song_selector">Library View</string>
<string name="selector_on_startup_title">Open on Startup</string>
<string name="selector_on_startup_summary">Open library on startup</string>
@ -121,8 +127,6 @@ THE SOFTWARE.
<string name="default_action_summary">What to do when an item is tapped</string>
<string name="pref_misc">Miscellaneous Features</string>
<string name="disable_cover_art_title">Disable Cover Art</string>
<string name="disable_cover_art_summary">Do not show cover art anywhere in the application</string>
<string name="use_idle_timeout_title">Enable Idle Timeout</string>
<string name="use_idle_timeout_summary">When active, playback will be stopped after the given period of inactivity</string>
<string name="idle_timeout_title">Idle Timeout</string>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -55,6 +55,20 @@ THE SOFTWARE.
android:entryValues="@array/entry_values"
android:defaultValue="0" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_playback_view">
<ListPreference
android:key="display_mode"
android:title="@string/display_mode_title"
android:summary="@string/display_mode_summary"
android:entries="@array/display_mode_entries"
android:entryValues="@array/entry_values"
android:defaultValue="0" />
<CheckBoxPreference
android:key="disable_cover_art"
android:title="@string/disable_cover_art_title"
android:summary="@string/disable_cover_art_summary"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_song_selector">
<CheckBoxPreference
android:key="selector_on_startup"
@ -75,11 +89,6 @@ THE SOFTWARE.
android:defaultValue="0" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_misc">
<CheckBoxPreference
android:key="disable_cover_art"
android:title="@string/disable_cover_art_title"
android:summary="@string/disable_cover_art_summary"
android:defaultValue="false" />
<CheckBoxPreference
android:key="use_idle_timeout"
android:title="@string/use_idle_timeout_title"

View File

@ -37,7 +37,7 @@ import android.provider.MediaStore;
*/
public class ContextApplication extends Application {
private static ContextApplication mInstance;
private static ArrayList<Activity> mActivities;
public static ArrayList<Activity> mActivities;
private static PlaybackService mService;
private static Random mRandom;

View File

@ -37,6 +37,21 @@ import android.util.TypedValue;
* album art.
*/
public final class CoverBitmap {
/**
* Draw cover in background and a box with song info on top.
*/
public static final int STYLE_OVERLAPPING_BOX = 0;
/**
* Draw cover on top or left with song info on bottom or right (depending
* on orientation).
*/
public static final int STYLE_INFO_BELOW = 1;
/**
* Draw no song info and zoom the cover so that it fills the entire bitmap
* (preserving aspect ratio---some parts of the cover may be cut off).
*/
public static final int STYLE_NO_INFO_ZOOMED = 2;
private static int TEXT_SIZE = -1;
private static int TEXT_SIZE_BIG;
private static int PADDING;
@ -201,19 +216,34 @@ public final class CoverBitmap {
}
/**
* Create a normal image, displaying cover art with the song title, album
* and artist overlaid in a box in the center.
* Create an image representing the given song. Includes cover art and
* possibly song title/artist/ablum, depending on the given style.
*
* @param style One of CoverBitmap.STYLE_*
* @param song The song to display information for
* @param width Maximum width of image
* @param height Maximum height of image
* @param bitmap A Bitmap to be drawn into. If null, a new Bitmap will be
* created. If too small, will be recycled and a new Bitmap will be
* created.
* created. If the bitmap cannot be used, it will be recycled and a new
* Bitmap created.
* @return The image, or null if the song was null, or width or height
* were less than 1
*/
public static Bitmap createOverlappingBitmap(Song song, int width, int height, Bitmap bitmap)
public static Bitmap createBitmap(int style, Song song, int width, int height, Bitmap bitmap)
{
switch (style) {
case STYLE_OVERLAPPING_BOX:
return createOverlappingBitmap(song, width, height, bitmap);
case STYLE_INFO_BELOW:
return createSeparatedBitmap(song, width, height, bitmap);
case STYLE_NO_INFO_ZOOMED:
return createZoomedBitmap(song, width, height, bitmap);
default:
throw new IllegalArgumentException("Invalid bitmap type given: " + style);
}
}
private static Bitmap createOverlappingBitmap(Song song, int width, int height, Bitmap bitmap)
{
if (song == null || width < 1 || height < 1)
return null;
@ -307,20 +337,7 @@ public final class CoverBitmap {
return bitmap;
}
/**
* Create a separated image, displaying cover art with the song title, album
* and artist below or to the right of the cover art.
*
* @param song The song to display information for
* @param width Maximum width of image
* @param height Maximum height of image
* @param bitmap A Bitmap to be drawn into. If null, a new Bitmap will be
* created. If too small, will be recycled and a new Bitmap will be
* created.
* @return The image, or null if the song was null, or width or height
* were less than 1
*/
public static Bitmap createSeparatedBitmap(Song song, int width, int height, Bitmap bitmap)
private static Bitmap createSeparatedBitmap(Song song, int width, int height, Bitmap bitmap)
{
if (song == null || width < 1 || height < 1)
return null;
@ -421,4 +438,38 @@ public final class CoverBitmap {
return bitmap;
}
private static Bitmap createZoomedBitmap(Song song, int width, int height, Bitmap bitmap)
{
if (song == null || width < 1 || height < 1)
return null;
Bitmap cover = song.getCover();
if (cover == null)
return null;
if (bitmap != null && (bitmap.getHeight() != height || bitmap.getWidth() != width)) {
bitmap.recycle();
bitmap = null;
}
if (bitmap == null)
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
int coverWidth = cover.getWidth();
int coverHeight = cover.getHeight();
int size = Math.max(width, height);
float scale = coverWidth < coverHeight ? (float)size / coverWidth : (float)size / coverHeight;
int srcWidth = (int)(Math.min(width, coverWidth * scale) / scale);
int srcHeight = (int)(Math.min(height, coverHeight * scale) / scale);
int xOffset = (coverWidth - srcWidth) / 2;
int yOffset = (coverHeight - srcHeight) / 2;
Rect src = new Rect(xOffset, yOffset, coverWidth - xOffset, coverHeight - yOffset);
Rect dest = new Rect(0, 0, width, height);
canvas.drawBitmap(cover, src, dest, null);
return bitmap;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -53,10 +53,10 @@ public final class CoverView extends View implements Handler.Callback {
*/
private Handler mHandler;
/**
* Whether or not to display song info on top of the cover art. Can be
* initialized by the containing Activity.
* How to render cover art and metadata. One of
* CoverBitmap.STYLE_*
*/
boolean mSeparateInfo;
private int mCoverStyle;
/**
* The current set of songs: previous, current, and next.
@ -103,6 +103,15 @@ public final class CoverView extends View implements Handler.Callback {
mHandler = new Handler(looper, this);
}
/**
* Set the cover style. Must be one of CoverBitmap.STYLE_. Will only apply
* to bitmaps rendered after this method is called.
*/
public void setCoverStyle(int style)
{
mCoverStyle = style;
}
/**
* Query the service for initial song info.
*/
@ -155,15 +164,6 @@ public final class CoverView extends View implements Handler.Callback {
setSong(i, mSongs[i]);
}
/**
* Toggle between separate and overlapping song info display modes.
*/
public void toggleDisplayMode()
{
mSeparateInfo = !mSeparateInfo;
regenerateBitmaps();
}
/**
* Recreate the cover art views and reset the scroll position whenever the
* size of this view changes.
@ -326,12 +326,7 @@ public final class CoverView extends View implements Handler.Callback {
Bitmap bitmap = mBitmapCache.get(song.id);
if (bitmap == null) {
bitmap = mBitmapCache.discardOldest();
if (mSeparateInfo)
bitmap = CoverBitmap.createSeparatedBitmap(song, getWidth(), getHeight(), bitmap);
else
bitmap = CoverBitmap.createOverlappingBitmap(song, getWidth(), getHeight(), bitmap);
mBitmapCache.put(song.id, bitmap);
mBitmapCache.put(song.id, CoverBitmap.createBitmap(mCoverStyle, song, getWidth(), getHeight(), mBitmapCache.discardOldest()));
postInvalidate();
} else {
mBitmapCache.touch(song.id);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -29,6 +29,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@ -40,6 +41,10 @@ import android.widget.SeekBar;
import android.widget.TextView;
public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.OnSeekBarChangeListener, View.OnLongClickListener {
public static final int DISPLAY_INFO_OVERLAP = 0;
public static final int DISPLAY_INFO_BELOW = 1;
public static final int DISPLAY_INFO_WIDGETS = 2;
/**
* A Handler running on the UI thread, in contrast with mHandler which runs
* on a worker thread.
@ -53,6 +58,10 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
private SeekBar mSeekBar;
private TextView mSeekText;
private TextView mTitle;
private TextView mAlbum;
private TextView mArtist;
private int mDuration;
private boolean mSeekBarTracking;
private boolean mPaused;
@ -62,15 +71,37 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
{
super.onCreate(icicle);
setContentView(R.layout.full_playback);
mCoverView = (CoverView)findViewById(R.id.cover_view);
mCoverView.setOnClickListener(this);
mCoverView.setOnLongClickListener(this);
mCoverView.setupHandler(mLooper);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
mCoverView.mSeparateInfo = settings.getBoolean("separate_info", false);
int displayMode = Integer.parseInt(settings.getString("display_mode", "0"));
int layout = R.layout.full_playback;
int coverStyle = -1;
switch (displayMode) {
default:
Log.e("VanillaMusic", "Invalid display mode given. Defaulting to overlap");
// fall through
case DISPLAY_INFO_OVERLAP:
coverStyle = CoverBitmap.STYLE_OVERLAPPING_BOX;
break;
case DISPLAY_INFO_BELOW:
coverStyle = CoverBitmap.STYLE_INFO_BELOW;
break;
case DISPLAY_INFO_WIDGETS:
coverStyle = CoverBitmap.STYLE_NO_INFO_ZOOMED;
layout = R.layout.full_playback_alt;
break;
}
setContentView(layout);
CoverView coverView = (CoverView)findViewById(R.id.cover_view);
coverView.setCoverStyle(coverStyle);
coverView.setOnClickListener(this);
coverView.setOnLongClickListener(this);
coverView.setupHandler(mLooper);
mCoverView = coverView;
mControlsTop = findViewById(R.id.controls_top);
mControlsBottom = findViewById(R.id.controls_bottom);
@ -82,6 +113,10 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
View nextButton = findViewById(R.id.next);
nextButton.setOnClickListener(this);
mTitle = (TextView)findViewById(R.id.title);
mAlbum = (TextView)findViewById(R.id.album);
mArtist = (TextView)findViewById(R.id.artist);
mSeekText = (TextView)findViewById(R.id.seek_text);
mSeekBar = (SeekBar)findViewById(R.id.seek_bar);
mSeekBar.setMax(1000);
@ -164,7 +199,10 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
{
super.onServiceReady();
mDuration = ContextApplication.getService().getDuration();
PlaybackService service = ContextApplication.getService();
if (mTitle != null)
mUiHandler.sendMessage(mUiHandler.obtainMessage(MSG_UPDATE_SONG, service.getSong(0)));
mDuration = service.getDuration();
}
@Override
@ -173,6 +211,10 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
super.receive(intent);
if (PlaybackService.EVENT_CHANGED.equals(intent.getAction())) {
if (mTitle != null) {
Song song = intent.getParcelableExtra("song");
mUiHandler.sendMessage(mUiHandler.obtainMessage(MSG_UPDATE_SONG, song));
}
mDuration = ContextApplication.getService().getDuration();
mUiHandler.sendEmptyMessage(MSG_UPDATE_PROGRESS);
}
@ -182,7 +224,6 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(0, MENU_LIBRARY, 0, R.string.library).setIcon(R.drawable.ic_menu_music_library);
menu.add(0, MENU_DISPLAY, 0, R.string.display_mode).setIcon(R.drawable.ic_menu_gallery);
return super.onCreateOptionsMenu(menu);
}
@ -193,10 +234,6 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
case MENU_LIBRARY:
startActivity(new Intent(this, SongSelector.class));
return true;
case MENU_DISPLAY:
mCoverView.toggleDisplayMode();
mHandler.sendEmptyMessage(MSG_SAVE_DISPLAY_MODE);
return true;
default:
return super.onOptionsItemSelected(item);
}
@ -215,7 +252,7 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
onClick(mCoverView);
toggleControls();
return true;
}
@ -260,29 +297,39 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
mUiHandler.sendEmptyMessageDelayed(MSG_UPDATE_PROGRESS, next);
}
/**
* Toggles the visibility of the playback controls.
*/
private void toggleControls()
{
if (mControlsTop.getVisibility() == View.VISIBLE) {
mControlsTop.setVisibility(View.GONE);
mControlsBottom.setVisibility(View.GONE);
} else {
mControlsTop.setVisibility(View.VISIBLE);
mControlsBottom.setVisibility(View.VISIBLE);
mPlayPauseButton.requestFocus();
updateProgress();
}
}
@Override
public void onClick(View view)
{
if (view == mCoverView) {
if (mControlsTop.getVisibility() == View.VISIBLE) {
mControlsTop.setVisibility(View.GONE);
mControlsBottom.setVisibility(View.GONE);
} else {
mControlsTop.setVisibility(View.VISIBLE);
mControlsBottom.setVisibility(View.VISIBLE);
mPlayPauseButton.requestFocus();
updateProgress();
}
} else {
switch (view.getId()) {
case R.id.cover_view:
toggleControls();
break;
default:
super.onClick(view);
}
}
public boolean onLongClick(View view)
{
if (view == mCoverView) {
if (view.getId() == R.id.cover_view) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_FLAG, PlaybackService.FLAG_PLAYING, 0));
return true;
}
@ -296,11 +343,9 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
*/
private static final int MSG_UPDATE_PROGRESS = 10;
/**
* Save the currently set CoverView display mode.
*
* @see CoverView#mSeparateInfo
* Update the metadata for the current song.
*/
private static final int MSG_SAVE_DISPLAY_MODE = 11;
private static final int MSG_UPDATE_SONG = 11;
/**
* The the no media overlay message. This must be called on the UI Handler.
*/
@ -314,15 +359,22 @@ public class FullPlaybackActivity extends PlaybackActivity implements SeekBar.On
public boolean handleMessage(Message message)
{
switch (message.what) {
case MSG_SAVE_DISPLAY_MODE:
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("separate_info", mCoverView.mSeparateInfo);
editor.commit();
break;
case MSG_UPDATE_PROGRESS:
updateProgress();
break;
case MSG_UPDATE_SONG: {
Song song = (Song)message.obj;
if (song == null) {
mTitle.setText(null);
mAlbum.setText(null);
mArtist.setText(null);
} else {
mTitle.setText(song.title);
mAlbum.setText(song.album);
mArtist.setText(song.artist);
}
break;
}
case MSG_SHOW_NO_MEDIA:
showMessageOverlay();
setNoMediaOverlayMessage();

View File

@ -25,7 +25,9 @@ package org.kreed.vanilla;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@ -453,6 +455,12 @@ public final class PlaybackService extends Service implements Handler.Callback,
userActionTriggered();
} else if ("disable_cover_art".equals(key)) {
Song.mDisableCoverArt = settings.getBoolean("disable_cover_art", false);
} else if ("display_mode".equals(key)) {
ArrayList<Activity> activities = ContextApplication.mActivities;
for (Activity activity : activities) {
if (activity instanceof FullPlaybackActivity)
activity.finish();
}
}
}