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 @@
- NEW: Added a drag view to playlist songs for easier rearrangement.
+ - 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.
+
+ - IMPROVEMENT: Synced translations.
- 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