Initial commit retro music app

This commit is contained in:
h4h13 2018-07-27 18:37:33 +05:30
parent ab332473bc
commit fe890632fd
932 changed files with 83126 additions and 0 deletions

View file

@ -0,0 +1,155 @@
package code.name.monkey.retromusic.providers;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import android.support.annotation.NonNull;
import code.name.monkey.retromusic.Constants;
import code.name.monkey.retromusic.util.FileUtil;
import code.name.monkey.retromusic.util.PreferenceUtil;
import java.io.File;
import java.util.ArrayList;
public class BlacklistStore extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "blacklist.db";
private static final int VERSION = 1;
private static BlacklistStore sInstance = null;
private Context context;
public BlacklistStore(final Context context) {
super(context, DATABASE_NAME, null, VERSION);
this.context = context;
}
@NonNull
public static synchronized BlacklistStore getInstance(@NonNull final Context context) {
if (sInstance == null) {
sInstance = new BlacklistStore(context.getApplicationContext());
if (!PreferenceUtil.getInstance(context).initializedBlacklist()) {
// blacklisted by default
sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS));
sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS));
sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES));
PreferenceUtil.getInstance(context).setInitializedBlacklist();
}
}
return sInstance;
}
@Override
public void onCreate(@NonNull final SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + BlacklistStoreColumns.NAME + " ("
+ BlacklistStoreColumns.PATH + " STRING NOT NULL);");
}
@Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME);
onCreate(db);
}
@Override
public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME);
onCreate(db);
}
public void addPath(File file) {
addPathImpl(file);
notifyMediaStoreChanged();
}
private void addPathImpl(File file) {
if (file == null || contains(file)) {
return;
}
String path = FileUtil.safeGetCanonicalPath(file);
final SQLiteDatabase database = getWritableDatabase();
database.beginTransaction();
try {
// add the entry
final ContentValues values = new ContentValues(1);
values.put(BlacklistStoreColumns.PATH, path);
database.insert(BlacklistStoreColumns.NAME, null, values);
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}
public boolean contains(File file) {
if (file == null) {
return false;
}
String path = FileUtil.safeGetCanonicalPath(file);
final SQLiteDatabase database = getReadableDatabase();
Cursor cursor = database.query(BlacklistStoreColumns.NAME,
new String[]{BlacklistStoreColumns.PATH},
BlacklistStoreColumns.PATH + "=?",
new String[]{path},
null, null, null, null);
boolean containsPath = cursor != null && cursor.moveToFirst();
if (cursor != null) {
cursor.close();
}
return containsPath;
}
public void removePath(File file) {
final SQLiteDatabase database = getWritableDatabase();
String path = FileUtil.safeGetCanonicalPath(file);
database.delete(BlacklistStoreColumns.NAME,
BlacklistStoreColumns.PATH + "=?",
new String[]{path});
notifyMediaStoreChanged();
}
public void clear() {
final SQLiteDatabase database = getWritableDatabase();
database.delete(BlacklistStoreColumns.NAME, null, null);
notifyMediaStoreChanged();
}
private void notifyMediaStoreChanged() {
context.sendBroadcast(new Intent(Constants.MEDIA_STORE_CHANGED));
}
@NonNull
public ArrayList<String> getPaths() {
Cursor cursor = getReadableDatabase().query(BlacklistStoreColumns.NAME,
new String[]{BlacklistStoreColumns.PATH},
null, null, null, null, null);
ArrayList<String> paths = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
paths.add(cursor.getString(0));
} while (cursor.moveToNext());
}
if (cursor != null)
cursor.close();
return paths;
}
public interface BlacklistStoreColumns {
String NAME = "blacklist";
String PATH = "path";
}
}

View file

@ -0,0 +1,152 @@
/*
* 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 code.name.monkey.retromusic.providers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class HistoryStore extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "history.db";
private static final int MAX_ITEMS_IN_DB = 100;
private static final int VERSION = 1;
@Nullable
private static HistoryStore sInstance = null;
public HistoryStore(final Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
@NonNull
public static synchronized HistoryStore getInstance(@NonNull final Context context) {
if (sInstance == null) {
sInstance = new HistoryStore(context.getApplicationContext());
}
return sInstance;
}
@Override
public void onCreate(@NonNull final SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + RecentStoreColumns.NAME + " ("
+ RecentStoreColumns.ID + " LONG NOT NULL," + RecentStoreColumns.TIME_PLAYED
+ " LONG NOT NULL);");
}
@Override
public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME);
onCreate(db);
}
@Override
public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME);
onCreate(db);
}
public void addSongId(final long songId) {
if (songId == -1) {
return;
}
final SQLiteDatabase database = getWritableDatabase();
database.beginTransaction();
try {
// remove previous entries
removeSongId(songId);
// add the entry
final ContentValues values = new ContentValues(2);
values.put(RecentStoreColumns.ID, songId);
values.put(RecentStoreColumns.TIME_PLAYED, System.currentTimeMillis());
database.insert(RecentStoreColumns.NAME, null, values);
// if our db is too large, delete the extra items
Cursor oldest = null;
try {
oldest = database.query(RecentStoreColumns.NAME,
new String[]{RecentStoreColumns.TIME_PLAYED}, null, null, null, null,
RecentStoreColumns.TIME_PLAYED + " ASC");
if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) {
oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB);
long timeOfRecordToKeep = oldest.getLong(0);
database.delete(RecentStoreColumns.NAME,
RecentStoreColumns.TIME_PLAYED + " < ?",
new String[]{String.valueOf(timeOfRecordToKeep)});
}
} finally {
if (oldest != null) {
oldest.close();
}
}
} finally {
database.setTransactionSuccessful();
database.endTransaction();
}
}
public void removeSongId(final long songId) {
final SQLiteDatabase database = getWritableDatabase();
database.delete(RecentStoreColumns.NAME, RecentStoreColumns.ID + " = ?", new String[]{
String.valueOf(songId)
});
}
public void clear() {
final SQLiteDatabase database = getWritableDatabase();
database.delete(RecentStoreColumns.NAME, null, null);
}
public boolean contains(long id) {
final SQLiteDatabase database = getReadableDatabase();
Cursor cursor = database.query(RecentStoreColumns.NAME,
new String[]{RecentStoreColumns.ID},
RecentStoreColumns.ID + "=?",
new String[]{String.valueOf(id)},
null, null, null, null);
boolean containsId = cursor != null && cursor.moveToFirst();
if (cursor != null) {
cursor.close();
}
return containsId;
}
public Cursor queryRecentIds() {
final SQLiteDatabase database = getReadableDatabase();
return database.query(RecentStoreColumns.NAME,
new String[]{RecentStoreColumns.ID}, null, null, null, null,
RecentStoreColumns.TIME_PLAYED + " DESC");
}
public interface RecentStoreColumns {
String NAME = "recent_history";
String ID = "song_id";
String TIME_PLAYED = "time_played";
}
}

View file

@ -0,0 +1,203 @@
/*
* 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 code.name.monkey.retromusic.providers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.provider.MediaStore.Audio.AudioColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.model.Song;
import java.util.ArrayList;
import io.reactivex.Observable;
/**
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
* <p/>
* This keeps track of the music playback and history state of the playback service
*/
public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "music_playback_state.db";
public static final String PLAYING_QUEUE_TABLE_NAME = "playing_queue";
public static final String ORIGINAL_PLAYING_QUEUE_TABLE_NAME = "original_playing_queue";
private static final int VERSION = 3;
@Nullable
private static MusicPlaybackQueueStore sInstance = null;
/**
* Constructor of <code>MusicPlaybackState</code>
*
* @param context The {@link Context} to use
*/
public MusicPlaybackQueueStore(final Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
/**
* @param context The {@link Context} to use
* @return A new instance of this class.
*/
@NonNull
public static synchronized MusicPlaybackQueueStore getInstance(@NonNull final Context context) {
if (sInstance == null) {
sInstance = new MusicPlaybackQueueStore(context.getApplicationContext());
}
return sInstance;
}
@Override
public void onCreate(@NonNull final SQLiteDatabase db) {
createTable(db, PLAYING_QUEUE_TABLE_NAME);
createTable(db, ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
}
private void createTable(@NonNull final SQLiteDatabase db, final String tableName) {
//noinspection StringBufferReplaceableByString
StringBuilder builder = new StringBuilder();
builder.append("CREATE TABLE IF NOT EXISTS ");
builder.append(tableName);
builder.append("(");
builder.append(BaseColumns._ID);
builder.append(" INT NOT NULL,");
builder.append(AudioColumns.TITLE);
builder.append(" STRING NOT NULL,");
builder.append(AudioColumns.TRACK);
builder.append(" INT NOT NULL,");
builder.append(AudioColumns.YEAR);
builder.append(" INT NOT NULL,");
builder.append(AudioColumns.DURATION);
builder.append(" LONG NOT NULL,");
builder.append(AudioColumns.DATA);
builder.append(" STRING NOT NULL,");
builder.append(AudioColumns.DATE_MODIFIED);
builder.append(" LONG NOT NULL,");
builder.append(AudioColumns.ALBUM_ID);
builder.append(" INT NOT NULL,");
builder.append(AudioColumns.ALBUM);
builder.append(" STRING NOT NULL,");
builder.append(AudioColumns.ARTIST_ID);
builder.append(" INT NOT NULL,");
builder.append(AudioColumns.ARTIST);
builder.append(" STRING NOT NULL);");
db.execSQL(builder.toString());
}
@Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) {
// not necessary yet
db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
onCreate(db);
}
@Override
public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
// If we ever have downgrade, drop the table to be safe
db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
onCreate(db);
}
public synchronized void saveQueues(@NonNull final ArrayList<Song> playingQueue, @NonNull final ArrayList<Song> originalPlayingQueue) {
saveQueue(PLAYING_QUEUE_TABLE_NAME, playingQueue);
saveQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME, originalPlayingQueue);
}
/**
* Clears the existing database and saves the queue into the db so that when the
* app is restarted, the tracks you were listening to is restored
*
* @param queue the queue to save
*/
private synchronized void saveQueue(final String tableName, @NonNull final ArrayList<Song> queue) {
final SQLiteDatabase database = getWritableDatabase();
database.beginTransaction();
try {
database.delete(tableName, null, null);
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
final int NUM_PROCESS = 20;
int position = 0;
while (position < queue.size()) {
database.beginTransaction();
try {
for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) {
Song song = queue.get(i);
ContentValues values = new ContentValues(4);
values.put(BaseColumns._ID, song.id);
values.put(AudioColumns.TITLE, song.title);
values.put(AudioColumns.TRACK, song.trackNumber);
values.put(AudioColumns.YEAR, song.year);
values.put(AudioColumns.DURATION, song.duration);
values.put(AudioColumns.DATA, song.data);
values.put(AudioColumns.DATE_MODIFIED, song.dateModified);
values.put(AudioColumns.ALBUM_ID, song.albumId);
values.put(AudioColumns.ALBUM, song.albumName);
values.put(AudioColumns.ARTIST_ID, song.artistId);
values.put(AudioColumns.ARTIST, song.artistName);
database.insert(tableName, null, values);
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
position += NUM_PROCESS;
}
}
}
@NonNull
public Observable<ArrayList<Song>> getSavedPlayingQueue() {
return getQueue(PLAYING_QUEUE_TABLE_NAME);
}
@NonNull
public Observable<ArrayList<Song>> getSavedOriginalPlayingQueue() {
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
}
@NonNull
private Observable<ArrayList<Song>> getQueue(@NonNull final String tableName) {
Cursor cursor = getReadableDatabase().query(tableName, null,
null, null, null, null, null);
return SongLoader.getSongs(cursor);
}
}

View file

@ -0,0 +1,169 @@
package code.name.monkey.retromusic.providers;
import android.content.Context;
import code.name.monkey.retromusic.RetroApplication;
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist;
import java.io.File;
import java.util.ArrayList;
import code.name.monkey.retromusic.Injection;
import code.name.monkey.retromusic.loaders.AlbumLoader;
import code.name.monkey.retromusic.loaders.ArtistLoader;
import code.name.monkey.retromusic.loaders.GenreLoader;
import code.name.monkey.retromusic.loaders.HomeLoader;
import code.name.monkey.retromusic.loaders.LastAddedSongsLoader;
import code.name.monkey.retromusic.loaders.PlaylistLoader;
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
import code.name.monkey.retromusic.loaders.SearchLoader;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
import code.name.monkey.retromusic.model.Album;
import code.name.monkey.retromusic.model.Artist;
import code.name.monkey.retromusic.model.Genre;
import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.providers.interfaces.Repository;
import code.name.monkey.retromusic.rest.model.KuGouRawLyric;
import code.name.monkey.retromusic.rest.model.KuGouSearchLyricResult;
import code.name.monkey.retromusic.rest.service.KuGouApiService;
import code.name.monkey.retromusic.util.LyricUtil;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
public class RepositoryImpl implements Repository {
private static RepositoryImpl INSTANCE;
private Context context;
public RepositoryImpl(Context context) {
this.context = context;
}
public static synchronized RepositoryImpl getInstance( ) {
if (INSTANCE == null) {
INSTANCE = new RepositoryImpl(RetroApplication.getInstance());
}
return INSTANCE;
}
@Override
public Observable<ArrayList<Song>> getAllSongs() {
return SongLoader.getAllSongs(context);
}
@Override
public Observable<ArrayList<AbsSmartPlaylist>> getSuggestionSongs() {
return HomeLoader.getRecentAndTopThings(context);
}
@Override
public Observable<Song> getSong(int id) {
return SongLoader.getSong(context, id);
}
@Override
public Observable<ArrayList<Album>> getAllAlbums() {
return AlbumLoader.getAllAlbums(context);
}
@Override
public Observable<ArrayList<Album>> getRecentAlbums() {
return LastAddedSongsLoader.getLastAddedAlbums(context);
}
@Override
public Observable<ArrayList<Album>> getTopAlbums() {
return TopAndRecentlyPlayedTracksLoader.getTopAlbums(context);
}
@Override
public Observable<Album> getAlbum(int albumId) {
return AlbumLoader.getAlbum(context, albumId);
}
@Override
public Observable<ArrayList<Artist>> getAllArtists() {
return ArtistLoader.getAllArtists(context);
}
@Override
public Observable<ArrayList<Artist>> getRecentArtists() {
return LastAddedSongsLoader.getLastAddedArtists(context);
}
@Override
public Observable<ArrayList<Artist>> getTopArtists() {
return TopAndRecentlyPlayedTracksLoader.getTopArtists(context);
}
@Override
public Observable<Artist> getArtistById(long artistId) {
return ArtistLoader.getArtist(context, (int) artistId);
}
@Override
public Observable<ArrayList<Playlist>> getAllPlaylists() {
return PlaylistLoader.getAllPlaylists(context);
}
@Override
public Observable<ArrayList<Song>> getFavoriteSongs() {
return null;
}
@Override
public Observable<ArrayList<Object>> search(String query) {
return SearchLoader.searchAll(context, query);
}
@Override
public Observable<ArrayList<Song>> getPlaylistSongs(Playlist playlist) {
return PlaylistSongsLoader.getPlaylistSongList(context, playlist);
}
@Override
public Observable<ArrayList<Playlist>> getHomeList() {
return HomeLoader.getHomeLoader(context);
}
@Override
public Observable<ArrayList<AbsSmartPlaylist>> getAllThings() {
return HomeLoader.getRecentAndTopThings(context);
}
@Override
public Observable<ArrayList<Genre>> getAllGenres() {
return GenreLoader.getAllGenres(context);
}
@Override
public Observable<ArrayList<Song>> getGenre(int genreId) {
return GenreLoader.getSongs(context, genreId);
}
@Override
public Observable<File> downloadLrcFile(String title, String artist, long duration) {
KuGouApiService service = Injection.provideKuGouApiService();
return service.searchLyric(title, String.valueOf(duration))
.subscribeOn(Schedulers.io())
.flatMap(kuGouSearchLyricResult -> {
if (kuGouSearchLyricResult.status == 200
&& kuGouSearchLyricResult.candidates != null
&& kuGouSearchLyricResult.candidates.size() != 0) {
KuGouSearchLyricResult.Candidates candidates = kuGouSearchLyricResult.candidates.get(0);
return service.getRawLyric(candidates.id, candidates.accesskey);
} else {
return Observable.just(new KuGouRawLyric());
}
}).map(kuGouRawLyric -> {
if (kuGouRawLyric == null) {
return null;
}
String rawLyric = LyricUtil.decryptBASE64(kuGouRawLyric.content);
if (rawLyric != null && rawLyric.isEmpty()) {
return null;
}
return LyricUtil.writeLrcToLoc(title, artist, rawLyric);
});
}
}

View file

@ -0,0 +1,404 @@
/*
* 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 code.name.monkey.retromusic.providers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
/**
* This database tracks the number of play counts for an individual song. This is used to drive
* the top played tracks as well as the playlist images
*/
public class SongPlayCountStore extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "song_play_count.db";
private static final int VERSION = 2;
// how many weeks worth of playback to track
private static final int NUM_WEEKS = 52;
@Nullable
private static SongPlayCountStore sInstance = null;
// interpolator curve applied for measuring the curve
@NonNull
private static Interpolator sInterpolator = new AccelerateInterpolator(1.5f);
// how high to multiply the interpolation curve
@SuppressWarnings("FieldCanBeLocal")
private static int INTERPOLATOR_HEIGHT = 50;
// how high the base value is. The ratio of the Height to Base is what really matters
@SuppressWarnings("FieldCanBeLocal")
private static int INTERPOLATOR_BASE = 25;
@SuppressWarnings("FieldCanBeLocal")
private static int ONE_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7;
@NonNull
private static String WHERE_ID_EQUALS = SongPlayCountColumns.ID + "=?";
// number of weeks since epoch time
private int mNumberOfWeeksSinceEpoch;
// used to track if we've walked through the db and updated all the rows
private boolean mDatabaseUpdated;
public SongPlayCountStore(final Context context) {
super(context, DATABASE_NAME, null, VERSION);
long msSinceEpoch = System.currentTimeMillis();
mNumberOfWeeksSinceEpoch = (int) (msSinceEpoch / ONE_WEEK_IN_MS);
mDatabaseUpdated = false;
}
/**
* @param context The {@link Context} to use
* @return A new instance of this class.
*/
@NonNull
public static synchronized SongPlayCountStore getInstance(@NonNull final Context context) {
if (sInstance == null) {
sInstance = new SongPlayCountStore(context.getApplicationContext());
}
return sInstance;
}
/**
* Calculates the score of the song given the play counts
*
* @param playCounts an array of the # of times a song has been played for each week
* where playCounts[N] is the # of times it was played N weeks ago
* @return the score
*/
private static float calculateScore(@Nullable final int[] playCounts) {
if (playCounts == null) {
return 0;
}
float score = 0;
for (int i = 0; i < Math.min(playCounts.length, NUM_WEEKS); i++) {
score += playCounts[i] * getScoreMultiplierForWeek(i);
}
return score;
}
/**
* Gets the column name for each week #
*
* @param week number
* @return the column name
*/
@NonNull
private static String getColumnNameForWeek(final int week) {
return SongPlayCountColumns.WEEK_PLAY_COUNT + String.valueOf(week);
}
/**
* Gets the score multiplier for each week
*
* @param week number
* @return the multiplier to apply
*/
private static float getScoreMultiplierForWeek(final int week) {
return sInterpolator.getInterpolation(1 - (week / (float) NUM_WEEKS)) * INTERPOLATOR_HEIGHT
+ INTERPOLATOR_BASE;
}
/**
* For some performance gain, return a static value for the column index for a week
* WARNING: This function assumes you have selected all columns for it to work
*
* @param week number
* @return column index of that week
*/
private static int getColumnIndexForWeek(final int week) {
// ID, followed by the weeks columns
return 1 + week;
}
@Override
public void onCreate(@NonNull final SQLiteDatabase db) {
// create the play count table
// WARNING: If you change the order of these columns
// please update getColumnIndexForWeek
StringBuilder builder = new StringBuilder();
builder.append("CREATE TABLE IF NOT EXISTS ");
builder.append(SongPlayCountColumns.NAME);
builder.append("(");
builder.append(SongPlayCountColumns.ID);
builder.append(" INT UNIQUE,");
for (int i = 0; i < NUM_WEEKS; i++) {
builder.append(getColumnNameForWeek(i));
builder.append(" INT DEFAULT 0,");
}
builder.append(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX);
builder.append(" INT NOT NULL,");
builder.append(SongPlayCountColumns.PLAY_COUNT_SCORE);
builder.append(" REAL DEFAULT 0);");
db.execSQL(builder.toString());
}
@Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME);
onCreate(db);
}
@Override
public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
// If we ever have downgrade, drop the table to be safe
db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME);
onCreate(db);
}
/**
* Increases the play count of a song by 1
*
* @param songId The song id to increase the play count
*/
public void bumpPlayCount(final long songId) {
if (songId == -1) {
return;
}
final SQLiteDatabase database = getWritableDatabase();
updateExistingRow(database, songId, true);
}
/**
* This creates a new entry that indicates a song has been played once as well as its score
*
* @param database a write able database
* @param songId the id of the track
*/
private void createNewPlayedEntry(@NonNull final SQLiteDatabase database, final long songId) {
// no row exists, create a new one
float newScore = getScoreMultiplierForWeek(0);
int newPlayCount = 1;
final ContentValues values = new ContentValues(3);
values.put(SongPlayCountColumns.ID, songId);
values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, newScore);
values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch);
values.put(getColumnNameForWeek(0), newPlayCount);
database.insert(SongPlayCountColumns.NAME, null, values);
}
/**
* This function will take a song entry and update it to the latest week and increase the count
* for the current week by 1 if necessary
*
* @param database a writeable database
* @param id the id of the track to bump
* @param bumpCount whether to bump the current's week play count by 1 and adjust the score
*/
private void updateExistingRow(@NonNull final SQLiteDatabase database, final long id, boolean bumpCount) {
String stringId = String.valueOf(id);
// begin the transaction
database.beginTransaction();
// get the cursor of this content inside the transaction
final Cursor cursor = database.query(SongPlayCountColumns.NAME, null, WHERE_ID_EQUALS,
new String[]{stringId}, null, null, null);
// if we have a result
if (cursor != null && cursor.moveToFirst()) {
// figure how many weeks since we last updated
int lastUpdatedIndex = cursor.getColumnIndex(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX);
int lastUpdatedWeek = cursor.getInt(lastUpdatedIndex);
int weekDiff = mNumberOfWeeksSinceEpoch - lastUpdatedWeek;
// if it's more than the number of weeks we track, delete it and create a new entry
if (Math.abs(weekDiff) >= NUM_WEEKS) {
// this entry needs to be dropped since it is too outdated
deleteEntry(database, stringId);
if (bumpCount) {
createNewPlayedEntry(database, id);
}
} else if (weekDiff != 0) {
// else, shift the weeks
int[] playCounts = new int[NUM_WEEKS];
if (weekDiff > 0) {
// time is shifted forwards
for (int i = 0; i < NUM_WEEKS - weekDiff; i++) {
playCounts[i + weekDiff] = cursor.getInt(getColumnIndexForWeek(i));
}
} else if (weekDiff < 0) {
// time is shifted backwards (by user) - nor typical behavior but we
// will still handle it
// since weekDiff is -ve, NUM_WEEKS + weekDiff is the real # of weeks we have to
// transfer. Then we transfer the old week i - weekDiff to week i
// for example if the user shifted back 2 weeks, ie -2, then for 0 to
// NUM_WEEKS + (-2) we set the new week i = old week i - (-2) or i+2
for (int i = 0; i < NUM_WEEKS + weekDiff; i++) {
playCounts[i] = cursor.getInt(getColumnIndexForWeek(i - weekDiff));
}
}
// bump the count
if (bumpCount) {
playCounts[0]++;
}
float score = calculateScore(playCounts);
// if the score is non-existant, then delete it
if (score < .01f) {
deleteEntry(database, stringId);
} else {
// create the content values
ContentValues values = new ContentValues(NUM_WEEKS + 2);
values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch);
values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score);
for (int i = 0; i < NUM_WEEKS; i++) {
values.put(getColumnNameForWeek(i), playCounts[i]);
}
// update the entry
database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS,
new String[]{stringId});
}
} else if (bumpCount) {
// else no shifting, just update the scores
ContentValues values = new ContentValues(2);
// increase the score by a single score amount
int scoreIndex = cursor.getColumnIndex(SongPlayCountColumns.PLAY_COUNT_SCORE);
float score = cursor.getFloat(scoreIndex) + getScoreMultiplierForWeek(0);
values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score);
// increase the play count by 1
values.put(getColumnNameForWeek(0), cursor.getInt(getColumnIndexForWeek(0)) + 1);
// update the entry
database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS,
new String[]{stringId});
}
cursor.close();
} else if (bumpCount) {
// if we have no existing results, create a new one
createNewPlayedEntry(database, id);
}
database.setTransactionSuccessful();
database.endTransaction();
}
public void clear() {
final SQLiteDatabase database = getWritableDatabase();
database.delete(SongPlayCountColumns.NAME, null, null);
}
/**
* Gets a cursor containing the top songs played. Note this only returns songs that have been
* played at least once in the past NUM_WEEKS
*
* @param numResults number of results to limit by. If <= 0 it returns all results
* @return the top tracks
*/
public Cursor getTopPlayedResults(int numResults) {
updateResults();
final SQLiteDatabase database = getReadableDatabase();
return database.query(SongPlayCountColumns.NAME, new String[]{SongPlayCountColumns.ID},
null, null, null, null, SongPlayCountColumns.PLAY_COUNT_SCORE + " DESC",
(numResults <= 0 ? null : String.valueOf(numResults)));
}
/**
* This updates all the results for the getTopPlayedResults so that we can get an
* accurate list of the top played results
*/
private synchronized void updateResults() {
if (mDatabaseUpdated) {
return;
}
final SQLiteDatabase database = getWritableDatabase();
database.beginTransaction();
int oldestWeekWeCareAbout = mNumberOfWeeksSinceEpoch - NUM_WEEKS + 1;
// delete rows we don't care about anymore
database.delete(SongPlayCountColumns.NAME, SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX
+ " < " + oldestWeekWeCareAbout, null);
// get the remaining rows
Cursor cursor = database.query(SongPlayCountColumns.NAME,
new String[]{SongPlayCountColumns.ID},
null, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
// for each row, update it
do {
updateExistingRow(database, cursor.getLong(0), false);
} while (cursor.moveToNext());
cursor.close();
}
mDatabaseUpdated = true;
database.setTransactionSuccessful();
database.endTransaction();
}
/**
* @param songId The song Id to remove.
*/
public void removeItem(final long songId) {
final SQLiteDatabase database = getWritableDatabase();
deleteEntry(database, String.valueOf(songId));
}
/**
* Deletes the entry
*
* @param database database to use
* @param stringId id to delete
*/
private void deleteEntry(@NonNull final SQLiteDatabase database, final String stringId) {
database.delete(SongPlayCountColumns.NAME, WHERE_ID_EQUALS, new String[]{stringId});
}
public interface SongPlayCountColumns {
String NAME = "song_play_count";
String ID = "song_id";
String WEEK_PLAY_COUNT = "week";
String LAST_UPDATED_WEEK_INDEX = "week_index";
String PLAY_COUNT_SCORE = "play_count_score";
}
}

View file

@ -0,0 +1,60 @@
package code.name.monkey.retromusic.providers.interfaces;
import code.name.monkey.retromusic.model.Album;
import code.name.monkey.retromusic.model.Artist;
import code.name.monkey.retromusic.model.Genre;
import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist;
import io.reactivex.Observable;
import java.io.File;
import java.util.ArrayList;
/**
* Created by hemanths on 11/08/17.
*/
public interface Repository {
Observable<ArrayList<Song>> getAllSongs();
Observable<ArrayList<AbsSmartPlaylist>> getSuggestionSongs();
Observable<Song> getSong(int id);
Observable<ArrayList<Album>> getAllAlbums();
Observable<ArrayList<Album>> getRecentAlbums();
Observable<ArrayList<Album>> getTopAlbums();
Observable<Album> getAlbum(int albumId);
Observable<ArrayList<Artist>> getAllArtists();
Observable<ArrayList<Artist>> getRecentArtists();
Observable<ArrayList<Artist>> getTopArtists();
Observable<Artist> getArtistById(long artistId);
Observable<ArrayList<Playlist>> getAllPlaylists();
Observable<ArrayList<Song>> getFavoriteSongs();
Observable<ArrayList<Object>> search(String query);
Observable<ArrayList<Song>> getPlaylistSongs(Playlist playlist);
Observable<ArrayList<Playlist>> getHomeList();
Observable<ArrayList<AbsSmartPlaylist>> getAllThings();
Observable<ArrayList<Genre>> getAllGenres();
Observable<ArrayList<Song>> getGenre(int genreId);
Observable<File> downloadLrcFile(final String title, final String artist, final long duration);
}