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 := \ ...@@ -11,7 +11,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android-common \ android-common \
android-visualizer \ android-visualizer \
eleven_support_v4 \ eleven_support_v4 \
eleven_recyclerview eleven_recyclerview \
guava
LOCAL_PACKAGE_NAME := Eleven LOCAL_PACKAGE_NAME := Eleven
LOCAL_OVERRIDES_PACKAGES := Music LOCAL_OVERRIDES_PACKAGES := Music
......
...@@ -206,6 +206,12 @@ ...@@ -206,6 +206,12 @@
<action android:name="android.media.AUDIO_BECOMING_NOISY" /> <action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter> </intent-filter>
</receiver> </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 --> <!-- Music service -->
<service <service
android:name="com.cyanogenmod.eleven.MusicPlaybackService" android:name="com.cyanogenmod.eleven.MusicPlaybackService"
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<com.viewpagerindicator.TabPageIndicator <com.viewpagerindicator.TabPageIndicator
android:id="@+id/fragment_home_phone_pager_titles" android:id="@+id/fragment_home_phone_pager_titles"
android:background="@color/tpi_background_color" android:background="@color/tpi_background_color"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="@dimen/tpi_height" /> android:layout_height="@dimen/tpi_height" />
<android.support.v4.view.ViewPager <android.support.v4.view.ViewPager
......
...@@ -29,7 +29,7 @@ import android.widget.TextView; ...@@ -29,7 +29,7 @@ import android.widget.TextView;
import com.cyanogenmod.eleven.Config; import com.cyanogenmod.eleven.Config;
import com.cyanogenmod.eleven.R; import com.cyanogenmod.eleven.R;
import com.cyanogenmod.eleven.cache.ImageFetcher; 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.model.Album;
import com.cyanogenmod.eleven.utils.ApolloUtils; import com.cyanogenmod.eleven.utils.ApolloUtils;
import com.cyanogenmod.eleven.utils.NavUtils; import com.cyanogenmod.eleven.utils.NavUtils;
...@@ -130,7 +130,7 @@ implements LoaderCallbacks<List<Album>>, IPopupMenuCallback { ...@@ -130,7 +130,7 @@ implements LoaderCallbacks<List<Album>>, IPopupMenuCallback {
@Override // LoaderCallbacks @Override // LoaderCallbacks
public Loader<List<Album>> onCreateLoader(int id, Bundle args) { 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 @Override // LoaderCallbacks
......
...@@ -17,6 +17,7 @@ package com.cyanogenmod.eleven.adapters; ...@@ -17,6 +17,7 @@ package com.cyanogenmod.eleven.adapters;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
...@@ -25,7 +26,7 @@ import android.widget.TextView; ...@@ -25,7 +26,7 @@ import android.widget.TextView;
import com.cyanogenmod.eleven.Config; import com.cyanogenmod.eleven.Config;
import com.cyanogenmod.eleven.R; import com.cyanogenmod.eleven.R;
import com.cyanogenmod.eleven.cache.ImageFetcher; 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 com.cyanogenmod.eleven.model.Song;
import java.util.List; import java.util.List;
...@@ -45,7 +46,8 @@ public abstract class ArtistDetailSongAdapter extends DetailSongAdapter { ...@@ -45,7 +46,8 @@ public abstract class ArtistDetailSongAdapter extends DetailSongAdapter {
public Loader<List<Song>> onCreateLoader(int id, Bundle args) { public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
onLoading(); onLoading();
setSourceId(args.getLong(Config.ID)); 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) { protected Holder newHolder(View root, ImageFetcher fetcher) {
......
...@@ -205,7 +205,7 @@ public class PlaylistWorkerTask extends BitmapWorkerTask<Void, Void, TransitionD ...@@ -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 // create a new cursor that takes the playlist cursor and the sorted order
sortedCursor = new SortedCursor(playlistCursor, 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 // since this cursor is now wrapped by SortedTracksCursor, remove the reference here
// so we don't accidentally close it in the finally loop // so we don't accidentally close it in the finally loop
......
...@@ -15,16 +15,19 @@ package com.cyanogenmod.eleven.loaders; ...@@ -15,16 +15,19 @@ package com.cyanogenmod.eleven.loaders;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AlbumColumns; import android.provider.MediaStore.Audio.AlbumColumns;
import com.cyanogenmod.eleven.model.Album; 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.sectionadapter.SectionCreator;
import com.cyanogenmod.eleven.utils.Lists; import com.cyanogenmod.eleven.utils.Lists;
import com.cyanogenmod.eleven.utils.MusicUtils;
import com.cyanogenmod.eleven.utils.PreferenceUtils; import com.cyanogenmod.eleven.utils.PreferenceUtils;
import com.cyanogenmod.eleven.utils.SortOrder; import com.cyanogenmod.eleven.utils.SortOrder;
import com.cyanogenmod.eleven.utils.SortUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -48,12 +51,25 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> { ...@@ -48,12 +51,25 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
private Cursor mCursor; private Cursor mCursor;
/** /**
* Constructor of <code>AlbumLoader</code> * Additional selection filter
* */
protected Long mArtistId;
/**
* @param context The {@link Context} to use * @param context The {@link Context} to use
*/ */
public AlbumLoader(final Context context) { 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); super(context);
mArtistId = artistId;
} }
/** /**
...@@ -62,7 +78,7 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> { ...@@ -62,7 +78,7 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
@Override @Override
public List<Album> loadInBackground() { public List<Album> loadInBackground() {
// Create the Cursor // Create the Cursor
mCursor = makeAlbumCursor(getContext()); mCursor = makeAlbumCursor(getContext(), mArtistId);
// Gather the data // Gather the data
if (mCursor != null && mCursor.moveToFirst()) { if (mCursor != null && mCursor.moveToFirst()) {
do { do {
...@@ -89,6 +105,10 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> { ...@@ -89,6 +105,10 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
// Create a new album // Create a new album
final Album album = new Album(id, albumName, artist, songCount, year); final Album album = new Album(id, albumName, artist, songCount, year);
if (mCursor instanceof SortedCursor) {
album.mBucketLabel = (String)((SortedCursor)mCursor).getExtraData();
}
// Add everything up // Add everything up
mAlbumsList.add(album); mAlbumsList.add(album);
} while (mCursor.moveToNext()); } while (mCursor.moveToNext());
...@@ -99,37 +119,40 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> { ...@@ -99,37 +119,40 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
mCursor = null; 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; return mAlbumsList;
} }
/** /**
* Evoke custom sorting routine if the sorting attribute is a String. MediaProvider's sort * For string-based sorts, return the localized store sort parameter, otherwise return null
* can be trusted in other instances * @param sortOrder the song ordering preference selected by the user
* @param sortOrder
* @return
*/ */
private boolean shouldEvokeCustomSortRoutine(String sortOrder) { private static LocalizedStore.SortParameter getSortParameter(String sortOrder) {
return sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z) || if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z) ||
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A) || sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)) {
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST); 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. * Creates the {@link Cursor} used to run the query.
* *
* @param context The {@link Context} to use. * @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. * @return The {@link Cursor} used to run the album query.
*/ */
public static final Cursor makeAlbumCursor(final Context context) { public static final Cursor makeAlbumCursor(final Context context, final Long artistId) {
return context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, // 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[] { new String[] {
/* 0 */ /* 0 */
BaseColumns._ID, BaseColumns._ID,
...@@ -141,6 +164,16 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> { ...@@ -141,6 +164,16 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
AlbumColumns.NUMBER_OF_SONGS, AlbumColumns.NUMBER_OF_SONGS,
/* 4 */ /* 4 */
AlbumColumns.FIRST_YEAR 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; ...@@ -15,16 +15,17 @@ package com.cyanogenmod.eleven.loaders;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore; 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.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.sectionadapter.SectionCreator;
import com.cyanogenmod.eleven.utils.Lists; import com.cyanogenmod.eleven.utils.Lists;
import com.cyanogenmod.eleven.utils.MusicUtils;
import com.cyanogenmod.eleven.utils.PreferenceUtils; import com.cyanogenmod.eleven.utils.PreferenceUtils;
import com.cyanogenmod.eleven.utils.SortOrder; import com.cyanogenmod.eleven.utils.SortOrder;
import com.cyanogenmod.eleven.utils.SortUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -86,6 +87,10 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> { ...@@ -86,6 +87,10 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
// Create a new artist // Create a new artist
final Artist artist = new Artist(id, artistName, songCount, albumCount); final Artist artist = new Artist(id, artistName, songCount, albumCount);
if (mCursor instanceof SortedCursor) {
artist.mBucketLabel = (String)((SortedCursor)mCursor).getExtraData();
}
mArtistsList.add(artist); mArtistsList.add(artist);
} while (mCursor.moveToNext()); } while (mCursor.moveToNext());
} }
...@@ -95,27 +100,21 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> { ...@@ -95,27 +100,21 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
mCursor = null; 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; return mArtistsList;
} }
/** /**
* Evoke custom sorting routine if the sorting attribute is a String. MediaProvider's sort * For string-based sorts, return the localized store sort parameter, otherwise return null
* can be trusted in other instances * @param sortOrder the song ordering preference selected by the user
* @param sortOrder
* @return
*/ */
private boolean shouldEvokeCustomSortRoutine(String sortOrder) { private static LocalizedStore.SortParameter getSortParameter(String sortOrder) {
return sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z) || if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z) ||
sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A); sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A)) {
} return LocalizedStore.SortParameter.Artist;
}
return null;
}
/** /**
* Creates the {@link Cursor} used to run the query. * Creates the {@link Cursor} used to run the query.
* *
...@@ -123,16 +122,29 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> { ...@@ -123,16 +122,29 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
* @return The {@link Cursor} used to run the artist query. * @return The {@link Cursor} used to run the artist query.
*/ */
public static final Cursor makeArtistCursor(final Context context) { 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[] { new String[] {
/* 0 */ /* 0 */
BaseColumns._ID, Artists._ID,
/* 1 */ /* 1 */
ArtistColumns.ARTIST, Artists.ARTIST,
/* 2 */ /* 2 */
ArtistColumns.NUMBER_OF_ALBUMS, Artists.NUMBER_OF_ALBUMS,
/* 3 */ /* 3 */
ArtistColumns.NUMBER_OF_TRACKS Artists.NUMBER_OF_TRACKS
}, null, null, PreferenceUtils.getInstance(context).getArtistSortOrder()); }, 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