Eleven: AlbumDetailPage implemented according to red lines

Change-Id: I6b5d5051951eda26018f1be67d19aab27af9ae1a
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fdd3a9c..cfc3834 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -153,6 +153,13 @@
             android:screenOrientation="portrait"
             android:theme="@style/Eleven.Theme.ActionBar.Overlay">
         </activity>
+        <!-- Album Detail Activity -->
+        <activity
+            android:name=".ui.activities.AlbumDetailActivity"
+            android:excludeFromRecents="true"
+            android:screenOrientation="portrait"
+            android:theme="@style/Eleven.Theme.ActionBar.Overlay">
+        </activity>
         <!-- Playlist Detail Activity -->
         <activity
             android:name=".ui.activities.PlaylistDetailActivity"
diff --git a/res/drawable-hdpi/stopwatch_icon_small_grey.png b/res/drawable-hdpi/stopwatch_icon_small_grey.png
new file mode 100644
index 0000000..ab2aa7e
--- /dev/null
+++ b/res/drawable-hdpi/stopwatch_icon_small_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/stopwatch_icon_small_grey.png b/res/drawable-mdpi/stopwatch_icon_small_grey.png
new file mode 100644
index 0000000..e3cf1f4
--- /dev/null
+++ b/res/drawable-mdpi/stopwatch_icon_small_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/stopwatch_icon_small_grey.png b/res/drawable-xhdpi/stopwatch_icon_small_grey.png
new file mode 100644
index 0000000..3a0d75f
--- /dev/null
+++ b/res/drawable-xhdpi/stopwatch_icon_small_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stopwatch_icon_small_grey.png b/res/drawable-xxhdpi/stopwatch_icon_small_grey.png
new file mode 100644
index 0000000..9b4db72
--- /dev/null
+++ b/res/drawable-xxhdpi/stopwatch_icon_small_grey.png
Binary files differ
diff --git a/res/layout/activity_album_detail.xml b/res/layout/activity_album_detail.xml
new file mode 100644
index 0000000..d93fc1d
--- /dev/null
+++ b/res/layout/activity_album_detail.xml
@@ -0,0 +1,100 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_color" >
+
+    <RelativeLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="108dp"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:background="@color/page_header_background" >
+
+        <ImageView
+            android:id="@+id/overflow"
+            android:layout_width="@dimen/overflow_width"
+            android:layout_height="@dimen/overflow_width"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentRight="true"
+            android:layout_marginBottom="40dp"
+            android:gravity="center"
+            android:src="@drawable/menu_item_button" />
+
+        <ImageView
+            android:id="@+id/album_art"
+            android:layout_width="108dp"
+            android:layout_height="108dp"
+            android:layout_marginRight="@dimen/standard_padding"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true" />
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/album_art"
+            android:layout_toLeftOf="@id/overflow"
+            android:layout_marginTop="21dp"
+            android:singleLine="true"
+            android:fontFamily="sans-serif"
+            android:textStyle="bold"
+            android:textSize="@dimen/text_size_large"
+            android:textColor="@color/default_text_color" />
+
+        <TextView
+            android:id="@+id/song_count_and_year"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_toRightOf="@id/album_art"
+            android:singleLine="true"
+            android:fontFamily="sans-serif-light"
+            android:textSize="@dimen/text_size_micro"
+            android:textColor="@color/default_text_color" />
+
+        <TextView
+            android:id="@+id/genre"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/song_count_and_year"
+            android:layout_toRightOf="@id/album_art"
+            android:layout_marginRight="@dimen/standard_padding"
+            android:singleLine="true"
+            android:fontFamily="sans-serif-light"
+            android:textSize="@dimen/text_size_micro"
+            android:textColor="@color/default_text_color"
+            android:visibility="gone" />
+
+        <ImageView
+            android:id="@+id/duration_icon"
+            android:layout_width="10dp"
+            android:layout_height="12dp"
+            android:layout_below="@id/song_count_and_year"
+            android:layout_toRightOf="@id/genre"
+            android:layout_marginTop="2dp"
+            android:layout_marginRight="4dp"
+            android:gravity="center"
+            android:src="@drawable/stopwatch_icon_small_grey" />
+
+        <TextView
+            android:id="@+id/duration"
+            android:layout_width="60dp"
+            android:layout_height="30dp"
+            android:layout_below="@id/song_count_and_year"
+            android:layout_toRightOf="@id/duration_icon"
+            android:singleLine="true"
+            android:fontFamily="sans-serif-light"
+            android:textSize="@dimen/text_size_micro"
+            android:textColor="@color/default_text_color" />
+    </RelativeLayout>
+
+    <ListView
+        android:id="@+id/songs"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/header"
+        android:layout_alignParentLeft="true"
+        android:divider="@color/background_color" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/album_detail_song.xml b/res/layout/album_detail_song.xml
new file mode 100644
index 0000000..e3b1587
--- /dev/null
+++ b/res/layout/album_detail_song.xml
@@ -0,0 +1,48 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="70dp" >
+
+    <!-- FIXME: hide this until we know what goes in the popup menu -->
+    <ImageView
+        android:visibility="gone"
+        android:id="@+id/overflow"
+        android:layout_width="@dimen/overflow_width"
+        android:layout_height="68dp"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        android:layout_marginBottom="2dp"
+        android:gravity="center"
+        android:src="@drawable/menu_item_button" />
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/ListItemMainText.Single"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_toLeftOf="@id/overflow"
+        android:paddingTop="6dp"
+        android:layout_marginTop="@dimen/list_preferred_item_padding"
+        android:layout_marginLeft="@dimen/list_preferred_item_padding" />
+
+    <TextView
+        android:id="@+id/duration"
+        style="@style/ListItemSecondaryText.Single"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/title"
+        android:layout_alignParentLeft="true"
+        android:layout_toLeftOf="@id/overflow"
+        android:layout_marginTop="-2dp"
+        android:layout_marginLeft="@dimen/standard_padding" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        android:layout_marginLeft="@dimen/list_preferred_item_padding"
+        android:layout_marginRight="@dimen/list_preferred_item_padding"
+        android:background="@color/list_item_divider_color" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/artist_detail_album.xml b/res/layout/artist_detail_album.xml
index f7469bc..93131a2 100644
--- a/res/layout/artist_detail_album.xml
+++ b/res/layout/artist_detail_album.xml
@@ -15,7 +15,7 @@
     <ImageView
         android:visibility="gone"
         android:id="@+id/overflow"
-        android:layout_width="24dp"
+        android:layout_width="@dimen/overflow_width"
         android:layout_height="40dp"
         android:layout_alignParentBottom="true"
         android:layout_alignParentRight="true"
@@ -26,30 +26,23 @@
 
     <TextView
         android:id="@+id/title"
+        style="@style/ListItemMainText.Single"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/album_art"
         android:layout_toLeftOf="@id/overflow"
         android:layout_alignParentLeft="true"
         android:layout_marginLeft="8dp"
-        android:layout_marginTop="3dp"
-        android:singleLine="true"
-        android:fontFamily="sans-serif"
-        android:textStyle="bold"
-        android:textSize="@dimen/text_size_small"
-        android:textColor="@color/default_text_color" />
+        android:layout_marginTop="3dp" />
 
     <TextView
         android:id="@+id/year"
+        style="@style/ListItemSecondaryText.Single"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/title"
         android:layout_alignParentLeft="true"
         android:layout_toLeftOf="@id/overflow"
         android:layout_marginLeft="8dp"
-        android:layout_marginTop="-4dp"
-        android:singleLine="true"
-        android:fontFamily="sans-serif-light"
-        android:textSize="@dimen/text_size_micro"
-        android:textColor="@color/default_text_color" />
+        android:layout_marginTop="-4dp" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/artist_detail_song.xml b/res/layout/artist_detail_song.xml
index 8f20e72..4973ae3 100644
--- a/res/layout/artist_detail_song.xml
+++ b/res/layout/artist_detail_song.xml
@@ -8,13 +8,13 @@
         android:layout_height="50dp"
         android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
-        android:layout_margin="10dp" />
+        android:layout_margin="@dimen/list_preferred_item_padding" />
 
     <!-- FIXME: hide this until we know what goes in the popup menu -->
     <ImageView
         android:visibility="gone"
         android:id="@+id/overflow"
-        android:layout_width="24dp"
+        android:layout_width="@dimen/overflow_width"
         android:layout_height="68dp"
         android:layout_alignParentTop="true"
         android:layout_alignParentRight="true"
@@ -24,37 +24,29 @@
 
     <TextView
         android:id="@+id/title"
+        style="@style/ListItemMainText.Single"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         android:layout_toLeftOf="@id/overflow"
-        android:layout_toRightOf="@id/album_art"
-        android:layout_marginTop="16dp"
-        android:singleLine="true"
-        android:fontFamily="sans-serif"
-        android:textStyle="bold"
-        android:textSize="@dimen/text_size_small"
-        android:textColor="@color/default_text_color" />
+        android:layout_toRightOf="@id/album_art" />
 
     <TextView
         android:id="@+id/album"
+        style="@style/ListItemSecondaryText.Single"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/title"
         android:layout_toRightOf="@id/album_art"
         android:layout_toLeftOf="@id/overflow"
-        android:layout_marginTop="-2dp"
-        android:singleLine="true"
-        android:fontFamily="sans-serif-light"
-        android:textSize="@dimen/text_size_micro"
-        android:textColor="@color/default_text_color" />
+        android:layout_marginTop="-2dp" />
 
     <View
         android:layout_width="match_parent"
         android:layout_height="1dp"
         android:layout_alignParentBottom="true"
         android:layout_alignParentLeft="true"
-        android:layout_marginLeft="10dp"
-        android:layout_marginRight="10dp"
+        android:layout_marginLeft="@dimen/list_preferred_item_padding"
+        android:layout_marginRight="@dimen/list_preferred_item_padding"
         android:background="@color/list_item_divider_color" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6c18a71..2bef984 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -66,6 +66,9 @@
     <color name="header_action_bar_text_color">@color/white</color>
     <color name="bottom_action_bar_text_color">@color/default_text_color_light</color>
 
+    <!-- Background color used on some page headers -->
+    <color name="page_header_background">#fff4f4f4</color>
+
     <!-- Color for background for shadow on playlist page -->
     <color name="header_shadow_color">#ea31353f</color>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3a766d1..5ab4b1e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,6 +24,13 @@
     <dimen name="text_size_x_large">24.0sp</dimen>
     <dimen name="text_size_dayum">36.0sp</dimen>
 
+    <!-- Frequently used spacing between elements in many layouts -->
+    <dimen name="standard_padding">10.0dip</dimen>
+
+    <!-- overflow menu used on page headers and in list items -->
+    <dimen name="overflow_height">30.0dip</dimen>
+    <dimen name="overflow_width">24.0dip</dimen>
+
     <!-- List item section header -->
     <dimen name="list_item_header_height">30.0dip</dimen>
 
@@ -35,8 +42,8 @@
     <dimen name="list_item_padding_top">10.0dip</dimen>
     <dimen name="list_item_padding_bottom">10.0dip</dimen>
     <dimen name="list_item_header_size">16.0sp</dimen>
-    <dimen name="list_item_main_text_size">14.0sp</dimen>
-    <dimen name="list_item_secondary_text_size">12.0sp</dimen>
+    <dimen name="list_item_main_text_size">@dimens/text_size_small</dimen>
+    <dimen name="list_item_secondary_text_size">@dimens/text_size_micro</dimen>
     <dimen name="list_item_queue_text_padding_left">15.0dip</dimen>
 
     <!-- List view fast scroll padding left -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ad3a907..260a285 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -202,6 +202,9 @@
     <string name="header_n_albums"><xliff:g id="number">%d</xliff:g> Albums</string>
     <string name="header_5_plus_albums">5+ Albums</string>
 
+    <string name="duration_album_mins_only"><xliff:g id="format">%1$dm</xliff:g></string>
+    <string name="duration_album_hour_mins"><xliff:g id="format">%1$dh %2$dm</xliff:g></string>
+
     <string name="duration_mins"><xliff:g id="format">%3$dm %4$ds</xliff:g></string>
     <string name="duration_hours"><xliff:g id="format">%2$dh %3$dm %4$ds</xliff:g></string>
     <string name="duration_days"><xliff:g id="format">%1$dd %2$dd %3$dm %4$ds</xliff:g></string>
diff --git a/src/com/cyngn/eleven/Config.java b/src/com/cyngn/eleven/Config.java
index 2d18d71..99e90fb 100644
--- a/src/com/cyngn/eleven/Config.java
+++ b/src/com/cyngn/eleven/Config.java
@@ -54,6 +54,9 @@
      */
     public static final String ALBUM_YEAR = "album_year";
 
+    /** number of songs in a album or track list */
+    public static final String SONG_COUNT = "song_count";
+
     /**
      * The MIME type passed to a the profile activity
      */
diff --git a/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java b/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java
new file mode 100644
index 0000000..7f5dcee
--- /dev/null
+++ b/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java
@@ -0,0 +1,58 @@
+package com.cyngn.eleven.adapters;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.TextView;
+
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.loaders.AlbumSongLoader;
+import com.cyngn.eleven.model.Song;
+import com.cyngn.eleven.ui.activities.AlbumDetailActivity;
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.List;
+
+public class AlbumDetailSongAdapter extends DetailSongAdapter {
+
+    public AlbumDetailSongAdapter(Activity activity) {
+        super(activity);
+    }
+
+    protected int rowLayoutId() { return R.layout.album_detail_song; }
+
+    @Override // LoaderCallbacks
+    public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
+        return new AlbumSongLoader(mActivity, args.getLong(Config.ID));
+    }
+
+    @Override // LoaderCallbacks
+    public void onLoadFinished(Loader<List<Song>> loader, List<Song> songs) {
+        super.onLoadFinished(loader, songs);
+        ((AlbumDetailActivity)mActivity).update(songs);
+    }
+
+    protected Holder newHolder(View root, ImageFetcher fetcher) {
+        return new AlbumHolder(root, fetcher, mActivity);
+    }
+
+    private static class AlbumHolder extends Holder {
+        TextView duration;
+        Context context;
+
+        protected AlbumHolder(View root, ImageFetcher fetcher, Context context) {
+            super(root, fetcher);
+            this.context = context;
+            duration = (TextView)root.findViewById(R.id.duration);
+        }
+
+        protected void update(Song song) {
+            title.setText(song.mSongName);
+            duration.setText(MusicUtils.makeShortTimeString(context, song.mDuration));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java b/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java
index 64a1ece..c24ba7e 100644
--- a/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java
+++ b/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java
@@ -2,12 +2,8 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -16,92 +12,43 @@
 import com.cyngn.eleven.cache.ImageFetcher;
 import com.cyngn.eleven.loaders.ArtistSongLoader;
 import com.cyngn.eleven.model.Song;
-import com.cyngn.eleven.utils.ApolloUtils;
-import com.cyngn.eleven.utils.MusicUtils;
 
-import java.util.Collections;
 import java.util.List;
 
-public class ArtistDetailSongAdapter extends BaseAdapter
-implements LoaderCallbacks<List<Song>> {
-    private final Activity mActivity;
-    private final ImageFetcher mImageFetcher;
-    private final LayoutInflater mInflater;
-    private List<Song> mSongs = Collections.emptyList();
+public class ArtistDetailSongAdapter extends DetailSongAdapter {
 
-    public ArtistDetailSongAdapter(final Activity activity) {
-        mActivity = activity;
-        mImageFetcher = ApolloUtils.getImageFetcher(activity);
-        mInflater = LayoutInflater.from(activity);
+    public ArtistDetailSongAdapter(Activity activity) {
+        super(activity);
     }
 
-    @Override
-    public int getCount() { return mSongs.size(); }
-
-    @Override
-    public Song getItem(int pos) { return mSongs.get(pos); }
-
-    @Override
-    public long getItemId(int pos) { return pos; }
-
-    @Override
-    public View getView(int pos, View convertView, ViewGroup parent) {
-        if(convertView == null) {
-            convertView = mInflater.inflate(R.layout.artist_detail_song, parent, false);
-            convertView.setTag(new Holder(convertView));
-        }
-
-        Holder h = (Holder)convertView.getTag();
-        Song s = getItem(pos);
-        h.title.setText(s.mSongName);
-        h.album.setText(s.mAlbumName);
-
-        if (s.mAlbumId >= 0) {
-            mImageFetcher.loadAlbumImage(s.mArtistName, s.mAlbumName, s.mAlbumId, h.art);
-        }
-
-        addAction(convertView, s);
-
-        return convertView;
-    }
-
-    private void addAction(View view, final Song song) {
-        view.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                MusicUtils.playAll(mActivity, new long[] { song.mSongId }, -1, false);
-            }
-        });
-    }
+    protected int rowLayoutId() { return R.layout.artist_detail_song; }
 
     @Override // LoaderCallbacks
     public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
         return new ArtistSongLoader(mActivity, args.getLong(Config.ID));
     }
 
-    @Override // LoaderCallbacks
-    public void onLoadFinished(Loader<List<Song>> loader, List<Song> songs) {
-        if (songs.isEmpty()) { return; }
-        mSongs = songs;
-        notifyDataSetChanged();
+    protected Holder newHolder(View root, ImageFetcher fetcher) {
+        return new ArtistHolder(root, fetcher);
     }
 
-    @Override // LoaderCallbacks
-    public void onLoaderReset(Loader<List<Song>> loader) {
-        mSongs = Collections.emptyList();
-        notifyDataSetChanged();
-        mImageFetcher.flush();
-    }
-
-    private class Holder {
+    private static class ArtistHolder extends Holder {
         ImageView art;
-        TextView title;
         TextView album;
 
-        Holder(View root) {
+        protected ArtistHolder(View root, ImageFetcher fetcher) {
+            super(root, fetcher);
             art = (ImageView)root.findViewById(R.id.album_art);
-            title = (TextView)root.findViewById(R.id.title);
             album = (TextView)root.findViewById(R.id.album);
         }
+
+        protected void update(Song song) {
+            title.setText(song.mSongName);
+            album.setText(song.mAlbumName);
+
+            if (song.mAlbumId >= 0) {
+                fetcher.loadAlbumImage(song.mArtistName, song.mAlbumName, song.mAlbumId, art);
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/adapters/DetailSongAdapter.java b/src/com/cyngn/eleven/adapters/DetailSongAdapter.java
new file mode 100644
index 0000000..3bd2321
--- /dev/null
+++ b/src/com/cyngn/eleven/adapters/DetailSongAdapter.java
@@ -0,0 +1,102 @@
+package com.cyngn.eleven.adapters;
+
+import android.app.Activity;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.model.Song;
+import com.cyngn.eleven.utils.ApolloUtils;
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+public abstract class DetailSongAdapter extends BaseAdapter
+implements LoaderCallbacks<List<Song>> {
+    protected final Activity mActivity;
+    private final ImageFetcher mImageFetcher;
+    private final LayoutInflater mInflater;
+    private List<Song> mSongs = Collections.emptyList();
+
+    public DetailSongAdapter(final Activity activity) {
+        mActivity = activity;
+        mImageFetcher = ApolloUtils.getImageFetcher(activity);
+        mInflater = LayoutInflater.from(activity);
+    }
+
+    @Override
+    public int getCount() { return mSongs.size(); }
+
+    @Override
+    public Song getItem(int pos) { return mSongs.get(pos); }
+
+    @Override
+    public long getItemId(int pos) { return pos; }
+
+    @Override
+    public View getView(int pos, View convertView, ViewGroup parent) {
+        if(convertView == null) {
+            convertView = mInflater.inflate(rowLayoutId(), parent, false);
+            convertView.setTag(newHolder(convertView, mImageFetcher));
+        }
+
+        Holder holder = (Holder)convertView.getTag();
+
+        Song song = getItem(pos);
+        holder.update(song);
+        addAction(convertView, pos);
+
+        return convertView;
+    }
+
+    protected abstract int rowLayoutId();
+
+    private void addAction(View view, final int position) {
+        view.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // play clicked song and enqueue all following songs
+                long[] toPlay = new long[getCount() - position];
+                for(int i = 0; i < toPlay.length; i++) {
+                    toPlay[i] = getItem(position + i).mSongId;
+                }
+                MusicUtils.playAll(mActivity, toPlay, -1, false);
+            }
+        });
+    }
+
+    @Override // LoaderCallbacks
+    public void onLoadFinished(Loader<List<Song>> loader, List<Song> songs) {
+        if (songs.isEmpty()) { return; }
+        mSongs = songs;
+        notifyDataSetChanged();
+    }
+
+    @Override // LoaderCallbacks
+    public void onLoaderReset(Loader<List<Song>> loader) {
+        mSongs = Collections.emptyList();
+        notifyDataSetChanged();
+        mImageFetcher.flush();
+    }
+
+    protected abstract Holder newHolder(View root, ImageFetcher fetcher);
+
+    protected static abstract class Holder {
+        protected ImageFetcher fetcher;
+        protected TextView title;
+
+        protected Holder(View root, ImageFetcher fetcher) {
+            this.fetcher = fetcher;
+            title = (TextView)root.findViewById(R.id.title);
+        }
+
+        protected abstract void update(Song song);
+    }
+}
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/ui/activities/AlbumDetailActivity.java b/src/com/cyngn/eleven/ui/activities/AlbumDetailActivity.java
new file mode 100644
index 0000000..e2ee2b9
--- /dev/null
+++ b/src/com/cyngn/eleven/ui/activities/AlbumDetailActivity.java
@@ -0,0 +1,139 @@
+package com.cyngn.eleven.ui.activities;
+
+import android.app.ActionBar;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.v4.app.LoaderManager;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.adapters.AlbumDetailSongAdapter;
+import com.cyngn.eleven.adapters.DetailSongAdapter;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.model.Song;
+import com.cyngn.eleven.utils.GenreFetcher;
+
+import java.util.List;
+import java.util.Locale;
+
+public class AlbumDetailActivity extends SlidingPanelActivity {
+
+    private ListView mSongs;
+    private DetailSongAdapter mSongAdapter;
+    private TextView mAlbumDuration;
+    private TextView mGenre;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Bundle arguments = getIntent().getExtras();
+        String artistName = arguments.getString(Config.ARTIST_NAME);
+
+        setupActionBar(artistName);
+
+        View root = findViewById(R.id.activity_base_content);
+
+        setupHeader(root, artistName, arguments);
+        setupSongList(root);
+
+        LoaderManager lm = getSupportLoaderManager();
+        lm.initLoader(1, arguments, mSongAdapter);
+    }
+
+    private void setupHeader(View root, String artist, Bundle arguments) {
+        String album = arguments.getString(Config.NAME);
+        String year = arguments.getString(Config.ALBUM_YEAR);
+        long albumId = arguments.getLong(Config.ID);
+        int songCount = arguments.getInt(Config.SONG_COUNT);
+
+        ImageView albumArt = (ImageView)root.findViewById(R.id.album_art);
+        albumArt.setContentDescription(album);
+        ImageFetcher.getInstance(this).loadAlbumImage(artist, album, albumId, albumArt);
+
+        TextView title = (TextView)root.findViewById(R.id.title);
+        title.setText(album);
+
+        setupCountAndYear(root, year, songCount);
+
+        // will be updated once we have song data
+        mAlbumDuration = (TextView)root.findViewById(R.id.duration);
+        mGenre = (TextView)root.findViewById(R.id.genre);
+    }
+
+    private void setupCountAndYear(View root, String year, int songCount) {
+        TextView songCountAndYear = (TextView)root.findViewById(R.id.song_count_and_year);
+        if(songCount > 0) {
+            String countText = getResources().
+                    getQuantityString(R.plurals.Nsongs, songCount, songCount);
+            if(year == null) {
+                songCountAndYear.setText(countText);
+            } else {
+                songCountAndYear.setText(getString(R.string.combine_two_strings, countText, year));
+            }
+        } else if(year != null) {
+            songCountAndYear.setText(year);
+        }
+    }
+
+    private void setupSongList(View root) {
+        mSongs = (ListView)root.findViewById(R.id.songs);
+        mSongAdapter = new AlbumDetailSongAdapter(this);
+        mSongs.setAdapter(mSongAdapter);
+    }
+
+    @Override
+    protected int getLayoutToInflate() { return R.layout.activity_album_detail; }
+
+    protected void setupActionBar(String name) {
+        ActionBar actionBar = getActionBar();
+        actionBar.setTitle(name.toUpperCase(Locale.getDefault()));
+        actionBar.setIcon(R.drawable.ic_action_back);
+        actionBar.setHomeButtonEnabled(true);
+    }
+
+    /** cause action bar icon tap to act like back -- boo-urns! */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                finish();
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /** called back by song loader */
+    public void update(List<Song> songs) {
+        /** compute total run time for album */
+        int duration = 0;
+        for(Song s : songs) { duration += s.mDuration; }
+        updateDuration(duration);
+
+        /** use the first song on the album to get a genre */
+        if(!songs.isEmpty()) {
+            GenreFetcher.fetch(this, (int)songs.get(0).mSongId, mGenre);
+        }
+    }
+
+    public void updateDuration(int duration) {
+        int mins = Math.round(duration/60);
+        int hours = mins/60;
+        mins %= 60;
+
+        String durationText = (hours == 0)
+            ? getString(R.string.duration_album_mins_only, mins)
+            : getString(R.string.duration_album_hour_mins, hours, mins);
+
+        mAlbumDuration.setText(durationText);
+    }
+}
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/utils/GenreFetcher.java b/src/com/cyngn/eleven/utils/GenreFetcher.java
new file mode 100644
index 0000000..befc03d
--- /dev/null
+++ b/src/com/cyngn/eleven/utils/GenreFetcher.java
@@ -0,0 +1,52 @@
+package com.cyngn.eleven.utils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.TextView;
+
+public class GenreFetcher implements LoaderCallbacks<Cursor> {
+    private static final String[] GENRE_PROJECTION = new String[] { MediaStore.Audio.Genres.NAME };
+
+    private Context mContext;
+    private int mSongId;
+    private TextView mTextView;
+
+    public static void fetch(FragmentActivity activity, int songId, TextView textView) {
+        LoaderManager lm = activity.getSupportLoaderManager();
+        lm.initLoader(0, null, new GenreFetcher(activity, songId, textView));
+    }
+
+    private GenreFetcher(Context context, int songId, TextView textView) {
+        mContext = context;
+        mSongId = songId;
+        mTextView = textView;
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        return new CursorLoader(mContext,
+            MediaStore.Audio.Genres.getContentUriForAudioId("external", mSongId),
+            GENRE_PROJECTION, null, null, null);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+        if(mTextView != null && cursor.moveToFirst()) {
+            mTextView.setText(cursor.getString(0));
+            mTextView.setVisibility(View.VISIBLE);
+        } else {
+            mTextView.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {}
+}
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/utils/MusicUtils.java b/src/com/cyngn/eleven/utils/MusicUtils.java
index 6b32d4d..c59dc71 100644
--- a/src/com/cyngn/eleven/utils/MusicUtils.java
+++ b/src/com/cyngn/eleven/utils/MusicUtils.java
@@ -22,7 +22,6 @@
 import android.content.ServiceConnection;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.BaseColumns;
@@ -36,7 +35,6 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.view.Menu;
-import android.view.SubMenu;
 
 import com.cyngn.eleven.IElevenService;
 import com.cyngn.eleven.MusicPlaybackService;
@@ -52,8 +50,6 @@
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.Formatter;
-import java.util.Locale;
 import java.util.WeakHashMap;
 
 /**
@@ -1049,12 +1045,17 @@
         }
     }
 
+    public static final String getSongCountForAlbum(final Context context, final long id) {
+        Integer i = getSongCountForAlbumInt(context, id);
+        return i == null ? null : Integer.toString(i);
+    }
+
     /**
      * @param context The {@link Context} to use.
      * @param id The id of the album.
      * @return The song count for an album.
      */
-    public static final String getSongCountForAlbum(final Context context, final long id) {
+    public static final Integer getSongCountForAlbumInt(final Context context, final long id) {
         if (id == -1) {
             return null;
         }
@@ -1062,11 +1063,13 @@
         Cursor cursor = context.getContentResolver().query(uri, new String[] {
                     AlbumColumns.NUMBER_OF_SONGS
                 }, null, null, null);
-        String songCount = null;
+        Integer songCount = null;
         if (cursor != null) {
             cursor.moveToFirst();
             if (!cursor.isAfterLast()) {
-                songCount = cursor.getString(0);
+                if(!cursor.isNull(0)) {
+                    songCount = cursor.getInt(0);
+                }
             }
             cursor.close();
             cursor = null;
diff --git a/src/com/cyngn/eleven/utils/NavUtils.java b/src/com/cyngn/eleven/utils/NavUtils.java
index 883824c..5dbc541 100644
--- a/src/com/cyngn/eleven/utils/NavUtils.java
+++ b/src/com/cyngn/eleven/utils/NavUtils.java
@@ -21,10 +21,10 @@
 
 import com.cyngn.eleven.Config;
 import com.cyngn.eleven.R;
+import com.cyngn.eleven.ui.activities.AlbumDetailActivity;
 import com.cyngn.eleven.ui.activities.ArtistDetailActivity;
 import com.cyngn.eleven.ui.activities.HomeActivity;
 import com.cyngn.eleven.ui.activities.PlaylistDetailActivity;
-import com.cyngn.eleven.ui.activities.ProfileActivity;
 import com.cyngn.eleven.ui.activities.SearchActivity;
 import com.cyngn.eleven.ui.activities.SettingsActivity;
 import com.cyngn.eleven.ui.activities.SmartPlaylistDetailActivity;
@@ -71,14 +71,15 @@
         // Create a new bundle to transfer the album info
         final Bundle bundle = new Bundle();
         bundle.putString(Config.ALBUM_YEAR, MusicUtils.getReleaseDateForAlbum(context, albumId));
+        bundle.putInt(Config.SONG_COUNT, MusicUtils.getSongCountForAlbumInt(context, albumId));
         bundle.putString(Config.ARTIST_NAME, artistName);
         bundle.putString(Config.MIME_TYPE, MediaStore.Audio.Albums.CONTENT_TYPE);
         bundle.putLong(Config.ID, albumId);
         bundle.putString(Config.NAME, albumName);
 
         // Create the intent to launch the profile activity
-        final Intent intent = new Intent(context, ProfileActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        final Intent intent = new Intent(context, AlbumDetailActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.putExtras(bundle);
         context.startActivity(intent);
     }