OSDN Git Service

Eleven: Add song indicators, change ProfileSongAdapter to use SongAdapter
authorlinus_lee <llee@cyngn.com>
Fri, 3 Oct 2014 18:06:43 +0000 (11:06 -0700)
committerlinus_lee <llee@cyngn.com>
Thu, 20 Nov 2014 20:51:32 +0000 (12:51 -0800)
Much larger change than anticipated.  A look of hook ins into the MusicPlaybackService
Also a lot of changes here and there
Still, a portion of the music playback service needs to be rewritten in bug https://cyanogen.atlassian.net/browse/MUSIC-44

https://cyanogen.atlassian.net/browse/MUSIC-56

Change-Id: I644b4347e1655ebb529cd0dd0a7a34ce2bd399c1

42 files changed:
res/drawable-xxhdpi/now_playing_icon.png [new file with mode: 0644]
res/layout/album_detail_song.xml
res/layout/artist_detail_song.xml
res/layout/list_item_common.xml
res/layout/list_item_top_tracks.xml
src/com/cyngn/eleven/Config.java
src/com/cyngn/eleven/IElevenService.aidl
src/com/cyngn/eleven/MusicPlaybackService.java
src/com/cyngn/eleven/adapters/AlbumAdapter.java
src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java
src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java
src/com/cyngn/eleven/adapters/DetailSongAdapter.java
src/com/cyngn/eleven/adapters/ProfileSongAdapter.java
src/com/cyngn/eleven/adapters/SongAdapter.java
src/com/cyngn/eleven/loaders/LastAddedLoader.java
src/com/cyngn/eleven/model/SearchResult.java
src/com/cyngn/eleven/provider/MusicDB.java
src/com/cyngn/eleven/provider/MusicPlaybackState.java [new file with mode: 0644]
src/com/cyngn/eleven/provider/PlaylistArtworkStore.java
src/com/cyngn/eleven/provider/RecentStore.java
src/com/cyngn/eleven/provider/SearchHistory.java
src/com/cyngn/eleven/provider/SongPlayCount.java
src/com/cyngn/eleven/service/MusicPlaybackTrack.aidl [new file with mode: 0644]
src/com/cyngn/eleven/service/MusicPlaybackTrack.java [new file with mode: 0644]
src/com/cyngn/eleven/ui/MusicHolder.java
src/com/cyngn/eleven/ui/activities/SearchActivity.java
src/com/cyngn/eleven/ui/activities/ShortcutActivity.java
src/com/cyngn/eleven/ui/fragments/AlbumDetailFragment.java
src/com/cyngn/eleven/ui/fragments/ArtistDetailFragment.java
src/com/cyngn/eleven/ui/fragments/ArtistFragment.java
src/com/cyngn/eleven/ui/fragments/PlaylistDetailFragment.java
src/com/cyngn/eleven/ui/fragments/PlaylistFragment.java
src/com/cyngn/eleven/ui/fragments/QueueFragment.java
src/com/cyngn/eleven/ui/fragments/RecentFragment.java
src/com/cyngn/eleven/ui/fragments/SongFragment.java
src/com/cyngn/eleven/ui/fragments/profile/BasicSongFragment.java
src/com/cyngn/eleven/ui/fragments/profile/LastAddedFragment.java
src/com/cyngn/eleven/ui/fragments/profile/TopTracksFragment.java
src/com/cyngn/eleven/utils/AlbumPopupMenuHelper.java
src/com/cyngn/eleven/utils/MusicUtils.java
src/com/cyngn/eleven/utils/NavUtils.java
src/com/cyngn/eleven/utils/PopupMenuHelper.java

diff --git a/res/drawable-xxhdpi/now_playing_icon.png b/res/drawable-xxhdpi/now_playing_icon.png
new file mode 100644 (file)
index 0000000..2792c21
Binary files /dev/null and b/res/drawable-xxhdpi/now_playing_icon.png differ
index 17fbdaa..e40d842 100644 (file)
@@ -2,15 +2,28 @@
     android:layout_width="match_parent"
     android:layout_height="70dp" >
 
-    <com.cyngn.eleven.widgets.PopupMenuButton
-        android:id="@+id/overflow"
-        android:layout_width="@dimen/overflow_width"
-        android:layout_height="68dp"
-        android:layout_alignParentTop="true"
+    <LinearLayout
+        android:id="@+id/right_container"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
         android:layout_alignParentRight="true"
-        android:layout_marginBottom="2dp"
-        android:gravity="center"
-        android:src="@drawable/menu_button" />
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@+id/now_playing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/now_playing_icon"
+            android:visibility="gone" />
+
+        <com.cyngn.eleven.widgets.PopupMenuButton
+            android:id="@+id/overflow"
+            android:layout_width="@dimen/overflow_width"
+            android:layout_height="@dimen/overflow_height"
+            android:gravity="center"
+            android:src="@drawable/menu_button" />
+    </LinearLayout>
 
     <TextView
         android:id="@+id/title"
@@ -19,7 +32,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@id/overflow"
+        android:layout_toLeftOf="@id/right_container"
         android:paddingTop="6dp"
         android:layout_marginTop="@dimen/list_preferred_item_padding"
         android:layout_marginLeft="@dimen/list_preferred_item_padding" />
@@ -31,7 +44,7 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/title"
         android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@id/overflow"
+        android:layout_toLeftOf="@id/right_container"
         android:layout_marginTop="-2dp"
         android:layout_marginLeft="@dimen/standard_padding" />
 
index e5cfa8d..ec99655 100644 (file)
         android:layout_alignParentLeft="true"
         android:layout_margin="@dimen/list_preferred_item_padding" />
 
-    <com.cyngn.eleven.widgets.PopupMenuButton
-        android:id="@+id/overflow"
-        android:layout_width="@dimen/overflow_width"
-        android:layout_height="68dp"
-        android:layout_alignParentTop="true"
+    <LinearLayout
+        android:id="@+id/right_container"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
         android:layout_alignParentRight="true"
-        android:layout_marginBottom="2dp"
-        android:gravity="center"
-        android:src="@drawable/menu_button" />
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@+id/now_playing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/now_playing_icon"
+            android:visibility="gone" />
+
+        <com.cyngn.eleven.widgets.PopupMenuButton
+            android:id="@+id/overflow"
+            android:layout_width="@dimen/overflow_width"
+            android:layout_height="@dimen/overflow_height"
+            android:src="@drawable/menu_button" />
+    </LinearLayout>
 
     <TextView
         android:id="@+id/title"
@@ -26,7 +38,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
-        android:layout_toLeftOf="@id/overflow"
+        android:layout_toLeftOf="@id/right_container"
         android:layout_toRightOf="@id/album_art"
         android:layout_marginTop="16dp" />
 
@@ -37,7 +49,7 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/title"
         android:layout_toRightOf="@id/album_art"
-        android:layout_toLeftOf="@id/overflow"
+        android:layout_toLeftOf="@id/right_container"
         android:layout_marginTop="-2dp" />
 
     <ImageView
index f9f09ee..b4ef0c7 100644 (file)
         style="@style/ListItemSecondaryText.Single"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_toLeftOf="@+id/popup_menu_button"
+        android:layout_toLeftOf="@+id/right_container"
         android:layout_centerVertical="true" />
 
-    <com.cyngn.eleven.widgets.PopupMenuButton
-        android:id="@id/popup_menu_button"
+    <LinearLayout
+        android:id="@id/right_container"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_height="fill_parent"
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
-        android:src="@drawable/menu_button" />
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@+id/now_playing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/now_playing_icon"
+            android:visibility="gone" />
 
+        <com.cyngn.eleven.widgets.PopupMenuButton
+            android:id="@+id/popup_menu_button"
+            android:layout_width="@dimen/overflow_width"
+            android:layout_height="@dimen/overflow_height"
+            android:src="@drawable/menu_button" />
+    </LinearLayout>
 </merge>
\ No newline at end of file
index 9a8bfb6..aae5043 100644 (file)
@@ -56,7 +56,7 @@
             android:layout_width="match_parent"
             android:layout_height="@dimen/list_item_image_height"
             android:layout_toRightOf="@+id/position_contanier"
-            android:layout_toLeftOf="@+id/popup_menu_button"
+            android:layout_toLeftOf="@+id/right_container"
             android:gravity="center_vertical"
             android:minHeight="@dimen/item_normal_height"
             android:paddingLeft="@dimen/list_preferred_item_padding" >
                 android:layout_below="@+id/line_one" />
         </RelativeLayout>
 
-        <com.cyngn.eleven.widgets.PopupMenuButton
-            android:id="@id/popup_menu_button"
+        <LinearLayout
+            android:id="@id/right_container"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_height="fill_parent"
             android:layout_alignParentRight="true"
             android:layout_centerVertical="true"
-            android:src="@drawable/menu_button" />
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+            <ImageView
+                android:id="@+id/now_playing"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/now_playing_icon"
+                android:visibility="gone" />
+
+            <com.cyngn.eleven.widgets.PopupMenuButton
+                android:id="@+id/popup_menu_button"
+                android:layout_width="@dimen/overflow_width"
+                android:layout_height="@dimen/overflow_height"
+                android:src="@drawable/menu_button" />
+        </LinearLayout>
     </RelativeLayout>
 
     <ImageView
index af8f1ee..c1b84f5 100644 (file)
@@ -98,4 +98,31 @@ public final class Config {
             return null;
         }
     }
+
+    /**
+     * This helps identify where an id has come from.  Mainly used to determine when a user
+     * clicks a song where that song came from (artist/album/playlist)
+     */
+    public static enum IdType {
+        NA(0),
+        Artist(1),
+        Album(2),
+        Playlist(3);
+
+        public final int mId;
+
+        IdType(final int id) {
+            mId = id;
+        }
+
+        public static IdType getTypeById(int id) {
+            for (IdType type : values()) {
+                if (type.mId == id) {
+                    return type;
+                }
+            }
+
+            throw new IllegalArgumentException("Unrecognized id: " + id);
+        }
+    }
 }
index c15ccf1..9f8813f 100644 (file)
@@ -1,17 +1,17 @@
 package com.cyngn.eleven;
 
-import android.graphics.Bitmap;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 
 interface IElevenService
 {
     void openFile(String path);
-    void open(in long [] list, int position);
+    void open(in long [] list, int position, long sourceId, int sourceType);
     void stop();
     void pause();
     void play();
     void prev(boolean forcePrevious);
     void next();
-    void enqueue(in long [] list, int action);
+    void enqueue(in long [] list, int action, long sourceId, int sourceType);
     void setQueuePosition(int index);
     void setShuffleMode(int shufflemode);
     void setRepeatMode(int repeatmode);
@@ -27,6 +27,8 @@ interface IElevenService
     long position();
     long seek(long pos);
     long getAudioId();
+    MusicPlaybackTrack getCurrentTrack();
+    MusicPlaybackTrack getTrack(int index);
     long getNextAudioId();
     long getPreviousAudioId();
     long getArtistId();
index 81d0067..443088c 100644 (file)
@@ -29,7 +29,6 @@ import android.media.AudioManager;
 import android.media.AudioManager.OnAudioFocusChangeListener;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
 import android.media.RemoteControlClient;
 import android.media.audiofx.AudioEffect;
 import android.net.Uri;
@@ -47,21 +46,24 @@ import android.provider.MediaStore.Audio.AlbumColumns;
 import android.provider.MediaStore.Audio.AudioColumns;
 import android.util.Log;
 
+import com.cyngn.eleven.Config.IdType;
 import com.cyngn.eleven.appwidgets.AppWidgetLarge;
 import com.cyngn.eleven.appwidgets.AppWidgetLargeAlternate;
 import com.cyngn.eleven.appwidgets.AppWidgetSmall;
 import com.cyngn.eleven.cache.ImageCache;
 import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.provider.MusicPlaybackState;
 import com.cyngn.eleven.provider.RecentStore;
 import com.cyngn.eleven.provider.SongPlayCount;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 import com.cyngn.eleven.utils.ApolloUtils;
 import com.cyngn.eleven.utils.Lists;
-import com.cyngn.eleven.utils.MusicUtils;
-import com.cyngn.eleven.utils.PreferenceUtils;
 
 import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.ListIterator;
 import java.util.Random;
 import java.util.TreeSet;
 
@@ -325,7 +327,7 @@ public class MusicPlaybackService extends Service {
     /**
      * Keeps a mapping of the track history
      */
-    private static final LinkedList<Integer> mHistory = Lists.newLinkedList();
+    private static LinkedList<Integer> mHistory = Lists.newLinkedList();
 
     /**
      * Used to shuffle the tracks
@@ -444,8 +446,6 @@ public class MusicPlaybackService extends Service {
     // playlists
     private int mCardId;
 
-    private int mPlayListLen = 0;
-
     private int mPlayPos = -1;
 
     private int mNextPlayPos = -1;
@@ -460,7 +460,7 @@ public class MusicPlaybackService extends Service {
 
     private int mServiceStartId = -1;
 
-    private long[] mPlayList = null;
+    private ArrayList<MusicPlaybackTrack> mPlaylist = new ArrayList<MusicPlaybackTrack>(100);
 
     private long[] mAutoShuffleList = null;
 
@@ -489,6 +489,11 @@ public class MusicPlaybackService extends Service {
     private SongPlayCount mSongPlayCountCache;
 
     /**
+     * Stores the playback state
+     */
+    private MusicPlaybackState mPlaybackStateStore;
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -518,7 +523,7 @@ public class MusicPlaybackService extends Service {
             // before stopping the service, so that pause/resume isn't slow.
             // Also delay stopping the service if we're transitioning between
             // tracks.
-        } else if (mPlayListLen > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
+        } else if (mPlaylist.size() > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
             scheduleDelayedShutdown();
             return true;
         }
@@ -549,6 +554,9 @@ public class MusicPlaybackService extends Service {
         // gets the song play count cache
         mSongPlayCountCache = SongPlayCount.getInstance(this);
 
+        // gets a pointer to the playback state store
+        mPlaybackStateStore = MusicPlaybackState.getInstance(this);
+
         // Initialize the notification helper
         mNotificationHelper = new NotificationHelper(this);
 
@@ -921,8 +929,8 @@ public class MusicPlaybackService extends Service {
                 return 0;
             } else if (first < 0) {
                 first = 0;
-            } else if (last >= mPlayListLen) {
-                last = mPlayListLen - 1;
+            } else if (last >= mPlaylist.size()) {
+                last = mPlaylist.size() - 1;
             }
 
             boolean gotonext = false;
@@ -932,21 +940,41 @@ public class MusicPlaybackService extends Service {
             } else if (mPlayPos > last) {
                 mPlayPos -= last - first + 1;
             }
-            final int num = mPlayListLen - last - 1;
-            for (int i = 0; i < num; i++) {
-                mPlayList[first + i] = mPlayList[last + 1 + i];
-            }
-            mPlayListLen -= last - first + 1;
+            final int numToRemove = last - first + 1;
 
+            if (first == 0 && last == mPlaylist.size() - 1) {
+                mPlaylist.clear();
+                mHistory.clear();
+            } else {
+                for (int i = 0; i < numToRemove; i++) {
+                    mPlaylist.remove(first);
+                }
+
+                // remove the items from the history
+                // this is not ideal as the history shouldn't be impacted by this
+                // but since we are removing items from the array, it will throw
+                // an exception if we keep it around.  Idealistically with the queue
+                // rewrite this should be all be fixed
+                // https://cyanogen.atlassian.net/browse/MUSIC-44
+                ListIterator<Integer> positionIterator = mHistory.listIterator();
+                while (positionIterator.hasNext()) {
+                    int pos = positionIterator.next();
+                    if (pos >= first && pos <= last) {
+                        positionIterator.remove();
+                    } else if (pos > last) {
+                        positionIterator.set(pos - numToRemove);
+                    }
+                }
+            }
             if (gotonext) {
-                if (mPlayListLen == 0) {
+                if (mPlaylist.size() == 0) {
                     stop(true);
                     mPlayPos = -1;
                     closeCursor();
                 } else {
                     if (mShuffleMode != SHUFFLE_NONE) {
                         mPlayPos = getNextPosition(true);
-                    } else if (mPlayPos >= mPlayListLen) {
+                    } else if (mPlayPos >= mPlaylist.size()) {
                         mPlayPos = 0;
                     }
                     final boolean wasPlaying = isPlaying();
@@ -968,27 +996,23 @@ public class MusicPlaybackService extends Service {
      * @param list The list to add
      * @param position The position to place the tracks
      */
-    private void addToPlayList(final long[] list, int position) {
+    private void addToPlayList(final long[] list, int position, long sourceId, IdType sourceType) {
         final int addlen = list.length;
         if (position < 0) {
-            mPlayListLen = 0;
+            mPlaylist.clear();
             position = 0;
         }
-        ensurePlayListCapacity(mPlayListLen + addlen);
-        if (position > mPlayListLen) {
-            position = mPlayListLen;
-        }
 
-        final int tailsize = mPlayListLen - position;
-        for (int i = tailsize; i > 0; i--) {
-            mPlayList[position + i] = mPlayList[position + i - addlen];
+        mPlaylist.ensureCapacity(mPlaylist.size() + addlen);
+        if (position > mPlaylist.size()) {
+            position = mPlaylist.size();
         }
 
-        for (int i = 0; i < addlen; i++) {
-            mPlayList[position + i] = list[i];
+        for (int i = 0; i < list.length; i++) {
+            mPlaylist.add(new MusicPlaybackTrack(list[i], sourceId, sourceType, i));
         }
-        mPlayListLen += addlen;
-        if (mPlayListLen == 0) {
+
+        if (mPlaylist.size() == 0) {
             closeCursor();
             notifyChange(META_CHANGED);
         }
@@ -1072,12 +1096,12 @@ public class MusicPlaybackService extends Service {
         synchronized (this) {
             closeCursor();
 
-            if (mPlayListLen == 0) {
+            if (mPlaylist.size() == 0) {
                 return;
             }
             stop(false);
 
-            updateCursor(mPlayList[mPlayPos]);
+            updateCursor(mPlaylist.get(mPlayPos).mId);
             while (true) {
                 if (mCursor != null
                         && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
@@ -1088,7 +1112,7 @@ public class MusicPlaybackService extends Service {
                 // cursor now, because
                 // we're either going to create a new one next, or stop trying
                 closeCursor();
-                if (mOpenFailedCounter++ < 10 && mPlayListLen > 1) {
+                if (mOpenFailedCounter++ < 10 && mPlaylist.size() > 1) {
                     final int pos = getNextPosition(false);
                     if (pos < 0) {
                         scheduleDelayedShutdown();
@@ -1101,7 +1125,7 @@ public class MusicPlaybackService extends Service {
                     mPlayPos = pos;
                     stop(false);
                     mPlayPos = pos;
-                    updateCursor(mPlayList[mPlayPos]);
+                    updateCursor(mPlaylist.get(mPlayPos).mId);
                 } else {
                     mOpenFailedCounter = 0;
                     Log.w(TAG, "Failed to open file for playback");
@@ -1133,7 +1157,7 @@ public class MusicPlaybackService extends Service {
             }
             return mPlayPos;
         } else if (mShuffleMode == SHUFFLE_NORMAL) {
-            final int numTracks = mPlayListLen;
+            final int numTracks = mPlaylist.size();
 
             // count the number of times a track has been played
             final int[] trackNumPlays = new int[numTracks];
@@ -1200,7 +1224,7 @@ public class MusicPlaybackService extends Service {
             doAutoShuffleUpdate();
             return mPlayPos + 1;
         } else {
-            if (mPlayPos >= mPlayListLen - 1) {
+            if (mPlayPos >= mPlaylist.size() - 1) {
                 if (mRepeatMode == REPEAT_NONE && !force) {
                     return -1;
                 } else if (mRepeatMode == REPEAT_ALL || force) {
@@ -1227,8 +1251,8 @@ public class MusicPlaybackService extends Service {
     private void setNextTrack(int position) {
         mNextPlayPos = position;
         if (D) Log.d(TAG, "setNextTrack: next play position = " + mNextPlayPos);
-        if (mNextPlayPos >= 0 && mPlayList != null) {
-            final long id = mPlayList[mNextPlayPos];
+        if (mNextPlayPos >= 0 && mPlaylist != null) {
+            final long id = mPlaylist.get(mNextPlayPos).mId;
             mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
         } else {
             mPlayer.setNextDataSource(null);
@@ -1275,7 +1299,7 @@ public class MusicPlaybackService extends Service {
             removeTracks(0, mPlayPos - 9);
             notify = true;
         }
-        final int toAdd = 7 - (mPlayListLen - (mPlayPos < 0 ? -1 : mPlayPos));
+        final int toAdd = 7 - (mPlaylist.size() - (mPlayPos < 0 ? -1 : mPlayPos));
         for (int i = 0; i < toAdd; i++) {
             int lookback = mHistory.size();
             int idx = -1;
@@ -1290,8 +1314,7 @@ public class MusicPlaybackService extends Service {
             if (mHistory.size() > MAX_HISTORY_SIZE) {
                 mHistory.remove(0);
             }
-            ensurePlayListCapacity(mPlayListLen + 1);
-            mPlayList[mPlayListLen++] = mAutoShuffleList[idx];
+            mPlaylist.add(new MusicPlaybackTrack(mAutoShuffleList[idx], -1, IdType.NA, -1));
             notify = true;
         }
         if (notify) {
@@ -1319,27 +1342,6 @@ public class MusicPlaybackService extends Service {
     }
 
     /**
-     * Makes sure the playlist has enough space to hold all of the songs
-     *
-     * @param size The size of the playlist
-     */
-    private void ensurePlayListCapacity(final int size) {
-        if (mPlayList == null || size > mPlayList.length) {
-            // reallocate at 2x requested size so we don't
-            // need to grow and copy the array for every
-            // insert
-            final long[] newlist = new long[size * 2];
-            final int len = mPlayList != null ? mPlayList.length : mPlayListLen;
-            for (int i = 0; i < len; i++) {
-                newlist[i] = mPlayList[i];
-            }
-            mPlayList = newlist;
-        }
-        // FIXME: shrink the array when the needed size is much smaller
-        // than the allocated size
-    }
-
-    /**
      * Notify the change-receivers that something has changed.
      */
     private void notifyChange(final String what) {
@@ -1443,43 +1445,9 @@ public class MusicPlaybackService extends Service {
 
         final SharedPreferences.Editor editor = mPreferences.edit();
         if (full) {
-            final StringBuilder q = new StringBuilder();
-            int len = mPlayListLen;
-            for (int i = 0; i < len; i++) {
-                long n = mPlayList[i];
-                if (n < 0) {
-                    continue;
-                } else if (n == 0) {
-                    q.append("0;");
-                } else {
-                    while (n != 0) {
-                        final int digit = (int)(n & 0xf);
-                        n >>>= 4;
-                        q.append(HEX_DIGITS[digit]);
-                    }
-                    q.append(";");
-                }
-            }
-            editor.putString("queue", q.toString());
+            mPlaybackStateStore.saveState(mPlaylist,
+                    mShuffleMode != SHUFFLE_NONE ? mHistory : null);
             editor.putInt("cardid", mCardId);
-            if (mShuffleMode != SHUFFLE_NONE) {
-                len = mHistory.size();
-                q.setLength(0);
-                for (int i = 0; i < len; i++) {
-                    int n = mHistory.get(i);
-                    if (n == 0) {
-                        q.append("0;");
-                    } else {
-                        while (n != 0) {
-                            final int digit = n & 0xf;
-                            n >>>= 4;
-                            q.append(HEX_DIGITS[digit]);
-                        }
-                        q.append(";");
-                    }
-                }
-                editor.putString("history", q.toString());
-            }
         }
         editor.putInt("curpos", mPlayPos);
         if (mPlayer.isInitialized()) {
@@ -1495,50 +1463,24 @@ public class MusicPlaybackService extends Service {
      * Apollo
      */
     private void reloadQueue() {
-        String q = null;
         int id = mCardId;
         if (mPreferences.contains("cardid")) {
             id = mPreferences.getInt("cardid", ~mCardId);
         }
         if (id == mCardId) {
-            q = mPreferences.getString("queue", "");
-        }
-        int qlen = q != null ? q.length() : 0;
-        if (qlen > 1) {
-            int plen = 0;
-            int n = 0;
-            int shift = 0;
-            for (int i = 0; i < qlen; i++) {
-                final char c = q.charAt(i);
-                if (c == ';') {
-                    ensurePlayListCapacity(plen + 1);
-                    mPlayList[plen] = n;
-                    plen++;
-                    n = 0;
-                    shift = 0;
-                } else {
-                    if (c >= '0' && c <= '9') {
-                        n += c - '0' << shift;
-                    } else if (c >= 'a' && c <= 'f') {
-                        n += 10 + c - 'a' << shift;
-                    } else {
-                        plen = 0;
-                        break;
-                    }
-                    shift += 4;
-                }
-            }
-            mPlayListLen = plen;
+            mPlaylist = mPlaybackStateStore.getQueue();
+        }
+        if (mPlaylist.size() > 0) {
             final int pos = mPreferences.getInt("curpos", 0);
-            if (pos < 0 || pos >= mPlayListLen) {
-                mPlayListLen = 0;
+            if (pos < 0 || pos >= mPlaylist.size()) {
+                mPlaylist.clear();
                 return;
             }
             mPlayPos = pos;
-            updateCursor(mPlayList[mPlayPos]);
+            updateCursor(mPlaylist.get(mPlayPos).mId);
             if (mCursor == null) {
                 SystemClock.sleep(3000);
-                updateCursor(mPlayList[mPlayPos]);
+                updateCursor(mPlaylist.get(mPlayPos).mId);
             }
             synchronized (this) {
                 closeCursor();
@@ -1546,7 +1488,7 @@ public class MusicPlaybackService extends Service {
                 openCurrentAndNext();
             }
             if (!mPlayer.isInitialized()) {
-                mPlayListLen = 0;
+                mPlaylist.clear();
                 return;
             }
 
@@ -1570,36 +1512,7 @@ public class MusicPlaybackService extends Service {
                 shufmode = SHUFFLE_NONE;
             }
             if (shufmode != SHUFFLE_NONE) {
-                q = mPreferences.getString("history", "");
-                qlen = q != null ? q.length() : 0;
-                if (qlen > 1) {
-                    plen = 0;
-                    n = 0;
-                    shift = 0;
-                    mHistory.clear();
-                    for (int i = 0; i < qlen; i++) {
-                        final char c = q.charAt(i);
-                        if (c == ';') {
-                            if (n >= mPlayListLen) {
-                                mHistory.clear();
-                                break;
-                            }
-                            mHistory.add(n);
-                            n = 0;
-                            shift = 0;
-                        } else {
-                            if (c >= '0' && c <= '9') {
-                                n += c - '0' << shift;
-                            } else if (c >= 'a' && c <= 'f') {
-                                n += 10 + c - 'a' << shift;
-                            } else {
-                                mHistory.clear();
-                                break;
-                            }
-                            shift += 4;
-                        }
-                    }
-                }
+                mHistory = mPlaybackStateStore.getHistory(mPlaylist.size());
             }
             if (shufmode == SHUFFLE_AUTO) {
                 if (!makeAutoShuffleList()) {
@@ -1647,9 +1560,8 @@ public class MusicPlaybackService extends Service {
                 }
                 try {
                     if (mCursor != null) {
-                        ensurePlayListCapacity(1);
-                        mPlayListLen = 1;
-                        mPlayList[0] = mCursor.getLong(IDCOLIDX);
+                        mPlaylist.clear();
+                        mPlaylist.add(new MusicPlaybackTrack(mCursor.getLong(IDCOLIDX), -1, IdType.NA, -1));
                         mPlayPos = 0;
                         mHistory.clear();
                     }
@@ -1714,8 +1626,8 @@ public class MusicPlaybackService extends Service {
     public int removeTrack(final long id) {
         int numremoved = 0;
         synchronized (this) {
-            for (int i = 0; i < mPlayListLen; i++) {
-                if (mPlayList[i] == id) {
+            for (int i = 0; i < mPlaylist.size(); i++) {
+                if (mPlaylist.get(i).mId == id) {
                     numremoved += removeTracksInternal(i, i);
                     i--;
                 }
@@ -1877,28 +1789,46 @@ public class MusicPlaybackService extends Service {
     }
 
     /**
-     * Returns the current audio ID
-     *
-     * @return The current track ID
+     * @return The audio id of the track
      */
     public long getAudioId() {
-        synchronized (this) {
-            if (mPlayPos >= 0 && mPlayer.isInitialized()) {
-                return mPlayList[mPlayPos];
-            }
+        MusicPlaybackTrack track = getCurrentTrack();
+        if (track != null) {
+            return track.mId;
         }
+
         return -1;
     }
 
     /**
+     * Gets the currently playing music track
+     */
+    public MusicPlaybackTrack getCurrentTrack() {
+        return getTrack(mPlayPos);
+    }
+
+    /**
+     * Gets the music track from the queue at the specified index
+     * @param index position
+     * @return music track or null
+     */
+    public synchronized MusicPlaybackTrack getTrack(int index) {
+        if (index >= 0 && index < mPlaylist.size() && mPlayer.isInitialized()) {
+            return mPlaylist.get(index);
+        }
+
+        return null;
+    }
+
+    /**
      * Returns the next audio ID
      *
      * @return The next track ID
      */
     public long getNextAudioId() {
         synchronized (this) {
-            if (mNextPlayPos >= 0 && mPlayer.isInitialized()) {
-                return mPlayList[mNextPlayPos];
+            if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size() && mPlayer.isInitialized()) {
+                return mPlaylist.get(mNextPlayPos).mId;
             }
         }
         return -1;
@@ -1913,8 +1843,8 @@ public class MusicPlaybackService extends Service {
         synchronized (this) {
             if (mPlayer.isInitialized()) {
                 int pos = getPreviousPlayPosition(false);
-                if (pos >= 0) {
-                    return mPlayList[pos];
+                if (pos >= 0 && pos < mPlaylist.size()) {
+                    return mPlaylist.get(pos).mId;
                 }
             }
         }
@@ -1972,10 +1902,10 @@ public class MusicPlaybackService extends Service {
      */
     public long[] getQueue() {
         synchronized (this) {
-            final int len = mPlayListLen;
+            final int len = mPlaylist.size();
             final long[] list = new long[len];
             for (int i = 0; i < len; i++) {
-                list[i] = mPlayList[i];
+                list[i] = mPlaylist.get(i).mId;
             }
             return list;
         }
@@ -1994,7 +1924,7 @@ public class MusicPlaybackService extends Service {
      * @param list The list of tracks to open
      * @param position The position to start playback at
      */
-    public void open(final long[] list, final int position) {
+    public void open(final long[] list, final int position, long sourceId, IdType sourceType) {
         synchronized (this) {
             if (mShuffleMode == SHUFFLE_AUTO) {
                 mShuffleMode = SHUFFLE_NORMAL;
@@ -2002,23 +1932,23 @@ public class MusicPlaybackService extends Service {
             final long oldId = getAudioId();
             final int listlength = list.length;
             boolean newlist = true;
-            if (mPlayListLen == listlength) {
+            if (mPlaylist.size() == listlength) {
                 newlist = false;
                 for (int i = 0; i < listlength; i++) {
-                    if (list[i] != mPlayList[i]) {
+                    if (list[i] != mPlaylist.get(i).mId) {
                         newlist = true;
                         break;
                     }
                 }
             }
             if (newlist) {
-                addToPlayList(list, -1);
+                addToPlayList(list, -1, sourceId, sourceType);
                 notifyChange(QUEUE_CHANGED);
             }
             if (position >= 0) {
                 mPlayPos = position;
             } else {
-                mPlayPos = mShuffler.nextInt(mPlayListLen);
+                mPlayPos = mShuffler.nextInt(mPlaylist.size());
             }
             mHistory.clear();
             openCurrentAndNext();
@@ -2084,7 +2014,7 @@ public class MusicPlaybackService extends Service {
 
             cancelShutdown();
             updateNotification();
-        } else if (mPlayListLen <= 0) {
+        } else if (mPlaylist.size() <= 0) {
             setShuffleMode(SHUFFLE_AUTO);
         }
     }
@@ -2111,7 +2041,7 @@ public class MusicPlaybackService extends Service {
     public void gotoNext(final boolean force) {
         if (D) Log.d(TAG, "Going to next track");
         synchronized (this) {
-            if (mPlayListLen <= 0) {
+            if (mPlaylist.size() <= 0) {
                 if (D) Log.d(TAG, "No play queue");
                 scheduleDelayedShutdown();
                 return;
@@ -2200,7 +2130,7 @@ public class MusicPlaybackService extends Service {
                 if (mPlayPos > 0) {
                     return mPlayPos - 1;
                 } else {
-                    return mPlayListLen - 1;
+                    return mPlaylist.size() - 1;
                 }
             }
         }
@@ -2223,29 +2153,23 @@ public class MusicPlaybackService extends Service {
      */
     public void moveQueueItem(int index1, int index2) {
         synchronized (this) {
-            if (index1 >= mPlayListLen) {
-                index1 = mPlayListLen - 1;
+            if (index1 >= mPlaylist.size()) {
+                index1 = mPlaylist.size() - 1;
             }
-            if (index2 >= mPlayListLen) {
-                index2 = mPlayListLen - 1;
+            if (index2 >= mPlaylist.size()) {
+                index2 = mPlaylist.size() - 1;
             }
+
+            final MusicPlaybackTrack track = mPlaylist.remove(index1);
             if (index1 < index2) {
-                final long tmp = mPlayList[index1];
-                for (int i = index1; i < index2; i++) {
-                    mPlayList[i] = mPlayList[i + 1];
-                }
-                mPlayList[index2] = tmp;
+                mPlaylist.add(index2, track);
                 if (mPlayPos == index1) {
                     mPlayPos = index2;
                 } else if (mPlayPos >= index1 && mPlayPos <= index2) {
                     mPlayPos--;
                 }
             } else if (index2 < index1) {
-                final long tmp = mPlayList[index1];
-                for (int i = index1; i > index2; i--) {
-                    mPlayList[i] = mPlayList[i - 1];
-                }
-                mPlayList[index2] = tmp;
+                mPlaylist.add(index2, track);
                 if (mPlayPos == index1) {
                     mPlayPos = index2;
                 } else if (mPlayPos >= index2 && mPlayPos <= index1) {
@@ -2277,14 +2201,14 @@ public class MusicPlaybackService extends Service {
      */
     public void setShuffleMode(final int shufflemode) {
         synchronized (this) {
-            if (mShuffleMode == shufflemode && mPlayListLen > 0) {
+            if (mShuffleMode == shufflemode && mPlaylist.size() > 0) {
                 return;
             }
 
             mShuffleMode = shufflemode;
             if (mShuffleMode == SHUFFLE_AUTO) {
                 if (makeAutoShuffleList()) {
-                    mPlayListLen = 0;
+                    mPlaylist.clear();
                     doAutoShuffleUpdate();
                     mPlayPos = 0;
                     openCurrentAndNext();
@@ -2326,16 +2250,16 @@ public class MusicPlaybackService extends Service {
      * @param list The list to queue
      * @param action The action to take
      */
-    public void enqueue(final long[] list, final int action) {
+    public void enqueue(final long[] list, final int action, long sourceId, IdType sourceType) {
         synchronized (this) {
-            if (action == NEXT && mPlayPos + 1 < mPlayListLen) {
-                addToPlayList(list, mPlayPos + 1);
+            if (action == NEXT && mPlayPos + 1 < mPlaylist.size()) {
+                addToPlayList(list, mPlayPos + 1, sourceId, sourceType);
                 notifyChange(QUEUE_CHANGED);
             } else {
-                addToPlayList(list, Integer.MAX_VALUE);
+                addToPlayList(list, Integer.MAX_VALUE, sourceId, sourceType);
                 notifyChange(QUEUE_CHANGED);
                 if (action == NOW) {
-                    mPlayPos = mPlayListLen - list.length;
+                    mPlayPos = mPlaylist.size() - list.length;
                     openCurrentAndNext();
                     play();
                     notifyChange(META_CHANGED);
@@ -2496,7 +2420,7 @@ public class MusicPlaybackService extends Service {
                     if (service.mCursor != null) {
                         service.mCursor.close();
                     }
-                    service.updateCursor(service.mPlayList[service.mPlayPos]);
+                    service.updateCursor(service.mPlaylist.get(service.mPlayPos).mId);
                     service.notifyChange(META_CHANGED);
                     service.updateNotification();
                     break;
@@ -2854,8 +2778,9 @@ public class MusicPlaybackService extends Service {
          * {@inheritDoc}
          */
         @Override
-        public void open(final long[] list, final int position) throws RemoteException {
-            mService.get().open(list, position);
+        public void open(final long[] list, final int position, long sourceId, int sourceType)
+                throws RemoteException {
+            mService.get().open(list, position, sourceId, IdType.getTypeById(sourceType));
         }
 
         /**
@@ -2902,8 +2827,9 @@ public class MusicPlaybackService extends Service {
          * {@inheritDoc}
          */
         @Override
-        public void enqueue(final long[] list, final int action) throws RemoteException {
-            mService.get().enqueue(list, action);
+        public void enqueue(final long[] list, final int action, long sourceId, int sourceType)
+                throws RemoteException {
+            mService.get().enqueue(list, action, sourceId, IdType.getTypeById(sourceType));
         }
 
         /**
@@ -3022,6 +2948,22 @@ public class MusicPlaybackService extends Service {
          * {@inheritDoc}
          */
         @Override
+        public MusicPlaybackTrack getCurrentTrack() throws RemoteException {
+            return mService.get().getCurrentTrack();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public MusicPlaybackTrack getTrack(int index) throws RemoteException {
+            return mService.get().getTrack(index);
+        }
+
+                /**
+         * {@inheritDoc}
+         */
+        @Override
         public long getNextAudioId() throws RemoteException {
             return mService.get().getNextAudioId();
         }
index d970d99..9ab3aad 100644 (file)
@@ -21,6 +21,7 @@ import android.widget.ArrayAdapter;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.cache.ImageFetcher;
 import com.cyngn.eleven.model.Album;
@@ -182,7 +183,7 @@ public class AlbumAdapter extends ArrayAdapter<Album>
             public void onClick(final View v) {
                 final long id = getItem(position).mAlbumId;
                 final long[] list = MusicUtils.getSongListForAlbum(getContext(), id);
-                MusicUtils.playAll(getContext(), list, 0, false);
+                MusicUtils.playAll(getContext(), list, 0, id, Config.IdType.Album, false);
             }
         });
     }
index c7dd899..431bffe 100644 (file)
@@ -27,10 +27,15 @@ public abstract class AlbumDetailSongAdapter extends DetailSongAdapter {
 
     protected int rowLayoutId() { return R.layout.album_detail_song; }
 
+    protected Config.IdType getSourceType() {
+        return Config.IdType.Album;
+    }
+
     @Override // LoaderCallbacks
     public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
         onLoading();
-        return new AlbumSongLoader(mActivity, args.getLong(Config.ID));
+        setSourceId(args.getLong(Config.ID));
+        return new AlbumSongLoader(mActivity, getSourceId());
     }
 
     @Override // LoaderCallbacks
index 384aa9a..f5d0f4f 100644 (file)
@@ -16,17 +16,21 @@ import com.cyngn.eleven.model.Song;
 import java.util.List;
 
 public abstract class ArtistDetailSongAdapter extends DetailSongAdapter {
-
     public ArtistDetailSongAdapter(Activity activity) {
         super(activity);
     }
 
     protected int rowLayoutId() { return R.layout.artist_detail_song; }
 
+    protected Config.IdType getSourceType() {
+        return Config.IdType.Artist;
+    }
+
     @Override // LoaderCallbacks
     public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
         onLoading();
-        return new ArtistSongLoader(mActivity, args.getLong(Config.ID));
+        setSourceId(args.getLong(Config.ID));
+        return new ArtistSongLoader(mActivity, getSourceId());
     }
 
     protected Holder newHolder(View root, ImageFetcher fetcher) {
index bed1956..38e0384 100644 (file)
@@ -9,11 +9,14 @@ import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.BaseAdapter;
+import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.cache.ImageFetcher;
 import com.cyngn.eleven.model.Song;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 import com.cyngn.eleven.utils.ApolloUtils;
 import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.widgets.IPopupMenuCallback;
@@ -29,6 +32,8 @@ public abstract class DetailSongAdapter extends BaseAdapter
     private final LayoutInflater mInflater;
     private List<Song> mSongs = Collections.emptyList();
     private IListener mListener;
+    private long mSourceId = -1;
+    private MusicPlaybackTrack mCurrentlyPlayingTrack;
 
     public DetailSongAdapter(final Activity activity) {
         mActivity = activity;
@@ -45,6 +50,16 @@ public abstract class DetailSongAdapter extends BaseAdapter
     @Override
     public long getItemId(int pos) { return pos; }
 
+    protected long getSourceId() { return mSourceId; }
+    protected void setSourceId(long id) { mSourceId = id; }
+
+    public void setCurrentlyPlayingTrack(MusicPlaybackTrack currentTrack) {
+        if (mCurrentlyPlayingTrack == null || !mCurrentlyPlayingTrack.equals(currentTrack)) {
+            mCurrentlyPlayingTrack = currentTrack;
+            notifyDataSetChanged();
+        }
+    }
+
     @Override
     public View getView(int pos, View convertView, ViewGroup parent) {
         if(convertView == null) {
@@ -59,12 +74,22 @@ public abstract class DetailSongAdapter extends BaseAdapter
         holder.popupMenuButton.setPopupMenuClickedListener(mListener);
         holder.popupMenuButton.setPosition(pos);
 
+        if (mCurrentlyPlayingTrack != null
+                && mCurrentlyPlayingTrack.mSourceId == getSourceId()
+                && mCurrentlyPlayingTrack.mSourceType == getSourceType()
+                && mCurrentlyPlayingTrack.mId == song.mSongId) {
+            holder.playIcon.setVisibility(View.VISIBLE);
+        } else {
+            holder.playIcon.setVisibility(View.GONE);
+        }
+
         return convertView;
     }
 
     protected abstract int rowLayoutId();
     protected abstract void onLoading();
     protected abstract void onNoResults();
+    protected abstract Config.IdType getSourceType();
 
     @Override // OnItemClickListener
     public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
@@ -76,7 +101,7 @@ public abstract class DetailSongAdapter extends BaseAdapter
         for(int i = 0; i < toPlay.length; i++) {
             toPlay[i] = getItem(position + i).mSongId;
         }
-        MusicUtils.playAll(mActivity, toPlay, -1, false);
+        MusicUtils.playAll(mActivity, toPlay, -1, getSourceId(), getSourceType(), false);
     }
 
     @Override // LoaderCallbacks
@@ -107,11 +132,13 @@ public abstract class DetailSongAdapter extends BaseAdapter
         protected ImageFetcher fetcher;
         protected TextView title;
         protected PopupMenuButton popupMenuButton;
+        protected ImageView playIcon;
 
         protected Holder(View root, ImageFetcher fetcher) {
             this.fetcher = fetcher;
             title = (TextView)root.findViewById(R.id.title);
             popupMenuButton = (PopupMenuButton)root.findViewById(R.id.overflow);
+            playIcon = (ImageView)root.findViewById(R.id.now_playing);
         }
 
         protected abstract void update(Song song);
index 88ecb53..c4318e8 100644 (file)
@@ -17,111 +17,40 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 
-import com.cyngn.eleven.cache.ImageFetcher;
-import com.cyngn.eleven.model.Artist;
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.model.Song;
-import com.cyngn.eleven.ui.MusicHolder;
-import com.cyngn.eleven.ui.fragments.profile.LastAddedFragment;
-import com.cyngn.eleven.utils.ApolloUtils;
-import com.cyngn.eleven.utils.Lists;
-import com.cyngn.eleven.utils.MusicUtils;
-import com.cyngn.eleven.widgets.IPopupMenuCallback;
 
-import java.util.List;
+import java.util.Collection;
 
 /**
- * This {@link ArrayAdapter} is used to display the songs for a particular
- * artist, album, playlist, or genre for
- * {@link com.cyngn.eleven.ui.fragments.PlaylistDetailFragment},{@link LastAddedFragment}.
+ * This {@link ArrayAdapter} is used to display the songs for a particular playlist
+ * {@link com.cyngn.eleven.ui.fragments.PlaylistDetailFragment}
  * 
  * @author Andrew Neal (andrewdneal@gmail.com)
  */
-public class ProfileSongAdapter extends ArrayAdapter<Song> implements IPopupMenuCallback {
-
-    /**
-     * Default display setting: title/album
-     */
-    public static final int DISPLAY_DEFAULT_SETTING = 0;
-
-    /**
-     * Playlist display setting: title/artist-album
-     */
-    public static final int DISPLAY_PLAYLIST_SETTING = 1;
-
-    /**
-     * Album display setting: title/duration
-     */
-    public static final int DISPLAY_ALBUM_SETTING = 2;
-
-    /**
-     * The header view
-     */
-    private static final int ITEM_VIEW_TYPE_HEADER = 0;
-
-    /**
-     * * The data in the list.
-     */
-    private static final int ITEM_VIEW_TYPE_MUSIC = 1;
-
-    /**
-     * Number of views (List Items, header)
-     */
-    private static final int VIEW_TYPE_COUNT = 2;
-
+public class ProfileSongAdapter extends SongAdapter {
     /**
-     * LayoutInflater
+     * Instead of having random +1 and -1 sprinkled around, this variable will show what is really
+     * related to the header
      */
-    private final LayoutInflater mInflater;
+    public static final int NUM_HEADERS = 1;
 
     /**
-     * Fake header Id
+     * Fake header layout Id
      */
     private final int mHeaderId;
 
     /**
-     * The resource Id of the layout to inflate
-     */
-    private final int mLayoutId;
-
-    /**
-     * Image cache and image fetcher
-     */
-    private final ImageFetcher mImageFetcher;
-
-    /**
-     * Display setting for the second line in a song fragment
-     */
-    private final int mDisplaySetting;
-
-    /**
-     * Used to set the size of the data in the adapter
-     */
-    private List<Song> mCount = Lists.newArrayList();
-
-    /**
-     * Used to listen to the pop up menu callbacks
-     */
-    private IListener mListener;
-
-    /**
      * Constructor of <code>ProfileSongAdapter</code>
      * 
      * @param activity The {@link Activity} to use
      * @param layoutId The resource Id of the view to inflate.
-     * @param setting defines the content of the second line
      */
-    public ProfileSongAdapter(final Activity activity, final int layoutId, final int headerId, final int setting) {
-        super(activity, 0);
-        // Used to create the custom layout
-        mInflater = LayoutInflater.from(activity);
+    public ProfileSongAdapter(final long playlistId, final Activity activity, final int layoutId,
+                              final int headerId) {
+        super(activity, layoutId, playlistId, Config.IdType.Playlist);
         // Cache the header
         mHeaderId = headerId;
-        // Get the layout Id
-        mLayoutId = layoutId;
-        // Know what to put in line two
-        mDisplaySetting = setting;
-        // Initialize the cache & image fetcher
-        mImageFetcher = ApolloUtils.getImageFetcher(activity);
     }
 
     /**
@@ -139,92 +68,24 @@ public class ProfileSongAdapter extends ArrayAdapter<Song> implements IPopupMenu
             return convertView;
         }
 
-        // Recycle MusicHolder's items
-        MusicHolder holder;
-        if (convertView == null) {
-            convertView = LayoutInflater.from(getContext()).inflate(mLayoutId, parent, false);
-            holder = new MusicHolder(convertView);
-            convertView.setTag(holder);
-
-            // set the pop up menu listener
-            holder.mPopupMenuButton.get().setPopupMenuClickedListener(mListener);
-        } else {
-            holder = (MusicHolder)convertView.getTag();
-        }
-
-        // Retrieve the album
-        final Song song = getItem(position - 1);
-
-        // because of recycling, we need to set the position each time
-        holder.mPopupMenuButton.get().setPosition(position);
-
-        // Set each track name (line one)
-        holder.mLineOne.get().setText(song.mSongName);
-        // Set the line two
-        switch (mDisplaySetting) {
-            // show duration if on album fragment
-            case DISPLAY_ALBUM_SETTING:
-                holder.mLineOneRight.get().setVisibility(View.GONE);
-
-                holder.mLineTwo.get().setText(
-                        MusicUtils.makeShortTimeString(getContext(), song.mDuration));
-                break;
-            case DISPLAY_PLAYLIST_SETTING:
-                if (song.mDuration == -1) {
-                    holder.mLineOneRight.get().setVisibility(View.GONE);
-                } else {
-                    holder.mLineOneRight.get().setVisibility(View.VISIBLE);
-                    holder.mLineOneRight.get().setText(
-                            MusicUtils.makeShortTimeString(getContext(), song.mDuration));
-                }
-
-                holder.mLineTwo.get().setText(MusicUtils.makeCombinedString(getContext(),
-                        song.mArtistName, song.mAlbumName));
-
-                // Asynchronously load the album image
-                if (song.mAlbumId >= 0) {
-                    mImageFetcher.loadAlbumImage(song.mArtistName, song.mAlbumName, song.mAlbumId,
-                            holder.mImage.get());
-                }
-                break;
-            case DISPLAY_DEFAULT_SETTING:
-            default:
-                holder.mLineOneRight.get().setVisibility(View.VISIBLE);
-
-                holder.mLineOneRight.get().setText(
-                        MusicUtils.makeShortTimeString(getContext(), song.mDuration));
-                holder.mLineTwo.get().setText(song.mAlbumName);
-                break;
-        }
-        return convertView;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean hasStableIds() {
-        return true;
+        return super.getView(position, convertView, parent);
     }
 
     /**
      * {@inheritDoc}
      */
-    @Override
-    public int getCount() {
-        final int size = mCount.size();
-        return size == 0 ? 0 : size + 1;
+    protected boolean showNowPlayingIndicator(final Song song, final int position) {
+        return super.showNowPlayingIndicator(song, position)
+                && mCurrentlyPlayingTrack.mSourcePosition == position - NUM_HEADERS;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public long getItemId(final int position) {
+    public boolean isEnabled(int position) {
         if (position == 0) {
-            return -1;
+            return false;
         }
-        return position - 1;
+
+        return super.isEnabled(position);
     }
 
     /**
@@ -232,7 +93,7 @@ public class ProfileSongAdapter extends ArrayAdapter<Song> implements IPopupMenu
      */
     @Override
     public int getViewTypeCount() {
-        return VIEW_TYPE_COUNT;
+        return super.getViewTypeCount() + NUM_HEADERS;
     }
 
     /**
@@ -241,55 +102,34 @@ public class ProfileSongAdapter extends ArrayAdapter<Song> implements IPopupMenu
     @Override
     public int getItemViewType(final int position) {
         if (position == 0) {
-            return ITEM_VIEW_TYPE_HEADER;
-        }
-        return ITEM_VIEW_TYPE_MUSIC;
-    }
-
-    /**
-     * @param pause True to temporarily pause the disk cache, false otherwise.
-     */
-    public void setPauseDiskCache(final boolean pause) {
-        if (mImageFetcher != null) {
-            mImageFetcher.setPauseDiskCache(pause);
-        }
-    }
-
-    /**
-     * @param artist The key used to find the cached artist to remove
-     */
-    public void removeFromCache(final Artist artist) {
-        if (mImageFetcher != null) {
-            mImageFetcher.removeFromCache(artist.mArtistName);
+            // since our view type count adds 1 to the super class, we can return viewtypecount - 1
+            return getViewTypeCount() - 1;
         }
+        return super.getItemViewType(position);
     }
 
-    /**
-     * Method that unloads and clears the items in the adapter
-     */
-    public void unload() {
-        clear();
+    @Override
+    public void addAll(Collection<? extends Song> collection) {
+        // insert a header if one is needed
+        insertHeader();
+        super.addAll(collection);
     }
 
-    /**
-     * @param data The {@link List} used to return the count for the adapter.
-     */
-    public void setCount(final List<Song> data) {
-        mCount = data;
+    @Override
+    public void addAll(Song... items) {
+        // insert a header if one is needed
+        insertHeader();
+        super.addAll(items);
     }
 
     /**
-     * Since we inject headers with this class, to actually determine if it is empty
-     * we need to look at the underlying data
-     * @return true if underlying data is empty
+     * Make sure we insert our header when we add items
      */
-    @Override
-    public boolean isEmpty() {
-        return (mCount == null || mCount.size() == 0);
-    }
-
-    @Override
-    public void setPopupMenuClickedListener(IPopupMenuCallback.IListener listener) {
-        mListener = listener;
+    private void insertHeader() {
+        if (getCount() == 0) {
+            // add a dummy entry to the underlying adapter.  This is needed otherwise the
+            // underlying adapter could crash because getCount() doesn't match up
+            add(new Song(-1, null, null, null, -1, -1, -1));
+        }
     }
 }
index ebc34e3..0293656 100644 (file)
@@ -18,10 +18,12 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.cache.ImageFetcher;
 import com.cyngn.eleven.model.Artist;
 import com.cyngn.eleven.model.Song;
 import com.cyngn.eleven.sectionadapter.SectionAdapter;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 import com.cyngn.eleven.ui.MusicHolder;
 import com.cyngn.eleven.ui.MusicHolder.DataHolder;
 import com.cyngn.eleven.ui.fragments.QueueFragment;
@@ -74,17 +76,34 @@ public class SongAdapter extends ArrayAdapter<Song>
     private IPopupMenuCallback.IListener mListener;
 
     /**
+     * Current music track
+     */
+    protected MusicPlaybackTrack mCurrentlyPlayingTrack;
+
+    /**
+     * Source id and type
+     */
+    protected long mSourceId;
+    protected Config.IdType mSourceType;
+
+    /**
      * Constructor of <code>SongAdapter</code>
      * 
      * @param context The {@link Context} to use.
      * @param layoutId The resource Id of the view to inflate.
+     * @param sourceId The source id that the adapter is created from
+     * @param sourceType The source type that the adapter is created from
      */
-    public SongAdapter(final Activity context, final int layoutId) {
+    public SongAdapter(final Activity context, final int layoutId, final long sourceId,
+                       final Config.IdType sourceType) {
         super(context, 0);
         // Get the layout Id
         mLayoutId = layoutId;
         // Initialize the cache & image fetcher
         mImageFetcher = ApolloUtils.getImageFetcher(context);
+        // set the source id and type
+        mSourceId = sourceId;
+        mSourceType = sourceType;
     }
 
     /**
@@ -138,10 +157,36 @@ public class SongAdapter extends ArrayAdapter<Song>
             }
         }
 
+        View nowPlayingIndicator = holder.mNowPlayingIndicator.get();
+        if (nowPlayingIndicator != null) {
+            if (showNowPlayingIndicator(item, position)) {
+                nowPlayingIndicator.setVisibility(View.VISIBLE);
+            } else {
+                nowPlayingIndicator.setVisibility(View.GONE);
+            }
+        }
+
         return convertView;
     }
 
     /**
+     * Determines whether the song at the position should show the currently playing indicator
+     * @param song the song in question
+     * @param position the position of the song
+     * @return true if we want to show the indicator
+     */
+    protected boolean showNowPlayingIndicator(final Song song, final int position) {
+        if (mCurrentlyPlayingTrack != null
+                && mCurrentlyPlayingTrack.mSourceId == mSourceId
+                && mCurrentlyPlayingTrack.mSourceType == mSourceType
+                && mCurrentlyPlayingTrack.mId == song.mSongId) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -168,6 +213,11 @@ public class SongAdapter extends ArrayAdapter<Song>
             // Build the song
             final Song song = getItem(i);
 
+            // skip special placeholders
+            if (song.mSongId == -1) {
+                continue;
+            }
+
             // Build the data holder
             mData[i] = new DataHolder();
             // Song Id
@@ -243,4 +293,20 @@ public class SongAdapter extends ArrayAdapter<Song>
     public void setPopupMenuClickedListener(IListener listener) {
         mListener = listener;
     }
+
+    /**
+     * Sets the currently playing track for the adapter to know when to show indicators
+     * @param currentTrack the currently playing track
+     * @return true if the current track is different
+     */
+    public boolean setCurrentlyPlayingTrack(MusicPlaybackTrack currentTrack) {
+        if (mCurrentlyPlayingTrack == null || !mCurrentlyPlayingTrack.equals(currentTrack)) {
+            mCurrentlyPlayingTrack = currentTrack;
+
+            notifyDataSetChanged();
+            return true;
+        }
+
+        return false;
+    }
 }
index 6f95490..ae62c0a 100644 (file)
@@ -31,7 +31,6 @@ import java.util.List;
  * @author Andrew Neal (andrewdneal@gmail.com)
  */
 public class LastAddedLoader extends SectionCreator.SimpleListLoader<Song> {
-
     /**
      * The result
      */
index b86ff07..f5736b1 100644 (file)
@@ -7,6 +7,7 @@ import android.database.Cursor;
 import android.provider.MediaStore;
 import android.util.Log;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.utils.MusicUtils;
 
 import java.util.Comparator;
@@ -60,6 +61,20 @@ public class SearchResult {
                 return Unknown;
             }
         }
+
+        public Config.IdType getSourceType() {
+            switch (this) {
+                case Artist:
+                    return Config.IdType.Artist;
+                case Album:
+                    return Config.IdType.Album;
+                case Playlist:
+                    return Config.IdType.Playlist;
+                case Song:
+                default:
+                    return Config.IdType.NA;
+            }
+        }
     };
 
     public ResultType mType;
index 794b661..c8b7c23 100644 (file)
@@ -6,10 +6,22 @@ package com.cyngn.eleven.provider;
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
 
 public class MusicDB extends SQLiteOpenHelper {
+    /**
+     * Version History
+     * v1 Sept 22 2014  Initial Merge of tables
+     *                  Has PlaylistArtworkstore, RecentStore, SearchHistory, SongPlayCount
+     * v2 Oct 7 2014    Added a new class MusicPlaybackState - need to bump version so the new
+     *                  tables are created, but need to remove all drops from other classes to
+     *                  maintain data
+     *
+     */
+
+
     /* Version constant to increment when the database should be rebuilt */
-    private static final int VERSION = 1;
+    private static final int VERSION = 2;
 
     /* Name of database file */
     public static final String DATABASENAME = "musicdb.db";
@@ -42,6 +54,7 @@ public class MusicDB extends SQLiteOpenHelper {
         RecentStore.getInstance(mContext).onCreate(db);
         SongPlayCount.getInstance(mContext).onCreate(db);
         SearchHistory.getInstance(mContext).onCreate(db);
+        MusicPlaybackState.getInstance(mContext).onCreate(db);
     }
 
     @Override
@@ -50,5 +63,17 @@ public class MusicDB extends SQLiteOpenHelper {
         RecentStore.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);
         SongPlayCount.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);
         SearchHistory.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);
+        MusicPlaybackState.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);
+    }
+
+    @Override
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        Log.w(MusicDB.class.getSimpleName(),
+                "Downgrading from: " + oldVersion + " to " + newVersion + ". Dropping tables");
+        PlaylistArtworkStore.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);
+        RecentStore.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);
+        SongPlayCount.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);
+        SearchHistory.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);
+        MusicPlaybackState.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);
     }
 }
diff --git a/src/com/cyngn/eleven/provider/MusicPlaybackState.java b/src/com/cyngn/eleven/provider/MusicPlaybackState.java
new file mode 100644 (file)
index 0000000..dfead29
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.provider;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
+import com.cyngn.eleven.utils.Lists;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * This keeps track of the music playback and history state of the playback service
+ */
+public class MusicPlaybackState {
+    private static MusicPlaybackState sInstance = null;
+
+    private MusicDB mMusicDatabase = null;
+
+    /**
+     * Constructor of <code>MusicPlaybackState</code>
+     *
+     * @param context The {@link android.content.Context} to use
+     */
+    public MusicPlaybackState(final Context context) {
+        mMusicDatabase = MusicDB.getInstance(context);
+    }
+
+    public void onCreate(final SQLiteDatabase db) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("CREATE TABLE IF NOT EXISTS ");
+        builder.append(PlaybackQueueColumns.NAME);
+        builder.append("(");
+
+        builder.append(PlaybackQueueColumns.TRACK_ID);
+        builder.append(" LONG NOT NULL,");
+
+        builder.append(PlaybackQueueColumns.SOURCE_ID);
+        builder.append(" LONG NOT NULL,");
+
+        builder.append(PlaybackQueueColumns.SOURCE_TYPE);
+        builder.append(" INT NOT NULL,");
+
+        builder.append(PlaybackQueueColumns.SOURCE_POSITION);
+        builder.append(" INT NOT NULL);");
+
+        db.execSQL(builder.toString());
+
+        builder = new StringBuilder();
+        builder.append("CREATE TABLE IF NOT EXISTS ");
+        builder.append(PlaybackHistoryColumns.NAME);
+        builder.append("(");
+
+        builder.append(PlaybackHistoryColumns.POSITION);
+        builder.append(" INT NOT NULL);");
+
+        db.execSQL(builder.toString());
+    }
+
+    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+        // this table was created in version 2 so call the onCreate method if we hit that scenario
+        if (oldVersion == 1 && newVersion > 1) {
+            onCreate(db);
+        }
+    }
+
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // If we ever have downgrade, drop the table to be safe
+        db.execSQL("DROP TABLE IF EXISTS " + PlaybackQueueColumns.NAME);
+        db.execSQL("DROP TABLE IF EXISTS " + PlaybackHistoryColumns.NAME);
+        onCreate(db);
+    }
+
+    /**
+     * @param context The {@link android.content.Context} to use
+     * @return A new instance of this class.
+     */
+    public static final synchronized MusicPlaybackState getInstance(final Context context) {
+        if (sInstance == null) {
+            sInstance = new MusicPlaybackState(context.getApplicationContext());
+        }
+        return sInstance;
+    }
+
+    /**
+     * Clears the existing database and saves the queue and history into the db so that when the
+     * app is restarted, the tracks you were listening to is restored
+     * @param queue the queue to save
+     * @param history the history to save
+     */
+    public synchronized void saveState(final ArrayList<MusicPlaybackTrack> queue,
+                                       LinkedList<Integer> history) {
+        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();
+        database.beginTransaction();
+
+        try {
+            database.delete(PlaybackQueueColumns.NAME, null, null);
+            database.delete(PlaybackHistoryColumns.NAME, null, null);
+            database.setTransactionSuccessful();
+        } finally {
+            database.endTransaction();
+        }
+
+        final int NUM_PROCESS = 20;
+        int position = 0;
+        while (position < queue.size()) {
+            database.beginTransaction();
+            try {
+                for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) {
+                    MusicPlaybackTrack track = queue.get(i);
+                    ContentValues values = new ContentValues(4);
+
+                    values.put(PlaybackQueueColumns.TRACK_ID, track.mId);
+                    values.put(PlaybackQueueColumns.SOURCE_ID, track.mSourceId);
+                    values.put(PlaybackQueueColumns.SOURCE_TYPE, track.mSourceType.mId);
+                    values.put(PlaybackQueueColumns.SOURCE_POSITION, track.mSourcePosition);
+
+                    database.insert(PlaybackQueueColumns.NAME, null, values);
+                }
+                database.setTransactionSuccessful();
+            } finally {
+                database.endTransaction();
+                position += NUM_PROCESS;
+            }
+        }
+
+        if (history != null) {
+            Iterator<Integer> iter = history.iterator();
+            while (iter.hasNext()) {
+                database.beginTransaction();
+                try {
+                    for (int i = 0; iter.hasNext() && i < NUM_PROCESS; i++) {
+                        ContentValues values = new ContentValues(1);
+                        values.put(PlaybackHistoryColumns.POSITION, iter.next());
+
+                        database.insert(PlaybackHistoryColumns.NAME, null, values);
+                    }
+
+                    database.setTransactionSuccessful();
+                } finally {
+                    database.endTransaction();
+                }
+            }
+        }
+    }
+
+    public ArrayList<MusicPlaybackTrack> getQueue() {
+        ArrayList<MusicPlaybackTrack> results = Lists.newArrayList();
+
+        Cursor cursor = null;
+        try {
+            cursor = mMusicDatabase.getReadableDatabase().query(PlaybackQueueColumns.NAME, null,
+                    null, null, null, null, null);
+
+            if (cursor != null && cursor.moveToFirst()) {
+                results.ensureCapacity(cursor.getCount());
+
+                do {
+                    results.add(new MusicPlaybackTrack(cursor.getLong(0), cursor.getLong(1),
+                            Config.IdType.getTypeById(cursor.getInt(2)), cursor.getInt(3)));
+                } while (cursor.moveToNext());
+            }
+
+            return results;
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+                cursor = null;
+            }
+        }
+    }
+
+    public LinkedList<Integer> getHistory(final int playlistSize) {
+        LinkedList<Integer> results = Lists.newLinkedList();
+
+        Cursor cursor = null;
+        try {
+            cursor = mMusicDatabase.getReadableDatabase().query(PlaybackHistoryColumns.NAME, null,
+                    null, null, null, null, null);
+
+            if (cursor != null && cursor.moveToFirst()) {
+                do {
+                    int pos = cursor.getInt(0);
+                    if (pos >= 0 && pos < playlistSize) {
+                        results.add(pos);
+                    }
+                } while (cursor.moveToNext());
+            }
+
+            return results;
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+                cursor = null;
+            }
+        }
+    }
+
+    public class PlaybackQueueColumns {
+        /* Table name */
+        public static final String NAME = "playbackqueue";
+
+        /* track id */
+        public static final String TRACK_ID = "trackid";
+
+        /* the id of the source where this track is being played from (artist/album/playlist) */
+        public static final String SOURCE_ID = "sourceid";
+
+        /* the type of the source where this track is being played from (artist/album/playlist) */
+        public static final String SOURCE_TYPE = "sourcetype";
+
+        /* the position - this is used in playlists where the song appears multiple times */
+        public static final String SOURCE_POSITION = "sourceposition";
+    }
+
+    public class PlaybackHistoryColumns {
+        /* Table name */
+        public static final String NAME = "playbackhistory";
+
+        /* the position of the history item within the queue */
+        public static final String POSITION = "position";
+    }
+}
index 632c967..ee8faf2 100644 (file)
@@ -86,7 +86,11 @@ public class PlaylistArtworkStore {
     }
 
     public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
-        // If we ever have upgrade, this code should be changed to handle this more gracefully
+        // No upgrade path needed yet
+    }
+
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // If we ever have downgrade, drop the table to be safe
         db.execSQL("DROP TABLE IF EXISTS " + PlaylistArtworkStoreColumns.NAME);
         onCreate(db);
     }
index 860c6e3..cffbe08 100644 (file)
@@ -18,7 +18,7 @@ import android.database.sqlite.SQLiteDatabase;
 
 public class RecentStore {
     /* Maximum # of items in the db */
-    private static final int MAX_ITEMS_IN_DB = 500;
+    private static final int MAX_ITEMS_IN_DB = 100;
 
     private static RecentStore sInstance = null;
 
@@ -40,6 +40,11 @@ public class RecentStore {
     }
 
     public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+        // No upgrade path needed yet
+    }
+
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // If we ever have downgrade, drop the table to be safe
         db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME);
         onCreate(db);
     }
index 3f02c65..8f86f8d 100644 (file)
@@ -29,6 +29,11 @@ public class SearchHistory {
     }
 
     public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+        // No upgrade path needed yet
+    }
+
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // If we ever have downgrade, drop the table to be safe
         db.execSQL("DROP TABLE IF EXISTS " + SearchHistoryColumns.NAME);
         onCreate(db);
     }
index 77ab5de..3e6e7d5 100644 (file)
@@ -85,7 +85,11 @@ public class SongPlayCount {
     }
 
     public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
-        // If we ever have upgrade, this code should be changed to handle this more gracefully
+        // No upgrade path needed yet
+    }
+
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // If we ever have downgrade, drop the table to be safe
         db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME);
         onCreate(db);
     }
diff --git a/src/com/cyngn/eleven/service/MusicPlaybackTrack.aidl b/src/com/cyngn/eleven/service/MusicPlaybackTrack.aidl
new file mode 100644 (file)
index 0000000..682a0df
--- /dev/null
@@ -0,0 +1,3 @@
+package com.cyngn.eleven.service;
+
+parcelable MusicPlaybackTrack;
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/service/MusicPlaybackTrack.java b/src/com/cyngn/eleven/service/MusicPlaybackTrack.java
new file mode 100644 (file)
index 0000000..71e5795
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.cyngn.eleven.Config;
+
+/**
+ * This is used by the music playback service to track the music tracks it is playing
+ * It has extra meta data to determine where the track came from so that we can show the appropriate
+ * song playing indicator
+ */
+public class MusicPlaybackTrack implements Parcelable {
+    /**
+     * The track id
+     */
+    public long mId;
+
+    /**
+     * Where was this track added from? Artist id/Album id/Playlist id
+     */
+    public long mSourceId;
+
+    /**
+     * Where was this track added from?  Artist/Album/Playlist
+     */
+    public Config.IdType mSourceType;
+
+    /**
+     * This is only used for playlists since it is possible that a playlist can contain the same
+     * song multiple times.  So to prevent the song indicator showing up multiple times, we need
+     * to keep track of the position
+     */
+    public int mSourcePosition;
+
+    /**
+     * Parcelable creator
+     */
+    public static final Creator<MusicPlaybackTrack> CREATOR = new Creator<MusicPlaybackTrack>() {
+        @Override
+        public MusicPlaybackTrack createFromParcel(Parcel source) {
+            return new MusicPlaybackTrack(source);
+        }
+
+        @Override
+        public MusicPlaybackTrack[] newArray(int size) {
+            return new MusicPlaybackTrack[size];
+        }
+    };
+
+    public MusicPlaybackTrack(long id, long sourceId, Config.IdType type, int sourcePosition) {
+        mId = id;
+        mSourceId = sourceId;
+        mSourceType = type;
+        mSourcePosition = sourcePosition;
+    }
+
+    public MusicPlaybackTrack(Parcel in) {
+        mId = in.readLong();
+        mSourceId = in.readLong();
+        mSourceType = Config.IdType.getTypeById(in.readInt());
+        mSourcePosition = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mId);
+        dest.writeLong(mSourceId);
+        dest.writeInt(mSourceType.mId);
+        dest.writeInt(mSourcePosition);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof MusicPlaybackTrack) {
+            MusicPlaybackTrack other = (MusicPlaybackTrack)o;
+            if (other != null) {
+                if (mId == other.mId
+                        && mSourceId == other.mSourceId
+                        && mSourceType == other.mSourceType
+                        && mSourcePosition == other.mSourcePosition) {
+                    return true;
+                }
+
+                return false;
+            }
+        }
+
+        return super.equals(o);
+    }
+}
index f9b4382..9c4ce78 100644 (file)
@@ -77,6 +77,11 @@ public class MusicHolder {
     public WeakReference<View> mPlayPauseProgressContainer;
 
     /**
+     * The song indicator for the currently playing track
+     */
+    public WeakReference<View> mNowPlayingIndicator;
+
+    /**
      * The divider for the list item
      */
     public WeakReference<View> mDivider;
@@ -114,6 +119,8 @@ public class MusicHolder {
         mPlayPauseProgressContainer = new WeakReference<View>(
                 view.findViewById(R.id.play_pause_container));
 
+        mNowPlayingIndicator = new WeakReference<View>(view.findViewById(R.id.now_playing));
+
         // Get the divider for the list item
         mDivider = new WeakReference<View>(view.findViewById(R.id.divider));
 
index 20d558e..712d997 100644 (file)
@@ -227,6 +227,16 @@ public class SearchActivity extends FragmentActivity implements
             }
 
             @Override
+            protected long getSourceId() {
+                return mSelectedItem.mId;
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return mSelectedItem.mType.getSourceType();
+            }
+
+            @Override
             protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
                 super.updateMenuIds(type, set);
 
@@ -684,7 +694,7 @@ public class SearchActivity extends FragmentActivity implements
                     final long[] list = new long[]{
                             item.mId
                     };
-                    MusicUtils.playAll(this, list, 0, false);
+                    MusicUtils.playAll(this, list, 0, -1, Config.IdType.NA, false);
                     break;
             }
         }
index f6d3a9e..79d3b57 100644 (file)
@@ -296,7 +296,7 @@ public class ShortcutActivity extends FragmentActivity implements ServiceConnect
         final boolean shouldOpenAudioPlayer = mIntent.getBooleanExtra(OPEN_AUDIO_PLAYER, true);
         // Play the list
         if (mList != null && mList.length > 0) {
-            MusicUtils.playAll(this, mList, 0, mShouldShuffle);
+            MusicUtils.playAll(this, mList, 0, -1, Config.IdType.NA, mShouldShuffle);
         }
 
         // Open the now playing screen
index 40de324..916aea5 100644 (file)
@@ -16,6 +16,7 @@ import com.cyngn.eleven.model.Album;
 import com.cyngn.eleven.model.Song;
 import com.cyngn.eleven.utils.AlbumPopupMenuHelper;
 import com.cyngn.eleven.utils.GenreFetcher;
+import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.utils.PopupMenuHelper;
 import com.cyngn.eleven.utils.SongPopupMenuHelper;
 import com.cyngn.eleven.widgets.IPopupMenuCallback;
@@ -114,6 +115,16 @@ public class AlbumDetailFragment extends BaseFragment {
             public Song getSong(int position) {
                 return mSongAdapter.getItem(position);
             }
+
+            @Override
+            protected long getSourceId() {
+                return mAlbumId;
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return Config.IdType.Album;
+            }
         };
 
         mHeaderPopupMenuHelper = new AlbumPopupMenuHelper(getActivity(), getChildFragmentManager()) {
@@ -178,4 +189,11 @@ public class AlbumDetailFragment extends BaseFragment {
     public void restartLoader() {
         getLoaderManager().restartLoader(LOADER_ID, getArguments(), mSongAdapter);
     }
+
+    @Override
+    public void onMetaChanged() {
+        super.onMetaChanged();
+
+        mSongAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
+    }
 }
\ No newline at end of file
index d1012e6..6388307 100644 (file)
@@ -19,6 +19,7 @@ import com.cyngn.eleven.menu.FragmentMenuItems;
 import com.cyngn.eleven.model.Album;
 import com.cyngn.eleven.model.Song;
 import com.cyngn.eleven.utils.AlbumPopupMenuHelper;
+import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.utils.SongPopupMenuHelper;
 import com.cyngn.eleven.widgets.IPopupMenuCallback;
 import com.cyngn.eleven.widgets.LoadingEmptyContainer;
@@ -50,6 +51,10 @@ public class ArtistDetailFragment extends DetailFragment {
         return getArguments().getString(Config.ARTIST_NAME);
     }
 
+    protected long getArtistId() {
+        return getArguments().getLong(Config.ID);
+    }
+
     @Override
     protected void onViewCreated() {
         super.onViewCreated();
@@ -129,6 +134,16 @@ public class ArtistDetailFragment extends DetailFragment {
             }
 
             @Override
+            protected long getSourceId() {
+                return getArtistId();
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return Config.IdType.Artist;
+            }
+
+            @Override
             protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
                 super.updateMenuIds(type, set);
 
@@ -165,4 +180,11 @@ public class ArtistDetailFragment extends DetailFragment {
         lm.restartLoader(ALBUM_LOADER_ID, arguments, mAlbumAdapter);
         lm.restartLoader(SONG_LOADER_ID, arguments, mSongAdapter);
     }
+
+    @Override
+    public void onMetaChanged() {
+        super.onMetaChanged();
+
+        mSongAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
+    }
 }
\ No newline at end of file
index e1507a2..465d5e1 100644 (file)
@@ -26,6 +26,7 @@ import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListView;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.MusicStateListener;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.adapters.ArtistAdapter;
@@ -121,6 +122,16 @@ public class ArtistFragment extends MusicBrowserFragment implements
             }
 
             @Override
+            protected long getSourceId() {
+                return mArtist.mArtistId;
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return Config.IdType.Artist;
+            }
+
+            @Override
             protected void onDeleteClicked() {
                 final String artist = mArtist.mArtistName;
                 DeleteDialog.newInstance(artist, getIdList(), artist).show(
index 0afec03..9306563 100644 (file)
@@ -33,7 +33,6 @@ import com.cyngn.eleven.widgets.IPopupMenuCallback;
 import com.cyngn.eleven.widgets.LoadingEmptyContainer;
 import com.cyngn.eleven.widgets.NoResultsContainer;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.TreeSet;
 
@@ -104,7 +103,7 @@ public class PlaylistDetailFragment extends DetailFragment implements
                     return null;
                 }
 
-                return mAdapter.getItem(position - 1);
+                return mAdapter.getItem(position);
             }
 
             @Override
@@ -115,6 +114,16 @@ public class PlaylistDetailFragment extends DetailFragment implements
             }
 
             @Override
+            protected long getSourceId() {
+                return mPlaylistId;
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return Config.IdType.Playlist;
+            }
+
+            @Override
             protected void removeFromPlaylist() {
                 mAdapter.remove(mSong);
                 mAdapter.notifyDataSetChanged();
@@ -141,10 +150,10 @@ public class PlaylistDetailFragment extends DetailFragment implements
         mListView.setOnScrollListener(PlaylistDetailFragment.this);
 
         mAdapter = new ProfileSongAdapter(
+                mPlaylistId,
                 getActivity(),
                 R.layout.edit_track_list_item,
-                R.layout.faux_playlist_header,
-                ProfileSongAdapter.DISPLAY_PLAYLIST_SETTING
+                R.layout.faux_playlist_header
         );
         mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
             @Override
@@ -201,8 +210,13 @@ public class PlaylistDetailFragment extends DetailFragment implements
      */
     @Override
     public void remove(final int which) {
-        Song song = mAdapter.getItem(which - 1);
+        if (which == 0) {
+            return;
+        }
+
+        Song song = mAdapter.getItem(which);
         mAdapter.remove(song);
+        mAdapter.buildCache();
         mAdapter.notifyDataSetChanged();
         final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", mPlaylistId);
         getActivity().getContentResolver().delete(uri,
@@ -214,17 +228,18 @@ public class PlaylistDetailFragment extends DetailFragment implements
      * {@inheritDoc}
      */
     @Override
-    public void drop(final int from, final int to) {
-        if (from == 0 || to == 0) {
-            mAdapter.notifyDataSetChanged();
-            return;
-        }
-        final int realFrom = from - 1;
-        final int realTo = to - 1;
-        Song song = mAdapter.getItem(realFrom);
+    public void drop(int from, int to) {
+        from = Math.max(ProfileSongAdapter.NUM_HEADERS, from);
+        to = Math.max(ProfileSongAdapter.NUM_HEADERS, to);
+
+        Song song = mAdapter.getItem(from);
         mAdapter.remove(song);
-        mAdapter.insert(song, realTo);
+        mAdapter.insert(song, to);
+        mAdapter.buildCache();
         mAdapter.notifyDataSetChanged();
+
+        final int realFrom = from - ProfileSongAdapter.NUM_HEADERS;
+        final int realTo = to - ProfileSongAdapter.NUM_HEADERS;
         MediaStore.Audio.Playlists.Members.moveItem(getActivity().getContentResolver(),
                 mPlaylistId, realFrom, realTo);
     }
@@ -241,7 +256,8 @@ public class PlaylistDetailFragment extends DetailFragment implements
         Cursor cursor = PlaylistSongLoader.makePlaylistSongCursor(getActivity(),
                 mPlaylistId);
         final long[] list = MusicUtils.getSongListForCursor(cursor);
-        MusicUtils.playAll(getActivity(), list, position - 1, false);
+        MusicUtils.playAll(getActivity(), list, position - ProfileSongAdapter.NUM_HEADERS,
+                mPlaylistId, Config.IdType.Playlist, false);
         cursor.close();
         cursor = null;
     }
@@ -278,8 +294,6 @@ public class PlaylistDetailFragment extends DetailFragment implements
             // hide the header container
             mHeaderContainer.setVisibility(View.INVISIBLE);
 
-            // Return the correct count
-            mAdapter.setCount(new ArrayList<Song>());
             // Start fresh
             mAdapter.unload();
         } else {
@@ -289,7 +303,9 @@ public class PlaylistDetailFragment extends DetailFragment implements
             // Start fresh
             mAdapter.unload();
             // Return the correct count
-            mAdapter.setCount(data);
+            mAdapter.addAll(data);
+            // build the cache
+            mAdapter.buildCache();
             // set the number of songs
             String numberOfSongs = MusicUtils.makeLabel(getActivity(), R.plurals.Nsongs,
                     data.size());
@@ -299,7 +315,6 @@ public class PlaylistDetailFragment extends DetailFragment implements
 
             // Add the data to the adapter
             for (final Song song : data) {
-                mAdapter.add(song);
                 duration += song.mDuration;
             }
 
@@ -322,4 +337,18 @@ public class PlaylistDetailFragment extends DetailFragment implements
 
         getLoaderManager().restartLoader(0, getArguments(), this);
     }
+
+    @Override
+    public void onMetaChanged() {
+        super.onMetaChanged();
+
+        mAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
+    }
+
+    @Override
+    public void onPlaylistChanged() {
+        super.onPlaylistChanged();
+
+        restartLoader();
+    }
 }
\ No newline at end of file
index 81bf19a..0041d6d 100644 (file)
@@ -28,6 +28,7 @@ import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListView;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.Config.SmartPlaylistType;
 import com.cyngn.eleven.MusicStateListener;
 import com.cyngn.eleven.R;
@@ -119,6 +120,16 @@ public class PlaylistFragment extends MusicBrowserFragment implements
             }
 
             @Override
+            protected long getSourceId() {
+                return mPlaylist.mPlaylistId;
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return Config.IdType.Playlist;
+            }
+
+            @Override
             protected void onDeleteClicked() {
                 buildDeleteDialog(getId(), mPlaylist.mPlaylistName).show();
             }
index a08ff6e..2acc275 100644 (file)
@@ -29,8 +29,8 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ProgressBar;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.MusicPlaybackService;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.adapters.SongAdapter;
@@ -43,6 +43,7 @@ import com.cyngn.eleven.loaders.QueueLoader;
 import com.cyngn.eleven.menu.DeleteDialog;
 import com.cyngn.eleven.model.Song;
 import com.cyngn.eleven.recycler.RecycleHolder;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 import com.cyngn.eleven.ui.activities.SlidingPanelActivity;
 import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.utils.PopupMenuHelper;
@@ -117,11 +118,13 @@ public class QueueFragment extends Fragment implements LoaderCallbacks<List<Song
         mPopupMenuHelper = new PopupMenuHelper(getActivity(), getFragmentManager()) {
             private Song mSong;
             private int mSelectedPosition;
+            private MusicPlaybackTrack mSelectedTrack;
 
             @Override
             protected PopupMenuType onPreparePopupMenu(int position) {
                 mSelectedPosition = position;
                 mSong = mAdapter.getItem(mSelectedPosition);
+                mSelectedTrack = MusicUtils.getTrack(mSelectedPosition);
 
                 return PopupMenuType.Queue;
             }
@@ -132,6 +135,24 @@ public class QueueFragment extends Fragment implements LoaderCallbacks<List<Song
             }
 
             @Override
+            protected long getSourceId() {
+                if (mSelectedTrack == null) {
+                    return -1;
+                }
+
+                return mSelectedTrack.mSourceId;
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                if (mSelectedTrack == null) {
+                    return Config.IdType.NA;
+                }
+
+                return mSelectedTrack.mSourceType;
+            }
+
+            @Override
             protected String getArtistName() {
                 return mSong.mArtistName;
             }
@@ -149,7 +170,7 @@ public class QueueFragment extends Fragment implements LoaderCallbacks<List<Song
                 queue.removeItem(mSelectedPosition);
                 queue.close();
                 queue = null;
-                MusicUtils.playNext(getIdList());
+                MusicUtils.playNext(getIdList(), getSourceId(), getSourceType());
                 refreshQueue();
             }
 
@@ -161,7 +182,8 @@ public class QueueFragment extends Fragment implements LoaderCallbacks<List<Song
         };
 
         // Create the adapter
-        mAdapter = new SongAdapter(getActivity(), R.layout.edit_queue_list_item);
+        mAdapter = new SongAdapter(getActivity(), R.layout.edit_queue_list_item,
+                -1, Config.IdType.NA);
         mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
             @Override
             public void onPopupMenuClicked(View v, int position) {
index a8b01f8..fc7f9b1 100644 (file)
 
 package com.cyngn.eleven.ui.fragments;
 
+import android.app.Activity;
 import android.os.Bundle;
 import android.support.v4.content.Loader;
 
 import com.cyngn.eleven.Config;
 import com.cyngn.eleven.R;
+import com.cyngn.eleven.adapters.SongAdapter;
 import com.cyngn.eleven.loaders.TopTracksLoader;
 import com.cyngn.eleven.menu.FragmentMenuItems;
 import com.cyngn.eleven.model.Song;
@@ -64,6 +66,8 @@ public class RecentFragment extends BasicSongFragment implements ISetupActionBar
      */
     @Override
     public void onMetaChanged() {
+        super.onMetaChanged();
+
         // refresh the list since a track playing means it should be recently played
         restartLoader();
     }
@@ -91,5 +95,36 @@ public class RecentFragment extends BasicSongFragment implements ISetupActionBar
     public void setupActionBar() {
         ((BaseActivity)getActivity()).setupActionBar(R.string.playlist_recently_played);
     }
+
+    @Override
+    protected long getFragmentSourceId() {
+        return Config.SmartPlaylistType.RecentlyPlayed.mId;
+    }
+
+    @Override
+    protected Config.IdType getFragmentSourceType() {
+        return Config.IdType.Playlist;
+    }
+
+    @Override
+    protected SongAdapter createAdapter() {
+        return new RecentAdapter(
+            getActivity(),
+            R.layout.list_item_normal,
+            getFragmentSourceId(),
+            getFragmentSourceType()
+        );
+    }
+
+    private class RecentAdapter extends SongAdapter {
+        public RecentAdapter(Activity context, int layoutId, long sourceId, Config.IdType sourceType) {
+            super(context, layoutId, sourceId, sourceType);
+        }
+
+        @Override
+        protected boolean showNowPlayingIndicator(Song song, int position) {
+            return position == 0 && super.showNowPlayingIndicator(song, position);
+        }
+    }
 }
 
index 0024314..4149716 100644 (file)
@@ -17,6 +17,7 @@ import android.os.Bundle;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.Loader;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.adapters.PagerAdapter;
 import com.cyngn.eleven.loaders.SongLoader;
 import com.cyngn.eleven.model.Song;
@@ -41,7 +42,7 @@ public class SongFragment extends BasicSongFragment {
         int internalPosition = mAdapter.getInternalPosition(position);
         Cursor cursor = SongLoader.makeSongCursor(getActivity(), null);
         final long[] list = MusicUtils.getSongListForCursor(cursor);
-        MusicUtils.playAll(getActivity(), list, internalPosition, false);
+        MusicUtils.playAll(getActivity(), list, internalPosition, -1, Config.IdType.NA, false);
         cursor.close();
         cursor = null;
     }
index 1b8b781..0541127 100644 (file)
@@ -11,7 +11,6 @@
 
 package com.cyngn.eleven.ui.fragments.profile;
 
-import android.app.Activity;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.support.v4.app.Fragment;
@@ -26,6 +25,7 @@ import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListView;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.MusicStateListener;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.adapters.SongAdapter;
@@ -33,7 +33,9 @@ import com.cyngn.eleven.model.Song;
 import com.cyngn.eleven.recycler.RecycleHolder;
 import com.cyngn.eleven.sectionadapter.SectionAdapter;
 import com.cyngn.eleven.sectionadapter.SectionListContainer;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 import com.cyngn.eleven.ui.activities.BaseActivity;
+import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.utils.PopupMenuHelper;
 import com.cyngn.eleven.utils.SongPopupMenuHelper;
 import com.cyngn.eleven.widgets.IPopupMenuCallback;
@@ -94,6 +96,16 @@ public abstract class BasicSongFragment extends Fragment implements
             }
 
             @Override
+            protected long getSourceId() {
+                return getFragmentSourceId();
+            }
+
+            @Override
+            protected Config.IdType getSourceType() {
+                return getFragmentSourceType();
+            }
+
+            @Override
             protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
                 super.updateMenuIds(type, set);
                 BasicSongFragment.this.updateMenuIds(set);
@@ -101,7 +113,7 @@ public abstract class BasicSongFragment extends Fragment implements
         };
 
         // Create the adapter
-        mAdapter = createAdapter();
+        mAdapter = new SectionAdapter(getActivity(), createAdapter());
         mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
             @Override
             public void onPopupMenuClicked(View v, int position) {
@@ -110,6 +122,14 @@ public abstract class BasicSongFragment extends Fragment implements
         });
     }
 
+    protected long getFragmentSourceId() {
+        return -1;
+    }
+
+    protected Config.IdType getFragmentSourceType() {
+        return Config.IdType.NA;
+    }
+
     protected void updateMenuIds(TreeSet<Integer> set) {
         // do nothing - let subclasses override
     }
@@ -245,12 +265,12 @@ public abstract class BasicSongFragment extends Fragment implements
      * If the subclasses want to use a customized SongAdapter they can override this method
      * @return the Song adapter
      */
-    protected SectionAdapter<Song, SongAdapter> createAdapter() {
-        return new SectionAdapter(getActivity(),
-                new SongAdapter(
-                    getActivity(),
-                    R.layout.list_item_normal
-                )
+    protected SongAdapter createAdapter() {
+        return new SongAdapter(
+            getActivity(),
+            R.layout.list_item_normal,
+            getFragmentSourceId(),
+            getFragmentSourceType()
         );
     }
 
@@ -264,7 +284,10 @@ public abstract class BasicSongFragment extends Fragment implements
 
     @Override
     public void onMetaChanged() {
-        // do nothing
+        MusicPlaybackTrack currentTrack = MusicUtils.getCurrentTrack();
+        if (mAdapter.getUnderlyingAdapter().setCurrentlyPlayingTrack(currentTrack)) {
+            mAdapter.notifyDataSetChanged();
+        }
     }
 
     @Override
index 92493ad..336c3c8 100644 (file)
@@ -74,4 +74,14 @@ public class LastAddedFragment extends BasicSongFragment implements ISetupAction
     public void setupActionBar() {
         ((BaseActivity)getActivity()).setupActionBar(R.string.playlist_last_added);
     }
+
+    @Override
+    protected long getFragmentSourceId() {
+        return Config.SmartPlaylistType.LastAdded.mId;
+    }
+
+    @Override
+    protected Config.IdType getFragmentSourceType() {
+        return Config.IdType.Playlist;
+    }
 }
index 7fbf413..b728596 100644 (file)
@@ -58,12 +58,10 @@ public class TopTracksFragment extends BasicSongFragment implements ISetupAction
     }
 
     @Override
-    protected SectionAdapter<Song, SongAdapter> createAdapter() {
-        return new SectionAdapter(getActivity(),
-                new TopTracksAdapter(
-                        getActivity(),
-                        R.layout.list_item_top_tracks
-                )
+    protected SongAdapter createAdapter() {
+        return new TopTracksAdapter(
+            getActivity(),
+            R.layout.list_item_top_tracks
         );
     }
 
@@ -85,7 +83,7 @@ public class TopTracksFragment extends BasicSongFragment implements ISetupAction
 
     public class TopTracksAdapter extends SongAdapter {
         public TopTracksAdapter (final Activity context, final int layoutId) {
-            super(context, layoutId);
+            super(context, layoutId, getFragmentSourceId(), getFragmentSourceType());
         }
 
         @Override
@@ -104,4 +102,14 @@ public class TopTracksFragment extends BasicSongFragment implements ISetupAction
         empty.setMainText(R.string.empty_top_tracks_main);
         empty.setSecondaryText(R.string.empty_top_tracks_secondary);
     }
+
+    @Override
+    protected long getFragmentSourceId() {
+        return Config.SmartPlaylistType.TopTracks.mId;
+    }
+
+    @Override
+    protected Config.IdType getFragmentSourceType() {
+        return Config.IdType.Playlist;
+    }
 }
index a4ea428..f03a0c8 100644 (file)
@@ -7,6 +7,7 @@ package com.cyngn.eleven.utils;
 import android.app.Activity;
 import android.support.v4.app.FragmentManager;
 
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.cache.ImageFetcher;
 import com.cyngn.eleven.menu.DeleteDialog;
 import com.cyngn.eleven.model.Album;
@@ -37,6 +38,16 @@ public abstract class AlbumPopupMenuHelper extends PopupMenuHelper {
     }
 
     @Override
+    protected long getSourceId() {
+        return mAlbum.mAlbumId;
+    }
+
+    @Override
+    protected Config.IdType getSourceType() {
+        return Config.IdType.Album;
+    }
+
+    @Override
     protected void onDeleteClicked() {
         final String album = mAlbum.mAlbumName;
         DeleteDialog.newInstance(album, getIdList(),
index f2cc778..9fb390e 100644 (file)
@@ -36,7 +36,7 @@ import android.provider.Settings;
 import android.util.Log;
 import android.view.Menu;
 
-import com.cyngn.eleven.Config;
+import com.cyngn.eleven.Config.IdType;
 import com.cyngn.eleven.Config.SmartPlaylistType;
 import com.cyngn.eleven.IElevenService;
 import com.cyngn.eleven.MusicPlaybackService;
@@ -50,6 +50,7 @@ import com.cyngn.eleven.menu.FragmentMenuItems;
 import com.cyngn.eleven.model.AlbumArtistDetails;
 import com.cyngn.eleven.provider.RecentStore;
 import com.cyngn.eleven.provider.SongPlayCount;
+import com.cyngn.eleven.service.MusicPlaybackTrack;
 import com.devspark.appmsg.AppMsg;
 
 import java.io.File;
@@ -464,6 +465,32 @@ public final class MusicUtils {
     }
 
     /**
+     * @return The current Music Playback Track
+     */
+    public static final MusicPlaybackTrack getCurrentTrack() {
+        if (mService != null) {
+            try {
+                return mService.getCurrentTrack();
+            } catch (final RemoteException ignored) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return The Music Playback Track at the specified index
+     */
+    public static final MusicPlaybackTrack getTrack(int index) {
+        if (mService != null) {
+            try {
+                return mService.getTrack(index);
+            } catch (final RemoteException ignored) {
+            }
+        }
+        return null;
+    }
+
+    /**
      * @return The next song Id.
      */
     public static final long getNextAudioId() {
@@ -664,7 +691,7 @@ public final class MusicUtils {
     public static void playArtist(final Context context, final long artistId, int position) {
         final long[] artistList = getSongListForArtist(context, artistId);
         if (artistList != null) {
-            playAll(context, artistList, position, false);
+            playAll(context, artistList, position, artistId, IdType.Artist, false);
         }
     }
 
@@ -726,7 +753,8 @@ public final class MusicUtils {
      * @param forceShuffle True to force a shuffle, false otherwise.
      */
     public static void playAll(final Context context, final long[] list, int position,
-            final boolean forceShuffle) {
+                               final long sourceId, final IdType sourceType,
+                               final boolean forceShuffle) {
         if (list == null || list.length == 0 || mService == null) {
             return;
         }
@@ -748,7 +776,7 @@ public final class MusicUtils {
             if (position < 0) {
                 position = 0;
             }
-            mService.open(list, forceShuffle ? -1 : position);
+            mService.open(list, forceShuffle ? -1 : position, sourceId, sourceType.mId);
             mService.play();
         } catch (final RemoteException ignored) {
         }
@@ -757,12 +785,12 @@ public final class MusicUtils {
     /**
      * @param list The list to enqueue.
      */
-    public static void playNext(final long[] list) {
+    public static void playNext(final long[] list, final long sourceId, final IdType sourceType) {
         if (mService == null) {
             return;
         }
         try {
-            mService.enqueue(list, MusicPlaybackService.NEXT);
+            mService.enqueue(list, MusicPlaybackService.NEXT, sourceId, sourceType.mId);
         } catch (final RemoteException ignored) {
         }
     }
@@ -789,7 +817,7 @@ public final class MusicUtils {
                     return;
                 }
             }
-            mService.open(mTrackList, -1);
+            mService.open(mTrackList, -1, -1, IdType.NA.mId);
             mService.play();
             cursor.close();
             cursor = null;
@@ -887,7 +915,7 @@ public final class MusicUtils {
     public static void playAlbum(final Context context, final long albumId, int position) {
         final long[] albumList = getSongListForAlbum(context, albumId);
         if (albumList != null) {
-            playAll(context, albumList, position, false);
+            playAll(context, albumList, position, albumId, IdType.Album, false);
         }
     }
 
@@ -1000,12 +1028,13 @@ public final class MusicUtils {
      * @param context The {@link Context} to use.
      * @param list The list to enqueue.
      */
-    public static void addToQueue(final Context context, final long[] list) {
+    public static void addToQueue(final Context context, final long[] list, long sourceId,
+                                  IdType sourceType) {
         if (mService == null) {
             return;
         }
         try {
-            mService.enqueue(list, MusicPlaybackService.LAST);
+            mService.enqueue(list, MusicPlaybackService.LAST, sourceId, sourceType.mId);
             final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length);
             AppMsg.makeText((Activity)context, message, AppMsg.STYLE_CONFIRM).show();
         } catch (final RemoteException ignored) {
@@ -1214,7 +1243,7 @@ public final class MusicUtils {
     public static void playPlaylist(final Context context, final long playlistId) {
         final long[] playlistList = getSongListForPlaylist(context, playlistId);
         if (playlistList != null) {
-            playAll(context, playlistList, -1, false);
+            playAll(context, playlistList, -1, playlistId, IdType.Playlist, false);
         }
     }
 
@@ -1256,7 +1285,7 @@ public final class MusicUtils {
     public static void playSmartPlaylist(final Context context, final int position,
                                          final SmartPlaylistType type) {
         final long[] list = getSongListForSmartPlaylist(context, type);
-        MusicUtils.playAll(context, list, position, false);
+        MusicUtils.playAll(context, list, position, type.mId, IdType.Playlist, false);
     }
 
     /**
index f7fd50d..1f8fdfd 100644 (file)
@@ -84,7 +84,7 @@ public final class NavUtils {
         // Create the intent to launch the profile activity
         final Intent intent = new Intent(context, HomeActivity.class);
         intent.setAction(HomeActivity.ACTION_VIEW_SMART_PLAYLIST);
-        intent.putExtra(Config.SMART_PLAYLIST_TYPE, type.ordinal());
+        intent.putExtra(Config.SMART_PLAYLIST_TYPE, type.mId);
         context.startActivity(intent);
     }
 
index e771578..5db7bdf 100644 (file)
@@ -12,13 +12,13 @@ import android.widget.PopupMenu;
 
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.MenuBuilder;
+import com.cyngn.eleven.Config;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.menu.CreateNewPlaylist;
 import com.cyngn.eleven.menu.FragmentMenuItems;
 import com.cyngn.eleven.menu.RenamePlaylist;
 import com.cyngn.eleven.provider.RecentStore;
 
-import java.util.ArrayList;
 import java.util.TreeSet;
 
 /**
@@ -82,6 +82,9 @@ public abstract class PopupMenuHelper implements PopupMenu.OnMenuItemClickListen
      */
     protected abstract long[] getIdList();
 
+    protected abstract long getSourceId();
+    protected abstract Config.IdType getSourceType();
+
     /**
      * @return the group id to be used for pop up menu inflating
      */
@@ -133,7 +136,7 @@ public abstract class PopupMenuHelper implements PopupMenu.OnMenuItemClickListen
      * Called when the user clicks "play next".  Has a default implementation
      */
     protected void playNext() {
-        MusicUtils.playNext(getIdList());
+        MusicUtils.playNext(getIdList(), getSourceId(), getSourceType());
     }
 
     /**
@@ -299,10 +302,11 @@ public abstract class PopupMenuHelper implements PopupMenu.OnMenuItemClickListen
                     MusicUtils.refresh();
                     return true;
                 case FragmentMenuItems.PLAY_SELECTION:
-                    MusicUtils.playAll(mActivity, getIdList(), 0, false);
+                    MusicUtils.playAll(mActivity, getIdList(), 0, getSourceId(), getSourceType(),
+                            false);
                     return true;
                 case FragmentMenuItems.ADD_TO_QUEUE:
-                    MusicUtils.addToQueue(mActivity, getIdList());
+                    MusicUtils.addToQueue(mActivity, getIdList(), getSourceId(), getSourceType());
                     return true;
                 case FragmentMenuItems.ADD_TO_PLAYLIST:
                     ContextMenuBuilder builder = new ContextMenuBuilder(mActivity);