From: linus_lee Date: Fri, 3 Oct 2014 18:06:43 +0000 (-0700) Subject: Eleven: Add song indicators, change ProfileSongAdapter to use SongAdapter X-Git-Tag: android-x86-6.0-r1~184 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=a03f5408a9f104ed814a4ad481d313ef85078e95;p=android-x86%2Fpackages-apps-Eleven.git Eleven: Add song indicators, change ProfileSongAdapter to use SongAdapter 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 --- diff --git a/res/drawable-xxhdpi/now_playing_icon.png b/res/drawable-xxhdpi/now_playing_icon.png new file mode 100644 index 0000000..2792c21 Binary files /dev/null and b/res/drawable-xxhdpi/now_playing_icon.png differ diff --git a/res/layout/album_detail_song.xml b/res/layout/album_detail_song.xml index 17fbdaa..e40d842 100644 --- a/res/layout/album_detail_song.xml +++ b/res/layout/album_detail_song.xml @@ -2,15 +2,28 @@ android:layout_width="match_parent" android:layout_height="70dp" > - + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + @@ -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" /> diff --git a/res/layout/artist_detail_song.xml b/res/layout/artist_detail_song.xml index e5cfa8d..ec99655 100644 --- a/res/layout/artist_detail_song.xml +++ b/res/layout/artist_detail_song.xml @@ -10,15 +10,27 @@ android:layout_alignParentLeft="true" android:layout_margin="@dimen/list_preferred_item_padding" /> - + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + @@ -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" /> - + android:gravity="center_vertical" + android:orientation="horizontal"> + + + \ No newline at end of file diff --git a/res/layout/list_item_top_tracks.xml b/res/layout/list_item_top_tracks.xml index 9a8bfb6..aae5043 100644 --- a/res/layout/list_item_top_tracks.xml +++ b/res/layout/list_item_top_tracks.xml @@ -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" > @@ -75,13 +75,27 @@ android:layout_below="@+id/line_one" /> - + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + mHistory = Lists.newLinkedList(); + private static LinkedList 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 mPlaylist = new ArrayList(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 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(); } diff --git a/src/com/cyngn/eleven/adapters/AlbumAdapter.java b/src/com/cyngn/eleven/adapters/AlbumAdapter.java index d970d99..9ab3aad 100644 --- a/src/com/cyngn/eleven/adapters/AlbumAdapter.java +++ b/src/com/cyngn/eleven/adapters/AlbumAdapter.java @@ -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 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); } }); } diff --git a/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java b/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java index c7dd899..431bffe 100644 --- a/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java +++ b/src/com/cyngn/eleven/adapters/AlbumDetailSongAdapter.java @@ -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> 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 diff --git a/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java b/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java index 384aa9a..f5d0f4f 100644 --- a/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java +++ b/src/com/cyngn/eleven/adapters/ArtistDetailSongAdapter.java @@ -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> 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) { diff --git a/src/com/cyngn/eleven/adapters/DetailSongAdapter.java b/src/com/cyngn/eleven/adapters/DetailSongAdapter.java index bed1956..38e0384 100644 --- a/src/com/cyngn/eleven/adapters/DetailSongAdapter.java +++ b/src/com/cyngn/eleven/adapters/DetailSongAdapter.java @@ -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 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); diff --git a/src/com/cyngn/eleven/adapters/ProfileSongAdapter.java b/src/com/cyngn/eleven/adapters/ProfileSongAdapter.java index 88ecb53..c4318e8 100644 --- a/src/com/cyngn/eleven/adapters/ProfileSongAdapter.java +++ b/src/com/cyngn/eleven/adapters/ProfileSongAdapter.java @@ -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 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 mCount = Lists.newArrayList(); - - /** - * Used to listen to the pop up menu callbacks - */ - private IListener mListener; - - /** * Constructor of ProfileSongAdapter * * @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 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 implements IPopupMenu */ @Override public int getViewTypeCount() { - return VIEW_TYPE_COUNT; + return super.getViewTypeCount() + NUM_HEADERS; } /** @@ -241,55 +102,34 @@ public class ProfileSongAdapter extends ArrayAdapter 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 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 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)); + } } } diff --git a/src/com/cyngn/eleven/adapters/SongAdapter.java b/src/com/cyngn/eleven/adapters/SongAdapter.java index ebc34e3..0293656 100644 --- a/src/com/cyngn/eleven/adapters/SongAdapter.java +++ b/src/com/cyngn/eleven/adapters/SongAdapter.java @@ -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 private IPopupMenuCallback.IListener mListener; /** + * Current music track + */ + protected MusicPlaybackTrack mCurrentlyPlayingTrack; + + /** + * Source id and type + */ + protected long mSourceId; + protected Config.IdType mSourceType; + + /** * Constructor of SongAdapter * * @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 } } + 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 // 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 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; + } } diff --git a/src/com/cyngn/eleven/loaders/LastAddedLoader.java b/src/com/cyngn/eleven/loaders/LastAddedLoader.java index 6f95490..ae62c0a 100644 --- a/src/com/cyngn/eleven/loaders/LastAddedLoader.java +++ b/src/com/cyngn/eleven/loaders/LastAddedLoader.java @@ -31,7 +31,6 @@ import java.util.List; * @author Andrew Neal (andrewdneal@gmail.com) */ public class LastAddedLoader extends SectionCreator.SimpleListLoader { - /** * The result */ diff --git a/src/com/cyngn/eleven/model/SearchResult.java b/src/com/cyngn/eleven/model/SearchResult.java index b86ff07..f5736b1 100644 --- a/src/com/cyngn/eleven/model/SearchResult.java +++ b/src/com/cyngn/eleven/model/SearchResult.java @@ -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; diff --git a/src/com/cyngn/eleven/provider/MusicDB.java b/src/com/cyngn/eleven/provider/MusicDB.java index 794b661..c8b7c23 100644 --- a/src/com/cyngn/eleven/provider/MusicDB.java +++ b/src/com/cyngn/eleven/provider/MusicDB.java @@ -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 index 0000000..dfead29 --- /dev/null +++ b/src/com/cyngn/eleven/provider/MusicPlaybackState.java @@ -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 MusicPlaybackState + * + * @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 queue, + LinkedList 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 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 getQueue() { + ArrayList 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 getHistory(final int playlistSize) { + LinkedList 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"; + } +} diff --git a/src/com/cyngn/eleven/provider/PlaylistArtworkStore.java b/src/com/cyngn/eleven/provider/PlaylistArtworkStore.java index 632c967..ee8faf2 100644 --- a/src/com/cyngn/eleven/provider/PlaylistArtworkStore.java +++ b/src/com/cyngn/eleven/provider/PlaylistArtworkStore.java @@ -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); } diff --git a/src/com/cyngn/eleven/provider/RecentStore.java b/src/com/cyngn/eleven/provider/RecentStore.java index 860c6e3..cffbe08 100644 --- a/src/com/cyngn/eleven/provider/RecentStore.java +++ b/src/com/cyngn/eleven/provider/RecentStore.java @@ -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); } diff --git a/src/com/cyngn/eleven/provider/SearchHistory.java b/src/com/cyngn/eleven/provider/SearchHistory.java index 3f02c65..8f86f8d 100644 --- a/src/com/cyngn/eleven/provider/SearchHistory.java +++ b/src/com/cyngn/eleven/provider/SearchHistory.java @@ -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); } diff --git a/src/com/cyngn/eleven/provider/SongPlayCount.java b/src/com/cyngn/eleven/provider/SongPlayCount.java index 77ab5de..3e6e7d5 100644 --- a/src/com/cyngn/eleven/provider/SongPlayCount.java +++ b/src/com/cyngn/eleven/provider/SongPlayCount.java @@ -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 index 0000000..682a0df --- /dev/null +++ b/src/com/cyngn/eleven/service/MusicPlaybackTrack.aidl @@ -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 index 0000000..71e5795 --- /dev/null +++ b/src/com/cyngn/eleven/service/MusicPlaybackTrack.java @@ -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 CREATOR = new Creator() { + @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); + } +} diff --git a/src/com/cyngn/eleven/ui/MusicHolder.java b/src/com/cyngn/eleven/ui/MusicHolder.java index f9b4382..9c4ce78 100644 --- a/src/com/cyngn/eleven/ui/MusicHolder.java +++ b/src/com/cyngn/eleven/ui/MusicHolder.java @@ -77,6 +77,11 @@ public class MusicHolder { public WeakReference mPlayPauseProgressContainer; /** + * The song indicator for the currently playing track + */ + public WeakReference mNowPlayingIndicator; + + /** * The divider for the list item */ public WeakReference mDivider; @@ -114,6 +119,8 @@ public class MusicHolder { mPlayPauseProgressContainer = new WeakReference( view.findViewById(R.id.play_pause_container)); + mNowPlayingIndicator = new WeakReference(view.findViewById(R.id.now_playing)); + // Get the divider for the list item mDivider = new WeakReference(view.findViewById(R.id.divider)); diff --git a/src/com/cyngn/eleven/ui/activities/SearchActivity.java b/src/com/cyngn/eleven/ui/activities/SearchActivity.java index 20d558e..712d997 100644 --- a/src/com/cyngn/eleven/ui/activities/SearchActivity.java +++ b/src/com/cyngn/eleven/ui/activities/SearchActivity.java @@ -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 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; } } diff --git a/src/com/cyngn/eleven/ui/activities/ShortcutActivity.java b/src/com/cyngn/eleven/ui/activities/ShortcutActivity.java index f6d3a9e..79d3b57 100644 --- a/src/com/cyngn/eleven/ui/activities/ShortcutActivity.java +++ b/src/com/cyngn/eleven/ui/activities/ShortcutActivity.java @@ -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 diff --git a/src/com/cyngn/eleven/ui/fragments/AlbumDetailFragment.java b/src/com/cyngn/eleven/ui/fragments/AlbumDetailFragment.java index 40de324..916aea5 100644 --- a/src/com/cyngn/eleven/ui/fragments/AlbumDetailFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/AlbumDetailFragment.java @@ -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 diff --git a/src/com/cyngn/eleven/ui/fragments/ArtistDetailFragment.java b/src/com/cyngn/eleven/ui/fragments/ArtistDetailFragment.java index d1012e6..6388307 100644 --- a/src/com/cyngn/eleven/ui/fragments/ArtistDetailFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/ArtistDetailFragment.java @@ -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 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 diff --git a/src/com/cyngn/eleven/ui/fragments/ArtistFragment.java b/src/com/cyngn/eleven/ui/fragments/ArtistFragment.java index e1507a2..465d5e1 100644 --- a/src/com/cyngn/eleven/ui/fragments/ArtistFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/ArtistFragment.java @@ -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( diff --git a/src/com/cyngn/eleven/ui/fragments/PlaylistDetailFragment.java b/src/com/cyngn/eleven/ui/fragments/PlaylistDetailFragment.java index 0afec03..9306563 100644 --- a/src/com/cyngn/eleven/ui/fragments/PlaylistDetailFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/PlaylistDetailFragment.java @@ -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()); // 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 diff --git a/src/com/cyngn/eleven/ui/fragments/PlaylistFragment.java b/src/com/cyngn/eleven/ui/fragments/PlaylistFragment.java index 81bf19a..0041d6d 100644 --- a/src/com/cyngn/eleven/ui/fragments/PlaylistFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/PlaylistFragment.java @@ -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(); } diff --git a/src/com/cyngn/eleven/ui/fragments/QueueFragment.java b/src/com/cyngn/eleven/ui/fragments/QueueFragment.java index a08ff6e..2acc275 100644 --- a/src/com/cyngn/eleven/ui/fragments/QueueFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/QueueFragment.java @@ -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 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 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 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 diff --git a/src/com/cyngn/eleven/ui/fragments/profile/LastAddedFragment.java b/src/com/cyngn/eleven/ui/fragments/profile/LastAddedFragment.java index 92493ad..336c3c8 100644 --- a/src/com/cyngn/eleven/ui/fragments/profile/LastAddedFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/profile/LastAddedFragment.java @@ -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; + } } diff --git a/src/com/cyngn/eleven/ui/fragments/profile/TopTracksFragment.java b/src/com/cyngn/eleven/ui/fragments/profile/TopTracksFragment.java index 7fbf413..b728596 100644 --- a/src/com/cyngn/eleven/ui/fragments/profile/TopTracksFragment.java +++ b/src/com/cyngn/eleven/ui/fragments/profile/TopTracksFragment.java @@ -58,12 +58,10 @@ public class TopTracksFragment extends BasicSongFragment implements ISetupAction } @Override - protected SectionAdapter 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; + } } diff --git a/src/com/cyngn/eleven/utils/AlbumPopupMenuHelper.java b/src/com/cyngn/eleven/utils/AlbumPopupMenuHelper.java index a4ea428..f03a0c8 100644 --- a/src/com/cyngn/eleven/utils/AlbumPopupMenuHelper.java +++ b/src/com/cyngn/eleven/utils/AlbumPopupMenuHelper.java @@ -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(), diff --git a/src/com/cyngn/eleven/utils/MusicUtils.java b/src/com/cyngn/eleven/utils/MusicUtils.java index f2cc778..9fb390e 100644 --- a/src/com/cyngn/eleven/utils/MusicUtils.java +++ b/src/com/cyngn/eleven/utils/MusicUtils.java @@ -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); } /** diff --git a/src/com/cyngn/eleven/utils/NavUtils.java b/src/com/cyngn/eleven/utils/NavUtils.java index f7fd50d..1f8fdfd 100644 --- a/src/com/cyngn/eleven/utils/NavUtils.java +++ b/src/com/cyngn/eleven/utils/NavUtils.java @@ -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); } diff --git a/src/com/cyngn/eleven/utils/PopupMenuHelper.java b/src/com/cyngn/eleven/utils/PopupMenuHelper.java index e771578..5db7bdf 100644 --- a/src/com/cyngn/eleven/utils/PopupMenuHelper.java +++ b/src/com/cyngn/eleven/utils/PopupMenuHelper.java @@ -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);