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