OSDN Git Service

Eleven: Cleanup all the whitespace
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / 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 com.cyanogenmod.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 com.cyanogenmod.eleven.model.Song;
28 import com.cyanogenmod.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 {@link Cursor} used to run the query.
49      */
50     private Cursor mCursor;
51
52     /**
53      * The Id of the playlist the songs belong to.
54      */
55     private final long mPlaylistID;
56
57     /**
58      * Constructor of <code>SongLoader</code>
59      *
60      * @param context    The {@link Context} to use
61      * @param playlistId The Id of the playlist the songs belong to.
62      */
63     public PlaylistSongLoader(final Context context, final long playlistId) {
64         super(context);
65         mPlaylistID = playlistId;
66     }
67
68     /**
69      * {@inheritDoc}
70      */
71     @Override
72     public List<Song> loadInBackground() {
73         final int playlistCount = countPlaylist(getContext(), mPlaylistID);
74
75         // Create the Cursor
76         mCursor = makePlaylistSongCursor(getContext(), mPlaylistID);
77
78         if (mCursor != null) {
79             boolean runCleanup = false;
80
81             // if the raw playlist count differs from the mapped playlist count (ie the raw mapping
82             // table vs the mapping table join the audio table) that means the playlist mapping table
83             // is messed up
84             if (mCursor.getCount() != playlistCount) {
85                 Log.w(TAG, "Count Differs - raw is: " + playlistCount + " while cursor is " +
86                         mCursor.getCount());
87
88                 runCleanup = true;
89             }
90
91             // check if the play order is already messed up by duplicates
92             if (!runCleanup && mCursor.moveToFirst()) {
93                 final int playOrderCol = mCursor.getColumnIndexOrThrow(Playlists.Members.PLAY_ORDER);
94
95                 int lastPlayOrder = -1;
96                 do {
97                     int playOrder = mCursor.getInt(playOrderCol);
98                     // if we have duplicate play orders, we need to recreate the playlist
99                     if (playOrder == lastPlayOrder) {
100                         runCleanup = true;
101                         break;
102                     }
103                     lastPlayOrder = playOrder;
104                 } while (mCursor.moveToNext());
105             }
106
107             if (runCleanup) {
108                 Log.w(TAG, "Playlist order has flaws - recreating playlist");
109
110                 // cleanup the playlist
111                 cleanupPlaylist(getContext(), mPlaylistID, mCursor);
112
113                 // create a new cursor
114                 mCursor.close();
115                 mCursor = makePlaylistSongCursor(getContext(), mPlaylistID);
116                 if (mCursor != null) {
117                     Log.d(TAG, "New Count is: " + mCursor.getCount());
118                 }
119             }
120         }
121
122         // Gather the data
123         if (mCursor != null && mCursor.moveToFirst()) {
124             do {
125                 // Copy the song Id
126                 final long id = mCursor.getLong(mCursor
127                         .getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID));
128
129                 // Copy the song name
130                 final String songName = mCursor.getString(mCursor
131                         .getColumnIndexOrThrow(AudioColumns.TITLE));
132
133                 // Copy the artist name
134                 final String artist = mCursor.getString(mCursor
135                         .getColumnIndexOrThrow(AudioColumns.ARTIST));
136
137                 // Copy the album id
138                 final long albumId = mCursor.getLong(mCursor
139                         .getColumnIndexOrThrow(AudioColumns.ALBUM_ID));
140
141                 // Copy the album name
142                 final String album = mCursor.getString(mCursor
143                         .getColumnIndexOrThrow(AudioColumns.ALBUM));
144
145                 // Copy the duration
146                 final long duration = mCursor.getLong(mCursor
147                         .getColumnIndexOrThrow(AudioColumns.DURATION));
148
149                 // Convert the duration into seconds
150                 final int durationInSecs = (int) duration / 1000;
151
152                 // Grab the Song Year
153                 final int year = mCursor.getInt(mCursor
154                         .getColumnIndexOrThrow(AudioColumns.YEAR));
155
156                 // Create a new song
157                 final Song song = new Song(id, songName, artist, album, albumId, durationInSecs, year);
158
159                 // Add everything up
160                 mSongList.add(song);
161             } while (mCursor.moveToNext());
162         }
163         // Close the cursor
164         if (mCursor != null) {
165             mCursor.close();
166             mCursor = null;
167         }
168         return mSongList;
169     }
170
171     /**
172      * Cleans up the playlist based on the passed in cursor's data
173      * @param context The {@link Context} to use
174      * @param playlistId playlistId to clean up
175      * @param cursor data to repopulate the playlist with
176      */
177     private static void cleanupPlaylist(final Context context, final long playlistId,
178                                  final Cursor cursor) {
179         Log.w(TAG, "Cleaning up playlist: " + playlistId);
180
181         final int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
182         final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
183
184         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
185
186         // Delete all results in the playlist
187         ops.add(ContentProviderOperation.newDelete(uri).build());
188
189         // yield the db every 100 records to prevent ANRs
190         final int YIELD_FREQUENCY = 100;
191
192         // for each item, reset the play order position
193         if (cursor.moveToFirst() && cursor.getCount() > 0) {
194             do {
195                 final ContentProviderOperation.Builder builder =
196                         ContentProviderOperation.newInsert(uri)
197                                 .withValue(Playlists.Members.PLAY_ORDER, cursor.getPosition())
198                                 .withValue(Playlists.Members.AUDIO_ID, cursor.getLong(idCol));
199
200                 // yield at the end and not at 0 by incrementing by 1
201                 if ((cursor.getPosition() + 1) % YIELD_FREQUENCY == 0) {
202                     builder.withYieldAllowed(true);
203                 }
204                 ops.add(builder.build());
205             } while (cursor.moveToNext());
206         }
207
208         try {
209             // run the batch operation
210             context.getContentResolver().applyBatch(MediaStore.AUTHORITY, ops);
211         } catch (RemoteException e) {
212             Log.e(TAG, "RemoteException " + e + " while cleaning up playlist " + playlistId);
213         } catch (OperationApplicationException e) {
214             Log.e(TAG, "OperationApplicationException " + e + " while cleaning up playlist "
215                     + playlistId);
216         }
217     }
218
219     /**
220      * Returns the playlist count for the raw playlist mapping table
221      * @param context The {@link Context} to use
222      * @param playlistId playlistId to count
223      * @return the number of tracks in the raw playlist mapping table
224      */
225     private static int countPlaylist(final Context context, final long playlistId) {
226         Cursor c = null;
227         try {
228             // when we query using only the audio_id column we will get the raw mapping table
229             // results - which will tell us if the table has rows that don't exist in the normal
230             // table
231             c = context.getContentResolver().query(
232                     MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
233                     new String[]{
234                             MediaStore.Audio.Playlists.Members.AUDIO_ID,
235                     }, null, null,
236                     MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
237
238             if (c != null) {
239                 return c.getCount();
240             }
241         } finally {
242             if (c != null) {
243                 c.close();
244                 c = null;
245             }
246         }
247
248         return 0;
249     }
250
251     /**
252      * Creates the {@link Cursor} used to run the query.
253      *
254      * @param context The {@link Context} to use.
255      * @param playlistID The playlist the songs belong to.
256      * @return The {@link Cursor} used to run the song query.
257      */
258     public static final Cursor makePlaylistSongCursor(final Context context, final Long playlistID) {
259         String mSelection = (AudioColumns.IS_MUSIC + "=1") +
260                 " AND " + AudioColumns.TITLE + " != ''";
261         return context.getContentResolver().query(
262                 MediaStore.Audio.Playlists.Members.getContentUri("external", playlistID),
263                 new String[] {
264                         /* 0 */
265                         MediaStore.Audio.Playlists.Members._ID,
266                         /* 1 */
267                         MediaStore.Audio.Playlists.Members.AUDIO_ID,
268                         /* 2 */
269                         AudioColumns.TITLE,
270                         /* 3 */
271                         AudioColumns.ARTIST,
272                         /* 4 */
273                         AudioColumns.ALBUM_ID,
274                         /* 5 */
275                         AudioColumns.ALBUM,
276                         /* 6 */
277                         AudioColumns.DURATION,
278                         /* 7 */
279                         AudioColumns.YEAR,
280                         /* 8 */
281                         Playlists.Members.PLAY_ORDER,
282                 }, mSelection, null,
283                 MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
284     }
285 }