Merge lp:~karni/ubuntuone-android-music/playlist-ux-and-sync into lp:ubuntuone-android-music/v2
- playlist-ux-and-sync
- Merge into v2
Proposed by
Michał Karnicki
on 2012-11-28
| Status: | Merged |
|---|---|
| Approved by: | Michał Karnicki on 2012-12-12 |
| Approved revision: | 32 |
| Merged at revision: | 32 |
| Proposed branch: | lp:~karni/ubuntuone-android-music/playlist-ux-and-sync |
| Merge into: | lp:ubuntuone-android-music/v2 |
| Prerequisite: | lp:~karni/ubuntuone-android-music/added-missing-dao |
| Diff against target: |
1525 lines (+527/-267) 25 files modified
AndroidManifest.xml (+7/-0) res/layout/activity_playlist.xml (+0/-76) res/layout/activity_playlists.xml (+13/-0) res/values/ids.xml (+1/-0) res/values/strings.xml (+5/-0) src/com/ubuntuone/android/music/adapter/U1PlaylistAdapter.java (+7/-2) src/com/ubuntuone/android/music/model/Song.java (+1/-1) src/com/ubuntuone/android/music/provider/MusicContract.java (+11/-3) src/com/ubuntuone/android/music/provider/MusicDatabase.java (+1/-0) src/com/ubuntuone/android/music/provider/MusicProvider.java (+6/-6) src/com/ubuntuone/android/music/provider/MusicProviderUtils.java (+70/-3) src/com/ubuntuone/android/music/service/MusicService.java (+16/-5) src/com/ubuntuone/android/music/service/SyncService.java (+59/-10) src/com/ubuntuone/android/music/ui/AlbumsFragment.java (+8/-1) src/com/ubuntuone/android/music/ui/HomeActivity.java (+50/-4) src/com/ubuntuone/android/music/ui/PlayerFragment.java (+1/-1) src/com/ubuntuone/android/music/ui/PlaylistActivity.java (+0/-1) src/com/ubuntuone/android/music/ui/PlaylistsActivity.java (+148/-6) src/com/ubuntuone/android/music/ui/PlaylistsFragment.java (+33/-111) src/com/ubuntuone/android/music/ui/SongsFragment.java (+58/-18) src/com/ubuntuone/android/music/ui/dialog/InfoAlertDialog.java (+12/-6) test/src/com/ubuntuone/android/music/provider/MusicProviderInsertTest.java (+2/-2) test/src/com/ubuntuone/android/music/provider/MusicProviderQueryTest.java (+4/-4) test/src/com/ubuntuone/android/music/provider/MusicProviderUtilsTest.java (+10/-7) test/src/com/ubuntuone/android/music/ui/SongsPageTest.java (+4/-0) |
| To merge this branch: | bzr merge lp:~karni/ubuntuone-android-music/playlist-ux-and-sync |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Roberto Alsina (community) | 2012-11-28 | Approve on 2012-12-12 | |
|
Review via email:
|
|||
Commit Message
Added playlist manipulation and sync.
Description of the Change
This branch allows adding and removing items from playlists, as well as sync them with U1.
Create new playlist feature is pending minor rework after recent u1-server changes rolled out.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'AndroidManifest.xml' |
| 2 | --- AndroidManifest.xml 2012-11-08 14:14:54 +0000 |
| 3 | +++ AndroidManifest.xml 2012-11-28 17:49:20 +0000 |
| 4 | @@ -63,6 +63,11 @@ |
| 5 | android:theme="@style/Theme.Sherlock.Light" > |
| 6 | </activity> |
| 7 | <activity |
| 8 | + android:name=".ui.PlaylistsActivity" |
| 9 | + android:label="U1 Playlists" |
| 10 | + android:theme="@style/Theme.Sherlock.Light.Dialog" > |
| 11 | + </activity> |
| 12 | + <activity |
| 13 | android:name=".ui.PlayerActivity" |
| 14 | android:label="U1 Player" |
| 15 | android:screenOrientation="portrait" > |
| 16 | @@ -78,6 +83,7 @@ |
| 17 | android:exported="false" > |
| 18 | <intent-filter> |
| 19 | <action android:name="com.ubuntuone.android.music.ACTION_SYNC_MUSIC_METADATA" /> |
| 20 | + <action android:name="com.ubuntuone.android.music.ACTION_SYNC_PLAYLISTS" /> |
| 21 | </intent-filter> |
| 22 | </service> |
| 23 | <service |
| 24 | @@ -87,6 +93,7 @@ |
| 25 | |
| 26 | <provider |
| 27 | android:name=".provider.MusicProvider" |
| 28 | + android:exported="false" |
| 29 | android:authorities="com.ubuntuone.android.music" |
| 30 | android:label="@string/app_name" |
| 31 | android:syncable="true" |
| 32 | |
| 33 | === removed file 'res/layout/activity_playlist.xml' |
| 34 | --- res/layout/activity_playlist.xml 2012-09-19 23:50:20 +0000 |
| 35 | +++ res/layout/activity_playlist.xml 1970-01-01 00:00:00 +0000 |
| 36 | @@ -1,76 +0,0 @@ |
| 37 | -<?xml version="1.0" encoding="utf-8"?> |
| 38 | -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 39 | - xmlns:tools="http://schemas.android.com/tools" |
| 40 | - android:layout_width="match_parent" |
| 41 | - android:layout_height="match_parent" > |
| 42 | - |
| 43 | - <ImageView |
| 44 | - android:id="@+id/image" |
| 45 | - android:layout_width="@dimen/header_image_size" |
| 46 | - android:layout_height="@dimen/header_image_size" |
| 47 | - android:layout_alignParentLeft="true" |
| 48 | - android:layout_alignParentTop="true" |
| 49 | - android:background="@drawable/ic_action_help" |
| 50 | - android:minHeight="@dimen/header_image_size" |
| 51 | - android:minWidth="@dimen/header_image_size" /> |
| 52 | - |
| 53 | - <TextView |
| 54 | - android:id="@+id/text1" |
| 55 | - android:layout_width="match_parent" |
| 56 | - android:layout_height="wrap_content" |
| 57 | - android:layout_alignTop="@id/image" |
| 58 | - android:layout_toRightOf="@id/image" |
| 59 | - android:gravity="top|left" |
| 60 | - android:paddingLeft="4dp" |
| 61 | - android:paddingTop="4dp" |
| 62 | - android:singleLine="true" |
| 63 | - android:textStyle="bold" |
| 64 | - android:textAppearance="@android:style/TextAppearance.Medium" |
| 65 | - android:textColor="@android:color/primary_text_light" /> |
| 66 | - |
| 67 | - <TextView |
| 68 | - android:id="@+id/text2" |
| 69 | - android:layout_width="match_parent" |
| 70 | - android:layout_height="wrap_content" |
| 71 | - android:layout_below="@id/text1" |
| 72 | - android:layout_toRightOf="@id/image" |
| 73 | - android:gravity="top|left" |
| 74 | - android:paddingLeft="4dp" |
| 75 | - android:singleLine="true" |
| 76 | - android:textAppearance="@android:style/TextAppearance.Small" |
| 77 | - android:textColor="@android:color/tertiary_text_light" /> |
| 78 | - |
| 79 | - <LinearLayout |
| 80 | - android:layout_width="match_parent" |
| 81 | - android:layout_height="wrap_content" |
| 82 | - android:layout_alignBottom="@id/image" |
| 83 | - android:layout_toRightOf="@id/image" |
| 84 | - android:weightSum="2" > |
| 85 | - |
| 86 | - <Button |
| 87 | - android:id="@+id/shuffle_all" |
| 88 | - android:layout_width="0dp" |
| 89 | - android:layout_height="wrap_content" |
| 90 | - android:layout_weight="1" |
| 91 | - android:text="@string/shuffle_all" |
| 92 | - android:textAppearance="@android:style/TextAppearance.Small" |
| 93 | - android:onClick="shuffleAll" /> |
| 94 | - |
| 95 | - <Button |
| 96 | - android:id="@+id/repeat_all" |
| 97 | - android:layout_width="0dp" |
| 98 | - android:layout_height="wrap_content" |
| 99 | - android:layout_weight="1" |
| 100 | - android:text="@string/repeat_all" |
| 101 | - android:textAppearance="@android:style/TextAppearance.Small" |
| 102 | - android:onClick="repeatAll" /> |
| 103 | - </LinearLayout> |
| 104 | - |
| 105 | - <FrameLayout |
| 106 | - android:id="@+id/songs" |
| 107 | - android:layout_width="match_parent" |
| 108 | - android:layout_height="match_parent" |
| 109 | - android:layout_below="@id/image" > |
| 110 | - </FrameLayout> |
| 111 | - |
| 112 | -</RelativeLayout> |
| 113 | \ No newline at end of file |
| 114 | |
| 115 | === added file 'res/layout/activity_playlists.xml' |
| 116 | --- res/layout/activity_playlists.xml 1970-01-01 00:00:00 +0000 |
| 117 | +++ res/layout/activity_playlists.xml 2012-11-28 17:49:20 +0000 |
| 118 | @@ -0,0 +1,13 @@ |
| 119 | +<?xml version="1.0" encoding="utf-8"?> |
| 120 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 121 | + xmlns:tools="http://schemas.android.com/tools" |
| 122 | + android:layout_width="match_parent" |
| 123 | + android:layout_height="match_parent" > |
| 124 | + |
| 125 | + <fragment |
| 126 | + android:id="@+id/playlists" |
| 127 | + android:name="com.ubuntuone.android.music.ui.PlaylistsFragment" |
| 128 | + android:layout_width="match_parent" |
| 129 | + android:layout_height="match_parent" /> |
| 130 | + |
| 131 | +</RelativeLayout> |
| 132 | |
| 133 | === modified file 'res/values/ids.xml' |
| 134 | --- res/values/ids.xml 2012-11-08 14:14:54 +0000 |
| 135 | +++ res/values/ids.xml 2012-11-28 17:49:20 +0000 |
| 136 | @@ -15,5 +15,6 @@ |
| 137 | <item type="id" name="context_play" /> |
| 138 | <item type="id" name="context_star" /> |
| 139 | <item type="id" name="context_add_to_playlist" /> |
| 140 | + <item type="id" name="context_remove_from_playlist" /> |
| 141 | <item type="id" name="context_add_to_queue" /> |
| 142 | </resources> |
| 143 | \ No newline at end of file |
| 144 | |
| 145 | === modified file 'res/values/strings.xml' |
| 146 | --- res/values/strings.xml 2012-11-08 14:14:54 +0000 |
| 147 | +++ res/values/strings.xml 2012-11-28 17:49:20 +0000 |
| 148 | @@ -31,7 +31,11 @@ |
| 149 | <string name="playlist_stats">%1$s songs</string> |
| 150 | <string name="playlist_title">Playlist title</string> |
| 151 | <string name="creating_playlist">Creating playlist…</string> |
| 152 | + <string name="add_to">Add to…</string> |
| 153 | + <string name="added_to_fmt">Added to %s.</string> |
| 154 | + <string name="sync_pending">Sync pending</string> |
| 155 | <string name="creating_playlist_failed_title">Cound\'t create playlist</string> |
| 156 | + <string name="updating_playlist_failed_title">Cound\'t update playlist</string> |
| 157 | |
| 158 | <string name="genres_title">genres</string> |
| 159 | <string name="genres_loading">Getting genres…</string> |
| 160 | @@ -56,6 +60,7 @@ |
| 161 | <string name="description_play">Play</string> |
| 162 | <string name="description_star">Star</string> |
| 163 | <string name="description_add_to_playlist">Add to playlist</string> |
| 164 | + <string name="description_remove_from_playlist">Remove from playlist</string> |
| 165 | <string name="description_add_to_queue">Add to queue</string> |
| 166 | |
| 167 | <string name="shuffle_all">Shuffle all</string> |
| 168 | |
| 169 | === modified file 'src/com/ubuntuone/android/music/adapter/U1PlaylistAdapter.java' |
| 170 | --- src/com/ubuntuone/android/music/adapter/U1PlaylistAdapter.java 2012-11-22 10:23:21 +0000 |
| 171 | +++ src/com/ubuntuone/android/music/adapter/U1PlaylistAdapter.java 2012-11-28 17:49:20 +0000 |
| 172 | @@ -157,8 +157,13 @@ |
| 173 | |
| 174 | String name = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_NAME)); |
| 175 | int length = cursor.getInt(cursor.getColumnIndex(Playlists.PLAYLIST_SONG_COUNT)); |
| 176 | - |
| 177 | - holder.playlistTitle.setText(name); |
| 178 | + int dirty = cursor.getInt(cursor.getColumnIndex(Playlists.DIRTY)); |
| 179 | + if (dirty != 0) { |
| 180 | + holder.playlistTitle.setText(String.format("%s [%s]", |
| 181 | + name, context.getString(R.string.sync_pending))); |
| 182 | + } else { |
| 183 | + holder.playlistTitle.setText(name); |
| 184 | + } |
| 185 | // TODO Get plural string resource for item(s). |
| 186 | if (length == 0) { |
| 187 | holder.playlistSongCount.setText(String.format("No songs", length)); |
| 188 | |
| 189 | === modified file 'src/com/ubuntuone/android/music/model/Song.java' |
| 190 | --- src/com/ubuntuone/android/music/model/Song.java 2012-11-08 14:14:54 +0000 |
| 191 | +++ src/com/ubuntuone/android/music/model/Song.java 2012-11-28 17:49:20 +0000 |
| 192 | @@ -69,7 +69,7 @@ |
| 193 | |
| 194 | public Song(Context context, Uri uri) { |
| 195 | ContentResolver resolver = context.getContentResolver(); |
| 196 | - String[] projection = Songs.DEFAULT_PROJECTION; |
| 197 | + String[] projection = Songs.getDefaultProjection(); |
| 198 | Cursor cursor = null; |
| 199 | try { |
| 200 | cursor = resolver.query(uri, projection, null, null, null); |
| 201 | |
| 202 | === modified file 'src/com/ubuntuone/android/music/provider/MusicContract.java' |
| 203 | --- src/com/ubuntuone/android/music/provider/MusicContract.java 2012-11-21 15:03:12 +0000 |
| 204 | +++ src/com/ubuntuone/android/music/provider/MusicContract.java 2012-11-28 17:49:20 +0000 |
| 205 | @@ -153,6 +153,8 @@ |
| 206 | String PLAYLIST_SONG_COUNT = "playlist_song_count"; |
| 207 | /** Full URL to this playlist online. */ |
| 208 | String PLAYLIST_URL = "playlist_url"; |
| 209 | + /** Flag indicating playlist should be sync'ed up. */ |
| 210 | + String DIRTY = "dirty"; |
| 211 | } |
| 212 | |
| 213 | interface PlaylistSongsColumns extends SongsColumns |
| 214 | @@ -376,7 +378,7 @@ |
| 215 | .buildUpon().appendPath(PATH_CACHED).build(); |
| 216 | |
| 217 | /** Default query projection. */ |
| 218 | - public static final String[] DEFAULT_PROJECTION = new String[] { |
| 219 | + private static final String[] DEFAULT_PROJECTION = new String[] { |
| 220 | Songs._ID, Songs.SONG_ID, Songs.SONG_TITLE, Songs.SONG_ALBUM, |
| 221 | Songs.SONG_ALBUM_ID, Songs.SONG_ALBUM_ARTIST, |
| 222 | Songs.SONG_ARTIST, Songs.SONG_ARTIST_ID, Songs.SONG_GENRE, |
| 223 | @@ -385,6 +387,12 @@ |
| 224 | Songs.SONG_BIT_RATE, Songs.SONG_SUFFIX, Songs.SONG_CONTENT_TYPE, |
| 225 | Songs.SONG_PATH, Songs.SONG_LAST_PLAYED_AT, Songs.STARRED, Songs.SONG_IS_PLAYING, |
| 226 | }; |
| 227 | + |
| 228 | + public static String[] getDefaultProjection() { |
| 229 | + // karni: Earn $10 and tell me why you have to return a COPY of a *static final* for it |
| 230 | + // not to change here, in MusicContract#DEFAULT_PROJECTION. Platform bug? |
| 231 | + return DEFAULT_PROJECTION.clone(); |
| 232 | + } |
| 233 | |
| 234 | /** Default "ORDER BY" clause. */ |
| 235 | public static final String DEFAULT_SORT = SongsColumns.SONG_TITLE |
| 236 | @@ -435,7 +443,7 @@ |
| 237 | /** Default query projection. */ |
| 238 | public static final String[] DEFAULT_PROJECTION = new String[] { |
| 239 | Playlists._ID, Playlists.PLAYLIST_ID, Playlists.PLAYLIST_NAME, |
| 240 | - Playlists.PLAYLIST_SONG_COUNT, Playlists.STARRED |
| 241 | + Playlists.PLAYLIST_SONG_COUNT, Playlists.STARRED, Playlists.DIRTY |
| 242 | }; |
| 243 | |
| 244 | public static final String[] SONGS_DEFAULT_PROJECTION = new String[] { |
| 245 | @@ -503,7 +511,7 @@ |
| 246 | } |
| 247 | |
| 248 | public static class PlaylistSongs implements PlaylistSongsColumns, SyncColumns, |
| 249 | - SongsColumns, BaseColumns |
| 250 | + SongsColumns, StarredColumn, BaseColumns |
| 251 | { |
| 252 | public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon() |
| 253 | .appendPath(PATH_PLAYLISTS_SONGS).build(); |
| 254 | |
| 255 | === modified file 'src/com/ubuntuone/android/music/provider/MusicDatabase.java' |
| 256 | --- src/com/ubuntuone/android/music/provider/MusicDatabase.java 2012-11-21 15:03:12 +0000 |
| 257 | +++ src/com/ubuntuone/android/music/provider/MusicDatabase.java 2012-11-28 17:49:20 +0000 |
| 258 | @@ -202,6 +202,7 @@ |
| 259 | + PlaylistsColumns.PLAYLIST_NAME + " TEXT NOT NULL, " |
| 260 | + PlaylistsColumns.PLAYLIST_SONG_COUNT + " INTEGER NOT NULL DEFAULT 0, " |
| 261 | + PlaylistsColumns.PLAYLIST_URL + " TEXT NOT NULL, " |
| 262 | + + PlaylistsColumns.DIRTY + " INTEGER NOT NULL DEFAULT 0, " |
| 263 | + "UNIQUE (" + PlaylistsColumns.PLAYLIST_ID + ") ON CONFLICT IGNORE)"); |
| 264 | |
| 265 | // Create the play queue playlist. |
| 266 | |
| 267 | === modified file 'src/com/ubuntuone/android/music/provider/MusicProvider.java' |
| 268 | --- src/com/ubuntuone/android/music/provider/MusicProvider.java 2012-11-22 10:23:21 +0000 |
| 269 | +++ src/com/ubuntuone/android/music/provider/MusicProvider.java 2012-11-28 17:49:20 +0000 |
| 270 | @@ -602,18 +602,18 @@ |
| 271 | final String playlistId = Playlists.getPlaylistId(uri); |
| 272 | return builder.table(Tables.PLAYLIST_SONGS_JOIN_SONGS) |
| 273 | .where(PlaylistSongs.PLAYLIST_ID + "=?", playlistId) |
| 274 | - .mapToTable(Songs._ID, Tables.SONGS) |
| 275 | - .mapToTable(Songs.SONG_ID, Tables.SONGS); |
| 276 | + .mapToTable(PlaylistSongs._ID, Tables.PLAYLISTS_SONGS) |
| 277 | + .mapToTable(PlaylistSongs.SONG_ID, Tables.SONGS); |
| 278 | } |
| 279 | case PLAYLISTS_ID_SONGS_ID: { |
| 280 | final String playlistId = Playlists.getPlaylistId(uri); |
| 281 | final String songId = Playlists.getSongId(uri); |
| 282 | - return builder.table(Tables.PLAYLISTS_SONGS) |
| 283 | + return builder.table(Tables.PLAYLIST_SONGS_JOIN_SONGS) |
| 284 | .where(PlaylistSongs.PLAYLIST_ID + "=?", playlistId) |
| 285 | .where(PlaylistSongs.SONG_ID + "=?", songId) |
| 286 | - .mapToTable(Songs._ID, Tables.SONGS) |
| 287 | - .mapToTable(Songs.SONG_ID, Tables.SONGS) |
| 288 | - .mapToTable(Songs.STARRED, Tables.SONGS); |
| 289 | + .mapToTable(PlaylistSongs._ID, Tables.PLAYLISTS_SONGS) |
| 290 | + .mapToTable(PlaylistSongs.SONG_ID, Tables.SONGS) |
| 291 | + .mapToTable(PlaylistSongs.STARRED, Tables.SONGS); |
| 292 | } |
| 293 | |
| 294 | case GENRES: { |
| 295 | |
| 296 | === modified file 'src/com/ubuntuone/android/music/provider/MusicProviderUtils.java' |
| 297 | --- src/com/ubuntuone/android/music/provider/MusicProviderUtils.java 2012-11-21 15:03:12 +0000 |
| 298 | +++ src/com/ubuntuone/android/music/provider/MusicProviderUtils.java 2012-11-28 17:49:20 +0000 |
| 299 | @@ -23,6 +23,7 @@ |
| 300 | |
| 301 | import static com.ubuntuone.android.music.util.LogUtils.makeLogTag; |
| 302 | |
| 303 | +import java.util.ArrayList; |
| 304 | import java.util.Random; |
| 305 | |
| 306 | import android.content.ContentResolver; |
| 307 | @@ -208,11 +209,14 @@ |
| 308 | |
| 309 | public static int getPlaylistSongCount(Context context, Uri playlistUri) { |
| 310 | ContentResolver resolver = context.getContentResolver(); |
| 311 | + String playlistId = Playlists.getPlaylistId(playlistUri); |
| 312 | + Uri playlistSongsUri = Playlists.buildPlaylistSongsUri(playlistId); |
| 313 | + |
| 314 | String[] projection = new String[] { Songs._ID }; |
| 315 | Cursor cursor = null; |
| 316 | int count = -1; |
| 317 | try { |
| 318 | - cursor = resolver.query(playlistUri, projection, null, null, null); |
| 319 | + cursor = resolver.query(playlistSongsUri, projection, null, null, null); |
| 320 | count = (cursor != null) ? cursor.getCount() : 0; |
| 321 | } finally { |
| 322 | if (cursor != null) cursor.close(); |
| 323 | @@ -220,6 +224,28 @@ |
| 324 | return count; |
| 325 | } |
| 326 | |
| 327 | + public static ArrayList<String> getPlaylistSongIdList(Context context, String playlistId) { |
| 328 | + ContentResolver resolver = context.getContentResolver(); |
| 329 | + Uri playlistSongsUri = Playlists.buildPlaylistSongsUri(playlistId); |
| 330 | + ArrayList<String> songIdList = new ArrayList<String>(); |
| 331 | + |
| 332 | + String[] projection = new String[] { PlaylistSongs.SONG_ID }; |
| 333 | + Cursor cursor = null; |
| 334 | + try { |
| 335 | + cursor = resolver.query(playlistSongsUri, projection, null, null, null); |
| 336 | + if (cursor != null && cursor.isBeforeFirst()) { |
| 337 | + while (cursor.moveToNext()) { |
| 338 | + String songId = cursor.getString(cursor.getColumnIndex(PlaylistSongs.SONG_ID)); |
| 339 | + songIdList.add(songId); |
| 340 | + } |
| 341 | + } |
| 342 | + } finally { |
| 343 | + if (cursor != null) cursor.close(); |
| 344 | + } |
| 345 | + |
| 346 | + return songIdList; |
| 347 | + } |
| 348 | + |
| 349 | public static int getPlayQueueSongCount(Context context) { |
| 350 | Uri playQueueUri = Playlists.buildPlaylistSongsUri(QUEUE_ID); |
| 351 | return getPlaylistSongCount(context, playQueueUri); |
| 352 | @@ -236,14 +262,55 @@ |
| 353 | Uri playQueueUri = Playlists.buildPlaylistSongsUri(MusicProviderUtils.QUEUE_ID); |
| 354 | clearPlaylist(context, playQueueUri); |
| 355 | } |
| 356 | - |
| 357 | + |
| 358 | + /** |
| 359 | + * Updates playlist song count, saves one request to the server upon |
| 360 | + * enqueuing content. |
| 361 | + * |
| 362 | + * @param context |
| 363 | + * The context to use. |
| 364 | + * @param playlistUri |
| 365 | + * Playlist Uri of which song count to update. |
| 366 | + */ |
| 367 | + public static void updatePlaylistSongCount(Context context, Uri playlistUri) { |
| 368 | + ContentResolver resolver = context.getContentResolver(); |
| 369 | + ContentValues values = new ContentValues(); |
| 370 | + int songCount = MusicProviderUtils.getPlaylistSongCount(context, playlistUri); |
| 371 | + values.put(Playlists.PLAYLIST_SONG_COUNT, songCount); |
| 372 | + resolver.update(playlistUri, values, null, null); |
| 373 | + } |
| 374 | + |
| 375 | + /** |
| 376 | + * Flags the playlist as dirty for sync up. Next time metadata is refreshed, |
| 377 | + * dirty playlists are first saved to the server. |
| 378 | + * |
| 379 | + * @param context |
| 380 | + * The context to use. |
| 381 | + * @param playlistUri |
| 382 | + * Uri of the playlist to flag. |
| 383 | + * @param isDirty |
| 384 | + * true to mark for sync up, false otherwise. |
| 385 | + */ |
| 386 | + public static void flagPlaylistForSync(Context context, Uri playlistUri, boolean isDirty) { |
| 387 | + ContentResolver resolver = context.getContentResolver(); |
| 388 | + ContentValues values = new ContentValues(); |
| 389 | + values.put(Playlists.DIRTY, isDirty); |
| 390 | + resolver.update(playlistUri, values, null, null); |
| 391 | + } |
| 392 | + |
| 393 | public static void enqueueSong(Context context, Uri songUri, Uri playlistUri) { |
| 394 | ContentResolver resolver = context.getContentResolver(); |
| 395 | String playlistId = Playlists.getPlaylistId(playlistUri); |
| 396 | String songId = Songs.getSongId(songUri); |
| 397 | + Uri playlistSongsUri = Playlists.buildPlaylistSongsUri(playlistId); |
| 398 | |
| 399 | ContentValues values = MusicContentValues.forPlaylistSong(playlistId, songId); |
| 400 | - resolver.insert(playlistUri, values); |
| 401 | + resolver.insert(playlistSongsUri, values); |
| 402 | + |
| 403 | + if (!QUEUE_ID.equals(playlistId)) { |
| 404 | + updatePlaylistSongCount(context, playlistUri); |
| 405 | + flagPlaylistForSync(context, playlistUri, true); |
| 406 | + } |
| 407 | } |
| 408 | |
| 409 | public static void enqueueSong(Context context, Uri songUri) { |
| 410 | |
| 411 | === modified file 'src/com/ubuntuone/android/music/service/MusicService.java' |
| 412 | --- src/com/ubuntuone/android/music/service/MusicService.java 2012-11-21 15:03:12 +0000 |
| 413 | +++ src/com/ubuntuone/android/music/service/MusicService.java 2012-11-28 17:49:20 +0000 |
| 414 | @@ -41,6 +41,7 @@ |
| 415 | import android.content.Context; |
| 416 | import android.content.Intent; |
| 417 | import android.content.SharedPreferences; |
| 418 | +import android.database.sqlite.SQLiteDatabaseLockedException; |
| 419 | import android.media.AudioManager; |
| 420 | import android.media.MediaPlayer; |
| 421 | import android.media.MediaPlayer.OnErrorListener; |
| 422 | @@ -57,6 +58,7 @@ |
| 423 | import com.ubuntuone.android.music.UbuntuOneMusic; |
| 424 | import com.ubuntuone.android.music.model.PlayerState; |
| 425 | import com.ubuntuone.android.music.model.Song; |
| 426 | +import com.ubuntuone.android.music.provider.MusicContract.Songs; |
| 427 | import com.ubuntuone.android.music.provider.MusicProviderUtils; |
| 428 | import com.ubuntuone.android.music.util.UIUtils; |
| 429 | import com.ubuntuone.api.music.U1MusicAPI; |
| 430 | @@ -123,9 +125,13 @@ |
| 431 | this.mShufflePlay = sharedPrefs.getBoolean(SHUFFLE_PREFERENCE_KEY, false); |
| 432 | this.mRepeatPlay = sharedPrefs.getBoolean(REPEAT_PREFERENCE_KEY, false); |
| 433 | |
| 434 | - Uri songUri = MusicProviderUtils.getPlayingSong(this); |
| 435 | - if (songUri != null) { |
| 436 | - mCurrentPlaying = new Song(this, songUri); |
| 437 | + try { |
| 438 | + Uri songUri = MusicProviderUtils.getPlayingSong(this); |
| 439 | + if (songUri != null) { |
| 440 | + mCurrentPlaying = new Song(this, songUri); |
| 441 | + } |
| 442 | + } catch (SQLiteDatabaseLockedException e) { |
| 443 | + // Ignore. Assume no song has been playing. |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | @@ -325,8 +331,13 @@ |
| 448 | } |
| 449 | |
| 450 | private synchronized void setCurrentPlaying(Uri uri) { |
| 451 | - MusicProviderUtils.setPlayingSong(this, uri); |
| 452 | - this.mCurrentPlaying = new Song(this, uri); |
| 453 | + // Make sure this is a song Uri, rather than playlist song Uri. |
| 454 | + // This has to be changed when "now playing" column is moved to the PlaylistSongs table. |
| 455 | + String songId = Songs.getSongId(uri); |
| 456 | + Uri songUri = Songs.buildSongUri(songId); |
| 457 | + |
| 458 | + MusicProviderUtils.setPlayingSong(this, songUri); |
| 459 | + this.mCurrentPlaying = new Song(this, songUri); |
| 460 | callbackOnSongChanged(); |
| 461 | if (mCurrentPlaying.isCompleteFileAvailable()) { |
| 462 | callbackOnSongDownload(uri, 100); |
| 463 | |
| 464 | === modified file 'src/com/ubuntuone/android/music/service/SyncService.java' |
| 465 | --- src/com/ubuntuone/android/music/service/SyncService.java 2012-11-28 17:49:20 +0000 |
| 466 | +++ src/com/ubuntuone/android/music/service/SyncService.java 2012-11-28 17:49:20 +0000 |
| 467 | @@ -22,12 +22,16 @@ |
| 468 | package com.ubuntuone.android.music.service; |
| 469 | |
| 470 | import static com.ubuntuone.android.music.util.LogUtils.makeLogTag; |
| 471 | + |
| 472 | +import java.util.ArrayList; |
| 473 | + |
| 474 | import android.app.IntentService; |
| 475 | import android.content.ContentResolver; |
| 476 | import android.content.Context; |
| 477 | import android.content.Intent; |
| 478 | import android.database.Cursor; |
| 479 | import android.database.sqlite.SQLiteDatabase; |
| 480 | +import android.net.Uri; |
| 481 | import android.os.Bundle; |
| 482 | import android.os.ResultReceiver; |
| 483 | import android.util.Log; |
| 484 | @@ -58,6 +62,7 @@ |
| 485 | import com.ubuntuone.api.music.model.U1Song; |
| 486 | import com.ubuntuone.api.music.request.U1AlbumListener; |
| 487 | import com.ubuntuone.api.music.request.U1ArtistListener; |
| 488 | +import com.ubuntuone.api.music.request.U1CallbackListener; |
| 489 | import com.ubuntuone.api.music.request.U1PlaylistListener; |
| 490 | import com.ubuntuone.api.music.request.U1SongListener; |
| 491 | |
| 492 | @@ -67,6 +72,8 @@ |
| 493 | |
| 494 | public static final String ACTION_SYNC_MUSIC_METADATA = |
| 495 | "com.ubuntuone.android.music.ACTION_SYNC_MUSIC_METADATA"; |
| 496 | + public static final String ACTION_SYNC_PLAYLISTS = |
| 497 | + "com.ubuntuone.android.music.ACTION_SYNC_PLAYLISTS"; |
| 498 | |
| 499 | public static final String EXTRA_RECEIVER = "result_receiver"; |
| 500 | public static final String EXTRA_LOADER_ID = "loader_id"; |
| 501 | @@ -103,19 +110,25 @@ |
| 502 | @Override |
| 503 | protected void onHandleIntent(Intent intent) { |
| 504 | final String action = intent.getAction(); |
| 505 | + if (action == null) return; |
| 506 | mReceiver = intent.getParcelableExtra(EXTRA_RECEIVER); |
| 507 | - if (action != null && ACTION_SYNC_MUSIC_METADATA.equals(action)) { |
| 508 | - mTimeSyncStarted = System.currentTimeMillis(); |
| 509 | - if (mApi != null && UbuntuOneMusic.SHOULD_SYNC) { |
| 510 | - sSyncStatus = SYNC_STARTED; |
| 511 | - if (mReceiver != null) mReceiver.send(SYNC_STARTED, Bundle.EMPTY); |
| 512 | + mTimeSyncStarted = System.currentTimeMillis(); |
| 513 | + if (mApi != null && UbuntuOneMusic.SHOULD_SYNC) { |
| 514 | + sSyncStatus = SYNC_STARTED; |
| 515 | + if (mReceiver != null) mReceiver.send(SYNC_STARTED, Bundle.EMPTY); |
| 516 | + |
| 517 | + if (ACTION_SYNC_MUSIC_METADATA.equals(action)) { |
| 518 | + syncPlaylistMetadata(); |
| 519 | syncMusicMetadata(); |
| 520 | - if (mReceiver != null) mReceiver.send(SYNC_FINISHED, Bundle.EMPTY); |
| 521 | - sSyncStatus = SYNC_FINISHED; |
| 522 | + } else if (ACTION_SYNC_PLAYLISTS.equals(action)) { |
| 523 | + syncPlaylistMetadata(); |
| 524 | } |
| 525 | - long now = System.currentTimeMillis(); |
| 526 | - Log.i(TAG, "Ubuntu One music metadata sync sync took " + (now - mTimeSyncStarted) + " ms"); |
| 527 | + |
| 528 | + if (mReceiver != null) mReceiver.send(SYNC_FINISHED, Bundle.EMPTY); |
| 529 | + sSyncStatus = SYNC_FINISHED; |
| 530 | } |
| 531 | + long now = System.currentTimeMillis(); |
| 532 | + Log.i(TAG, "Ubuntu One music metadata sync sync took " + (now - mTimeSyncStarted) + " ms"); |
| 533 | } |
| 534 | |
| 535 | private void syncMusicMetadata() { |
| 536 | @@ -130,6 +143,10 @@ |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | + private void syncPlaylistMetadata() { |
| 541 | + syncUpPlaylists(); |
| 542 | + } |
| 543 | + |
| 544 | private void syncArtists() { |
| 545 | final long timeMillis = System.currentTimeMillis(); |
| 546 | mApi.getArtists(mArtistListener); |
| 547 | @@ -158,6 +175,38 @@ |
| 548 | Log.i(TAG, "Playlists sync took " + (nowMillis - timeMillis) + "ms"); |
| 549 | } |
| 550 | |
| 551 | + private void syncUpPlaylists() { |
| 552 | + final long timeMillis = System.currentTimeMillis(); |
| 553 | + Cursor cursor = null; |
| 554 | + try { |
| 555 | + String[] projection = new String[] { Playlists.PLAYLIST_ID, Playlists.PLAYLIST_NAME }; |
| 556 | + String selection = Playlists.DIRTY + "=1"; |
| 557 | + cursor = getContentResolver().query(Playlists.CONTENT_URI, |
| 558 | + projection, selection, null, null); |
| 559 | + if (cursor != null && cursor.isBeforeFirst()) { |
| 560 | + while (cursor.moveToNext()) { |
| 561 | + final String playlistId = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_ID)); |
| 562 | + String playlistName = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_NAME)); |
| 563 | + ArrayList<String> songIdList = MusicProviderUtils.getPlaylistSongIdList(this, playlistId); |
| 564 | + |
| 565 | + mApi.updatePlaylist(playlistId, playlistName, songIdList, new U1CallbackListener() { |
| 566 | + @Override |
| 567 | + public void onSuccess() { |
| 568 | + Log.i(TAG, "Updated playlist: " + playlistId); |
| 569 | + Uri playlistUri = Playlists.buildPlaylistUri(playlistId); |
| 570 | + MusicProviderUtils.flagPlaylistForSync(SyncService.this, playlistUri, false); |
| 571 | + // TODO Persist playlist entity from response once CSRF fix is rolled out. |
| 572 | + } |
| 573 | + }); |
| 574 | + } |
| 575 | + } |
| 576 | + } finally { |
| 577 | + if (cursor != null) cursor.close(); |
| 578 | + } |
| 579 | + final long nowMillis = System.currentTimeMillis(); |
| 580 | + Log.i(TAG, "Playlist songs sync up took " + (nowMillis - timeMillis) + "ms"); |
| 581 | + } |
| 582 | + |
| 583 | private void syncPlaylistSongs() { |
| 584 | final long timeMillis = System.currentTimeMillis(); |
| 585 | Cursor cursor = null; |
| 586 | @@ -175,7 +224,7 @@ |
| 587 | if (cursor != null) cursor.close(); |
| 588 | } |
| 589 | final long nowMillis = System.currentTimeMillis(); |
| 590 | - Log.i(TAG, "Playlist songs sync took " + (nowMillis - timeMillis) + "ms"); |
| 591 | + Log.i(TAG, "Playlist songs sync down took " + (nowMillis - timeMillis) + "ms"); |
| 592 | } |
| 593 | |
| 594 | private U1ArtistListener mArtistListener = new U1ArtistListener() { |
| 595 | |
| 596 | === modified file 'src/com/ubuntuone/android/music/ui/AlbumsFragment.java' |
| 597 | --- src/com/ubuntuone/android/music/ui/AlbumsFragment.java 2012-11-22 10:23:21 +0000 |
| 598 | +++ src/com/ubuntuone/android/music/ui/AlbumsFragment.java 2012-11-28 17:49:20 +0000 |
| 599 | @@ -25,6 +25,7 @@ |
| 600 | import static com.ubuntuone.android.music.util.LogUtils.makeLogTag; |
| 601 | import android.app.Activity; |
| 602 | import android.content.Context; |
| 603 | +import android.content.Intent; |
| 604 | import android.database.Cursor; |
| 605 | import android.net.Uri; |
| 606 | import android.os.Bundle; |
| 607 | @@ -187,7 +188,13 @@ |
| 608 | } |
| 609 | |
| 610 | private boolean onAddToPlaylistContextItemSelected(int position) { |
| 611 | - // TODO |
| 612 | + Cursor cursor = (Cursor) mListView.getItemAtPosition(position); |
| 613 | + String albumId = cursor.getString(cursor.getColumnIndex(Albums.ALBUM_ID)); |
| 614 | + Uri albumUri = Albums.buildAlbumUri(albumId); |
| 615 | + |
| 616 | + Intent intent = new Intent(getActivity(), PlaylistsActivity.class); |
| 617 | + intent.putExtra(PlaylistsActivity.EXTRA_ENQUEUE_CONTENT_ID, albumUri); |
| 618 | + startActivity(intent); |
| 619 | return true; |
| 620 | } |
| 621 | |
| 622 | |
| 623 | === modified file 'src/com/ubuntuone/android/music/ui/HomeActivity.java' |
| 624 | --- src/com/ubuntuone/android/music/ui/HomeActivity.java 2012-11-21 15:03:12 +0000 |
| 625 | +++ src/com/ubuntuone/android/music/ui/HomeActivity.java 2012-11-28 17:49:20 +0000 |
| 626 | @@ -44,8 +44,10 @@ |
| 627 | import android.support.v4.app.LoaderManager; |
| 628 | import android.support.v4.content.Loader; |
| 629 | import android.support.v4.view.ViewPager; |
| 630 | +import android.support.v4.widget.CursorAdapter; |
| 631 | import android.view.View; |
| 632 | import android.view.View.OnClickListener; |
| 633 | +import android.widget.Adapter; |
| 634 | import android.widget.LinearLayout; |
| 635 | import android.widget.Toast; |
| 636 | |
| 637 | @@ -55,22 +57,26 @@ |
| 638 | import com.actionbarsherlock.view.MenuItem; |
| 639 | import com.ubuntuone.android.music.R; |
| 640 | import com.ubuntuone.android.music.UbuntuOneMusic; |
| 641 | +import com.ubuntuone.android.music.adapter.U1PlaylistAdapter; |
| 642 | +import com.ubuntuone.android.music.adapter.U1PlaylistAdapter.Position; |
| 643 | import com.ubuntuone.android.music.adapter.holder.NowPlayingViewHolder; |
| 644 | import com.ubuntuone.android.music.model.PlayerState; |
| 645 | +import com.ubuntuone.android.music.provider.MusicContract.Playlists; |
| 646 | import com.ubuntuone.android.music.provider.MusicContract.Songs; |
| 647 | import com.ubuntuone.android.music.provider.MusicProviderUtils; |
| 648 | import com.ubuntuone.android.music.service.MusicService; |
| 649 | import com.ubuntuone.android.music.service.MusicServiceBinder; |
| 650 | import com.ubuntuone.android.music.service.MusicServiceCallback; |
| 651 | import com.ubuntuone.android.music.service.SyncService; |
| 652 | +import com.ubuntuone.android.music.ui.PlaylistsFragment.OnPlaylistSelectedListener; |
| 653 | import com.ubuntuone.android.music.ui.dialog.InputAlertDialog.InputAlertDialogListener; |
| 654 | import com.ubuntuone.android.music.util.AccountUtils; |
| 655 | import com.ubuntuone.android.music.util.U1ImageDownloader; |
| 656 | import com.ubuntuone.android.music.util.UIUtils; |
| 657 | |
| 658 | public class HomeActivity extends SherlockFragmentActivity implements ActionBar.TabListener, |
| 659 | - ViewPager.OnPageChangeListener, OnClickListener, InputAlertDialogListener, |
| 660 | - MusicServiceCallback |
| 661 | + ViewPager.OnPageChangeListener, OnClickListener, OnPlaylistSelectedListener, |
| 662 | + InputAlertDialogListener, MusicServiceCallback |
| 663 | { |
| 664 | @SuppressWarnings("unused") |
| 665 | private static final String TAG = makeLogTag(HomeActivity.class); |
| 666 | @@ -80,7 +86,7 @@ |
| 667 | private static final int REQUEST_AUTHENTICATE = 1; |
| 668 | |
| 669 | /* package */ static final int PLEASE_WAIT_DIALOG_ID = 1; |
| 670 | - /* package */ static final int INFO_DIALOG_ID = 2; |
| 671 | + /* package */ static final int ERROR_DIALOG_ID = 2; |
| 672 | /* package */ static final int CREATE_PLAYLIST_DIALOG_ID = 3; |
| 673 | |
| 674 | private Handler mHandler = new Handler(); |
| 675 | @@ -572,7 +578,47 @@ |
| 676 | mShuffleAllSongsTask = null; |
| 677 | } |
| 678 | } |
| 679 | - |
| 680 | + |
| 681 | + @Override |
| 682 | + public void onPlaylistSelected(Adapter adapter, int position, boolean hasHeaders) { |
| 683 | + if (hasHeaders && position < U1PlaylistAdapter.Position.COUNT) { |
| 684 | + switch (position) { |
| 685 | + case Position.STARRED: { |
| 686 | + int count = MusicProviderUtils.getStarredSongCount(this); |
| 687 | + PlaylistActivity.startFrom(this, MusicProviderUtils.STARRED_ID, |
| 688 | + getString(R.string.starred), count); |
| 689 | + break; |
| 690 | + } |
| 691 | + case Position.CACHED: { |
| 692 | + int count = MusicProviderUtils.getCachedSongCount(this); |
| 693 | + PlaylistActivity.startFrom(this, MusicProviderUtils.CACHED_ID, |
| 694 | + getString(R.string.cached), count); |
| 695 | + break; |
| 696 | + } |
| 697 | + case Position.QUEUED: { |
| 698 | + PlayerActivity.startWithQueue(this); |
| 699 | + break; |
| 700 | + } |
| 701 | + } |
| 702 | + } else { |
| 703 | + if (hasHeaders) position -= Position.COUNT; |
| 704 | + |
| 705 | + Cursor cursor = ((CursorAdapter) adapter).getCursor(); |
| 706 | + cursor.moveToPosition(position); |
| 707 | + |
| 708 | + int songCount = cursor.getInt(cursor.getColumnIndex(Playlists.PLAYLIST_SONG_COUNT)); |
| 709 | + if (songCount == 0) { |
| 710 | + String title = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_NAME)); |
| 711 | + String text = getString(R.string.playlist_is_empty, title); |
| 712 | + if (!isFinishing()) { |
| 713 | + Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); |
| 714 | + } |
| 715 | + } else { |
| 716 | + PlaylistActivity.startFrom(this, cursor); |
| 717 | + } |
| 718 | + } |
| 719 | + } |
| 720 | + |
| 721 | @Override |
| 722 | public void onClick(View v) { |
| 723 | switch (v.getId()) { |
| 724 | |
| 725 | === modified file 'src/com/ubuntuone/android/music/ui/PlayerFragment.java' |
| 726 | --- src/com/ubuntuone/android/music/ui/PlayerFragment.java 2012-11-08 14:14:54 +0000 |
| 727 | +++ src/com/ubuntuone/android/music/ui/PlayerFragment.java 2012-11-28 17:49:20 +0000 |
| 728 | @@ -401,7 +401,7 @@ |
| 729 | |
| 730 | Uri playlistSongsUri = Playlists.buildPlaylistSongsUri(MusicProviderUtils.QUEUE_ID); |
| 731 | CursorLoader loader = new CursorLoader(getSherlockActivity(), playlistSongsUri, |
| 732 | - Songs.DEFAULT_PROJECTION, null, null, PlaylistSongs.DEFAULT_SORT); |
| 733 | + Songs.getDefaultProjection(), null, null, PlaylistSongs.DEFAULT_SORT); |
| 734 | |
| 735 | setIsLoading(true); |
| 736 | return loader; |
| 737 | |
| 738 | === modified file 'src/com/ubuntuone/android/music/ui/PlaylistActivity.java' |
| 739 | --- src/com/ubuntuone/android/music/ui/PlaylistActivity.java 2012-11-08 14:14:54 +0000 |
| 740 | +++ src/com/ubuntuone/android/music/ui/PlaylistActivity.java 2012-11-28 17:49:20 +0000 |
| 741 | @@ -58,7 +58,6 @@ |
| 742 | @Override |
| 743 | protected void onCreate(Bundle savedInstanceState) { |
| 744 | super.onCreate(savedInstanceState); |
| 745 | - // TODO Rename layout? |
| 746 | setContentView(R.layout.activity_collection); |
| 747 | |
| 748 | mPlaylistArt = (ImageView) findViewById(R.id.image); |
| 749 | |
| 750 | === modified file 'src/com/ubuntuone/android/music/ui/PlaylistsActivity.java' |
| 751 | --- src/com/ubuntuone/android/music/ui/PlaylistsActivity.java 2012-11-08 14:14:54 +0000 |
| 752 | +++ src/com/ubuntuone/android/music/ui/PlaylistsActivity.java 2012-11-28 17:49:20 +0000 |
| 753 | @@ -21,16 +21,158 @@ |
| 754 | |
| 755 | package com.ubuntuone.android.music.ui; |
| 756 | |
| 757 | +import static com.ubuntuone.android.music.ui.HomeActivity.ERROR_DIALOG_ID; |
| 758 | +import static com.ubuntuone.android.music.util.LogUtils.makeLogTag; |
| 759 | +import android.app.Activity; |
| 760 | +import android.content.ContentResolver; |
| 761 | +import android.content.Intent; |
| 762 | +import android.database.Cursor; |
| 763 | +import android.net.Uri; |
| 764 | +import android.os.AsyncTask; |
| 765 | +import android.os.Bundle; |
| 766 | +import android.support.v4.app.LoaderManager; |
| 767 | +import android.support.v4.content.Loader; |
| 768 | +import android.support.v4.widget.CursorAdapter; |
| 769 | +import android.util.Log; |
| 770 | +import android.widget.Adapter; |
| 771 | +import android.widget.Toast; |
| 772 | + |
| 773 | import com.actionbarsherlock.app.SherlockFragmentActivity; |
| 774 | +import com.actionbarsherlock.view.MenuItem; |
| 775 | +import com.ubuntuone.android.music.R; |
| 776 | import com.ubuntuone.android.music.provider.MusicContract.Albums; |
| 777 | import com.ubuntuone.android.music.provider.MusicContract.Artists; |
| 778 | +import com.ubuntuone.android.music.provider.MusicContract.Playlists; |
| 779 | import com.ubuntuone.android.music.provider.MusicContract.Songs; |
| 780 | +import com.ubuntuone.android.music.provider.MusicProviderUtils; |
| 781 | +import com.ubuntuone.android.music.service.SyncService; |
| 782 | +import com.ubuntuone.android.music.ui.PlaylistsFragment.OnPlaylistSelectedListener; |
| 783 | +import com.ubuntuone.android.music.ui.dialog.InfoAlertDialog.InfoAlertDialogListener; |
| 784 | +import com.ubuntuone.android.music.util.UIUtils; |
| 785 | |
| 786 | -public class PlaylistsActivity extends SherlockFragmentActivity |
| 787 | +public class PlaylistsActivity extends SherlockFragmentActivity implements |
| 788 | + OnPlaylistSelectedListener, InfoAlertDialogListener |
| 789 | { |
| 790 | - public static final String EXTRA_ENQUEUE_SONG_ID = Songs.SONG_ID; |
| 791 | - public static final String EXTRA_ENQUEUE_ALBUM_ID = Albums.ALBUM_ID; |
| 792 | - public static final String EXTRA_ENQUEUE_ARTIST_ID = Artists.ARTIST_ID; |
| 793 | - |
| 794 | - // TODO enqueue provided content to selected playlist, allow creating a playlist |
| 795 | + private static final String TAG = makeLogTag(PlaylistActivity.class); |
| 796 | + |
| 797 | + public static final String EXTRA_ENQUEUE_CONTENT_ID = "content_uri"; |
| 798 | + |
| 799 | + private Uri mContentUri; |
| 800 | + |
| 801 | + @Override |
| 802 | + protected void onCreate(Bundle savedInstanceState) { |
| 803 | + super.onCreate(savedInstanceState); |
| 804 | + setContentView(R.layout.activity_playlists); |
| 805 | + |
| 806 | + mContentUri = getIntent().getParcelableExtra(EXTRA_ENQUEUE_CONTENT_ID); |
| 807 | + |
| 808 | + if (mContentUri == null) { |
| 809 | + throw new IllegalStateException("You must provide content_uri which is " + |
| 810 | + "to be added to a playlist."); |
| 811 | + } |
| 812 | + |
| 813 | + LoaderManager lm = getSupportLoaderManager(); |
| 814 | + Loader<?> loader = lm.getLoader(R.id.playlist_songs_loader_id); |
| 815 | + if (loader != null) { |
| 816 | + loader.stopLoading(); |
| 817 | + } |
| 818 | + } |
| 819 | + |
| 820 | + @Override |
| 821 | + public boolean onOptionsItemSelected(MenuItem item) { |
| 822 | + switch (item.getItemId()) { |
| 823 | + case android.R.id.home: |
| 824 | + finish(); |
| 825 | + return true; |
| 826 | + |
| 827 | + default: |
| 828 | + return super.onOptionsItemSelected(item); |
| 829 | + } |
| 830 | + } |
| 831 | + |
| 832 | + @Override |
| 833 | + public void onPlaylistSelected(Adapter adapter, int position, boolean hasHeaders) { |
| 834 | + if (hasHeaders) { |
| 835 | + throw new IllegalArgumentException( |
| 836 | + "PlaylistsActivity must not contain custom playlists."); |
| 837 | + } |
| 838 | + |
| 839 | + Cursor cursor = ((CursorAdapter) adapter).getCursor(); |
| 840 | + cursor.moveToPosition(position); |
| 841 | + |
| 842 | + String playlistId = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_ID)); |
| 843 | + Uri playlistUri = Playlists.buildPlaylistUri(playlistId); |
| 844 | + |
| 845 | + if (mContentUri != null) { |
| 846 | + AppendToPlaylistTask appendTask = new AppendToPlaylistTask(); |
| 847 | + appendTask.execute(playlistUri, mContentUri); |
| 848 | + } |
| 849 | + } |
| 850 | + |
| 851 | + private class AppendToPlaylistTask extends AsyncTask<Uri, Void, Uri> { |
| 852 | + @Override |
| 853 | + protected Uri doInBackground(Uri... params) { |
| 854 | + if (params.length != 2) { |
| 855 | + throw new IllegalArgumentException( |
| 856 | + "AppendToPlaylistTask takes two arguments, playlist uri and content uri"); |
| 857 | + } |
| 858 | + final Uri playlistUri = params[0]; |
| 859 | + final Uri contentUri = params[1]; |
| 860 | + |
| 861 | + ContentResolver resolver = getContentResolver(); |
| 862 | + |
| 863 | + String mimeType = resolver.getType(contentUri); |
| 864 | + Activity activity = PlaylistsActivity.this; |
| 865 | + if (mimeType.equals(Artists.CONTENT_ITEM_TYPE)) { |
| 866 | + Log.e(TAG, "artist"); |
| 867 | + MusicProviderUtils.enqueueArtist(activity, contentUri, playlistUri); |
| 868 | + } else if (mimeType.equals(Albums.CONTENT_ITEM_TYPE)) { |
| 869 | + Log.e(TAG, "album"); |
| 870 | + MusicProviderUtils.enqueueAlbum(activity, contentUri, playlistUri); |
| 871 | + } else if (mimeType.equals(Songs.CONTENT_ITEM_TYPE)) { |
| 872 | + Log.e(TAG, "song"); |
| 873 | + MusicProviderUtils.enqueueSong(activity, contentUri, playlistUri); |
| 874 | + } else { |
| 875 | + Log.e(TAG, "Unknown content uri to enqueue to playlist: " + contentUri.toString()); |
| 876 | + } |
| 877 | + return playlistUri; |
| 878 | + } |
| 879 | + |
| 880 | + @Override |
| 881 | + protected void onPostExecute(Uri playlistUri) { |
| 882 | + if (playlistUri == null) { |
| 883 | + Log.e(TAG, "PlaylistsActivity#AppendToPlaylistTask#onPostExecute null playlistUri"); |
| 884 | + return; |
| 885 | + } |
| 886 | + |
| 887 | + String playlistName = null; |
| 888 | + Cursor cursor = null; |
| 889 | + try { |
| 890 | + final String[] projection = new String[] { Playlists.PLAYLIST_NAME }; |
| 891 | + cursor = getContentResolver().query(playlistUri, projection, null, null, null); |
| 892 | + if (cursor != null && cursor.moveToFirst()) { |
| 893 | + playlistName = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_NAME)); |
| 894 | + } |
| 895 | + } finally { |
| 896 | + cursor.close(); |
| 897 | + } |
| 898 | + |
| 899 | + String msg = getString(R.string.added_to_fmt, playlistName); |
| 900 | + Toast.makeText(PlaylistsActivity.this, msg, Toast.LENGTH_SHORT).show(); |
| 901 | + |
| 902 | + if (UIUtils.isNetworkConnected(PlaylistsActivity.this)) { |
| 903 | + Intent syncPlaylists = new Intent(SyncService.ACTION_SYNC_PLAYLISTS); |
| 904 | + startService(syncPlaylists); |
| 905 | + } |
| 906 | + |
| 907 | + finish(); |
| 908 | + } |
| 909 | + } |
| 910 | + |
| 911 | + @Override |
| 912 | + public void onPositiveButtonClicked(int dialogId) { |
| 913 | + if (dialogId == ERROR_DIALOG_ID) { |
| 914 | + finish(); |
| 915 | + } |
| 916 | + } |
| 917 | } |
| 918 | |
| 919 | === modified file 'src/com/ubuntuone/android/music/ui/PlaylistsFragment.java' |
| 920 | --- src/com/ubuntuone/android/music/ui/PlaylistsFragment.java 2012-11-22 10:23:21 +0000 |
| 921 | +++ src/com/ubuntuone/android/music/ui/PlaylistsFragment.java 2012-11-28 17:49:20 +0000 |
| 922 | @@ -21,7 +21,7 @@ |
| 923 | |
| 924 | package com.ubuntuone.android.music.ui; |
| 925 | |
| 926 | -import static com.ubuntuone.android.music.ui.HomeActivity.INFO_DIALOG_ID; |
| 927 | +import static com.ubuntuone.android.music.ui.HomeActivity.ERROR_DIALOG_ID; |
| 928 | import static com.ubuntuone.android.music.util.LogUtils.makeLogTag; |
| 929 | |
| 930 | import java.util.ArrayList; |
| 931 | @@ -30,7 +30,6 @@ |
| 932 | import android.content.ContentResolver; |
| 933 | import android.content.Context; |
| 934 | import android.database.Cursor; |
| 935 | -import android.net.Uri; |
| 936 | import android.os.AsyncTask; |
| 937 | import android.os.Bundle; |
| 938 | import android.support.v4.app.DialogFragment; |
| 939 | @@ -44,27 +43,26 @@ |
| 940 | import android.view.View; |
| 941 | import android.view.View.OnClickListener; |
| 942 | import android.view.ViewGroup; |
| 943 | +import android.widget.Adapter; |
| 944 | import android.widget.Button; |
| 945 | import android.widget.ListView; |
| 946 | -import android.widget.Toast; |
| 947 | |
| 948 | import com.actionbarsherlock.app.SherlockFragmentActivity; |
| 949 | import com.ubuntuone.android.music.R; |
| 950 | import com.ubuntuone.android.music.UbuntuOneMusic; |
| 951 | import com.ubuntuone.android.music.adapter.U1PlaylistAdapter; |
| 952 | -import com.ubuntuone.android.music.adapter.U1PlaylistAdapter.Position; |
| 953 | -import com.ubuntuone.android.music.provider.MusicContract.Albums; |
| 954 | -import com.ubuntuone.android.music.provider.MusicContract.Artists; |
| 955 | import com.ubuntuone.android.music.provider.MusicContract.Playlists; |
| 956 | -import com.ubuntuone.android.music.provider.MusicContract.Songs; |
| 957 | import com.ubuntuone.android.music.provider.MusicProviderUtils; |
| 958 | +import com.ubuntuone.android.music.provider.dao.PlaylistDao; |
| 959 | import com.ubuntuone.android.music.ui.dialog.InfoAlertDialog; |
| 960 | import com.ubuntuone.android.music.ui.dialog.InputAlertDialog; |
| 961 | import com.ubuntuone.android.music.ui.dialog.InputAlertDialog.InputAlertDialogListener; |
| 962 | import com.ubuntuone.android.music.ui.dialog.ModalAlertDialog; |
| 963 | import com.ubuntuone.api.music.U1MusicAPI; |
| 964 | import com.ubuntuone.api.music.client.Failure; |
| 965 | +import com.ubuntuone.api.music.model.U1Playlist; |
| 966 | import com.ubuntuone.api.music.request.U1PlaylistCreateListener; |
| 967 | +import com.ubuntuone.api.music.request.U1PlaylistListener; |
| 968 | |
| 969 | public class PlaylistsFragment extends LoadingFragment<U1PlaylistAdapter> implements |
| 970 | OnClickListener, InputAlertDialogListener |
| 971 | @@ -74,6 +72,8 @@ |
| 972 | public static final String EXTRA_INCLUDE_CUSTOM = "include_custom"; |
| 973 | |
| 974 | private boolean mIncludeCustom = false; |
| 975 | + |
| 976 | + private OnPlaylistSelectedListener mOnPlaylistSelectedListener; |
| 977 | |
| 978 | @Override |
| 979 | public int onGetHeaderResId() { |
| 980 | @@ -113,52 +113,18 @@ |
| 981 | Button createPlaylist = (Button) view.findViewById(R.id.create_playlist); |
| 982 | createPlaylist.setOnClickListener(this); |
| 983 | |
| 984 | + try { |
| 985 | + mOnPlaylistSelectedListener = (OnPlaylistSelectedListener) getActivity(); |
| 986 | + } catch (ClassCastException e) { |
| 987 | + throw new IllegalArgumentException("Activity must implement OnPlaylistSelectedListener"); |
| 988 | + } |
| 989 | + |
| 990 | return view; |
| 991 | } |
| 992 | |
| 993 | @Override |
| 994 | public void onListItemClick(ListView l, View v, int position, long id) { |
| 995 | - showPlaylistSongs(position); |
| 996 | - } |
| 997 | - |
| 998 | - private void showPlaylistSongs(int position) { |
| 999 | - if (mIncludeCustom && position < U1PlaylistAdapter.Position.COUNT) { |
| 1000 | - Context context = getActivity(); |
| 1001 | - switch (position) { |
| 1002 | - case Position.STARRED: { |
| 1003 | - int count = MusicProviderUtils.getStarredSongCount(context); |
| 1004 | - PlaylistActivity.startFrom(context, MusicProviderUtils.STARRED_ID, |
| 1005 | - context.getString(R.string.starred), count); |
| 1006 | - break; |
| 1007 | - } |
| 1008 | - case Position.CACHED: { |
| 1009 | - int count = MusicProviderUtils.getCachedSongCount(context); |
| 1010 | - PlaylistActivity.startFrom(getActivity(), MusicProviderUtils.CACHED_ID, |
| 1011 | - context.getString(R.string.cached), count); |
| 1012 | - break; |
| 1013 | - } |
| 1014 | - case Position.QUEUED: { |
| 1015 | - PlayerActivity.startWithQueue(getSherlockActivity()); |
| 1016 | - break; |
| 1017 | - } |
| 1018 | - } |
| 1019 | - } else { |
| 1020 | - if (mIncludeCustom) position -= Position.COUNT; |
| 1021 | - |
| 1022 | - Cursor cursor = mAdapter.getCursor(); |
| 1023 | - cursor.moveToPosition(position); |
| 1024 | - |
| 1025 | - int songCount = cursor.getInt(cursor.getColumnIndex(Playlists.PLAYLIST_SONG_COUNT)); |
| 1026 | - if (songCount == 0) { |
| 1027 | - String title = cursor.getString(cursor.getColumnIndex(Playlists.PLAYLIST_NAME)); |
| 1028 | - String text = getString(R.string.playlist_is_empty, title); |
| 1029 | - if (isResumed()) { |
| 1030 | - Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show(); |
| 1031 | - } |
| 1032 | - } else { |
| 1033 | - PlaylistActivity.startFrom(getActivity(), cursor); |
| 1034 | - } |
| 1035 | - } |
| 1036 | + mOnPlaylistSelectedListener.onPlaylistSelected(mAdapter, position, mIncludeCustom); |
| 1037 | } |
| 1038 | |
| 1039 | @Override |
| 1040 | @@ -238,6 +204,7 @@ |
| 1041 | api.createPlaylist(name, emptyList, new U1PlaylistCreateListener() { |
| 1042 | @Override |
| 1043 | public void onSuccess(String playlistId) { |
| 1044 | + // TODO Persist playlist once CSRF fix is rolled out. |
| 1045 | Log.i(TAG, "Created playlist with id: " + playlistId); |
| 1046 | mPlaylistId = playlistId; |
| 1047 | } |
| 1048 | @@ -250,85 +217,40 @@ |
| 1049 | } |
| 1050 | }); |
| 1051 | |
| 1052 | - // TODO karni: save the playlist |
| 1053 | + // TODO This block will be removed soon, see TODO above. |
| 1054 | + Activity activity = getSherlockActivity(); |
| 1055 | + final ContentResolver resolver = activity != null ? activity.getContentResolver() : null; |
| 1056 | + if (mPlaylistId != null && resolver != null) { |
| 1057 | + api.getPlaylists(new U1PlaylistListener() { |
| 1058 | + @Override |
| 1059 | + public void onSuccess(U1Playlist playlist) { |
| 1060 | + if (mPlaylistId.equals(playlist.getId())) { |
| 1061 | + new PlaylistDao().updateOrInsert(resolver, playlist); |
| 1062 | + } |
| 1063 | + } |
| 1064 | + }); |
| 1065 | + } |
| 1066 | + |
| 1067 | return mPlaylistId != null; |
| 1068 | } |
| 1069 | |
| 1070 | @Override |
| 1071 | - protected void onPostExecute(Boolean result) { |
| 1072 | + protected void onPostExecute(Boolean created) { |
| 1073 | if (mDialogFragment != null && mDialogFragment.isAdded()) { |
| 1074 | mDialogFragment.dismiss(); |
| 1075 | } |
| 1076 | |
| 1077 | SherlockFragmentActivity activity = getSherlockActivity(); |
| 1078 | - boolean created = mPlaylistId != null; |
| 1079 | if (created && activity != null) { |
| 1080 | ((HomeActivity) activity).forcePlaylistsRefresh(); |
| 1081 | } else if (!created && activity != null && !TextUtils.isEmpty(mErrorMessage)) { |
| 1082 | - DialogFragment frag = InfoAlertDialog.newInstance(INFO_DIALOG_ID, |
| 1083 | - R.string.creating_playlist_failed_title, mErrorMessage); |
| 1084 | + DialogFragment frag = InfoAlertDialog.newInstance(ERROR_DIALOG_ID, 0, mErrorMessage); |
| 1085 | frag.show(activity.getSupportFragmentManager(), null); |
| 1086 | } |
| 1087 | } |
| 1088 | } |
| 1089 | |
| 1090 | - private class AppendToPlaylistTask extends AsyncTask<Uri, Void, Boolean> { |
| 1091 | - private DialogFragment mDialogFragment; |
| 1092 | - private Boolean mUpdated = false; |
| 1093 | - private String mErrorMessage; |
| 1094 | - |
| 1095 | - @Override |
| 1096 | - protected void onPreExecute() { |
| 1097 | - SherlockFragmentActivity activity = getSherlockActivity(); |
| 1098 | - if (activity != null) { |
| 1099 | - mDialogFragment = ModalAlertDialog.newInstance(); |
| 1100 | - mDialogFragment.show(activity.getSupportFragmentManager(), "pleaseWaitDialog"); |
| 1101 | - } |
| 1102 | - } |
| 1103 | - |
| 1104 | - @Override |
| 1105 | - protected Boolean doInBackground(Uri... params) { |
| 1106 | - if (params.length != 1) { |
| 1107 | - throw new IllegalArgumentException( |
| 1108 | - "AppendToPlaylistTask takes two arguments, playlist uri and content uri"); |
| 1109 | - } |
| 1110 | - final Uri playlistUri = params[0]; |
| 1111 | - final Uri contentUri = params[1]; |
| 1112 | - |
| 1113 | - Activity activity = getSherlockActivity(); |
| 1114 | - ContentResolver resolver = activity != null ? activity.getContentResolver() : null; |
| 1115 | - if (resolver == null) return mUpdated; |
| 1116 | - |
| 1117 | - String mimeType = resolver.getType(contentUri); |
| 1118 | - if (mimeType.equals(Artists.CONTENT_ITEM_TYPE)) { |
| 1119 | - MusicProviderUtils.enqueueArtist(activity, contentUri, playlistUri); |
| 1120 | - } else if (mimeType.equals(Albums.CONTENT_ITEM_TYPE)) { |
| 1121 | - MusicProviderUtils.enqueueAlbum(activity, contentUri, playlistUri); |
| 1122 | - } else if (mimeType.equals(Songs.CONTENT_ITEM_TYPE)) { |
| 1123 | - MusicProviderUtils.enqueueSong(activity, contentUri, playlistUri); |
| 1124 | - } else { |
| 1125 | - Log.e(TAG, "Unknown content uri to enqueue to playlist: " + contentUri.toString()); |
| 1126 | - } |
| 1127 | - |
| 1128 | - U1MusicAPI api = UbuntuOneMusic.getInstance().getMusicApi(); |
| 1129 | - // TODO save playlist to server |
| 1130 | - return mUpdated; |
| 1131 | - } |
| 1132 | - |
| 1133 | - @Override |
| 1134 | - protected void onPostExecute(Boolean result) { |
| 1135 | - if (mDialogFragment != null && mDialogFragment.isAdded()) { |
| 1136 | - mDialogFragment.dismiss(); |
| 1137 | - } |
| 1138 | - |
| 1139 | - SherlockFragmentActivity activity = getSherlockActivity(); |
| 1140 | - if (mUpdated && activity != null) { |
| 1141 | - ((HomeActivity) activity).forcePlaylistsRefresh(); |
| 1142 | - } else if (!mUpdated && activity != null && !TextUtils.isEmpty(mErrorMessage)) { |
| 1143 | - DialogFragment frag = InfoAlertDialog.newInstance(INFO_DIALOG_ID, |
| 1144 | - R.string.creating_playlist_failed_title, mErrorMessage); |
| 1145 | - frag.show(activity.getSupportFragmentManager(), null); |
| 1146 | - } |
| 1147 | - } |
| 1148 | + public interface OnPlaylistSelectedListener { |
| 1149 | + public void onPlaylistSelected(Adapter adapter, int position, boolean hasHeaders); |
| 1150 | } |
| 1151 | } |
| 1152 | |
| 1153 | === modified file 'src/com/ubuntuone/android/music/ui/SongsFragment.java' |
| 1154 | --- src/com/ubuntuone/android/music/ui/SongsFragment.java 2012-11-08 14:14:54 +0000 |
| 1155 | +++ src/com/ubuntuone/android/music/ui/SongsFragment.java 2012-11-28 17:49:20 +0000 |
| 1156 | @@ -26,7 +26,9 @@ |
| 1157 | import static com.ubuntuone.android.music.ui.PlaylistActivity.EXTRA_PLAYLIST_ID; |
| 1158 | import static com.ubuntuone.android.music.util.LogUtils.makeLogTag; |
| 1159 | import android.app.Activity; |
| 1160 | +import android.content.ContentResolver; |
| 1161 | import android.content.Context; |
| 1162 | +import android.content.Intent; |
| 1163 | import android.database.Cursor; |
| 1164 | import android.net.Uri; |
| 1165 | import android.os.Bundle; |
| 1166 | @@ -49,9 +51,11 @@ |
| 1167 | import com.ubuntuone.android.music.adapter.U1SongAdapter; |
| 1168 | import com.ubuntuone.android.music.provider.MusicContract.Albums; |
| 1169 | import com.ubuntuone.android.music.provider.MusicContract.Genres; |
| 1170 | +import com.ubuntuone.android.music.provider.MusicContract.PlaylistSongs; |
| 1171 | import com.ubuntuone.android.music.provider.MusicContract.Playlists; |
| 1172 | import com.ubuntuone.android.music.provider.MusicContract.Songs; |
| 1173 | import com.ubuntuone.android.music.provider.MusicProviderUtils; |
| 1174 | +import com.ubuntuone.android.music.service.SyncService; |
| 1175 | import com.ubuntuone.android.music.util.UIUtils; |
| 1176 | |
| 1177 | public class SongsFragment extends LoadingFragment<U1SongAdapter> implements OnClickListener |
| 1178 | @@ -129,28 +133,28 @@ |
| 1179 | if (mAlbumId != null) { |
| 1180 | Uri albumSongsUri = Albums.buildSongsUri(mAlbumId); |
| 1181 | loader = new CursorLoader(getSherlockActivity(), albumSongsUri, |
| 1182 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1183 | + Songs.getDefaultProjection(), null, null, Albums.DEFAULT_SONG_SORT); |
| 1184 | } else if (mPlaylistId != null) { |
| 1185 | if (mPlaylistId.equals(MusicProviderUtils.STARRED_ID)) { |
| 1186 | loader = new CursorLoader(getSherlockActivity(), Songs.STARRED_SONGS_URI, |
| 1187 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1188 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1189 | loader.onContentChanged(); |
| 1190 | } else if (mPlaylistId.equals(MusicProviderUtils.CACHED_ID)) { |
| 1191 | loader = new CursorLoader(getSherlockActivity(), Songs.CACHED_SONGS_URI, |
| 1192 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1193 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1194 | loader.onContentChanged(); |
| 1195 | } else { |
| 1196 | Uri playlistSongsUri = Playlists.buildPlaylistSongsUri(mPlaylistId); |
| 1197 | loader = new CursorLoader(getSherlockActivity(), playlistSongsUri, |
| 1198 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1199 | + PlaylistSongs.DEFAULT_PROJECTION.clone(), null, null, PlaylistSongs.DEFAULT_SORT); |
| 1200 | } |
| 1201 | } else if (mGenreId != null) { |
| 1202 | Uri genreSongsUri = Genres.buildGenreSongsUri(mGenreId); |
| 1203 | loader = new CursorLoader(getSherlockActivity(), genreSongsUri, |
| 1204 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1205 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1206 | } else { |
| 1207 | loader = new CursorLoader(getSherlockActivity(), Songs.CONTENT_URI, |
| 1208 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1209 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1210 | } |
| 1211 | setIsLoading(true); |
| 1212 | return loader; |
| 1213 | @@ -189,15 +193,18 @@ |
| 1214 | @Override |
| 1215 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { |
| 1216 | if (v.getId() == android.R.id.list) { |
| 1217 | - menu.add(Menu.NONE, R.id.context_play, 0, R.string.description_play); |
| 1218 | - |
| 1219 | - menu.add(Menu.NONE, R.id.context_star, 0, R.string.description_star); |
| 1220 | - Activity activity = getSherlockActivity(); |
| 1221 | - if (activity != null && UIUtils.isNetworkConnected(activity)) { |
| 1222 | - menu.add(Menu.NONE, R.id.context_add_to_playlist, 0, |
| 1223 | - R.string.description_add_to_playlist); |
| 1224 | + menu.add(Menu.NONE, R.id.context_play, 0, |
| 1225 | + R.string.description_play); |
| 1226 | + menu.add(Menu.NONE, R.id.context_star, 0, |
| 1227 | + R.string.description_star); |
| 1228 | + menu.add(Menu.NONE, R.id.context_add_to_playlist, 0, |
| 1229 | + R.string.description_add_to_playlist); |
| 1230 | + if (mPlaylistId != null) { |
| 1231 | + menu.add(Menu.NONE, R.id.context_remove_from_playlist, 0, |
| 1232 | + R.string.description_remove_from_playlist); |
| 1233 | } |
| 1234 | - menu.add(Menu.NONE, R.id.context_add_to_queue, 0, R.string.description_add_to_queue); |
| 1235 | + menu.add(Menu.NONE, R.id.context_add_to_queue, 0, |
| 1236 | + R.string.description_add_to_queue); |
| 1237 | } else { |
| 1238 | super.onCreateContextMenu(menu, v, menuInfo); |
| 1239 | } |
| 1240 | @@ -216,6 +223,8 @@ |
| 1241 | return onStarContextItemSelected(position); |
| 1242 | case R.id.context_add_to_playlist: |
| 1243 | return onAddToPlaylistContextItemSelected(position); |
| 1244 | + case R.id.context_remove_from_playlist: |
| 1245 | + return onRemoveFromPlaylistContextItemSelected(position); |
| 1246 | case R.id.context_add_to_queue: |
| 1247 | return onAddToPlayQueueContextItemSelected(position); |
| 1248 | default: |
| 1249 | @@ -223,6 +232,12 @@ |
| 1250 | } |
| 1251 | } |
| 1252 | |
| 1253 | + private void forceReload() { |
| 1254 | + int loaderId = getLoaderId(); |
| 1255 | + LoaderManager lm = getSherlockActivity().getSupportLoaderManager(); |
| 1256 | + lm.getLoader(loaderId).forceLoad(); |
| 1257 | + } |
| 1258 | + |
| 1259 | private boolean onPlayContextItemSelected(int position) { |
| 1260 | clearQueueAndPlay(mListView, position); |
| 1261 | return true; |
| 1262 | @@ -238,16 +253,41 @@ |
| 1263 | |
| 1264 | MusicProviderUtils.toggleSongStarred(context, songUri); |
| 1265 | |
| 1266 | - int loaderId = getLoaderId(); |
| 1267 | - LoaderManager lm = getSherlockActivity().getSupportLoaderManager(); |
| 1268 | - lm.getLoader(loaderId).forceLoad(); |
| 1269 | + forceReload(); |
| 1270 | } |
| 1271 | } |
| 1272 | return true; |
| 1273 | } |
| 1274 | |
| 1275 | private boolean onAddToPlaylistContextItemSelected(int position) { |
| 1276 | - // TODO |
| 1277 | + Cursor cursor = (Cursor) mListView.getItemAtPosition(position); |
| 1278 | + String songId = cursor.getString(cursor.getColumnIndex(Songs.SONG_ID)); |
| 1279 | + Uri songUri = Songs.buildSongUri(songId); |
| 1280 | + |
| 1281 | + Intent intent = new Intent(getActivity(), PlaylistsActivity.class); |
| 1282 | + intent.putExtra(PlaylistsActivity.EXTRA_ENQUEUE_CONTENT_ID, songUri); |
| 1283 | + startActivity(intent); |
| 1284 | + return true; |
| 1285 | + } |
| 1286 | + |
| 1287 | + private boolean onRemoveFromPlaylistContextItemSelected(int position) { |
| 1288 | + Cursor cursor = (Cursor) mListView.getItemAtPosition(position); |
| 1289 | + String rowId = cursor.getString(cursor.getColumnIndex(Songs._ID)); |
| 1290 | + |
| 1291 | + ContentResolver resolver = getActivity().getContentResolver(); |
| 1292 | + String selection = Songs._ID + "=?"; |
| 1293 | + String[] selectionArgs = new String[] { rowId }; |
| 1294 | + resolver.delete(PlaylistSongs.CONTENT_URI, selection, selectionArgs); |
| 1295 | + |
| 1296 | + Activity activity = getActivity(); |
| 1297 | + Uri playlistUri = Playlists.buildPlaylistUri(mPlaylistId); |
| 1298 | + MusicProviderUtils.flagPlaylistForSync(activity, playlistUri, true); |
| 1299 | + |
| 1300 | + forceReload(); |
| 1301 | + if (activity != null && UIUtils.isNetworkConnected(activity)) { |
| 1302 | + Intent syncPlaylists = new Intent(SyncService.ACTION_SYNC_PLAYLISTS); |
| 1303 | + activity.startService(syncPlaylists); |
| 1304 | + } |
| 1305 | return true; |
| 1306 | } |
| 1307 | |
| 1308 | |
| 1309 | === modified file 'src/com/ubuntuone/android/music/ui/dialog/InfoAlertDialog.java' |
| 1310 | --- src/com/ubuntuone/android/music/ui/dialog/InfoAlertDialog.java 2012-11-08 14:14:54 +0000 |
| 1311 | +++ src/com/ubuntuone/android/music/ui/dialog/InfoAlertDialog.java 2012-11-28 17:49:20 +0000 |
| 1312 | @@ -28,6 +28,7 @@ |
| 1313 | import android.content.DialogInterface.OnClickListener; |
| 1314 | import android.os.Bundle; |
| 1315 | import android.view.Gravity; |
| 1316 | +import android.view.ViewGroup.LayoutParams; |
| 1317 | import android.widget.TextView; |
| 1318 | |
| 1319 | import com.actionbarsherlock.app.SherlockDialogFragment; |
| 1320 | @@ -41,7 +42,7 @@ |
| 1321 | InfoAlertDialog frag = new InfoAlertDialog(); |
| 1322 | Bundle args = new Bundle(); |
| 1323 | args.putInt("dialogId", dialogId); |
| 1324 | - args.putInt("titleId", titleId); |
| 1325 | + if (titleId != 0) args.putInt("titleId", titleId); |
| 1326 | args.putString("message", message); |
| 1327 | frag.setArguments(args); |
| 1328 | return frag; |
| 1329 | @@ -61,11 +62,12 @@ |
| 1330 | public Dialog onCreateDialog(Bundle savedInstanceState) { |
| 1331 | Bundle args = getArguments(); |
| 1332 | final int dialogId = args.getInt("dialogId"); |
| 1333 | - final int titleId = args.getInt("titleId"); |
| 1334 | final String message = args.getString("message"); |
| 1335 | |
| 1336 | final TextView view = new TextView(getSherlockActivity()); |
| 1337 | + view.setTextAppearance(getSherlockActivity(), android.R.style.TextAppearance_Large); |
| 1338 | view.setGravity(Gravity.CENTER); |
| 1339 | + view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT)); |
| 1340 | view.setText(message); |
| 1341 | |
| 1342 | OnClickListener listener = new OnClickListener() { |
| 1343 | @@ -79,11 +81,15 @@ |
| 1344 | } |
| 1345 | }; |
| 1346 | |
| 1347 | - return new AlertDialog.Builder(getActivity()) |
| 1348 | - .setTitle(titleId) |
| 1349 | + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) |
| 1350 | .setView(view) |
| 1351 | - .setPositiveButton(R.string.ok, listener) |
| 1352 | - .create(); |
| 1353 | + .setPositiveButton(R.string.ok, listener); |
| 1354 | + if (args.containsKey("titleId")) { |
| 1355 | + int titleId = args.getInt("titleId"); |
| 1356 | + builder.setTitle(titleId); |
| 1357 | + } |
| 1358 | + |
| 1359 | + return builder.create(); |
| 1360 | } |
| 1361 | |
| 1362 | public static interface InfoAlertDialogListener { |
| 1363 | |
| 1364 | === modified file 'test/src/com/ubuntuone/android/music/provider/MusicProviderInsertTest.java' |
| 1365 | --- test/src/com/ubuntuone/android/music/provider/MusicProviderInsertTest.java 2012-11-22 11:17:42 +0000 |
| 1366 | +++ test/src/com/ubuntuone/android/music/provider/MusicProviderInsertTest.java 2012-11-28 17:49:20 +0000 |
| 1367 | @@ -161,7 +161,7 @@ |
| 1368 | |
| 1369 | assertNotNull("Inserted song uri is null.", uri); |
| 1370 | |
| 1371 | - Cursor cursor = mResolver.query(uri, Songs.DEFAULT_PROJECTION, null, null, null); |
| 1372 | + Cursor cursor = mResolver.query(uri, Songs.getDefaultProjection(), null, null, null); |
| 1373 | assertEquals("Wrong song row count returned.", 1, cursor.getCount()); |
| 1374 | assertTrue("Cursor missing rows.", cursor.moveToNext()); |
| 1375 | |
| 1376 | @@ -218,7 +218,7 @@ |
| 1377 | |
| 1378 | Uri uri = Songs.buildSongUri(song.getId()); |
| 1379 | |
| 1380 | - Cursor cursor = mResolver.query(uri, Songs.DEFAULT_PROJECTION, null, null, null); |
| 1381 | + Cursor cursor = mResolver.query(uri, Songs.getDefaultProjection(), null, null, null); |
| 1382 | assertEquals("Wrong song row count returned.", 1, cursor.getCount()); |
| 1383 | assertTrue("Cursor missing rows.", cursor.moveToNext()); |
| 1384 | |
| 1385 | |
| 1386 | === modified file 'test/src/com/ubuntuone/android/music/provider/MusicProviderQueryTest.java' |
| 1387 | --- test/src/com/ubuntuone/android/music/provider/MusicProviderQueryTest.java 2012-11-08 14:14:54 +0000 |
| 1388 | +++ test/src/com/ubuntuone/android/music/provider/MusicProviderQueryTest.java 2012-11-28 17:49:20 +0000 |
| 1389 | @@ -81,7 +81,7 @@ |
| 1390 | public void testQueryArtistSongs() { |
| 1391 | Uri artistSongsUri = Artists.buildSongsUri(HashUtils.md5("artist1-artist_name")); |
| 1392 | Cursor cursor = mResolver.query(artistSongsUri, |
| 1393 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1394 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1395 | |
| 1396 | assertEquals("Wrong artist songs count returned.", 2, cursor.getCount()); |
| 1397 | |
| 1398 | @@ -141,7 +141,7 @@ |
| 1399 | Uri albumSongsUri = Albums.buildSongsUri( |
| 1400 | HashUtils.md5("album1-album_titleartist1-artist_name")); |
| 1401 | Cursor cursor = mResolver.query(albumSongsUri, |
| 1402 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1403 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1404 | |
| 1405 | assertEquals("Wrong album songs count returned.", 2, cursor.getCount()); |
| 1406 | |
| 1407 | @@ -156,7 +156,7 @@ |
| 1408 | |
| 1409 | public void testQuerySongs() { |
| 1410 | Cursor cursor = mResolver.query(Songs.CONTENT_URI, |
| 1411 | - Songs.DEFAULT_PROJECTION, null, null, Songs.DEFAULT_SORT); |
| 1412 | + Songs.getDefaultProjection(), null, null, Songs.DEFAULT_SORT); |
| 1413 | |
| 1414 | assertEquals("Wrong song count returned.", MockData.SONGS_COUNT, cursor.getCount()); |
| 1415 | |
| 1416 | @@ -173,7 +173,7 @@ |
| 1417 | public void testQuerySongsId() { |
| 1418 | Uri songUri = Songs.buildSongUri("song1-song_id"); |
| 1419 | Cursor cursor = mResolver.query(songUri, |
| 1420 | - Songs.DEFAULT_PROJECTION, null, null, null); |
| 1421 | + Songs.getDefaultProjection(), null, null, null); |
| 1422 | |
| 1423 | assertEquals("Wrong songs count returned.", |
| 1424 | 1, cursor.getCount()); |
| 1425 | |
| 1426 | === modified file 'test/src/com/ubuntuone/android/music/provider/MusicProviderUtilsTest.java' |
| 1427 | --- test/src/com/ubuntuone/android/music/provider/MusicProviderUtilsTest.java 2012-11-13 13:58:31 +0000 |
| 1428 | +++ test/src/com/ubuntuone/android/music/provider/MusicProviderUtilsTest.java 2012-11-28 17:49:20 +0000 |
| 1429 | @@ -113,7 +113,7 @@ |
| 1430 | } |
| 1431 | |
| 1432 | public void testGetPlaylistSongCount() { |
| 1433 | - Uri playlistUri = Playlists.buildPlaylistSongsUri("playlist1-playlist_id"); |
| 1434 | + Uri playlistUri = Playlists.buildPlaylistUri("playlist1-playlist_id"); |
| 1435 | int songCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1436 | assertEquals("Wrong playlist song count.", 2, songCount); |
| 1437 | } |
| 1438 | @@ -181,7 +181,7 @@ |
| 1439 | } |
| 1440 | |
| 1441 | public void testEnqueueSongInPlaylist() { |
| 1442 | - Uri playlistUri = Playlists.buildPlaylistSongsUri("playlist2-playlist_id"); |
| 1443 | + Uri playlistUri = Playlists.buildPlaylistUri("playlist2-playlist_id"); |
| 1444 | int songCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1445 | assertEquals("Wrong song count of playlist.", 1, songCount); |
| 1446 | |
| 1447 | @@ -192,10 +192,11 @@ |
| 1448 | int newSongCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1449 | assertEquals("Wrong new song count of playlist.", songCount + 1, newSongCount); |
| 1450 | |
| 1451 | + Uri playlistSongsUri = Playlists.buildPlaylistSongsUri("playlist2-playlist_id"); |
| 1452 | Cursor cursor = null; |
| 1453 | try { |
| 1454 | String[] projection = new String[] { Songs.SONG_ID }; |
| 1455 | - cursor = mResolver.query(playlistUri, projection, |
| 1456 | + cursor = mResolver.query(playlistSongsUri, projection, |
| 1457 | null, null, PlaylistSongs.DEFAULT_SORT); |
| 1458 | if (cursor != null && cursor.isBeforeFirst()) { |
| 1459 | cursor.moveToLast(); |
| 1460 | @@ -232,7 +233,7 @@ |
| 1461 | } |
| 1462 | |
| 1463 | public void testEnqueueAlbumInPlaylist() { |
| 1464 | - Uri playlistUri = Playlists.buildPlaylistSongsUri("playlist2-playlist_id"); |
| 1465 | + Uri playlistUri = Playlists.buildPlaylistUri("playlist2-playlist_id"); |
| 1466 | int songCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1467 | assertEquals("Wrong song count of playlist.", 1, songCount); |
| 1468 | |
| 1469 | @@ -244,10 +245,11 @@ |
| 1470 | int newSongCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1471 | assertEquals("Wrong new song count of playlist.", songCount + albumSongCount, newSongCount); |
| 1472 | |
| 1473 | + Uri playlistSongsUri = Playlists.buildPlaylistSongsUri("playlist2-playlist_id"); |
| 1474 | Cursor cursor = null; |
| 1475 | try { |
| 1476 | String[] projection = new String[] { Songs.SONG_ID }; |
| 1477 | - cursor = mResolver.query(playlistUri, projection, |
| 1478 | + cursor = mResolver.query(playlistSongsUri, projection, |
| 1479 | null, null, PlaylistSongs.DEFAULT_SORT); |
| 1480 | if (cursor != null && cursor.isBeforeFirst()) { |
| 1481 | cursor.moveToFirst(); |
| 1482 | @@ -298,7 +300,7 @@ |
| 1483 | } |
| 1484 | |
| 1485 | public void testEnqueueArtistInPlaylist() { |
| 1486 | - Uri playlistUri = Playlists.buildPlaylistSongsUri("playlist2-playlist_id"); |
| 1487 | + Uri playlistUri = Playlists.buildPlaylistUri("playlist2-playlist_id"); |
| 1488 | int songCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1489 | assertEquals("Wrong song count of playlist.", 1, songCount); |
| 1490 | |
| 1491 | @@ -311,10 +313,11 @@ |
| 1492 | int newSongCount = MusicProviderUtils.getPlaylistSongCount(mContext, playlistUri); |
| 1493 | assertEquals("Wrong new song count of playlist.", songCount + artistSongCount, newSongCount); |
| 1494 | |
| 1495 | + Uri playlistSongsUri = Playlists.buildPlaylistSongsUri("playlist2-playlist_id"); |
| 1496 | Cursor cursor = null; |
| 1497 | try { |
| 1498 | String[] projection = new String[] { Songs.SONG_ID }; |
| 1499 | - cursor = mResolver.query(playlistUri, projection, |
| 1500 | + cursor = mResolver.query(playlistSongsUri, projection, |
| 1501 | null, null, PlaylistSongs.DEFAULT_SORT); |
| 1502 | if (cursor != null && cursor.isBeforeFirst()) { |
| 1503 | cursor.moveToFirst(); |
| 1504 | |
| 1505 | === modified file 'test/src/com/ubuntuone/android/music/ui/SongsPageTest.java' |
| 1506 | --- test/src/com/ubuntuone/android/music/ui/SongsPageTest.java 2012-11-08 14:14:54 +0000 |
| 1507 | +++ test/src/com/ubuntuone/android/music/ui/SongsPageTest.java 2012-11-28 17:49:20 +0000 |
| 1508 | @@ -100,6 +100,8 @@ |
| 1509 | assertNotNull("PlayerActivity was not started", |
| 1510 | mInstrumentation.checkMonitorHit(mPlayerActivityMonitor, 1)); |
| 1511 | activity.finish(); |
| 1512 | + |
| 1513 | + mInstrumentation.waitForIdleSync(); |
| 1514 | } |
| 1515 | |
| 1516 | public void testClearQueueAndPlaySongOnOnListItemClick() { |
| 1517 | @@ -154,6 +156,8 @@ |
| 1518 | assertNotNull("PlayerActivity was not started", |
| 1519 | mInstrumentation.checkMonitorHit(mPlayerActivityMonitor, 1)); |
| 1520 | activity.finish(); |
| 1521 | + |
| 1522 | + mInstrumentation.waitForIdleSync(); |
| 1523 | } |
| 1524 | |
| 1525 | public void testOnStarContextItemSelected() { |

Looks good!