OSDN Git Service

Merge "Add improved localized sorting (similar to contacts sorting) to Eleven" into...
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / utils / MusicUtils.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.utils;
15
16 import android.app.Activity;
17 import android.content.ComponentName;
18 import android.content.ContentResolver;
19 import android.content.ContentUris;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.ContextWrapper;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.os.AsyncTask;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.SystemClock;
31 import android.provider.BaseColumns;
32 import android.provider.MediaStore;
33 import android.provider.MediaStore.Audio.AlbumColumns;
34 import android.provider.MediaStore.Audio.ArtistColumns;
35 import android.provider.MediaStore.Audio.AudioColumns;
36 import android.provider.MediaStore.Audio.Playlists;
37 import android.provider.MediaStore.Audio.PlaylistsColumns;
38 import android.provider.MediaStore.MediaColumns;
39 import android.provider.Settings;
40 import android.util.Log;
41 import android.view.Menu;
42
43 import com.cyanogenmod.eleven.Config.IdType;
44 import com.cyanogenmod.eleven.Config.SmartPlaylistType;
45 import com.cyanogenmod.eleven.IElevenService;
46 import com.cyanogenmod.eleven.MusicPlaybackService;
47 import com.cyanogenmod.eleven.R;
48 import com.cyanogenmod.eleven.cache.ImageFetcher;
49 import com.cyanogenmod.eleven.loaders.LastAddedLoader;
50 import com.cyanogenmod.eleven.loaders.PlaylistLoader;
51 import com.cyanogenmod.eleven.loaders.PlaylistSongLoader;
52 import com.cyanogenmod.eleven.loaders.SongLoader;
53 import com.cyanogenmod.eleven.loaders.TopTracksLoader;
54 import com.cyanogenmod.eleven.locale.LocaleUtils;
55 import com.cyanogenmod.eleven.menu.FragmentMenuItems;
56 import com.cyanogenmod.eleven.model.Album;
57 import com.cyanogenmod.eleven.model.AlbumArtistDetails;
58 import com.cyanogenmod.eleven.model.Artist;
59 import com.cyanogenmod.eleven.model.Song;
60 import com.cyanogenmod.eleven.provider.RecentStore;
61 import com.cyanogenmod.eleven.provider.SongPlayCount;
62 import com.cyanogenmod.eleven.service.MusicPlaybackTrack;
63
64 import java.io.File;
65 import java.util.Arrays;
66 import java.util.Collection;
67 import java.util.Iterator;
68 import java.util.WeakHashMap;
69
70 /**
71  * A collection of helpers directly related to music or Apollo's service.
72  *
73  * @author Andrew Neal (andrewdneal@gmail.com)
74  */
75 public final class MusicUtils {
76
77     public static IElevenService mService = null;
78
79     private static int sForegroundActivities = 0;
80
81     private static final WeakHashMap<Context, ServiceBinder> mConnectionMap;
82
83     private static final long[] sEmptyList;
84
85     private static ContentValues[] mContentValuesCache = null;
86
87     private static final int MIN_VALID_YEAR = 1900; // used to remove invalid years from metadata
88
89     public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"
90                     + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; //$NON-NLS-2$
91
92     static {
93         mConnectionMap = new WeakHashMap<Context, ServiceBinder>();
94         sEmptyList = new long[0];
95     }
96
97     /* This class is never initiated */
98     public MusicUtils() {
99     }
100
101     /**
102      * @param context The {@link Context} to use
103      * @param callback The {@link ServiceConnection} to use
104      * @return The new instance of {@link ServiceToken}
105      */
106     public static final ServiceToken bindToService(final Context context,
107             final ServiceConnection callback) {
108         Activity realActivity = ((Activity)context).getParent();
109         if (realActivity == null) {
110             realActivity = (Activity)context;
111         }
112         final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
113         contextWrapper.startService(new Intent(contextWrapper, MusicPlaybackService.class));
114         final ServiceBinder binder = new ServiceBinder(callback);
115         if (contextWrapper.bindService(
116                 new Intent().setClass(contextWrapper, MusicPlaybackService.class), binder, 0)) {
117             mConnectionMap.put(contextWrapper, binder);
118             return new ServiceToken(contextWrapper);
119         }
120         return null;
121     }
122
123     /**
124      * @param token The {@link ServiceToken} to unbind from
125      */
126     public static void unbindFromService(final ServiceToken token) {
127         if (token == null) {
128             return;
129         }
130         final ContextWrapper mContextWrapper = token.mWrappedContext;
131         final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);
132         if (mBinder == null) {
133             return;
134         }
135         mContextWrapper.unbindService(mBinder);
136         if (mConnectionMap.isEmpty()) {
137             mService = null;
138         }
139     }
140
141     public static final class ServiceBinder implements ServiceConnection {
142         private final ServiceConnection mCallback;
143
144         /**
145          * Constructor of <code>ServiceBinder</code>
146          *
147          * @param context The {@link ServiceConnection} to use
148          */
149         public ServiceBinder(final ServiceConnection callback) {
150             mCallback = callback;
151         }
152
153         @Override
154         public void onServiceConnected(final ComponentName className, final IBinder service) {
155             mService = IElevenService.Stub.asInterface(service);
156             if (mCallback != null) {
157                 mCallback.onServiceConnected(className, service);
158             }
159         }
160
161         @Override
162         public void onServiceDisconnected(final ComponentName className) {
163             if (mCallback != null) {
164                 mCallback.onServiceDisconnected(className);
165             }
166             mService = null;
167         }
168     }
169
170     public static final class ServiceToken {
171         public ContextWrapper mWrappedContext;
172
173         /**
174          * Constructor of <code>ServiceToken</code>
175          *
176          * @param context The {@link ContextWrapper} to use
177          */
178         public ServiceToken(final ContextWrapper context) {
179             mWrappedContext = context;
180         }
181     }
182
183     public static final boolean isPlaybackServiceConnected() {
184         return mService != null;
185     }
186
187     /**
188      * Used to make number of labels for the number of artists, albums, songs,
189      * genres, and playlists.
190      *
191      * @param context The {@link Context} to use.
192      * @param pluralInt The ID of the plural string to use.
193      * @param number The number of artists, albums, songs, genres, or playlists.
194      * @return A {@link String} used as a label for the number of artists,
195      *         albums, songs, genres, and playlists.
196      */
197     public static final String makeLabel(final Context context, final int pluralInt,
198             final int number) {
199         return context.getResources().getQuantityString(pluralInt, number, number);
200     }
201
202     /**
203      * * Used to create a formatted time string for the duration of tracks.
204      *
205      * @param context The {@link Context} to use.
206      * @param secs The track in seconds.
207      * @return Duration of a track that's properly formatted.
208      */
209     public static final String makeShortTimeString(final Context context, long secs) {
210         long hours, mins;
211
212         hours = secs / 3600;
213         secs %= 3600;
214         mins = secs / 60;
215         secs %= 60;
216
217         final String durationFormat = context.getResources().getString(
218                 hours == 0 ? R.string.durationformatshort : R.string.durationformatlong);
219         return String.format(durationFormat, hours, mins, secs);
220     }
221
222     /**
223      * Used to create a formatted time string in the format of #h #m or #m if there is only minutes
224      *
225      * @param context The {@link Context} to use.
226      * @param secs The duration seconds.
227      * @return Duration properly formatted in #h #m format
228      */
229     public static final String makeLongTimeString(final Context context, long secs) {
230         long hours, mins;
231
232         hours = secs / 3600;
233         secs %= 3600;
234         mins = secs / 60;
235
236         String hoursString = MusicUtils.makeLabel(context, R.plurals.Nhours, (int)hours);
237         String minutesString = MusicUtils.makeLabel(context, R.plurals.Nminutes, (int)mins);
238
239         if (hours == 0) {
240             return minutesString;
241         } else if (mins == 0) {
242             return hoursString;
243         }
244
245         final String durationFormat = context.getResources().getString(R.string.duration_format);
246         return String.format(durationFormat, hoursString, minutesString);
247     }
248
249     /**
250      * Used to combine two strings with some kind of separator in between
251      *
252      * @param context The {@link Context} to use.
253      * @param first string to combine
254      * @param second string to combine
255      * @return the combined string
256      */
257     public static final String makeCombinedString(final Context context, final String first,
258                                                   final String second) {
259         final String formatter = context.getResources().getString(R.string.combine_two_strings);
260         return String.format(formatter, first, second);
261     }
262
263     /**
264      * Changes to the next track
265      */
266     public static void next() {
267         try {
268             if (mService != null) {
269                 mService.next();
270             }
271         } catch (final RemoteException ignored) {
272         }
273     }
274
275     /**
276      * Changes to the next track asynchronously
277      */
278     public static void asyncNext(final Context context) {
279         final Intent previous = new Intent(context, MusicPlaybackService.class);
280         previous.setAction(MusicPlaybackService.NEXT_ACTION);
281         context.startService(previous);
282     }
283
284     /**
285      * Changes to the previous track.
286      *
287      * @NOTE The AIDL isn't used here in order to properly use the previous
288      *       action. When the user is shuffling, because {@link
289      *       MusicPlaybackService.#openCurrentAndNext()} is used, the user won't
290      *       be able to travel to the previously skipped track. To remedy this,
291      *       {@link MusicPlaybackService.#openCurrent()} is called in {@link
292      *       MusicPlaybackService.#prev()}. {@code #startService(Intent intent)}
293      *       is called here to specifically invoke the onStartCommand used by
294      *       {@link MusicPlaybackService}, which states if the current position
295      *       less than 2000 ms, start the track over, otherwise move to the
296      *       previously listened track.
297      */
298     public static void previous(final Context context, final boolean force) {
299         final Intent previous = new Intent(context, MusicPlaybackService.class);
300         if (force) {
301             previous.setAction(MusicPlaybackService.PREVIOUS_FORCE_ACTION);
302         } else {
303             previous.setAction(MusicPlaybackService.PREVIOUS_ACTION);
304         }
305         context.startService(previous);
306     }
307
308     /**
309      * Plays or pauses the music.
310      */
311     public static void playOrPause() {
312         try {
313             if (mService != null) {
314                 if (mService.isPlaying()) {
315                     mService.pause();
316                 } else {
317                     mService.play();
318                 }
319             }
320         } catch (final Exception ignored) {
321         }
322     }
323
324     /**
325      * Cycles through the repeat options.
326      */
327     public static void cycleRepeat() {
328         try {
329             if (mService != null) {
330                 switch (mService.getRepeatMode()) {
331                     case MusicPlaybackService.REPEAT_NONE:
332                         mService.setRepeatMode(MusicPlaybackService.REPEAT_ALL);
333                         break;
334                     case MusicPlaybackService.REPEAT_ALL:
335                         mService.setRepeatMode(MusicPlaybackService.REPEAT_CURRENT);
336                         if (mService.getShuffleMode() != MusicPlaybackService.SHUFFLE_NONE) {
337                             mService.setShuffleMode(MusicPlaybackService.SHUFFLE_NONE);
338                         }
339                         break;
340                     default:
341                         mService.setRepeatMode(MusicPlaybackService.REPEAT_NONE);
342                         break;
343                 }
344             }
345         } catch (final RemoteException ignored) {
346         }
347     }
348
349     /**
350      * Cycles through the shuffle options.
351      */
352     public static void cycleShuffle() {
353         try {
354             if (mService != null) {
355                 switch (mService.getShuffleMode()) {
356                     case MusicPlaybackService.SHUFFLE_NONE:
357                         mService.setShuffleMode(MusicPlaybackService.SHUFFLE_NORMAL);
358                         if (mService.getRepeatMode() == MusicPlaybackService.REPEAT_CURRENT) {
359                             mService.setRepeatMode(MusicPlaybackService.REPEAT_ALL);
360                         }
361                         break;
362                     case MusicPlaybackService.SHUFFLE_NORMAL:
363                         mService.setShuffleMode(MusicPlaybackService.SHUFFLE_NONE);
364                         break;
365                     case MusicPlaybackService.SHUFFLE_AUTO:
366                         mService.setShuffleMode(MusicPlaybackService.SHUFFLE_NONE);
367                         break;
368                     default:
369                         break;
370                 }
371             }
372         } catch (final RemoteException ignored) {
373         }
374     }
375
376     /**
377      * @return True if we're playing music, false otherwise.
378      */
379     public static final boolean isPlaying() {
380         if (mService != null) {
381             try {
382                 return mService.isPlaying();
383             } catch (final RemoteException ignored) {
384             }
385         }
386         return false;
387     }
388
389     /**
390      * @return The current shuffle mode.
391      */
392     public static final int getShuffleMode() {
393         if (mService != null) {
394             try {
395                 return mService.getShuffleMode();
396             } catch (final RemoteException ignored) {
397             }
398         }
399         return 0;
400     }
401
402     /**
403      * @return The current repeat mode.
404      */
405     public static final int getRepeatMode() {
406         if (mService != null) {
407             try {
408                 return mService.getRepeatMode();
409             } catch (final RemoteException ignored) {
410             }
411         }
412         return 0;
413     }
414
415     /**
416      * @return The current track name.
417      */
418     public static final String getTrackName() {
419         if (mService != null) {
420             try {
421                 return mService.getTrackName();
422             } catch (final RemoteException ignored) {
423             }
424         }
425         return null;
426     }
427
428     /**
429      * @return The current artist name.
430      */
431     public static final String getArtistName() {
432         if (mService != null) {
433             try {
434                 return mService.getArtistName();
435             } catch (final RemoteException ignored) {
436             }
437         }
438         return null;
439     }
440
441     /**
442      * @return The current album name.
443      */
444     public static final String getAlbumName() {
445         if (mService != null) {
446             try {
447                 return mService.getAlbumName();
448             } catch (final RemoteException ignored) {
449             }
450         }
451         return null;
452     }
453
454     /**
455      * @return The current album Id.
456      */
457     public static final long getCurrentAlbumId() {
458         if (mService != null) {
459             try {
460                 return mService.getAlbumId();
461             } catch (final RemoteException ignored) {
462             }
463         }
464         return -1;
465     }
466
467     /**
468      * @return The current song Id.
469      */
470     public static final long getCurrentAudioId() {
471         if (mService != null) {
472             try {
473                 return mService.getAudioId();
474             } catch (final RemoteException ignored) {
475             }
476         }
477         return -1;
478     }
479
480     /**
481      * @return The current Music Playback Track
482      */
483     public static final MusicPlaybackTrack getCurrentTrack() {
484         if (mService != null) {
485             try {
486                 return mService.getCurrentTrack();
487             } catch (final RemoteException ignored) {
488             }
489         }
490         return null;
491     }
492
493     /**
494      * @return The Music Playback Track at the specified index
495      */
496     public static final MusicPlaybackTrack getTrack(int index) {
497         if (mService != null) {
498             try {
499                 return mService.getTrack(index);
500             } catch (final RemoteException ignored) {
501             }
502         }
503         return null;
504     }
505
506     /**
507      * @return The next song Id.
508      */
509     public static final long getNextAudioId() {
510         if (mService != null) {
511             try {
512                 return mService.getNextAudioId();
513             } catch (final RemoteException ignored) {
514             }
515         }
516         return -1;
517     }
518
519     /**
520      * @return The previous song Id.
521      */
522     public static final long getPreviousAudioId() {
523         if (mService != null) {
524             try {
525                 return mService.getPreviousAudioId();
526             } catch (final RemoteException ignored) {
527             }
528         }
529         return -1;
530     }
531
532     /**
533      * @return The current artist Id.
534      */
535     public static final long getCurrentArtistId() {
536         if (mService != null) {
537             try {
538                 return mService.getArtistId();
539             } catch (final RemoteException ignored) {
540             }
541         }
542         return -1;
543     }
544
545     /**
546      * @return The audio session Id.
547      */
548     public static final int getAudioSessionId() {
549         if (mService != null) {
550             try {
551                 return mService.getAudioSessionId();
552             } catch (final RemoteException ignored) {
553             }
554         }
555         return -1;
556     }
557
558     /**
559      * @return The queue.
560      */
561     public static final long[] getQueue() {
562         try {
563             if (mService != null) {
564                 return mService.getQueue();
565             } else {
566             }
567         } catch (final RemoteException ignored) {
568         }
569         return sEmptyList;
570     }
571
572     /**
573      * @param position
574      * @return the id of the track in the queue at the given position
575      */
576     public static final long getQueueItemAtPosition(int position) {
577         try {
578             if (mService != null) {
579                 return mService.getQueueItemAtPosition(position);
580             } else {
581             }
582         } catch (final RemoteException ignored) {
583         }
584         return -1;
585     }
586
587     /**
588      * @return the current queue size
589      */
590     public static final int getQueueSize() {
591         try {
592             if (mService != null) {
593                 return mService.getQueueSize();
594             } else {
595             }
596         } catch (final RemoteException ignored) {
597         }
598         return 0;
599     }
600
601     /**
602      * @return The position of the current track in the queue.
603      */
604     public static final int getQueuePosition() {
605         try {
606             if (mService != null) {
607                 return mService.getQueuePosition();
608             }
609         } catch (final RemoteException ignored) {
610         }
611         return 0;
612     }
613
614     /**
615      * @return The queue history size
616      */
617     public static final int getQueueHistorySize() {
618         if (mService != null) {
619             try {
620                 return mService.getQueueHistorySize();
621             } catch (final RemoteException ignored) {
622             }
623         }
624         return 0;
625     }
626
627     /**
628      * @return The queue history position at the position
629      */
630     public static final int getQueueHistoryPosition(int position) {
631         if (mService != null) {
632             try {
633                 return mService.getQueueHistoryPosition(position);
634             } catch (final RemoteException ignored) {
635             }
636         }
637         return -1;
638     }
639
640     /**
641      * @return The queue history
642      */
643     public static final int[] getQueueHistoryList() {
644         if (mService != null) {
645             try {
646                 return mService.getQueueHistoryList();
647             } catch (final RemoteException ignored) {
648             }
649         }
650         return null;
651     }
652
653     /**
654      * @param id The ID of the track to remove.
655      * @return removes track from a playlist or the queue.
656      */
657     public static final int removeTrack(final long id) {
658         try {
659             if (mService != null) {
660                 return mService.removeTrack(id);
661             }
662         } catch (final RemoteException ingored) {
663         }
664         return 0;
665     }
666
667     /**
668      * Remove song at a specified position in the list
669      *
670      * @param id The ID of the track to remove
671      * @param position The position of the song
672      *
673      * @return true if successful, false otherwise
674      */
675     public static final boolean removeTrackAtPosition(final long id, final int position) {
676         try {
677             if (mService != null) {
678                 return mService.removeTrackAtPosition(id, position);
679             }
680         } catch (final RemoteException ingored) {
681         }
682         return false;
683     }
684
685     /**
686      * @param cursor The {@link Cursor} used to perform our query.
687      * @return The song list for a MIME type.
688      */
689     public static final long[] getSongListForCursor(Cursor cursor) {
690         if (cursor == null) {
691             return sEmptyList;
692         }
693         final int len = cursor.getCount();
694         final long[] list = new long[len];
695         cursor.moveToFirst();
696         int columnIndex = -1;
697         try {
698             columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
699         } catch (final IllegalArgumentException notaplaylist) {
700             columnIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID);
701         }
702         for (int i = 0; i < len; i++) {
703             list[i] = cursor.getLong(columnIndex);
704             cursor.moveToNext();
705         }
706         cursor.close();
707         cursor = null;
708         return list;
709     }
710
711     /**
712      * @param context The {@link Context} to use.
713      * @param id The ID of the artist.
714      * @return The song list for an artist.
715      */
716     public static final long[] getSongListForArtist(final Context context, final long id) {
717         final String[] projection = new String[] {
718             BaseColumns._ID
719         };
720         final String selection = AudioColumns.ARTIST_ID + "=" + id + " AND "
721                 + AudioColumns.IS_MUSIC + "=1";
722         Cursor cursor = context.getContentResolver().query(
723                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
724                 AudioColumns.ALBUM_KEY + "," + AudioColumns.TRACK);
725         if (cursor != null) {
726             final long[] mList = getSongListForCursor(cursor);
727             cursor.close();
728             cursor = null;
729             return mList;
730         }
731         return sEmptyList;
732     }
733
734     /**
735      * @param context The {@link Context} to use.
736      * @param id The ID of the album.
737      * @return The song list for an album.
738      */
739     public static final long[] getSongListForAlbum(final Context context, final long id) {
740         final String[] projection = new String[] {
741             BaseColumns._ID
742         };
743         final String selection = AudioColumns.ALBUM_ID + "=" + id + " AND " + AudioColumns.IS_MUSIC
744                 + "=1";
745         Cursor cursor = context.getContentResolver().query(
746                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,
747                 AudioColumns.TRACK + ", " + MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
748         if (cursor != null) {
749             final long[] mList = getSongListForCursor(cursor);
750             cursor.close();
751             cursor = null;
752             return mList;
753         }
754         return sEmptyList;
755     }
756
757     /**
758      * Plays songs by an artist.
759      *
760      * @param context The {@link Context} to use.
761      * @param artistId The artist Id.
762      * @param position Specify where to start.
763      */
764     public static void playArtist(final Context context, final long artistId, int position, boolean shuffle) {
765         final long[] artistList = getSongListForArtist(context, artistId);
766         if (artistList != null) {
767             playAll(context, artistList, position, artistId, IdType.Artist, shuffle);
768         }
769     }
770
771     /**
772      * @param context The {@link Context} to use.
773      * @param id The ID of the genre.
774      * @return The song list for an genre.
775      */
776     public static final long[] getSongListForGenre(final Context context, final long id) {
777         final String[] projection = new String[] {
778             BaseColumns._ID
779         };
780         final StringBuilder selection = new StringBuilder();
781         selection.append(AudioColumns.IS_MUSIC + "=1");
782         selection.append(" AND " + MediaColumns.TITLE + "!=''");
783         final Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external", Long.valueOf(id));
784         Cursor cursor = context.getContentResolver().query(uri, projection, selection.toString(),
785                 null, null);
786         if (cursor != null) {
787             final long[] mList = getSongListForCursor(cursor);
788             cursor.close();
789             cursor = null;
790             return mList;
791         }
792         return sEmptyList;
793     }
794
795     /**
796      * @param context The {@link Context} to use
797      * @param uri The source of the file
798      */
799     public static void playFile(final Context context, final Uri uri) {
800         if (uri == null || mService == null) {
801             return;
802         }
803
804         // If this is a file:// URI, just use the path directly instead
805         // of going through the open-from-filedescriptor codepath.
806         String filename;
807         String scheme = uri.getScheme();
808         if ("file".equals(scheme)) {
809             filename = uri.getPath();
810         } else {
811             filename = uri.toString();
812         }
813
814         try {
815             mService.stop();
816             mService.openFile(filename);
817             mService.play();
818         } catch (final RemoteException ignored) {
819         }
820     }
821
822     /**
823      * @param context The {@link Context} to use.
824      * @param list The list of songs to play.
825      * @param position Specify where to start.
826      * @param forceShuffle True to force a shuffle, false otherwise.
827      */
828     public static void playAll(final Context context, final long[] list, int position,
829                                final long sourceId, final IdType sourceType,
830                                final boolean forceShuffle) {
831         if (list == null || list.length == 0 || mService == null) {
832             return;
833         }
834         try {
835             if (forceShuffle) {
836                 mService.setShuffleMode(MusicPlaybackService.SHUFFLE_NORMAL);
837             }
838             final long currentId = mService.getAudioId();
839             final int currentQueuePosition = getQueuePosition();
840             if (position != -1 && currentQueuePosition == position && currentId == list[position]) {
841                 final long[] playlist = getQueue();
842                 if (Arrays.equals(list, playlist)) {
843                     mService.play();
844                     return;
845                 }
846             }
847             if (position < 0) {
848                 position = 0;
849             }
850             mService.open(list, forceShuffle ? -1 : position, sourceId, sourceType.mId);
851             mService.play();
852         } catch (final RemoteException ignored) {
853         }
854     }
855
856     /**
857      * @param list The list to enqueue.
858      */
859     public static void playNext(final long[] list, final long sourceId, final IdType sourceType) {
860         if (mService == null) {
861             return;
862         }
863         try {
864             mService.enqueue(list, MusicPlaybackService.NEXT, sourceId, sourceType.mId);
865         } catch (final RemoteException ignored) {
866         }
867     }
868
869     /**
870      * @param context The {@link Context} to use.
871      */
872     public static void shuffleAll(final Context context) {
873         Cursor cursor = SongLoader.makeSongCursor(context, null);
874         final long[] mTrackList = getSongListForCursor(cursor);
875         final int position = 0;
876         if (mTrackList.length == 0 || mService == null) {
877             return;
878         }
879         try {
880             mService.setShuffleMode(MusicPlaybackService.SHUFFLE_NORMAL);
881             final long mCurrentId = mService.getAudioId();
882             final int mCurrentQueuePosition = getQueuePosition();
883             if (position != -1 && mCurrentQueuePosition == position
884                     && mCurrentId == mTrackList[position]) {
885                 final long[] mPlaylist = getQueue();
886                 if (Arrays.equals(mTrackList, mPlaylist)) {
887                     mService.play();
888                     return;
889                 }
890             }
891             mService.open(mTrackList, -1, -1, IdType.NA.mId);
892             mService.play();
893             cursor.close();
894             cursor = null;
895         } catch (final RemoteException ignored) {
896         }
897     }
898
899     /**
900      * Returns The ID for a playlist.
901      *
902      * @param context The {@link Context} to use.
903      * @param name The name of the playlist.
904      * @return The ID for a playlist.
905      */
906     public static final long getIdForPlaylist(final Context context, final String name) {
907         Cursor cursor = context.getContentResolver().query(
908                 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, new String[]{
909                         BaseColumns._ID
910                 }, PlaylistsColumns.NAME + "=?", new String[]{
911                         name
912                 }, PlaylistsColumns.NAME);
913         int id = -1;
914         if (cursor != null) {
915             cursor.moveToFirst();
916             if (!cursor.isAfterLast()) {
917                 id = cursor.getInt(0);
918             }
919             cursor.close();
920             cursor = null;
921         }
922         return id;
923     }
924
925     /** @param context The {@link Context} to use.
926      *  @param id The id of the playlist.
927      *  @return The name for a playlist. */
928     public static final String getNameForPlaylist(final Context context, final long id) {
929         Cursor cursor = context.getContentResolver().query(
930                 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
931                 new String[] { PlaylistsColumns.NAME },
932                 BaseColumns._ID + "=?",
933                 new String[] { Long.toString(id) },
934                 null);
935         if (cursor != null) {
936             try {
937                 if(cursor.moveToFirst()) { return cursor.getString(0); }
938             } finally { cursor.close(); }
939         }
940         // nothing found
941         return null;
942     }
943
944     /**
945      * Returns the Id for an artist.
946      *
947      * @param context The {@link Context} to use.
948      * @param name The name of the artist.
949      * @return The ID for an artist.
950      */
951     public static final long getIdForArtist(final Context context, final String name) {
952         Cursor cursor = context.getContentResolver().query(
953                 MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{
954                         BaseColumns._ID
955                 }, ArtistColumns.ARTIST + "=?", new String[]{
956                         name
957                 }, ArtistColumns.ARTIST);
958         int id = -1;
959         if (cursor != null) {
960             cursor.moveToFirst();
961             if (!cursor.isAfterLast()) {
962                 id = cursor.getInt(0);
963             }
964             cursor.close();
965             cursor = null;
966         }
967         return id;
968     }
969
970     /**
971      * Returns the ID for an album.
972      *
973      * @param context The {@link Context} to use.
974      * @param albumName The name of the album.
975      * @param artistName The name of the artist
976      * @return The ID for an album.
977      */
978     public static final long getIdForAlbum(final Context context, final String albumName,
979             final String artistName) {
980         Cursor cursor = context.getContentResolver().query(
981                 MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, new String[] {
982                     BaseColumns._ID
983                 }, AlbumColumns.ALBUM + "=? AND " + AlbumColumns.ARTIST + "=?", new String[] {
984                     albumName, artistName
985                 }, AlbumColumns.ALBUM);
986         int id = -1;
987         if (cursor != null) {
988             cursor.moveToFirst();
989             if (!cursor.isAfterLast()) {
990                 id = cursor.getInt(0);
991             }
992             cursor.close();
993             cursor = null;
994         }
995         return id;
996     }
997
998     /**
999      * Plays songs from an album.
1000      *
1001      * @param context The {@link Context} to use.
1002      * @param albumId The album Id.
1003      * @param position Specify where to start.
1004      */
1005     public static void playAlbum(final Context context, final long albumId, int position, boolean shuffle) {
1006         final long[] albumList = getSongListForAlbum(context, albumId);
1007         if (albumList != null) {
1008             playAll(context, albumList, position, albumId, IdType.Album, shuffle);
1009         }
1010     }
1011
1012     /*  */
1013     public static void makeInsertItems(final long[] ids, final int offset, int len, final int base) {
1014         if (offset + len > ids.length) {
1015             len = ids.length - offset;
1016         }
1017
1018         if (mContentValuesCache == null || mContentValuesCache.length != len) {
1019             mContentValuesCache = new ContentValues[len];
1020         }
1021         for (int i = 0; i < len; i++) {
1022             if (mContentValuesCache[i] == null) {
1023                 mContentValuesCache[i] = new ContentValues();
1024             }
1025             mContentValuesCache[i].put(Playlists.Members.PLAY_ORDER, base + offset + i);
1026             mContentValuesCache[i].put(Playlists.Members.AUDIO_ID, ids[offset + i]);
1027         }
1028     }
1029
1030     /**
1031      * @param context The {@link Context} to use.
1032      * @param name The name of the new playlist.
1033      * @return A new playlist ID.
1034      */
1035     public static final long createPlaylist(final Context context, final String name) {
1036         if (name != null && name.length() > 0) {
1037             final ContentResolver resolver = context.getContentResolver();
1038             final String[] projection = new String[] {
1039                 PlaylistsColumns.NAME
1040             };
1041             final String selection = PlaylistsColumns.NAME + " = '" + name + "'";
1042             Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
1043                     projection, selection, null, null);
1044             if (cursor.getCount() <= 0) {
1045                 final ContentValues values = new ContentValues(1);
1046                 values.put(PlaylistsColumns.NAME, name);
1047                 final Uri uri = resolver.insert(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
1048                         values);
1049                 return Long.parseLong(uri.getLastPathSegment());
1050             }
1051             if (cursor != null) {
1052                 cursor.close();
1053                 cursor = null;
1054             }
1055             return -1;
1056         }
1057         return -1;
1058     }
1059
1060     /**
1061      * @param context The {@link Context} to use.
1062      * @param playlistId The playlist ID.
1063      */
1064     public static void clearPlaylist(final Context context, final int playlistId) {
1065         final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
1066         context.getContentResolver().delete(uri, null, null);
1067         return;
1068     }
1069
1070     /** remove all backing data for top tracks playlist */
1071     public static void clearTopTracks(Context context) {
1072         SongPlayCount.getInstance(context).deleteAll();
1073     }
1074
1075     /** remove all backing data for top tracks playlist */
1076     public static void clearRecent(Context context) {
1077         RecentStore.getInstance(context).deleteAll();
1078     }
1079
1080     /** move up cutoff for last added songs so playlist will be cleared */
1081     public static void clearLastAdded(Context context) {
1082         PreferenceUtils.getInstance(context)
1083             .setLastAddedCutoff(System.currentTimeMillis());
1084     }
1085
1086     /**
1087      * @param context The {@link Context} to use.
1088      * @param ids The id of the song(s) to add.
1089      * @param playlistid The id of the playlist being added to.
1090      */
1091     public static void addToPlaylist(final Context context, final long[] ids, final long playlistid) {
1092         final int size = ids.length;
1093         final ContentResolver resolver = context.getContentResolver();
1094         final String[] projection = new String[] {
1095             "max(" + Playlists.Members.PLAY_ORDER + ")",
1096         };
1097         final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid);
1098         Cursor cursor = null;
1099         int base = 0;
1100
1101         try {
1102             cursor = resolver.query(uri, projection, null, null, null);
1103
1104             if (cursor != null && cursor.moveToFirst()) {
1105                 base = cursor.getInt(0) + 1;
1106             }
1107         } finally {
1108             if (cursor != null) {
1109                 cursor.close();
1110                 cursor = null;
1111             }
1112         }
1113
1114         int numinserted = 0;
1115         for (int offSet = 0; offSet < size; offSet += 1000) {
1116             makeInsertItems(ids, offSet, 1000, base);
1117             numinserted += resolver.bulkInsert(uri, mContentValuesCache);
1118         }
1119         final String message = context.getResources().getQuantityString(
1120                 R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
1121         CustomToast.makeText((Activity)context, message, CustomToast.LENGTH_SHORT).show();
1122         playlistChanged();
1123     }
1124
1125     /**
1126      * Removes a single track from a given playlist
1127      * @param context The {@link Context} to use.
1128      * @param id The id of the song to remove.
1129      * @param playlistId The id of the playlist being removed from.
1130      */
1131     public static void removeFromPlaylist(final Context context, final long id,
1132             final long playlistId) {
1133         final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
1134         final ContentResolver resolver = context.getContentResolver();
1135         resolver.delete(uri, Playlists.Members.AUDIO_ID + " = ? ", new String[] {
1136             Long.toString(id)
1137         });
1138         final String message = context.getResources().getQuantityString(
1139                 R.plurals.NNNtracksfromplaylist, 1, 1);
1140         CustomToast.makeText((Activity)context, message, CustomToast.LENGTH_SHORT).show();
1141         playlistChanged();
1142     }
1143
1144     /**
1145      * @param context The {@link Context} to use.
1146      * @param list The list to enqueue.
1147      */
1148     public static void addToQueue(final Context context, final long[] list, long sourceId,
1149                                   IdType sourceType) {
1150         if (mService == null) {
1151             return;
1152         }
1153         try {
1154             mService.enqueue(list, MusicPlaybackService.LAST, sourceId, sourceType.mId);
1155             final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length);
1156             CustomToast.makeText((Activity) context, message, CustomToast.LENGTH_SHORT).show();
1157         } catch (final RemoteException ignored) {
1158         }
1159     }
1160
1161     /**
1162      * @param context The {@link Context} to use
1163      * @param id The song ID.
1164      */
1165     public static void setRingtone(final Context context, final long id) {
1166         final ContentResolver resolver = context.getContentResolver();
1167         final Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
1168         try {
1169             final ContentValues values = new ContentValues(2);
1170             values.put(AudioColumns.IS_RINGTONE, "1");
1171             values.put(AudioColumns.IS_ALARM, "1");
1172             resolver.update(uri, values, null, null);
1173         } catch (final UnsupportedOperationException ingored) {
1174             return;
1175         }
1176
1177         final String[] projection = new String[] {
1178                 BaseColumns._ID, MediaColumns.DATA, MediaColumns.TITLE
1179         };
1180
1181         final String selection = BaseColumns._ID + "=" + id;
1182         Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection,
1183                 selection, null, null);
1184         try {
1185             if (cursor != null && cursor.getCount() == 1) {
1186                 cursor.moveToFirst();
1187                 Settings.System.putString(resolver, Settings.System.RINGTONE, uri.toString());
1188                 final String message = context.getString(R.string.set_as_ringtone,
1189                         cursor.getString(2));
1190                 CustomToast.makeText((Activity)context, message, CustomToast.LENGTH_SHORT).show();
1191             }
1192         } finally {
1193             if (cursor != null) {
1194                 cursor.close();
1195                 cursor = null;
1196             }
1197         }
1198     }
1199
1200     /**
1201      * @param context The {@link Context} to use.
1202      * @param id The id of the album.
1203      * @return The song count for an album.
1204      */
1205     public static final int getSongCountForAlbumInt(final Context context, final long id) {
1206         int songCount = 0;
1207         if (id == -1) { return songCount; }
1208
1209         Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id);
1210         Cursor cursor = context.getContentResolver().query(uri,
1211                 new String[] { AlbumColumns.NUMBER_OF_SONGS }, null, null, null);
1212         if (cursor != null) {
1213             cursor.moveToFirst();
1214             if (!cursor.isAfterLast()) {
1215                 if(!cursor.isNull(0)) {
1216                     songCount = cursor.getInt(0);
1217                 }
1218             }
1219             cursor.close();
1220             cursor = null;
1221         }
1222
1223         return songCount;
1224     }
1225
1226     /**
1227      * Gets the number of songs for a playlist
1228      * @param context The {@link Context} to use.
1229      * @param playlistId the id of the playlist
1230      * @return the # of songs in the playlist
1231      */
1232     public static final int getSongCountForPlaylist(final Context context, final long playlistId) {
1233         Cursor c = context.getContentResolver().query(
1234                 MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
1235                 new String[]{BaseColumns._ID}, MusicUtils.MUSIC_ONLY_SELECTION, null, null);
1236
1237         if (c != null) {
1238             int count = 0;
1239             if (c.moveToFirst()) {
1240                 count = c.getCount();
1241             }
1242             c.close();
1243             c = null;
1244             return count;
1245         }
1246
1247         return 0;
1248     }
1249
1250     public static final AlbumArtistDetails getAlbumArtDetails(final Context context, final long trackId) {
1251         final StringBuilder selection = new StringBuilder();
1252         selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
1253         selection.append(" AND " + BaseColumns._ID + " = '" + trackId + "'");
1254
1255         Cursor cursor = context.getContentResolver().query(
1256             MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1257             new String[] {
1258                     /* 0 */
1259                 MediaStore.Audio.AudioColumns.ALBUM_ID,
1260                     /* 1 */
1261                 MediaStore.Audio.AudioColumns.ALBUM,
1262                     /* 2 */
1263                 MediaStore.Audio.AlbumColumns.ARTIST,
1264             }, selection.toString(), null, null
1265         );
1266
1267         if (!cursor.moveToFirst()) {
1268             cursor.close();
1269             return null;
1270         }
1271
1272         AlbumArtistDetails result = new AlbumArtistDetails();
1273         result.mAudioId = trackId;
1274         result.mAlbumId = cursor.getLong(0);
1275         result.mAlbumName = cursor.getString(1);
1276         result.mArtistName = cursor.getString(2);
1277         cursor.close();
1278
1279         return result;
1280     }
1281
1282     /**
1283      * @param context The {@link Context} to use.
1284      * @param id The id of the album.
1285      * @return The release date for an album.
1286      */
1287     public static final String getReleaseDateForAlbum(final Context context, final long id) {
1288         if (id == -1) {
1289             return null;
1290         }
1291         Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id);
1292         Cursor cursor = context.getContentResolver().query(uri, new String[] {
1293                     AlbumColumns.FIRST_YEAR
1294                 }, null, null, null);
1295         String releaseDate = null;
1296         if (cursor != null) {
1297             cursor.moveToFirst();
1298             if (!cursor.isAfterLast()) {
1299                 releaseDate = cursor.getString(0);
1300             }
1301             cursor.close();
1302             cursor = null;
1303         }
1304         return releaseDate;
1305     }
1306
1307     /**
1308      * @return The path to the currently playing file as {@link String}
1309      */
1310     public static final String getFilePath() {
1311         try {
1312             if (mService != null) {
1313                 return mService.getPath();
1314             }
1315         } catch (final RemoteException ignored) {
1316         }
1317         return null;
1318     }
1319
1320     /**
1321      * @param from The index the item is currently at.
1322      * @param to The index the item is moving to.
1323      */
1324     public static void moveQueueItem(final int from, final int to) {
1325         try {
1326             if (mService != null) {
1327                 mService.moveQueueItem(from, to);
1328             } else {
1329             }
1330         } catch (final RemoteException ignored) {
1331         }
1332     }
1333
1334     /**
1335      * @param context The {@link Context} to sue
1336      * @param playlistId The playlist Id
1337      * @return The track list for a playlist
1338      */
1339     public static final long[] getSongListForPlaylist(final Context context, final long playlistId) {
1340         Cursor cursor = PlaylistSongLoader.makePlaylistSongCursor(context, playlistId);
1341
1342         if (cursor != null) {
1343             final long[] list = getSongListForCursor(cursor);
1344             cursor.close();
1345             cursor = null;
1346             return list;
1347         }
1348         return sEmptyList;
1349     }
1350
1351     /**
1352      * Plays a user created playlist.
1353      *
1354      * @param context The {@link Context} to use.
1355      * @param playlistId The playlist Id.
1356      */
1357     public static void playPlaylist(final Context context, final long playlistId, boolean shuffle) {
1358         final long[] playlistList = getSongListForPlaylist(context, playlistId);
1359         if (playlistList != null) {
1360             playAll(context, playlistList, -1, playlistId, IdType.Playlist, shuffle);
1361         }
1362     }
1363
1364     /**
1365      * @param context The {@link Context} to use
1366      * @param type The Smart Playlist Type
1367      * @return The song list for the last added playlist
1368      */
1369     public static final long[] getSongListForSmartPlaylist(final Context context,
1370                                                            final SmartPlaylistType type) {
1371         Cursor cursor = null;
1372         try {
1373             switch (type) {
1374                 case LastAdded:
1375                     cursor = LastAddedLoader.makeLastAddedCursor(context);
1376                     break;
1377                 case RecentlyPlayed:
1378                     cursor = TopTracksLoader.makeRecentTracksCursor(context);
1379                     break;
1380                 case TopTracks:
1381                     cursor = TopTracksLoader.makeTopTracksCursor(context);
1382                     break;
1383             }
1384             return MusicUtils.getSongListForCursor(cursor);
1385         } finally {
1386             if (cursor != null) {
1387                 cursor.close();
1388                 cursor = null;
1389             }
1390         }
1391     }
1392
1393     /**
1394      * Plays the smart playlist
1395      * @param context The {@link Context} to use
1396      * @param position the position to start playing from
1397      * @param type The Smart Playlist Type
1398      */
1399     public static void playSmartPlaylist(final Context context, final int position,
1400                                          final SmartPlaylistType type, final boolean shuffle) {
1401         final long[] list = getSongListForSmartPlaylist(context, type);
1402         MusicUtils.playAll(context, list, position, type.mId, IdType.Playlist, shuffle);
1403     }
1404
1405     /**
1406      * Creates a sub menu used to add items to a new playlist or an existsing
1407      * one.
1408      *
1409      * @param context The {@link Context} to use.
1410      * @param groupId The group Id of the menu.
1411      * @param menu The {@link Menu} to add to.
1412      */
1413     public static void makePlaylistMenu(final Context context, final int groupId,
1414             final Menu menu) {
1415         menu.clear();
1416         menu.add(groupId, FragmentMenuItems.NEW_PLAYLIST, Menu.NONE, R.string.new_playlist);
1417         Cursor cursor = PlaylistLoader.makePlaylistCursor(context);
1418         if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
1419             while (!cursor.isAfterLast()) {
1420                 final Intent intent = new Intent();
1421                 String name = cursor.getString(1);
1422                 if (name != null) {
1423                     intent.putExtra("playlist", getIdForPlaylist(context, name));
1424                     menu.add(groupId, FragmentMenuItems.PLAYLIST_SELECTED, Menu.NONE,
1425                             name).setIntent(intent);
1426                 }
1427                 cursor.moveToNext();
1428             }
1429         }
1430         if (cursor != null) {
1431             cursor.close();
1432             cursor = null;
1433         }
1434     }
1435
1436     /**
1437      * Called when one of the lists should refresh or requery.
1438      */
1439     public static void refresh() {
1440         try {
1441             if (mService != null) {
1442                 mService.refresh();
1443             }
1444         } catch (final RemoteException ignored) {
1445         }
1446     }
1447
1448     /**
1449      * Called when one of playlists have changed
1450      */
1451     public static void playlistChanged() {
1452         try {
1453             if (mService != null) {
1454                 mService.playlistChanged();
1455             }
1456         } catch (final RemoteException ignored) {
1457         }
1458     }
1459
1460     /**
1461      * Seeks the current track to a desired position
1462      *
1463      * @param position The position to seek to
1464      */
1465     public static void seek(final long position) {
1466         if (mService != null) {
1467             try {
1468                 mService.seek(position);
1469             } catch (final RemoteException ignored) {
1470             }
1471         }
1472     }
1473
1474     /**
1475      * Seeks the current track to a desired relative position.  This can be used
1476      * to simulate fastforward and rewind
1477      *
1478      * @param deltaInMs The delta in ms to seek from the current position
1479      */
1480     public static void seekRelative(final long deltaInMs) {
1481         if (mService != null) {
1482             try {
1483                 mService.seekRelative(deltaInMs);
1484             } catch (final RemoteException ignored) {
1485             } catch (final IllegalStateException ignored) {
1486                 // Illegal State Exception message is empty so logging will actually throw an
1487                 // exception.  We should come back and figure out why we get an exception in the
1488                 // first place and make sure we understand it completely.  I will use
1489                 // https://cyanogen.atlassian.net/browse/MUSIC-125 to track investigating this more
1490             }
1491         }
1492     }
1493
1494     /**
1495      * @return The current position time of the track
1496      */
1497     public static final long position() {
1498         if (mService != null) {
1499             try {
1500                 return mService.position();
1501             } catch (final RemoteException ignored) {
1502             } catch (final IllegalStateException ex) {
1503                 // Illegal State Exception message is empty so logging will actually throw an
1504                 // exception.  We should come back and figure out why we get an exception in the
1505                 // first place and make sure we understand it completely.  I will use
1506                 // https://cyanogen.atlassian.net/browse/MUSIC-125 to track investigating this more
1507             }
1508         }
1509         return 0;
1510     }
1511
1512     /**
1513      * @return The total length of the current track
1514      */
1515     public static final long duration() {
1516         if (mService != null) {
1517             try {
1518                 return mService.duration();
1519             } catch (final RemoteException ignored) {
1520             } catch (final IllegalStateException ignored) {
1521                 // Illegal State Exception message is empty so logging will actually throw an
1522                 // exception.  We should come back and figure out why we get an exception in the
1523                 // first place and make sure we understand it completely.  I will use
1524                 // https://cyanogen.atlassian.net/browse/MUSIC-125 to track investigating this more
1525             }
1526         }
1527         return 0;
1528     }
1529
1530     /**
1531      * @param position The position to move the queue to
1532      */
1533     public static void setQueuePosition(final int position) {
1534         if (mService != null) {
1535             try {
1536                 mService.setQueuePosition(position);
1537             } catch (final RemoteException ignored) {
1538             }
1539         }
1540     }
1541
1542     /**
1543      * Clears the qeueue
1544      */
1545     public static void clearQueue() {
1546         try {
1547             mService.removeTracks(0, Integer.MAX_VALUE);
1548         } catch (final RemoteException ignored) {
1549         }
1550     }
1551
1552     /**
1553      * Used to build and show a notification when Apollo is sent into the
1554      * background
1555      *
1556      * @param context The {@link Context} to use.
1557      */
1558     public static void notifyForegroundStateChanged(final Context context, boolean inForeground) {
1559         int old = sForegroundActivities;
1560         if (inForeground) {
1561             sForegroundActivities++;
1562         } else {
1563             sForegroundActivities--;
1564         }
1565
1566         if (old == 0 || sForegroundActivities == 0) {
1567             final Intent intent = new Intent(context, MusicPlaybackService.class);
1568             intent.setAction(MusicPlaybackService.FOREGROUND_STATE_CHANGED);
1569             intent.putExtra(MusicPlaybackService.NOW_IN_FOREGROUND, sForegroundActivities != 0);
1570             context.startService(intent);
1571         }
1572     }
1573
1574     /**
1575      * Perminately deletes item(s) from the user's device
1576      *
1577      * @param context The {@link Context} to use.
1578      * @param list The item(s) to delete.
1579      */
1580     public static void deleteTracks(final Context context, final long[] list) {
1581         final String[] projection = new String[] {
1582                 BaseColumns._ID, MediaColumns.DATA, AudioColumns.ALBUM_ID
1583         };
1584         final StringBuilder selection = new StringBuilder();
1585         selection.append(BaseColumns._ID + " IN (");
1586         for (int i = 0; i < list.length; i++) {
1587             selection.append(list[i]);
1588             if (i < list.length - 1) {
1589                 selection.append(",");
1590             }
1591         }
1592         selection.append(")");
1593         final Cursor c = context.getContentResolver().query(
1594                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
1595                 null, null);
1596         if (c != null) {
1597             // Step 1: Remove selected tracks from the current playlist, as well
1598             // as from the album art cache
1599             c.moveToFirst();
1600             while (!c.isAfterLast()) {
1601                 // Remove from current playlist
1602                 final long id = c.getLong(0);
1603                 removeTrack(id);
1604                 // Remove the track from the play count
1605                 SongPlayCount.getInstance(context).removeItem(id);
1606                 // Remove any items in the recents database
1607                 RecentStore.getInstance(context).removeItem(id);
1608                 c.moveToNext();
1609             }
1610
1611             // Step 2: Remove selected tracks from the database
1612             context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1613                     selection.toString(), null);
1614
1615             // Step 3: Remove files from card
1616             c.moveToFirst();
1617             while (!c.isAfterLast()) {
1618                 final String name = c.getString(1);
1619                 final File f = new File(name);
1620                 try { // File.delete can throw a security exception
1621                     if (!f.delete()) {
1622                         // I'm not sure if we'd ever get here (deletion would
1623                         // have to fail, but no exception thrown)
1624                         Log.e("MusicUtils", "Failed to delete file " + name);
1625                     }
1626                     c.moveToNext();
1627                 } catch (final SecurityException ex) {
1628                     c.moveToNext();
1629                 }
1630             }
1631             c.close();
1632         }
1633
1634         final String message = makeLabel(context, R.plurals.NNNtracksdeleted, list.length);
1635
1636         CustomToast.makeText((Activity)context, message, CustomToast.LENGTH_SHORT).show();
1637         // We deleted a number of tracks, which could affect any number of
1638         // things
1639         // in the media content domain, so update everything.
1640         context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
1641         // Notify the lists to update
1642         refresh();
1643     }
1644
1645     /**
1646      * Simple function used to determine if the song/album year is invalid
1647      * @param year value to test
1648      * @return true if the app considers it valid
1649      */
1650     public static boolean isInvalidYear(int year) {
1651         return year < MIN_VALID_YEAR;
1652     }
1653
1654     /**
1655      * A snippet is taken from MediaStore.Audio.keyFor method
1656      * This will take a name, removes things like "the", "an", etc
1657      * as well as special characters and return it
1658      * @param name the string to trim
1659      * @return the trimmed name
1660      */
1661     public static String getTrimmedName(String name) {
1662         if (name == null || name.length() == 0) {
1663             return name;
1664         }
1665
1666         name = name.trim().toLowerCase();
1667         if (name.startsWith("the ")) {
1668             name = name.substring(4);
1669         }
1670         if (name.startsWith("an ")) {
1671             name = name.substring(3);
1672         }
1673         if (name.startsWith("a ")) {
1674             name = name.substring(2);
1675         }
1676         if (name.endsWith(", the") || name.endsWith(",the") ||
1677                 name.endsWith(", an") || name.endsWith(",an") ||
1678                 name.endsWith(", a") || name.endsWith(",a")) {
1679             name = name.substring(0, name.lastIndexOf(','));
1680         }
1681         name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
1682
1683         return name;
1684     }
1685
1686     /**
1687      * A snippet is taken from MediaStore.Audio.keyFor method
1688      * This will take a name, removes things like "the", "an", etc
1689      * as well as special characters, then find the localized label
1690      * @param name Name to get the label of
1691      * @return the localized label of the bucket that the name falls into
1692      */
1693     public static String getLocalizedBucketLetter(String name) {
1694         if (name == null || name.length() == 0) {
1695             return null;
1696         }
1697
1698         name = getTrimmedName(name);
1699
1700         if (name.length() > 0) {
1701             return LocaleUtils.getInstance().getLabel(name);
1702         }
1703
1704         return null;
1705     }
1706
1707     /** @return true if a string is null, empty, or contains only whitespace */
1708     public static boolean isBlank(String s) {
1709         if(s == null) { return true; }
1710         if(s.isEmpty()) { return true; }
1711         for(int i = 0; i < s.length(); i++) {
1712             char c = s.charAt(i);
1713             if(!Character.isWhitespace(c)) { return false; }
1714         }
1715         return true;
1716     }
1717
1718     /**
1719      * Removes the header image from the cache.
1720      */
1721     public static void removeFromCache(Activity activity, String key) {
1722         ImageFetcher imageFetcher = ApolloUtils.getImageFetcher(activity);
1723         imageFetcher.removeFromCache(key);
1724         // Give the disk cache a little time before requesting a new image.
1725         SystemClock.sleep(80);
1726     }
1727
1728     /**
1729      * Removes image from cache so that the stock image is retrieved on reload
1730      */
1731     public static void selectOldPhoto(Activity activity, String key) {
1732         // First remove the old image
1733         removeFromCache(activity, key);
1734         MusicUtils.refresh();
1735     }
1736
1737     /**
1738      *
1739      * @param sortOrder values are mostly derived from SortOrder.class or could also be any sql
1740      *                  order clause
1741      * @return
1742      */
1743     public static boolean isSortOrderDesending(String sortOrder) {
1744         return sortOrder.endsWith(" DESC");
1745     }
1746
1747     /**
1748      * Takes a collection of items and builds a comma-separated list of them
1749      * @param items collection of items
1750      * @return comma-separted list of items
1751      */
1752     public static final <E> String buildCollectionAsString(Collection<E> items) {
1753         Iterator<E> iterator = items.iterator();
1754         StringBuilder str = new StringBuilder();
1755         if (iterator.hasNext()) {
1756             str.append(iterator.next());
1757             while (iterator.hasNext()) {
1758                 str.append(",");
1759                 str.append(iterator.next());
1760             }
1761         }
1762
1763         return str.toString();
1764     }
1765 }