Commit 40030b0a authored by linus_lee's avatar linus_lee Committed by Gerrit Code Review
Browse files

Merge "Add improved localized sorting (similar to contacts sorting) to Eleven" into cm-12.0

parents aa02c18e 7124d4f4
......@@ -11,7 +11,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android-common \
android-visualizer \
eleven_support_v4 \
eleven_recyclerview
eleven_recyclerview \
guava
LOCAL_PACKAGE_NAME := Eleven
LOCAL_OVERRIDES_PACKAGES := Music
......
......@@ -206,6 +206,12 @@
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
<!-- Used to recalculate sorting of songs based on the user's locale -->
<receiver android:name=".locale.LocaleChangeReceiver">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
</intent-filter>
</receiver>
<!-- Music service -->
<service
android:name="com.cyanogenmod.eleven.MusicPlaybackService"
......
......@@ -27,7 +27,7 @@
<com.viewpagerindicator.TabPageIndicator
android:id="@+id/fragment_home_phone_pager_titles"
android:background="@color/tpi_background_color"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="@dimen/tpi_height" />
<android.support.v4.view.ViewPager
......
......@@ -29,7 +29,7 @@ import android.widget.TextView;
import com.cyanogenmod.eleven.Config;
import com.cyanogenmod.eleven.R;
import com.cyanogenmod.eleven.cache.ImageFetcher;
import com.cyanogenmod.eleven.loaders.ArtistAlbumLoader;
import com.cyanogenmod.eleven.loaders.AlbumLoader;
import com.cyanogenmod.eleven.model.Album;
import com.cyanogenmod.eleven.utils.ApolloUtils;
import com.cyanogenmod.eleven.utils.NavUtils;
......@@ -130,7 +130,7 @@ implements LoaderCallbacks<List<Album>>, IPopupMenuCallback {
@Override // LoaderCallbacks
public Loader<List<Album>> onCreateLoader(int id, Bundle args) {
return new ArtistAlbumLoader(mActivity, args.getLong(Config.ID));
return new AlbumLoader(mActivity, args.getLong(Config.ID));
}
@Override // LoaderCallbacks
......
......@@ -17,6 +17,7 @@ package com.cyanogenmod.eleven.adapters;
import android.app.Activity;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.ImageView;
......@@ -25,7 +26,7 @@ import android.widget.TextView;
import com.cyanogenmod.eleven.Config;
import com.cyanogenmod.eleven.R;
import com.cyanogenmod.eleven.cache.ImageFetcher;
import com.cyanogenmod.eleven.loaders.ArtistSongLoader;
import com.cyanogenmod.eleven.loaders.SongLoader;
import com.cyanogenmod.eleven.model.Song;
import java.util.List;
......@@ -45,7 +46,8 @@ public abstract class ArtistDetailSongAdapter extends DetailSongAdapter {
public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
onLoading();
setSourceId(args.getLong(Config.ID));
return new ArtistSongLoader(mActivity, getSourceId());
final String selection = MediaStore.Audio.AudioColumns.ARTIST_ID + "=" + getSourceId();
return new SongLoader(mActivity, selection);
}
protected Holder newHolder(View root, ImageFetcher fetcher) {
......
......@@ -205,7 +205,7 @@ public class PlaylistWorkerTask extends BitmapWorkerTask<Void, Void, TransitionD
// create a new cursor that takes the playlist cursor and the sorted order
sortedCursor = new SortedCursor(playlistCursor, order,
MediaStore.Audio.Playlists.Members.AUDIO_ID);
MediaStore.Audio.Playlists.Members.AUDIO_ID, null);
// since this cursor is now wrapped by SortedTracksCursor, remove the reference here
// so we don't accidentally close it in the finally loop
......
......@@ -15,16 +15,19 @@ package com.cyanogenmod.eleven.loaders;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AlbumColumns;
import com.cyanogenmod.eleven.model.Album;
import com.cyanogenmod.eleven.provider.LocalizedStore;
import com.cyanogenmod.eleven.provider.LocalizedStore.SortParameter;
import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
import com.cyanogenmod.eleven.utils.Lists;
import com.cyanogenmod.eleven.utils.MusicUtils;
import com.cyanogenmod.eleven.utils.PreferenceUtils;
import com.cyanogenmod.eleven.utils.SortOrder;
import com.cyanogenmod.eleven.utils.SortUtils;
import java.util.ArrayList;
import java.util.List;
......@@ -48,12 +51,25 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
private Cursor mCursor;
/**
* Constructor of <code>AlbumLoader</code>
*
* Additional selection filter
*/
protected Long mArtistId;
/**
* @param context The {@link Context} to use
*/
public AlbumLoader(final Context context) {
this(context, null);
}
/**
* @param context The {@link Context} to use
* @param artistId The artistId to filter against or null if none
*/
public AlbumLoader(final Context context, final Long artistId) {
super(context);
mArtistId = artistId;
}
/**
......@@ -62,7 +78,7 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
@Override
public List<Album> loadInBackground() {
// Create the Cursor
mCursor = makeAlbumCursor(getContext());
mCursor = makeAlbumCursor(getContext(), mArtistId);
// Gather the data
if (mCursor != null && mCursor.moveToFirst()) {
do {
......@@ -89,6 +105,10 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
// Create a new album
final Album album = new Album(id, albumName, artist, songCount, year);
if (mCursor instanceof SortedCursor) {
album.mBucketLabel = (String)((SortedCursor)mCursor).getExtraData();
}
// Add everything up
mAlbumsList.add(album);
} while (mCursor.moveToNext());
......@@ -99,37 +119,40 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
mCursor = null;
}
// requested album ordering
String albumSortOrder = PreferenceUtils.getInstance(mContext).getAlbumSortOrder();
// run a custom localized sort to try to fit items in to header buckets more nicely
if (shouldEvokeCustomSortRoutine(albumSortOrder)) {
mAlbumsList = SortUtils.localizeSortList(mAlbumsList, albumSortOrder);
}
return mAlbumsList;
}
/**
* Evoke custom sorting routine if the sorting attribute is a String. MediaProvider's sort
* can be trusted in other instances
* @param sortOrder
* @return
* For string-based sorts, return the localized store sort parameter, otherwise return null
* @param sortOrder the song ordering preference selected by the user
*/
private boolean shouldEvokeCustomSortRoutine(String sortOrder) {
return sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z) ||
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A) ||
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST);
private static LocalizedStore.SortParameter getSortParameter(String sortOrder) {
if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z) ||
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)) {
return LocalizedStore.SortParameter.Album;
} else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST)) {
return LocalizedStore.SortParameter.Artist;
}
return null;
}
/**
* Creates the {@link Cursor} used to run the query.
*
* @param context The {@link Context} to use.
* @param artistId The artistId we want to find albums for or null if we want all albums
* @return The {@link Cursor} used to run the album query.
*/
public static final Cursor makeAlbumCursor(final Context context) {
return context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
public static final Cursor makeAlbumCursor(final Context context, final Long artistId) {
// requested album ordering
final String albumSortOrder = PreferenceUtils.getInstance(context).getAlbumSortOrder();
Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
if (artistId != null) {
uri = MediaStore.Audio.Artists.Albums.getContentUri("external", artistId);
}
Cursor cursor = context.getContentResolver().query(uri,
new String[] {
/* 0 */
BaseColumns._ID,
......@@ -141,6 +164,16 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
AlbumColumns.NUMBER_OF_SONGS,
/* 4 */
AlbumColumns.FIRST_YEAR
}, null, null, PreferenceUtils.getInstance(context).getAlbumSortOrder());
}, null, null, albumSortOrder);
// if our sort is a localized-based sort, grab localized data from the store
final SortParameter sortParameter = getSortParameter(albumSortOrder);
if (sortParameter != null && cursor != null) {
final boolean descending = MusicUtils.isSortOrderDesending(albumSortOrder);
return LocalizedStore.getInstance(context).getLocalizedSort(cursor, BaseColumns._ID,
SortParameter.Album, sortParameter, descending, artistId == null);
}
return cursor;
}
}
/*
* Copyright (C) 2012 Andrew Neal
* Copyright (C) 2014 The CyanogenMod Project
* Licensed under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.cyanogenmod.eleven.loaders;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AlbumColumns;
import android.util.Log;
import com.cyanogenmod.eleven.model.Album;
import com.cyanogenmod.eleven.utils.ApolloUtils;
import com.cyanogenmod.eleven.utils.Lists;
import com.cyanogenmod.eleven.utils.PreferenceUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Used to query {@link MediaStore.Audio.Artists.Albums} and return the albums
* for a particular artist.
*
* @author Andrew Neal (andrewdneal@gmail.com)
*/
public class ArtistAlbumLoader extends WrappedAsyncTaskLoader<List<Album>> {
private static final String TAG = ArtistAlbumLoader.class.getSimpleName();
/**
* The result
*/
private final ArrayList<Album> mAlbumsList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
*/
private Cursor mCursor;
/**
* The Id of the artist the albums belong to.
*/
private final Long mArtistID;
/**
* Constructor of <code>ArtistAlbumHandler</code>
*
* @param context The {@link Context} to use.
* @param artistId The Id of the artist the albums belong to.
*/
public ArtistAlbumLoader(final Context context, final Long artistId) {
super(context);
mArtistID = artistId;
}
/**
* {@inheritDoc}
*/
@Override
public List<Album> loadInBackground() {
// Create the Cursor
mCursor = makeArtistAlbumCursor(getContext(), mArtistID);
// Gather the dataS
if (mCursor != null && mCursor.moveToFirst()) {
do {
// Copy the album id
final long id = mCursor.getLong(0);
// Copy the album name
final String albumName = mCursor.getString(1);
// Copy the artist name
final String artist = mCursor.getString(2);
// Copy the number of songs
final int songCount = mCursor.getInt(3);
// Copy the release year
final String year = mCursor.getString(4);
// as per designer's request, don't show unknown albums
if (MediaStore.UNKNOWN_STRING.equals(albumName)) {
continue;
}
// Create a new album
final Album album = new Album(id, albumName, artist, songCount, year);
// Add everything up
mAlbumsList.add(album);
} while (mCursor.moveToNext());
}
// Close the cursor
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
return mAlbumsList;
}
/**
* @param context The {@link Context} to use.
* @param artistId The Id of the artist the albums belong to.
*/
public static final Cursor makeArtistAlbumCursor(final Context context, final Long artistId) {
try {
return context.getContentResolver().query(
MediaStore.Audio.Artists.Albums.getContentUri("external", artistId), new String[] {
/* 0 */
BaseColumns._ID,
/* 1 */
AlbumColumns.ALBUM,
/* 2 */
AlbumColumns.ARTIST,
/* 3 */
AlbumColumns.NUMBER_OF_SONGS,
/* 4 */
AlbumColumns.FIRST_YEAR
}, null, null, PreferenceUtils.getInstance(context).getArtistAlbumSortOrder());
} catch(Exception e) {
Log.e(TAG, ApolloUtils.formatException("unable to make ArtistAlbum cursor", e));
return null;
}
}
}
......@@ -15,16 +15,17 @@ package com.cyanogenmod.eleven.loaders;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.ArtistColumns;
import android.provider.MediaStore.Audio.Artists;
import com.cyanogenmod.eleven.model.Artist;
import com.cyanogenmod.eleven.provider.LocalizedStore;
import com.cyanogenmod.eleven.provider.LocalizedStore.SortParameter;
import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
import com.cyanogenmod.eleven.utils.Lists;
import com.cyanogenmod.eleven.utils.MusicUtils;
import com.cyanogenmod.eleven.utils.PreferenceUtils;
import com.cyanogenmod.eleven.utils.SortOrder;
import com.cyanogenmod.eleven.utils.SortUtils;
import java.util.ArrayList;
import java.util.List;
......@@ -86,6 +87,10 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
// Create a new artist
final Artist artist = new Artist(id, artistName, songCount, albumCount);
if (mCursor instanceof SortedCursor) {
artist.mBucketLabel = (String)((SortedCursor)mCursor).getExtraData();
}
mArtistsList.add(artist);
} while (mCursor.moveToNext());
}
......@@ -95,27 +100,21 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
mCursor = null;
}
// requested artist ordering
String artistSortOrder = PreferenceUtils.getInstance(mContext).getArtistSortOrder();
// run a custom localized sort to try to fit items in to header buckets more nicely
if (shouldEvokeCustomSortRoutine(artistSortOrder)) {
mArtistsList = SortUtils.localizeSortList(mArtistsList, artistSortOrder);
}
return mArtistsList;
}
/**
* Evoke custom sorting routine if the sorting attribute is a String. MediaProvider's sort
* can be trusted in other instances
* @param sortOrder
* @return
* For string-based sorts, return the localized store sort parameter, otherwise return null
* @param sortOrder the song ordering preference selected by the user
*/
private boolean shouldEvokeCustomSortRoutine(String sortOrder) {
return sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z) ||
sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A);
}
private static LocalizedStore.SortParameter getSortParameter(String sortOrder) {
if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z) ||
sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A)) {
return LocalizedStore.SortParameter.Artist;
}
return null;
}
/**
* Creates the {@link Cursor} used to run the query.
*
......@@ -123,16 +122,29 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
* @return The {@link Cursor} used to run the artist query.
*/
public static final Cursor makeArtistCursor(final Context context) {
return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
// requested artist ordering
final String artistSortOrder = PreferenceUtils.getInstance(context).getArtistSortOrder();
Cursor cursor = context.getContentResolver().query(Artists.EXTERNAL_CONTENT_URI,
new String[] {
/* 0 */
BaseColumns._ID,
Artists._ID,
/* 1 */
ArtistColumns.ARTIST,
Artists.ARTIST,
/* 2 */
ArtistColumns.NUMBER_OF_ALBUMS,
Artists.NUMBER_OF_ALBUMS,
/* 3 */
ArtistColumns.NUMBER_OF_TRACKS
}, null, null, PreferenceUtils.getInstance(context).getArtistSortOrder());
Artists.NUMBER_OF_TRACKS
}, null, null, artistSortOrder);
// if our sort is a localized-based sort, grab localized data from the store
final SortParameter sortParameter = getSortParameter(artistSortOrder);
if (sortParameter != null && cursor != null) {
final boolean descending = MusicUtils.isSortOrderDesending(artistSortOrder);
return LocalizedStore.getInstance(context).getLocalizedSort(cursor, Artists._ID,
SortParameter.Artist, sortParameter, descending, true);
}
return cursor;
}
}
/*
* Copyright (C) 2012 Andrew Neal
* Copyright (C) 2014 The CyanogenMod Project
* Licensed under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.cyanogenmod.eleven.loaders;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AudioColumns;
import com.cyanogenmod.eleven.model.Song;
import com.cyanogenmod.eleven.utils.Lists;
import com.cyanogenmod.eleven.utils.PreferenceUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Used to query {@link MediaStore.Audio.Media.EXTERNAL_CONTENT_URI} and return
* the songs for a particular artist.
*
* @author Andrew Neal (andrewdneal@gmail.com)
*/
public class ArtistSongLoader extends WrappedAsyncTaskLoader<List<Song>> {
/**
* The result
*/
private final ArrayList<Song> mSongList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
*/
private Cursor mCursor;
/**
* The Id of the artist the songs belong to.
*/
private final Long mArtistID;
/**
* Constructor of <code>ArtistSongLoader</code>
*
* @param context The {@link Context} to use.
* @param artistId The Id of the artist the songs belong to.
*/
public ArtistSongLoader(final Context context, final Long artistId) {
super(context);
mArtistID = artistId;
}
/**
* {@inheritDoc}
*/
@Override
public List<Song> loadInBackground() {
// Create the Cursor
mCursor = makeArtistSongCursor(getContext(), mArtistID);
// Gather the data
if (mCursor != null && mCursor.moveToFirst()) {
do {
// Copy the song Id
final long id = mCursor.getLong(0);
// Copy the song name
final String songName = mCursor.getString(1);
// Copy the artist name
final String artist = mCursor.getString(2);
// Copy the album id
final long albumId = mCursor.getLong(3);
// Copy the album name
final String album = mCursor.getString(4);
// Copy the duration
final long duration = mCursor.getLong(5);
// Convert the duration into seconds
final int durationInSecs = (int) duration / 1000;
// Grab the Song Year
final int year = mCursor.getInt(6);
// Create a new song
final Song song = new Song(id, songName, artist, album, albumId, durationInSecs, year);
// Add everything up
mSongList.add(song);
} while (mCursor.moveToNext());
}
// Close the cursor
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
return mSongList;
}
/**
* @param context The {@link Context} to use.
* @param artistId The Id of the artist the songs belong to.
* @return The {@link Cursor} used to run the query.
*/
public static final Cursor makeArtistSongCursor(final Context context, final Long artistId) {
// Match the songs up with the artist
final StringBuilder selection = new StringBuilder();
selection.append(AudioColumns.IS_MUSIC + "=1");
selection.append(" AND " + AudioColumns.TITLE + " != ''");
selection.append(" AND " + AudioColumns.ARTIST_ID + "=" + artistId);
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[] {
/* 0 */
BaseColumns._ID,
/* 1 */