From cf4ed6d5c0c405b927c6ad4a7bfb3b42aa795384 Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Sat, 12 Sep 2015 15:14:01 +0200 Subject: [PATCH] Recently played is now history. Songs are always added to the history but their play count will be only increased if they were played at least half. This makes the top tracks much more accurate. --- app/src/main/assets/changelog.html | 7 ++ .../gramophone/helper/StopWatch.java | 82 +++++++++++++++++++ .../TopAndRecentlyPlayedTracksLoader.java | 8 +- ...ayedPlaylist.java => HistoryPlaylist.java} | 10 +-- ...ntlyPlayedStore.java => HistoryStore.java} | 17 ++-- .../provider/SongPlayCountStore.java | 6 +- .../gramophone/service/MusicService.java | 56 ++++++++++--- .../base/AbsMusicServiceActivity.java | 2 + .../PlaylistViewFragment.java | 4 +- app/src/main/res/values/strings.xml | 2 +- 10 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/helper/StopWatch.java rename app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/{RecentlyPlayedPlaylist.java => HistoryPlaylist.java} (64%) rename app/src/main/java/com/kabouzeid/gramophone/provider/{RecentlyPlayedStore.java => HistoryStore.java} (91%) diff --git a/app/src/main/assets/changelog.html b/app/src/main/assets/changelog.html index e5bdcb9b..472a0416 100644 --- a/app/src/main/assets/changelog.html +++ b/app/src/main/assets/changelog.html @@ -29,6 +29,13 @@
  1. NEW: Added a drag view to playlist songs for easier rearrangement.
  2. +
  3. IMPROVEMENT: Replaced recently played with history. Songs are always added to the + history + once they are started but their + play count will be only increased if they were played at least half. This makes the top + tracks playlist much more accurate. +
  4. +
  5. IMPROVEMENT: Synced translations.
  6. FIX: Cab (contextual action bar) content not visible with light primary colors.
diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/StopWatch.java b/app/src/main/java/com/kabouzeid/gramophone/helper/StopWatch.java new file mode 100644 index 00000000..d65f9075 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/StopWatch.java @@ -0,0 +1,82 @@ +package com.kabouzeid.gramophone.helper; + +/** + * Simple thread safe stop watch. + * + * @author Karim Abou Zeid (kabouzeid) + */ +public class StopWatch { + + /** + * The time the stop watch was last started. + */ + private long startTime; + + /** + * The time elapsed before the current {@link #startTime}. + */ + private long previousElapsedTime; + + /** + * Whether the stop watch is currently running or not. + */ + private boolean isRunning; + + /** + * Starts or continues the stop watch. + * + * @see #pause() + * @see #reset() + */ + public void start() { + synchronized (this) { + startTime = System.currentTimeMillis(); + isRunning = true; + } + } + + /** + * Pauses the stop watch. It can be continued later from {@link #start()}. + * + * @see #start() + * @see #reset() + */ + public void pause() { + synchronized (this) { + previousElapsedTime += System.currentTimeMillis() - startTime; + isRunning = false; + } + } + + /** + * Stops and resets the stop watch to zero milliseconds. + * + * @see #start() + * @see #pause() + */ + public void reset() { + synchronized (this) { + startTime = 0; + previousElapsedTime = 0; + isRunning = false; + } + } + + /** + * @return the total elapsed time in milliseconds + */ + public final long getElapsedTime() { + synchronized (this) { + long currentElapsedTime = 0; + if (isRunning) { + currentElapsedTime = System.currentTimeMillis() - startTime; + } + return previousElapsedTime + currentElapsedTime; + } + } + + @Override + public String toString() { + return String.format("%d millis", getElapsedTime()); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java index 258abb34..0bc24f9f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java @@ -23,7 +23,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.kabouzeid.gramophone.model.Song; -import com.kabouzeid.gramophone.provider.RecentlyPlayedStore; +import com.kabouzeid.gramophone.provider.HistoryStore; import com.kabouzeid.gramophone.provider.SongPlayCountStore; import java.util.ArrayList; @@ -50,7 +50,7 @@ public class TopAndRecentlyPlayedTracksLoader { ArrayList missingIds = retCursor.getMissingIds(); if (missingIds != null && missingIds.size() > 0) { for (long id : missingIds) { - RecentlyPlayedStore.getInstance(context).removeSongId(id); + HistoryStore.getInstance(context).removeSongId(id); } } } @@ -76,11 +76,11 @@ public class TopAndRecentlyPlayedTracksLoader { @Nullable private static SortedCursor makeRecentTracksCursorImpl(@NonNull final Context context) { // first get the top results ids from the internal database - Cursor songs = RecentlyPlayedStore.getInstance(context).queryRecentIds(); + Cursor songs = HistoryStore.getInstance(context).queryRecentIds(); try { return makeSortedCursor(context, songs, - songs.getColumnIndex(RecentlyPlayedStore.RecentStoreColumns.ID)); + songs.getColumnIndex(HistoryStore.RecentStoreColumns.ID)); } finally { if (songs != null) { songs.close(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/RecentlyPlayedPlaylist.java b/app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/HistoryPlaylist.java similarity index 64% rename from app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/RecentlyPlayedPlaylist.java rename to app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/HistoryPlaylist.java index 0b3e8781..ad042a7c 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/RecentlyPlayedPlaylist.java +++ b/app/src/main/java/com/kabouzeid/gramophone/model/smartplaylist/HistoryPlaylist.java @@ -6,17 +6,17 @@ import android.support.annotation.NonNull; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.loader.TopAndRecentlyPlayedTracksLoader; import com.kabouzeid.gramophone.model.Song; -import com.kabouzeid.gramophone.provider.RecentlyPlayedStore; +import com.kabouzeid.gramophone.provider.HistoryStore; import java.util.ArrayList; /** * @author Karim Abou Zeid (kabouzeid) */ -public class RecentlyPlayedPlaylist extends AbsSmartPlaylist { +public class HistoryPlaylist extends AbsSmartPlaylist { - public RecentlyPlayedPlaylist(@NonNull Context context) { - super(context.getString(R.string.recently_played), R.drawable.ic_access_time_white_24dp); + public HistoryPlaylist(@NonNull Context context) { + super(context.getString(R.string.history), R.drawable.ic_access_time_white_24dp); } @NonNull @@ -27,6 +27,6 @@ public class RecentlyPlayedPlaylist extends AbsSmartPlaylist { @Override public void clear(@NonNull Context context) { - RecentlyPlayedStore.getInstance(context).clear(); + HistoryStore.getInstance(context).clear(); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/provider/RecentlyPlayedStore.java b/app/src/main/java/com/kabouzeid/gramophone/provider/HistoryStore.java similarity index 91% rename from app/src/main/java/com/kabouzeid/gramophone/provider/RecentlyPlayedStore.java rename to app/src/main/java/com/kabouzeid/gramophone/provider/HistoryStore.java index 204005c8..357be410 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/provider/RecentlyPlayedStore.java +++ b/app/src/main/java/com/kabouzeid/gramophone/provider/HistoryStore.java @@ -24,15 +24,15 @@ import android.database.sqlite.SQLiteOpenHelper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -public class RecentlyPlayedStore extends SQLiteOpenHelper { +public class HistoryStore extends SQLiteOpenHelper { private static final int MAX_ITEMS_IN_DB = 100; - public static final String DATABASE_NAME = "recently_played.db"; + public static final String DATABASE_NAME = "history.db"; private static final int VERSION = 1; @Nullable - private static RecentlyPlayedStore sInstance = null; + private static HistoryStore sInstance = null; - public RecentlyPlayedStore(final Context context) { + public HistoryStore(final Context context) { super(context, DATABASE_NAME, null, VERSION); } @@ -56,18 +56,23 @@ public class RecentlyPlayedStore extends SQLiteOpenHelper { } @NonNull - public static synchronized RecentlyPlayedStore getInstance(@NonNull final Context context) { + public static synchronized HistoryStore getInstance(@NonNull final Context context) { if (sInstance == null) { - sInstance = new RecentlyPlayedStore(context.getApplicationContext()); + sInstance = new HistoryStore(context.getApplicationContext()); } return sInstance; } 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 diff --git a/app/src/main/java/com/kabouzeid/gramophone/provider/SongPlayCountStore.java b/app/src/main/java/com/kabouzeid/gramophone/provider/SongPlayCountStore.java index c11ba37b..203bc02b 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/provider/SongPlayCountStore.java +++ b/app/src/main/java/com/kabouzeid/gramophone/provider/SongPlayCountStore.java @@ -35,7 +35,7 @@ public class SongPlayCountStore extends SQLiteOpenHelper { private static SongPlayCountStore sInstance = null; public static final String DATABASE_NAME = "song_play_count.db"; - private static final int VERSION = 1; + private static final int VERSION = 2; // interpolator curve applied for measuring the curve @NonNull @@ -129,8 +129,8 @@ public class SongPlayCountStore extends SQLiteOpenHelper { * * @param songId The song id to increase the play count */ - public void bumpSongCount(final long songId) { - if (songId < 0) { + public void bumpPlayCount(final long songId) { + if (songId == -1) { return; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index 8df6cd85..30a28d98 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -35,9 +35,10 @@ import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.appwidget.WidgetMedium; import com.kabouzeid.gramophone.helper.PlayingNotificationHelper; import com.kabouzeid.gramophone.helper.ShuffleHelper; +import com.kabouzeid.gramophone.helper.StopWatch; import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.provider.HistoryStore; import com.kabouzeid.gramophone.provider.MusicPlaybackQueueStore; -import com.kabouzeid.gramophone.provider.RecentlyPlayedStore; import com.kabouzeid.gramophone.provider.SongPlayCountStore; import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; @@ -101,8 +102,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private final IBinder musicBind = new MusicBinder(); private MultiPlayer player; - private ArrayList playingQueue; - private ArrayList originalPlayingQueue; + private ArrayList playingQueue = new ArrayList<>(); + private ArrayList originalPlayingQueue = new ArrayList<>(); private int position = -1; private int nextPosition = -1; private int shuffleMode; @@ -124,8 +125,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private QueueSaveHandler queueSaveHandler; private HandlerThread musicPlayerHandlerThread; private HandlerThread queueSaveHandlerThread; - private RecentlyPlayedStore recentlyPlayedStore; + private HistoryStore historyStore; private SongPlayCountStore songPlayCountStore; + private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, @NonNull Intent intent) { @@ -145,12 +147,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onCreate() { super.onCreate(); - playingQueue = new ArrayList<>(); - originalPlayingQueue = new ArrayList<>(); playingNotificationHelper = new PlayingNotificationHelper(this); - recentlyPlayedStore = RecentlyPlayedStore.getInstance(this); + historyStore = HistoryStore.getInstance(this); songPlayCountStore = SongPlayCountStore.getInstance(this); shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); @@ -809,8 +809,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } private void notifyChange(@NonNull final String what) { - sendChangeIntent(what); handleChange(what); + sendChangeIntent(what); } private void sendChangeIntent(@NonNull final String what) { @@ -844,6 +844,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP if (!isPlaying && getSongProgressMillis() > 0) { savePositionInTrack(); } + songPlayCountHelper.notifyPlayStateChanged(isPlaying); break; case META_CHANGED: updateNotification(); @@ -852,8 +853,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP savePosition(); savePositionInTrack(); final Song currentSong = getCurrentSong(); - recentlyPlayedStore.addSongId(currentSong.id); - songPlayCountStore.bumpSongCount(currentSong.id); + historyStore.addSongId(currentSong.id); + if (songPlayCountHelper.shouldBumpPlayCount()) { + songPlayCountStore.bumpPlayCount(songPlayCountHelper.getSong().id); + } + songPlayCountHelper.notifySongChanged(currentSong); break; case QUEUE_CHANGED: saveState(); @@ -1057,4 +1061,36 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP sendBroadcast(new Intent(MEDIA_STORE_CHANGED)); } } + + private static class SongPlayCountHelper { + public static final String TAG = SongPlayCountHelper.class.getSimpleName(); + + private StopWatch stopWatch = new StopWatch(); + private Song song = new Song(); + + public Song getSong() { + return song; + } + + boolean shouldBumpPlayCount() { + return song.duration * 0.5d < stopWatch.getElapsedTime(); + } + + void notifySongChanged(Song song) { + synchronized (this) { + stopWatch.reset(); + this.song = song; + } + } + + void notifyPlayStateChanged(boolean isPlaying) { + synchronized (this) { + if (isPlaying) { + stopWatch.start(); + } else { + stopWatch.pause(); + } + } + } + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java index fd7a04ba..b7dfb36e 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java @@ -1,6 +1,7 @@ package com.kabouzeid.gramophone.ui.activities.base; import android.Manifest; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -204,6 +205,7 @@ public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements } } + @SuppressLint("NewApi") private boolean hasExternalStoragePermission() { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java index 5a597bc0..014c1600 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java @@ -7,9 +7,9 @@ import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.PlaylistAdapter; import com.kabouzeid.gramophone.loader.PlaylistLoader; import com.kabouzeid.gramophone.model.Playlist; +import com.kabouzeid.gramophone.model.smartplaylist.HistoryPlaylist; import com.kabouzeid.gramophone.model.smartplaylist.LastAddedPlaylist; import com.kabouzeid.gramophone.model.smartplaylist.MyTopTracksPlaylist; -import com.kabouzeid.gramophone.model.smartplaylist.RecentlyPlayedPlaylist; import java.util.ArrayList; @@ -46,7 +46,7 @@ public class PlaylistViewFragment extends AbsMainActivityRecyclerViewFragment playlists = new ArrayList<>(); playlists.add(new LastAddedPlaylist(getActivity())); - playlists.add(new RecentlyPlayedPlaylist(getActivity())); + playlists.add(new HistoryPlaylist(getActivity())); playlists.add(new MyTopTracksPlaylist(getActivity())); playlists.addAll(PlaylistLoader.getAllPlaylists(getActivity())); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5a646fc3..ecd395ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -153,7 +153,7 @@ Rescanning media… Favorites Last added - Recently played + History My top tracks Remove cover Download from Last.fm