Rework playback activity layout.

Display all info in the table layout, including title, artist and album.
Add queue position display.
This commit is contained in:
Christopher Eby 2012-03-13 10:01:44 -05:00
parent 95debe778b
commit 7cc59120d4
8 changed files with 245 additions and 108 deletions

@ -26,6 +26,7 @@ THE SOFTWARE.
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:layout_width="fill_parent" /> android:layout_width="fill_parent" />
<LinearLayout <LinearLayout
android:id="@+id/controls_top"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_gravity="top" android:layout_gravity="top"

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org> Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -25,93 +25,125 @@ THE SOFTWARE.
android:id="@+id/cover_view" android:id="@+id/cover_view"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:layout_width="fill_parent" /> android:layout_width="fill_parent" />
<LinearLayout <TableLayout
android:id="@+id/controls_top" android:id="@+id/info_table"
android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_gravity="top|left" android:layout_height="wrap_content"
android:layout_margin="0dip" android:layout_gravity="top"
android:collapseColumns="0"
android:stretchColumns="1"
android:shrinkColumns="1"
android:background="#a000" android:background="#a000"
android:orientation="vertical"> android:paddingLeft="5dip"
android:paddingRight="5dip">
<TableRow>
<TextView
android:text="@string/_title"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:ellipsize="marquee" android:textStyle="bold"
android:layout_height="wrap_content" android:singleLine="true" />
android:layout_width="fill_parent" <TextView
android:gravity="center" android:id="@+id/queue_pos"
android:singleLine="true" android:singleLine="true"
android:textStyle="bold" /> android:layout_gravity="top" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_artist"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView <TextView
android:id="@+id/artist" android:id="@+id/artist"
android:ellipsize="marquee"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:gravity="center"
android:singleLine="true" /> android:singleLine="true" />
<LinearLayout </TableRow>
android:layout_height="wrap_content" <TableRow>
android:layout_width="fill_parent" <TextView
android:orientation="horizontal"> android:text="@string/_album"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView
android:id="@+id/album"
android:singleLine="true" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_genre"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView
android:id="@+id/genre"
android:singleLine="true" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_track"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView
android:id="@+id/track"
android:singleLine="true" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_year"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView
android:id="@+id/year"
android:singleLine="true" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_composer"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView
android:id="@+id/composer"
android:singleLine="true" />
</TableRow>
<TableRow>
<TextView
android:text="@string/_format"
android:paddingRight="5dip"
android:singleLine="true"
android:gravity="right" />
<TextView
android:id="@+id/format"
android:singleLine="true" />
</TableRow>
<LinearLayout android:id="@+id/controls_top">
<TextView <TextView
android:id="@+id/elapsed" android:id="@+id/elapsed"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:gravity="bottom"
android:textSize="10sp" android:textSize="10sp"
android:paddingLeft="5dip" /> android:layout_gravity="center" />
<TextView <SeekBar
android:id="@+id/album" android:id="@+id/seek_bar"
android:ellipsize="marquee"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="0px" android:layout_width="0px"
android:layout_gravity="center"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginLeft="2dip" android:paddingLeft="18dip"
android:layout_marginRight="2dip" android:paddingRight="18dip" />
android:gravity="center"
android:singleLine="true" />
<TextView <TextView
android:id="@+id/duration" android:id="@+id/duration"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:gravity="bottom"
android:textSize="10sp" android:textSize="10sp"
android:paddingRight="5dip" /> android:layout_gravity="center" />
</LinearLayout> </LinearLayout>
<SeekBar
android:id="@+id/seek_bar"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:paddingTop="5dip"
android:paddingBottom="5dip"
android:paddingLeft="15dip"
android:paddingRight="15dip" />
<TableLayout
android:id="@+id/extra_info"
android:visibility="gone"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TableRow>
<TextView android:text="@string/genre" />
<TextView android:id="@+id/genre" android:paddingLeft="5dip" />
</TableRow>
<TableRow>
<TextView android:text="@string/track" />
<TextView android:id="@+id/track" android:paddingLeft="5dip" />
</TableRow>
<TableRow>
<TextView android:text="@string/year" />
<TextView android:id="@+id/year" android:paddingLeft="5dip" />
</TableRow>
<TableRow>
<TextView android:text="@string/composer" />
<TextView android:id="@+id/composer" android:paddingLeft="5dip" />
</TableRow>
<TableRow>
<TextView android:text="@string/format" />
<TextView android:id="@+id/format" android:paddingLeft="5dip" />
</TableRow>
</TableLayout> </TableLayout>
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/controls_bottom" android:id="@+id/controls_bottom"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -41,10 +41,14 @@ THE SOFTWARE.
<string name="queue_cleared">Queue cleared.</string> <string name="queue_cleared">Queue cleared.</string>
<string name="cover_art">Cover art</string> <string name="cover_art">Cover art</string>
<string name="close_notification">Close notification</string> <string name="close_notification">Close notification</string>
<string name="genre">Genre</string> <string name="_genre">genre</string>
<string name="track">Track</string> <string name="_track">track</string>
<string name="composer">Composer</string> <string name="_composer">composer</string>
<string name="format">Format</string> <string name="_format">format</string>
<string name="_title">title</string>
<string name="_artist">artist</string>
<string name="_album">album</string>
<string name="_year">year</string>
<!-- New Playlist Dialog --> <!-- New Playlist Dialog -->
<string name="choose_playlist_name">Choose Playlist Name</string> <string name="choose_playlist_name">Choose Playlist Name</string>

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org> * Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -55,12 +55,14 @@ public class FullPlaybackActivity extends PlaybackActivity
public static final int DISPLAY_INFO_WIDGETS = 2; public static final int DISPLAY_INFO_WIDGETS = 2;
private TextView mOverlayText; private TextView mOverlayText;
private View mControlsTop;
private View mControlsBottom; private View mControlsBottom;
private SeekBar mSeekBar; private SeekBar mSeekBar;
private TableLayout mInfoTable;
private TextView mElapsedView; private TextView mElapsedView;
private TextView mDurationView; private TextView mDurationView;
private TableLayout mExtraInfo; private TextView mQueuePosView;
private TextView mTitle; private TextView mTitle;
private TextView mAlbum; private TextView mAlbum;
@ -155,23 +157,25 @@ public class FullPlaybackActivity extends PlaybackActivity
View nextButton = findViewById(R.id.next); View nextButton = findViewById(R.id.next);
nextButton.setOnClickListener(this); nextButton.setOnClickListener(this);
View controlsTop = findViewById(R.id.controls_top); TableLayout table = (TableLayout)findViewById(R.id.info_table);
if (controlsTop != null) { if (table != null) {
controlsTop.setOnClickListener(this); table.setOnClickListener(this);
controlsTop.setOnLongClickListener(this); table.setOnLongClickListener(this);
mInfoTable = table;
} }
mTitle = (TextView)findViewById(R.id.title); mTitle = (TextView)findViewById(R.id.title);
mAlbum = (TextView)findViewById(R.id.album); mAlbum = (TextView)findViewById(R.id.album);
mArtist = (TextView)findViewById(R.id.artist); mArtist = (TextView)findViewById(R.id.artist);
mControlsTop = findViewById(R.id.controls_top);
mElapsedView = (TextView)findViewById(R.id.elapsed); mElapsedView = (TextView)findViewById(R.id.elapsed);
mDurationView = (TextView)findViewById(R.id.duration); mDurationView = (TextView)findViewById(R.id.duration);
mSeekBar = (SeekBar)findViewById(R.id.seek_bar); mSeekBar = (SeekBar)findViewById(R.id.seek_bar);
mSeekBar.setMax(1000); mSeekBar.setMax(1000);
mSeekBar.setOnSeekBarChangeListener(this); mSeekBar.setOnSeekBarChangeListener(this);
mQueuePosView = (TextView)findViewById(R.id.queue_pos);
mExtraInfo = (TableLayout)findViewById(R.id.extra_info);
mGenreView = (TextView)findViewById(R.id.genre); mGenreView = (TextView)findViewById(R.id.genre);
mTrackView = (TextView)findViewById(R.id.track); mTrackView = (TextView)findViewById(R.id.track);
mYearView = (TextView)findViewById(R.id.year); mYearView = (TextView)findViewById(R.id.year);
@ -210,7 +214,7 @@ public class FullPlaybackActivity extends PlaybackActivity
{ {
super.onResume(); super.onResume();
mPaused = false; mPaused = false;
updateProgress(); updateElapsedTime();
} }
@Override @Override
@ -272,7 +276,10 @@ public class FullPlaybackActivity extends PlaybackActivity
} }
if ((state & PlaybackService.FLAG_PLAYING) != 0) if ((state & PlaybackService.FLAG_PLAYING) != 0)
updateProgress(); updateElapsedTime();
if (mQueuePosView != null)
updateQueuePosition();
} }
@Override @Override
@ -292,16 +299,40 @@ public class FullPlaybackActivity extends PlaybackActivity
mAlbum.setText(song.album); mAlbum.setText(song.album);
mArtist.setText(song.artist); mArtist.setText(song.artist);
} }
updateQueuePosition();
} }
mCurrentSong = song; mCurrentSong = song;
updateProgress(); updateElapsedTime();
if (mExtraInfoVisible) { if (mExtraInfoVisible) {
mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO); mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO);
} }
} }
/**
* Update the queue position display. mQueuePos must not be null.
*/
private void updateQueuePosition()
{
if (PlaybackService.finishAction(mState) == SongTimeline.FINISH_RANDOM) {
// Not very useful in random mode; it will always show something
// like 11/13 since the timeline is trimmed to 10 previous songs.
// So just hide it.
mQueuePosView.setText(null);
} else {
PlaybackService service = PlaybackService.get(this);
mQueuePosView.setText((service.getTimelinePosition() + 1) + "/" + service.getTimelineLength());
}
}
@Override
public void onPositionInfoChanged()
{
if (mQueuePosView != null)
mUiHandler.sendEmptyMessage(MSG_UPDATE_POSITION);
}
/** /**
* Update the current song duration fields. * Update the current song duration fields.
* *
@ -400,7 +431,7 @@ public class FullPlaybackActivity extends PlaybackActivity
/** /**
* Update seek bar progress and schedule another update in one second * Update seek bar progress and schedule another update in one second
*/ */
private void updateProgress() private void updateElapsedTime()
{ {
long position = PlaybackService.hasInstance() ? PlaybackService.get(this).getPosition() : 0; long position = PlaybackService.hasInstance() ? PlaybackService.get(this).getPosition() : 0;
@ -427,15 +458,13 @@ public class FullPlaybackActivity extends PlaybackActivity
private void setControlsVisible(boolean visible) private void setControlsVisible(boolean visible)
{ {
int mode = visible ? View.VISIBLE : View.GONE; int mode = visible ? View.VISIBLE : View.GONE;
mSeekBar.setVisibility(mode); mControlsTop.setVisibility(mode);
mElapsedView.setVisibility(mode);
mDurationView.setVisibility(mode);
mControlsBottom.setVisibility(mode); mControlsBottom.setVisibility(mode);
mControlsVisible = visible; mControlsVisible = visible;
if (visible) { if (visible) {
mPlayPauseButton.requestFocus(); mPlayPauseButton.requestFocus();
updateProgress(); updateElapsedTime();
} }
} }
@ -446,10 +475,17 @@ public class FullPlaybackActivity extends PlaybackActivity
*/ */
private void setExtraInfoVisible(boolean visible) private void setExtraInfoVisible(boolean visible)
{ {
if (mExtraInfo == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1) TableLayout table = mInfoTable;
if (table == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1)
return; return;
mExtraInfo.setVisibility(visible ? View.VISIBLE : View.GONE); table.setColumnCollapsed(0, !visible);
int visibility = visible ? View.VISIBLE : View.GONE;
// toggle visibility of all but the first three rows (the title/artist/
// album rows) and the last row (the seek bar)
for (int i = table.getChildCount() - 1; --i != 2; ) {
table.getChildAt(i).setVisibility(visibility);
}
mExtraInfoVisible = visible; mExtraInfoVisible = visible;
if (visible && !mHandler.hasMessages(MSG_LOAD_EXTRA_INFO)) { if (visible && !mHandler.hasMessages(MSG_LOAD_EXTRA_INFO)) {
mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO); mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO);
@ -539,6 +575,10 @@ public class FullPlaybackActivity extends PlaybackActivity
* Pass obj to mExtraInfo.setText() * Pass obj to mExtraInfo.setText()
*/ */
private static final int MSG_COMMIT_INFO = 16; private static final int MSG_COMMIT_INFO = 16;
/**
* Calls {@link #updateQueuePosition()}.
*/
private static final int MSG_UPDATE_POSITION = 17;
@Override @Override
public boolean handleMessage(Message message) public boolean handleMessage(Message message)
@ -552,7 +592,7 @@ public class FullPlaybackActivity extends PlaybackActivity
break; break;
} }
case MSG_UPDATE_PROGRESS: case MSG_UPDATE_PROGRESS:
updateProgress(); updateElapsedTime();
break; break;
case MSG_LOAD_EXTRA_INFO: case MSG_LOAD_EXTRA_INFO:
loadExtraInfo(); loadExtraInfo();
@ -565,6 +605,9 @@ public class FullPlaybackActivity extends PlaybackActivity
mFormatView.setText(mFormat); mFormatView.setText(mFormat);
break; break;
} }
case MSG_UPDATE_POSITION:
updateQueuePosition();
break;
default: default:
return super.handleMessage(message); return super.handleMessage(message);
} }
@ -575,9 +618,11 @@ public class FullPlaybackActivity extends PlaybackActivity
@Override @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{ {
if (fromUser) if (fromUser) {
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, progress * mDuration / 1000000));
PlaybackService.get(this).seekToProgress(progress); PlaybackService.get(this).seekToProgress(progress);
} }
}
@Override @Override
public void onStartTrackingTouch(SeekBar seekBar) public void onStartTrackingTouch(SeekBar seekBar)
@ -608,7 +653,7 @@ public class FullPlaybackActivity extends PlaybackActivity
setState(PlaybackService.get(this).setFinishAction(SongTimeline.FINISH_RANDOM)); setState(PlaybackService.get(this).setFinishAction(SongTimeline.FINISH_RANDOM));
} else if (view == mCoverView) { } else if (view == mCoverView) {
performAction(mCoverPressAction); performAction(mCoverPressAction);
} else if (view.getId() == R.id.controls_top) { } else if (view.getId() == R.id.info_table) {
openLibrary(mCurrentSong); openLibrary(mCurrentSong);
} else { } else {
super.onClick(view); super.onClick(view);
@ -622,7 +667,7 @@ public class FullPlaybackActivity extends PlaybackActivity
case R.id.cover_view: case R.id.cover_view:
performAction(mCoverLongPressAction); performAction(mCoverLongPressAction);
break; break;
case R.id.controls_top: case R.id.info_table:
setExtraInfoVisible(!mExtraInfoVisible); setExtraInfoVisible(!mExtraInfoVisible);
mHandler.sendEmptyMessage(MSG_SAVE_CONTROLS); mHandler.sendEmptyMessage(MSG_SAVE_CONTROLS);
break; break;

@ -299,6 +299,13 @@ public abstract class PlaybackActivity extends Activity
mCoverView.setSong(delta + 1, song); mCoverView.setSong(delta + 1, song);
} }
/**
* Called when the song timeline position/size has changed.
*/
public void onPositionInfoChanged()
{
}
/** /**
* Called when the content of the media store has changed. * Called when the content of the media store has changed.
*/ */

@ -1464,6 +1464,14 @@ public final class PlaybackService extends Service
mHandler.sendEmptyMessageDelayed(SAVE_STATE, 5000); mHandler.sendEmptyMessageDelayed(SAVE_STATE, 5000);
} }
@Override
public void positionInfoChanged()
{
ArrayList<PlaybackActivity> list = sActivities;
for (int i = list.size(); --i != -1; )
list.get(i).onPositionInfoChanged();
}
private final ContentObserver mObserver = new ContentObserver(null) { private final ContentObserver mObserver = new ContentObserver(null) {
@Override @Override
public void onChange(boolean selfChange) public void onChange(boolean selfChange)
@ -1821,4 +1829,20 @@ public final class PlaybackService extends Service
throw new IllegalArgumentException("Invalid action: " + action); throw new IllegalArgumentException("Invalid action: " + action);
} }
} }
/**
* Returns the position of the current song in the song timeline.
*/
public int getTimelinePosition()
{
return mTimeline.getPosition();
}
/**
* Returns the number of songs in the song timeline.
*/
public int getTimelineLength()
{
return mTimeline.getLength();
}
} }

@ -224,6 +224,8 @@ public final class SongTimeline {
private Song mSavedPrevious; private Song mSavedPrevious;
private Song mSavedCurrent; private Song mSavedCurrent;
private Song mSavedNext; private Song mSavedNext;
private int mSavedPos;
private int mSavedSize;
/** /**
* Interface to respond to timeline changes. * Interface to respond to timeline changes.
@ -244,6 +246,11 @@ public final class SongTimeline {
* storage. * storage.
*/ */
public void timelineChanged(); public void timelineChanged();
/**
* Called when the length of the timeline has changed.
*/
public void positionInfoChanged();
} }
/** /**
* The current Callback, if any. * The current Callback, if any.
@ -710,6 +717,7 @@ public final class SongTimeline {
} }
mCallback.activeSongReplaced(+1, getSong(+1)); mCallback.activeSongReplaced(+1, getSong(+1));
mCallback.positionInfoChanged();
changed(); changed();
} }
@ -724,6 +732,8 @@ public final class SongTimeline {
mSavedPrevious = getSong(-1); mSavedPrevious = getSong(-1);
mSavedCurrent = getSong(0); mSavedCurrent = getSong(0);
mSavedNext = getSong(+1); mSavedNext = getSong(+1);
mSavedPos = mCurrentPos;
mSavedSize = mSongs.size();
} }
/** /**
@ -738,16 +748,15 @@ public final class SongTimeline {
Song current = getSong(0); Song current = getSong(0);
Song next = getSong(+1); Song next = getSong(+1);
if (mCallback != null) {
if (Song.getId(mSavedPrevious) != Song.getId(previous)) if (Song.getId(mSavedPrevious) != Song.getId(previous))
mCallback.activeSongReplaced(-1, previous); mCallback.activeSongReplaced(-1, previous);
if (Song.getId(mSavedNext) != Song.getId(next)) if (Song.getId(mSavedNext) != Song.getId(next))
mCallback.activeSongReplaced(1, next); mCallback.activeSongReplaced(1, next);
} if (Song.getId(mSavedCurrent) != Song.getId(current))
if (Song.getId(mSavedCurrent) != Song.getId(current)) {
if (mCallback != null)
mCallback.activeSongReplaced(0, current); mCallback.activeSongReplaced(0, current);
}
if (mCurrentPos != mSavedPos || mSongs.size() != mSavedSize)
mCallback.positionInfoChanged();
} }
/** /**
@ -796,4 +805,20 @@ public final class SongTimeline {
return mFinishAction == FINISH_STOP && mCurrentPos == mSongs.size() - 1; return mFinishAction == FINISH_STOP && mCurrentPos == mSongs.size() - 1;
} }
} }
/**
* Returns the position of the current song in the timeline.
*/
public int getPosition()
{
return mCurrentPos;
}
/**
* Returns the current number of songs in the timeline.
*/
public int getLength()
{
return mSongs.size();
}
} }

@ -88,7 +88,6 @@ public class TabOrderActivity extends Activity implements View.OnClickListener,
*/ */
public void restoreDefault() public void restoreDefault()
{ {
android.util.Log.i("VanillaMusic", "restore default");
mAdapter.setTabIds(LibraryPagerAdapter.DEFAULT_ORDER.clone()); mAdapter.setTabIds(LibraryPagerAdapter.DEFAULT_ORDER.clone());
DragListView list = mList; DragListView list = mList;
for (int i = 0; i != LibraryPagerAdapter.MAX_ADAPTER_COUNT; ++i) { for (int i = 0; i != LibraryPagerAdapter.MAX_ADAPTER_COUNT; ++i) {