OSDN Git Service

029743423471a39e62002d626e8fcf4bf747984c
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / loaders / PlaylistSongLoader.java
1 /*
2  * Copyright (C) 2012 Andrew Neal
3  * Copyright (C) 2014 The CyanogenMod Project
4  * Licensed under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with the
6  * License. You may obtain a copy of the License at
7  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
8  * or agreed to in writing, software distributed under the License is
9  * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
10  * KIND, either express or implied. See the License for the specific language
11  * governing permissions and limitations under the License.
12  */
13
14 package org.lineageos.eleven.loaders;
15
16 import android.content.ContentProviderOperation;
17 import android.content.Context;
18 import android.content.OperationApplicationException;
19 import android.database.Cursor;
20 import android.net.Uri;
21 import android.os.RemoteException;
22 import android.provider.MediaStore;
23 import android.provider.MediaStore.Audio.AudioColumns;
24 import android.provider.MediaStore.Audio.Playlists;
25 import android.util.Log;
26
27 import org.lineageos.eleven.model.Song;
28 import org.lineageos.eleven.utils.Lists;
29
30 import java.util.ArrayList;
31 import java.util.List;
32
33 /**
34  * Used to query {@link MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI} and
35  * return the songs for a particular playlist.
36  *
37  * @author Andrew Neal (andrewdneal@gmail.com)
38  */
39 public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> {
40     private static final String TAG = PlaylistSongLoader.class.getSimpleName();
41
42     /**
43      * The result
44      */
45     private final ArrayList<Song> mSongList = Lists.newArrayList();
46
47     /**
48      * The Id of the playlist the songs belong to.
49      */
50     private final long mPlaylistID;
51
52     /**
53      * Constructor of <code>SongLoader</code>
54      *
55      * @param context    The {@link Context} to use
56      * @param playlistId The Id of the playlist the songs belong to.
57      */
58     public PlaylistSongLoader(final Context context, final long playlistId) {
59         super(context);
60         mPlaylistID = playlistId;
61     }
62
63     /**
64      * {@inheritDoc}
65      */
66     @Override
67     public List<Song> loadInBackground() {
68         final int playlistCount = countPlaylist(getContext(), mPlaylistID);
69
70         // Create the Cursor
71         Cursor cursor = makePlaylistSongCursor(getContext(), mPlaylistID);
72
73         if (cursor != null) {
74             boolean runCleanup = false;
75
76             // if the raw playlist count differs from the mapped playlist count (ie the raw mapping
77             // table vs the mapping table join the audio table) that means the playlist mapping table
78             // is messed up
79             if (cursor.getCount() != playlistCount) {
80                 Log.w(TAG, "Count Differs - raw is: " + playlistCount + " while cursor is " +
81                         cursor.getCount());
82
83                 runCleanup = true;
84             }
85
86             // check if the play order is already messed up by duplicates
87             if (!runCleanup && cursor.moveToFirst()) {
88                 final int playOrderCol = cursor.getColumnIndexOrThrow(Playlists.Members.PLAY_ORDER);
89
90                 int lastPlayOrder = -1;
91                 do {
92                     int playOrder = cursor.getInt(playOrderCol);
93                     // if we have duplicate play orders, we need to recreate the playlist
94                     if (playOrder == lastPlayOrder) {
95                         runCleanup = true;
96                         break;
97                     }
98                     lastPlayOrder = playOrder;
99                 } while (cursor.moveToNext());
100             }
101
102             if (runCleanup) {
103                 Log.w(TAG, "Playlist order has flaws - recreating playlist");
104
105                 // cleanup the playlist
106                 cleanupPlaylist(getContext(), mPlaylistID, cursor);
107
108                 // create a new cursor
109                 cursor.close();
110                 cursor = makePlaylistSongCursor(getContext(), mPlaylistID);
111                 if (cursor != null) {
112                     Log.d(TAG, "New Count is: " + cursor.getCount());
113                 }
114             }
115         }
116
117         // Gather the data
118         if (cursor != null && cursor.moveToFirst()) {
119             do {
120                 // Copy the song Id
121                 final long id = cursor.getLong(cursor
122                         .getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID));
123
124                 // Copy the song name
125                 final String songName = cursor.getString(cursor
126                         .getColumnIndexOrThrow(AudioColumns.TITLE));
127
128                 // Copy the artist name
129                 final String artist = cursor.getString(cursor
130                         .getColumnIndexOrThrow(AudioColumns.ARTIST));
131
132                 // Copy the album id
133                 final long albumId = cursor.getLong(cursor
134                         .getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
135
136                 // Copy the album name
137                 final String album = cursor.getString(cursor
138                         .getColumnIndexOrThrow(AudioColumns.ALBUM));
139
140                 // Copy the duration
141                 final long duration = cursor.getLong(cursor
142                         .getColumnIndexOrThrow(AudioColumns.DURATION));
143
144                 // Convert the duration into seconds
145                 final int durationInSecs = (int) duration / 1000;
146
147                 // Grab the Song Year
148                 final int year = cursor.getInt(cursor
149                         .getColumnIndexOrThrow(AudioColumns.YEAR));
150
151                 // Create a new song
152                 final Song song = new Song(id, songName, artist, album, albumId, durationInSecs, year);
153
154                 // Add everything up
155                 mSongList.add(song);
156             } while (cursor.moveToNext());
157         }
158         // Close the cursor
159         if (cursor != null) {
160             cursor.close();
161             cursor = null;
162         }
163         return mSongList;
164     }
165
166     /**
167      * Cleans up the playlist based on the passed in cursor's data
168      * @param context The {@link Context} to use
169      * @param playlistId playlistId to clean up
170      * @param cursor data to repopulate the playlist with
171      */
172     private static void cleanupPlaylist(final Context context, final long playlistId,
173                                  final Cursor cursor) {
174         Log.w(TAG, "Cleaning up playlist: " + playlistId);
175
176         final int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
177         final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
178
179         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
180
181         // Delete all results in the playlist
182         ops.add(ContentProviderOperation.newDelete(uri).build());
183
184         // yield the db every 100 records to prevent ANRs
185         final int YIELD_FREQUENCY = 100;
186
187         // for each item, reset the play order position
188         if (cursor.moveToFirst() && cursor.getCount() > 0) {
189             do {
190                 final ContentProviderOperation.Builder builder =
191                         ContentProviderOperation.newInsert(uri)
192                                 .withValue(Playlists.Members.PLAY_ORDER, cursor.getPosition())
193                                 .withValue(Playlists.Members.AUDIO_ID, cursor.getLong(idCol));
194
195                 // yield at the end and not at 0 by incrementing by 1
196                 if ((cursor.getPosition() + 1) % YIELD_FREQUENCY == 0) {
197                     builder.withYieldAllowed(true);
198                 }
199                 ops.add(builder.build());
200             } while (cursor.moveToNext());
201         }
202
203         try {
204             // run the batch operation
205             context.getContentResolver().applyBatch(MediaStore.AUTHORITY, ops);
206         } catch (RemoteException e) {
207             Log.e(TAG, "RemoteException " + e + " while cleaning up playlist " + playlistId);
208         } catch (OperationApplicationException e) {
209             Log.e(TAG, "OperationApplicationException " + e + " while cleaning up playlist "
210                     + playlistId);
211         }
212     }
213
214     /**
215      * Returns the playlist count for the raw playlist mapping table
216      * @param context The {@link Context} to use
217      * @param playlistId playlistId to count
218      * @return the number of tracks in the raw playlist mapping table
219      */
220     private static int countPlaylist(final Context context, final long playlistId) {
221         Cursor c = null;
222         try {
223             // when we query using only the audio_id column we will get the raw mapping table
224             // results - which will tell us if the table has rows that don't exist in the normal
225             // table
226             c = context.getContentResolver().query(
227                     MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
228                     new String[]{
229                             MediaStore.Audio.Playlists.Members.AUDIO_ID,
230                     }, null, null,
231                     MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
232
233             if (c != null) {
234                 return c.getCount();
235             }
236         } finally {
237             if (c != null) {
238                 c.close();
239                 c = null;
240             }
241         }
242
243         return 0;
244     }
245
246     /**
247      * Creates the {@link Cursor} used to run the query.
248      *
249      * @param context The {@link Context} to use.
250      * @param playlistID The playlist the songs belong to.
251      * @return The {@link Cursor} used to run the song query.
252      */
253     public static final Cursor makePlaylistSongCursor(final Context context, final Long playlistID) {
254         String mSelection = (AudioColumns.IS_MUSIC + "=1") +
255                 " AND " + AudioColumns.TITLE + " != ''";
256         return context.getContentResolver().query(
257                 MediaStore.Audio.Playlists.Members.getContentUri("external", playlistID),
258                 new String[] {
259                         /* 0 */
260                         MediaStore.Audio.Playlists.Members._ID,
261                         /* 1 */
262                         MediaStore.Audio.Playlists.Members.AUDIO_ID,
263                         /* 2 */
264                         AudioColumns.TITLE,
265                         /* 3 */
266                         AudioColumns.ARTIST,
267                         /* 4 */
268                         AudioColumns.ALBUM_ID,
269                         /* 5 */
270                         AudioColumns.ALBUM,
271                         /* 6 */
272                         AudioColumns.DURATION,
273                         /* 7 */
274                         AudioColumns.YEAR,
275                         /* 8 */
276                         Playlists.Members.PLAY_ORDER,
277                 }, mSelection, null,
278                 MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
279     }
280 }