import android.content.ServiceConnection;
import android.database.Cursor;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.Settings;
import android.util.Log;
import android.view.Menu;
-import android.view.SubMenu;
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.Config.SmartPlaylistType;
import com.cyngn.eleven.IElevenService;
import com.cyngn.eleven.MusicPlaybackService;
import com.cyngn.eleven.R;
import com.cyngn.eleven.loaders.LastAddedLoader;
import com.cyngn.eleven.loaders.PlaylistLoader;
+import com.cyngn.eleven.loaders.PlaylistSongLoader;
import com.cyngn.eleven.loaders.SongLoader;
+import com.cyngn.eleven.loaders.TopTracksLoader;
import com.cyngn.eleven.menu.FragmentMenuItems;
+import com.cyngn.eleven.model.AlbumArtistDetails;
import com.cyngn.eleven.provider.RecentStore;
+import com.cyngn.eleven.provider.SongPlayCount;
import com.devspark.appmsg.AppMsg;
import java.io.File;
import java.util.Arrays;
-import java.util.Formatter;
-import java.util.Locale;
import java.util.WeakHashMap;
/**
private static final int MIN_VALID_YEAR = 1900; // used to remove invalid years from metadata
+ public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"
+ + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; //$NON-NLS-2$
+
static {
mConnectionMap = new WeakHashMap<Context, ServiceBinder>();
sEmptyList = new long[0];
* @param secs The track in seconds.
* @return Duration of a track that's properly formatted.
*/
- public static final String makeTimeString(final Context context, long secs) {
+ public static final String makeShortTimeString(final Context context, long secs) {
long hours, mins;
hours = secs / 3600;
- secs -= hours * 3600;
+ secs %= 3600;
mins = secs / 60;
- secs -= mins * 60;
+ secs %= 60;
final String durationFormat = context.getResources().getString(
hours == 0 ? R.string.durationformatshort : R.string.durationformatlong);
}
/**
+ * * Used to create a formatted time string in the format of #d #h #m #s
+ *
+ * @param context The {@link Context} to use.
+ * @param secs The duration seconds.
+ * @return Duration properly formatted in #d #h #m #s format
+ */
+ public static final String makeLongTimeString(final Context context, long secs) {
+ long days, hours, mins;
+
+ days = secs / (3600 * 24);
+ secs %= (3600 * 24);
+ hours = secs / 3600;
+ secs %= 3600;
+ mins = secs / 60;
+ secs %= 60;
+
+ int stringId = R.string.duration_mins;
+ if (days != 0) {
+ stringId = R.string.duration_days;
+ } else if (hours != 0) {
+ stringId = R.string.duration_hours;
+ }
+
+ final String durationFormat = context.getResources().getString(stringId);
+ return String.format(durationFormat, days, hours, mins, secs);
+ }
+
+ /**
+ * Used to combine two strings with some kind of separator in between
+ *
+ * @param context The {@link Context} to use.
+ * @param first string to combine
+ * @param second string to combine
+ * @return the combined string
+ */
+ public static final String makeCombinedString(final Context context, final String first,
+ final String second) {
+ final String formatter = context.getResources().getString(R.string.combine_two_strings);
+ return String.format(formatter, first, second);
+ }
+
+ /**
* Changes to the next track
*/
public static void next() {
*/
public static void playAll(final Context context, final long[] list, int position,
final boolean forceShuffle) {
- if (list.length == 0 || mService == null) {
+ if (list == null || list.length == 0 || mService == null) {
return;
}
try {
* @param context The {@link Context} to use.
*/
public static void shuffleAll(final Context context) {
- Cursor cursor = SongLoader.makeSongCursor(context);
+ Cursor cursor = SongLoader.makeSongCursor(context, null);
final long[] mTrackList = getSongListForCursor(cursor);
final int position = 0;
if (mTrackList.length == 0 || mService == null) {
*/
public static final long getIdForArtist(final Context context, final String name) {
Cursor cursor = context.getContentResolver().query(
- MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[] {
- BaseColumns._ID
- }, ArtistColumns.ARTIST + "=?", new String[] {
- name
+ MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{
+ BaseColumns._ID
+ }, ArtistColumns.ARTIST + "=?", new String[]{
+ name
}, ArtistColumns.ARTIST);
int id = -1;
if (cursor != null) {
final String message = context.getResources().getQuantityString(
R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
AppMsg.makeText((Activity)context, message, AppMsg.STYLE_CONFIRM).show();
+ playlistChanged();
}
/**
final String message = context.getResources().getQuantityString(
R.plurals.NNNtracksfromplaylist, 1, 1);
AppMsg.makeText((Activity)context, message, AppMsg.STYLE_CONFIRM).show();
+ playlistChanged();
}
/**
}
}
+ public static final String getSongCountForAlbum(final Context context, final long id) {
+ Integer i = getSongCountForAlbumInt(context, id);
+ return i == null ? null : Integer.toString(i);
+ }
+
/**
* @param context The {@link Context} to use.
* @param id The id of the album.
* @return The song count for an album.
*/
- public static final String getSongCountForAlbum(final Context context, final long id) {
+ public static final Integer getSongCountForAlbumInt(final Context context, final long id) {
if (id == -1) {
return null;
}
Cursor cursor = context.getContentResolver().query(uri, new String[] {
AlbumColumns.NUMBER_OF_SONGS
}, null, null, null);
- String songCount = null;
+ Integer songCount = null;
if (cursor != null) {
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
- songCount = cursor.getString(0);
+ if(!cursor.isNull(0)) {
+ songCount = cursor.getInt(0);
+ }
}
cursor.close();
cursor = null;
}
/**
+ * Gets the number of songs for a playlist
+ * @param context The {@link Context} to use.
+ * @param playlistId the id of the playlist
+ * @return the # of songs in the playlist
+ */
+ public static final int getSongCountForPlaylist(final Context context, final long playlistId) {
+ Cursor c = context.getContentResolver().query(
+ MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
+ new String[]{BaseColumns._ID}, MusicUtils.MUSIC_ONLY_SELECTION, null, null);
+
+ if (c != null && c.moveToFirst()) {
+ int count = c.getCount();
+ c.close();
+ c = null;
+ return count;
+ }
+
+ return 0;
+ }
+
+ public static final AlbumArtistDetails getAlbumArtDetails(final Context context, final long trackId) {
+ final StringBuilder selection = new StringBuilder();
+ selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
+ selection.append(" AND " + BaseColumns._ID + " = '" + trackId + "'");
+
+ Cursor cursor = context.getContentResolver().query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String[] {
+ /* 0 */
+ MediaStore.Audio.AudioColumns.ALBUM_ID,
+ /* 1 */
+ MediaStore.Audio.AudioColumns.ALBUM,
+ /* 2 */
+ MediaStore.Audio.AlbumColumns.ARTIST,
+ }, selection.toString(), null, null
+ );
+
+ if (!cursor.moveToFirst()) {
+ cursor.close();
+ return null;
+ }
+
+ AlbumArtistDetails result = new AlbumArtistDetails();
+ result.mAudioId = trackId;
+ result.mAlbumId = cursor.getLong(0);
+ result.mAlbumName = cursor.getString(1);
+ result.mArtistName = cursor.getString(2);
+ cursor.close();
+
+ return result;
+ }
+
+ /**
* @param context The {@link Context} to use.
* @param id The id of the album.
* @return The release date for an album.
* @return The track list for a playlist
*/
public static final long[] getSongListForPlaylist(final Context context, final long playlistId) {
- final String[] projection = new String[] {
- MediaStore.Audio.Playlists.Members.AUDIO_ID
- };
- Cursor cursor = context.getContentResolver().query(
- MediaStore.Audio.Playlists.Members.getContentUri("external",
- Long.valueOf(playlistId)), projection, null, null,
- MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
+ Cursor cursor = PlaylistSongLoader.makePlaylistSongCursor(context, playlistId);
if (cursor != null) {
final long[] list = getSongListForCursor(cursor);
/**
* @param context The {@link Context} to use
+ * @param type The Smart Playlist Type
* @return The song list for the last added playlist
*/
- public static final long[] getSongListForLastAdded(final Context context) {
- final Cursor cursor = LastAddedLoader.makeLastAddedCursor(context);
- if (cursor != null) {
- final int count = cursor.getCount();
- final long[] list = new long[count];
- for (int i = 0; i < count; i++) {
- cursor.moveToNext();
- list[i] = cursor.getLong(0);
+ public static final long[] getSongListForSmartPlaylist(final Context context,
+ final SmartPlaylistType type) {
+ Cursor cursor = null;
+ try {
+ switch (type) {
+ case LastAdded:
+ cursor = LastAddedLoader.makeLastAddedCursor(context);
+ break;
+ case RecentlyPlayed:
+ cursor = TopTracksLoader.makeRecentTracksCursor(context);
+ break;
+ case TopTracks:
+ cursor = TopTracksLoader.makeTopTracksCursor(context);
+ break;
+ }
+ return MusicUtils.getSongListForCursor(cursor);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ cursor = null;
}
- return list;
}
- return sEmptyList;
}
/**
- * Plays the last added songs from the past two weeks.
- *
+ * Plays the smart playlist
* @param context The {@link Context} to use
+ * @param position the position to start playing from
+ * @param type The Smart Playlist Type
*/
- public static void playLastAdded(final Context context) {
- playAll(context, getSongListForLastAdded(context), 0, false);
+ public static void playSmartPlaylist(final Context context, final int position,
+ final SmartPlaylistType type) {
+ final long[] list = getSongListForSmartPlaylist(context, type);
+ MusicUtils.playAll(context, list, position, false);
}
/**
}
/**
- * Queries {@link RecentStore} for the last album played by an artist
- *
- * @param context The {@link Context} to use
- * @param artistName The artist name
- * @return The last album name played by an artist
+ * Called when one of playlists have changed
*/
- public static final String getLastAlbumForArtist(final Context context, final String artistName) {
- return RecentStore.getInstance(context).getAlbumName(artistName);
+ public static void playlistChanged() {
+ try {
+ if (mService != null) {
+ mService.playlistChanged();
+ }
+ } catch (final RemoteException ignored) {
+ }
}
/**
try {
return mService.position();
} catch (final RemoteException ignored) {
+ } catch (final IllegalStateException ex) {
+ // Illegal State Exception message is empty so logging will actually throw an
+ // exception. We should come back and figure out why we get an exception in the
+ // first place and make sure we understand it completely. I will use
+ // https://cyanogen.atlassian.net/browse/MUSIC-125 to track investigating this more
}
}
return 0;
try {
return mService.duration();
} catch (final RemoteException ignored) {
+ } catch (final IllegalStateException ignored) {
+ // Illegal State Exception message is empty so logging will actually throw an
+ // exception. We should come back and figure out why we get an exception in the
+ // first place and make sure we understand it completely. I will use
+ // https://cyanogen.atlassian.net/browse/MUSIC-125 to track investigating this more
}
}
return 0;
// Remove from current playlist
final long id = c.getLong(0);
removeTrack(id);
+ // Remove the track from the play count
+ SongPlayCount.getInstance(context).removeItem(id);
// Remove any items in the recents database
- RecentStore.getInstance(context).removeItem(c.getLong(2));
+ RecentStore.getInstance(context).removeItem(id);
c.moveToNext();
}
/**
* A snippet is taken from MediaStore.Audio.keyFor method
* This will take a name, removes things like "the", "an", etc
+ * as well as special characters and return it
+ * @param name the string to trim
+ * @return the trimmed name
+ */
+ public static String getTrimmedName(String name) {
+ if (name == null || name.length() == 0) {
+ return name;
+ }
+
+ name = name.trim().toLowerCase();
+ if (name.startsWith("the ")) {
+ name = name.substring(4);
+ }
+ if (name.startsWith("an ")) {
+ name = name.substring(3);
+ }
+ if (name.startsWith("a ")) {
+ name = name.substring(2);
+ }
+ if (name.endsWith(", the") || name.endsWith(",the") ||
+ name.endsWith(", an") || name.endsWith(",an") ||
+ name.endsWith(", a") || name.endsWith(",a")) {
+ name = name.substring(0, name.lastIndexOf(','));
+ }
+ name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
+
+ return name;
+ }
+
+ /**
+ * A snippet is taken from MediaStore.Audio.keyFor method
+ * This will take a name, removes things like "the", "an", etc
* as well as special characters, then find the localized label
* @param name Name to get the label of
* @param trimName boolean flag to run the trimmer on the name
* @return the localized label of the bucket that the name falls into
*/
public static String getLocalizedBucketLetter(String name, boolean trimName) {
+ if (name == null || name.length() == 0) {
+ return null;
+ }
+
if (trimName) {
- name = name.trim().toLowerCase();
- if (name.startsWith("the ")) {
- name = name.substring(4);
- }
- if (name.startsWith("an ")) {
- name = name.substring(3);
- }
- if (name.startsWith("a ")) {
- name = name.substring(2);
- }
- if (name.endsWith(", the") || name.endsWith(",the") ||
- name.endsWith(", an") || name.endsWith(",an") ||
- name.endsWith(", a") || name.endsWith(",a")) {
- name = name.substring(0, name.lastIndexOf(','));
- }
- name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
+ name = getTrimmedName(name);
}
if (name.length() > 0) {
- return LocaleUtils.getInstance().getLabel(name);
+ String lbl = LocaleUtils.getInstance().getLabel(name);
+ // For now let's cap it to latin alphabet and the # sign
+ // since chinese characters are resulting in " " and other random
+ // characters but the sort doesn't match the sql sort so it is
+ // not quite sorted
+ if (lbl != null && lbl.length() > 0) {
+ char ch = lbl.charAt(0);
+ if (ch < 'A' && ch > 'Z' && ch != '#') {
+ return null;
+ }
+ }
+
+ if (lbl != null && lbl.length() > 0) {
+ return lbl;
+ }
}
return null;
}
+
+ /** @return true if a string is null, empty, or contains only whitespace */
+ public static boolean isBlank(String s) {
+ if(s == null) { return true; }
+ if(s.isEmpty()) { return true; }
+ for(int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if(!Character.isWhitespace(c)) { return false; }
+ }
+ return true;
+ }
}