From 4078b3475e5140e6f5f9ef924bc8ff8d9e345113 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Sun, 24 Mar 2024 21:08:06 +0100 Subject: Simplify playback preferences and move to :storage:preferences (#7024) --- .../storage/preferences/PlaybackPreferences.java | 170 +++++++++++++++++++++ .../storage/preferences/SleepTimerPreferences.java | 106 +++++++++++++ .../storage/preferences/UsageStatistics.java | 70 +++++++++ 3 files changed, 346 insertions(+) create mode 100644 storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/PlaybackPreferences.java create mode 100644 storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/SleepTimerPreferences.java create mode 100644 storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UsageStatistics.java (limited to 'storage/preferences') diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/PlaybackPreferences.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/PlaybackPreferences.java new file mode 100644 index 000000000..d6d260446 --- /dev/null +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/PlaybackPreferences.java @@ -0,0 +1,170 @@ +package de.danoeh.antennapod.storage.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.preference.PreferenceManager; + +import android.util.Log; +import de.danoeh.antennapod.model.feed.FeedMedia; +import de.danoeh.antennapod.model.feed.FeedPreferences; +import de.danoeh.antennapod.model.playback.MediaType; +import de.danoeh.antennapod.model.playback.Playable; + +/** + * Provides access to preferences set by the playback service. A private + * instance of this class must first be instantiated via createInstance() or + * otherwise every public method will throw an Exception when called. + */ +public abstract class PlaybackPreferences { + + private static final String TAG = "PlaybackPreferences"; + + /** + * Contains the feed id of the currently playing item if it is a FeedMedia + * object. + */ + private static final String PREF_CURRENTLY_PLAYING_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; + + /** + * Contains the id of the currently playing FeedMedia object or + * NO_MEDIA_PLAYING if the currently playing media is no FeedMedia object. + */ + private static final String PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID + = "de.danoeh.antennapod.preferences.lastPlayedFeedMediaId"; + + /** + * Type of the media object that is currently being played. This preference + * is set to NO_MEDIA_PLAYING after playback has been completed and is set + * as soon as the 'play' button is pressed. + */ + private static final String PREF_CURRENTLY_PLAYING_MEDIA_TYPE + = "de.danoeh.antennapod.preferences.currentlyPlayingMedia"; + + /** + * True if last played media was a video. + */ + private static final String PREF_CURRENT_EPISODE_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo"; + + /** + * The current player status as int. + */ + private static final String PREF_CURRENT_PLAYER_STATUS = "de.danoeh.antennapod.preferences.currentPlayerStatus"; + + /** + * A temporary playback speed which overrides the per-feed playback speed for the currently playing + * media. Considered unset if set to SPEED_USE_GLOBAL; + */ + private static final String PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED + = "de.danoeh.antennapod.preferences.temporaryPlaybackSpeed"; + + /** + * A temporary skip silence preference which overrides the per-feed skip silence for the currently playing + * media. Considered unset if set to null; + */ + private static final String PREF_CURRENTLY_PLAYING_TEMPORARY_SKIP_SILENCE + = "de.danoeh.antennapod.preferences.temporarySkipSilence"; + + /** + * Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. + */ + public static final long NO_MEDIA_PLAYING = -1; + + /** + * Value of PREF_CURRENT_PLAYER_STATUS if media player status is playing. + */ + public static final int PLAYER_STATUS_PLAYING = 1; + + /** + * Value of PREF_CURRENT_PLAYER_STATUS if media player status is paused. + */ + public static final int PLAYER_STATUS_PAUSED = 2; + + /** + * Value of PREF_CURRENT_PLAYER_STATUS if media player status is neither playing nor paused. + */ + public static final int PLAYER_STATUS_OTHER = 3; + + private static SharedPreferences prefs; + + public static void init(Context context) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public static long getCurrentlyPlayingMediaType() { + return prefs.getLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING); + } + + public static long getCurrentlyPlayingFeedMediaId() { + if (PlaybackPreferences.getCurrentlyPlayingMediaType() == NO_MEDIA_PLAYING) { + return NO_MEDIA_PLAYING; + } + return prefs.getLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); + } + + public static boolean getCurrentEpisodeIsVideo() { + return prefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false); + } + + public static int getCurrentPlayerStatus() { + return prefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER); + } + + public static float getCurrentlyPlayingTemporaryPlaybackSpeed() { + return prefs.getFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, FeedPreferences.SPEED_USE_GLOBAL); + } + + public static FeedPreferences.SkipSilence getCurrentlyPlayingTemporarySkipSilence() { + return FeedPreferences.SkipSilence.fromCode(prefs.getInt( + PREF_CURRENTLY_PLAYING_TEMPORARY_SKIP_SILENCE, FeedPreferences.SkipSilence.GLOBAL.code)); + } + + public static void writeNoMediaPlaying() { + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING); + editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING); + editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); + editor.putInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER); + editor.apply(); + } + + public static void writeMediaPlaying(Playable playable) { + Log.d(TAG, "Writing playback preferences"); + SharedPreferences.Editor editor = prefs.edit(); + + if (playable == null) { + writeNoMediaPlaying(); + } else { + editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, playable.getPlayableType()); + editor.putBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, playable.getMediaType() == MediaType.VIDEO); + if (playable instanceof FeedMedia) { + FeedMedia feedMedia = (FeedMedia) playable; + editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, feedMedia.getItem().getFeed().getId()); + editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, feedMedia.getId()); + } else { + editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING); + editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); + } + } + editor.apply(); + } + + public static void setCurrentlyPlayingTemporaryPlaybackSpeed(float speed) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, speed); + editor.apply(); + } + + public static void setCurrentlyPlayingTemporarySkipSilence(boolean skipSilence) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_CURRENTLY_PLAYING_TEMPORARY_SKIP_SILENCE, skipSilence + ? FeedPreferences.SkipSilence.AGGRESSIVE.code : FeedPreferences.SkipSilence.OFF.code); + editor.apply(); + } + + public static void clearCurrentlyPlayingTemporaryPlaybackSettings() { + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED); + editor.remove(PREF_CURRENTLY_PLAYING_TEMPORARY_SKIP_SILENCE); + editor.apply(); + } +} diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/SleepTimerPreferences.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/SleepTimerPreferences.java new file mode 100644 index 000000000..5c2643c51 --- /dev/null +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/SleepTimerPreferences.java @@ -0,0 +1,106 @@ +package de.danoeh.antennapod.storage.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.util.concurrent.TimeUnit; + +public class SleepTimerPreferences { + + private static final String TAG = "SleepTimerPreferences"; + + public static final String PREF_NAME = "SleepTimerDialog"; + private static final String PREF_VALUE = "LastValue"; + + private static final String PREF_VIBRATE = "Vibrate"; + private static final String PREF_SHAKE_TO_RESET = "ShakeToReset"; + private static final String PREF_AUTO_ENABLE = "AutoEnable"; + private static final String PREF_AUTO_ENABLE_FROM = "AutoEnableFrom"; + private static final String PREF_AUTO_ENABLE_TO = "AutoEnableTo"; + + private static final String DEFAULT_LAST_TIMER = "15"; + private static final int DEFAULT_AUTO_ENABLE_FROM = 22; + private static final int DEFAULT_AUTO_ENABLE_TO = 6; + + private static SharedPreferences prefs; + + /** + * Sets up the UserPreferences class. + * + * @throws IllegalArgumentException if context is null + */ + public static void init(@NonNull Context context) { + Log.d(TAG, "Creating new instance of SleepTimerPreferences"); + SleepTimerPreferences.prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + } + + public static void setLastTimer(String value) { + prefs.edit().putString(PREF_VALUE, value).apply(); + } + + public static String lastTimerValue() { + return prefs.getString(PREF_VALUE, DEFAULT_LAST_TIMER); + } + + public static long timerMillis() { + long value = Long.parseLong(lastTimerValue()); + return TimeUnit.MINUTES.toMillis(value); + } + + public static void setVibrate(boolean vibrate) { + prefs.edit().putBoolean(PREF_VIBRATE, vibrate).apply(); + } + + public static boolean vibrate() { + return prefs.getBoolean(PREF_VIBRATE, false); + } + + public static void setShakeToReset(boolean shakeToReset) { + prefs.edit().putBoolean(PREF_SHAKE_TO_RESET, shakeToReset).apply(); + } + + public static boolean shakeToReset() { + return prefs.getBoolean(PREF_SHAKE_TO_RESET, true); + } + + public static void setAutoEnable(boolean autoEnable) { + prefs.edit().putBoolean(PREF_AUTO_ENABLE, autoEnable).apply(); + } + + public static boolean autoEnable() { + return prefs.getBoolean(PREF_AUTO_ENABLE, false); + } + + public static void setAutoEnableFrom(int hourOfDay) { + prefs.edit().putInt(PREF_AUTO_ENABLE_FROM, hourOfDay).apply(); + } + + public static int autoEnableFrom() { + return prefs.getInt(PREF_AUTO_ENABLE_FROM, DEFAULT_AUTO_ENABLE_FROM); + } + + public static void setAutoEnableTo(int hourOfDay) { + prefs.edit().putInt(PREF_AUTO_ENABLE_TO, hourOfDay).apply(); + } + + public static int autoEnableTo() { + return prefs.getInt(PREF_AUTO_ENABLE_TO, DEFAULT_AUTO_ENABLE_TO); + } + + public static boolean isInTimeRange(int from, int to, int current) { + // Range covers one day + if (from < to) { + return from <= current && current < to; + } + + // Range covers two days + if (from <= current) { + return true; + } + + return current < to; + } +} diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UsageStatistics.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UsageStatistics.java new file mode 100644 index 000000000..e8da0d194 --- /dev/null +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UsageStatistics.java @@ -0,0 +1,70 @@ +package de.danoeh.antennapod.storage.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.annotation.NonNull; + +/** + * Collects statistics about the app usage. The statistics are used to allow on-demand configuration: + * "Looks like you stream a lot. Do you want to toggle the 'Prefer streaming' setting?". + * The data is only stored locally on the device. It is NOT used for analytics/tracking. + * A private instance of this class must first be instantiated via + * init() or otherwise every public method will throw an Exception + * when called. + */ +public class UsageStatistics { + private UsageStatistics() { + + } + + private static final String PREF_DB_NAME = "UsageStatistics"; + private static final float MOVING_AVERAGE_WEIGHT = 0.8f; + private static final float MOVING_AVERAGE_BIAS_THRESHOLD = 0.1f; + private static final String SUFFIX_HIDDEN = "_hidden"; + private static SharedPreferences prefs; + + public static final StatsAction ACTION_STREAM = new StatsAction("downloadVsStream", 0); + public static final StatsAction ACTION_DOWNLOAD = new StatsAction("downloadVsStream", 1); + + /** + * Sets up the UsageStatistics class. + * + * @throws IllegalArgumentException if context is null + */ + public static void init(@NonNull Context context) { + prefs = context.getSharedPreferences(PREF_DB_NAME, Context.MODE_PRIVATE); + } + + public static void logAction(StatsAction action) { + int numExecutions = prefs.getInt(action.type + action.value, 0); + float movingAverage = prefs.getFloat(action.type, 0.5f); + prefs.edit() + .putInt(action.type + action.value, numExecutions + 1) + .putFloat(action.type, MOVING_AVERAGE_WEIGHT * movingAverage + + (1 - MOVING_AVERAGE_WEIGHT) * action.value) + .apply(); + } + + public static boolean hasSignificantBiasTo(StatsAction action) { + if (prefs.getBoolean(action.type + SUFFIX_HIDDEN, false)) { + return false; + } else { + final float movingAverage = prefs.getFloat(action.type, 0.5f); + return Math.abs(action.value - movingAverage) < MOVING_AVERAGE_BIAS_THRESHOLD; + } + } + + public static void doNotAskAgain(StatsAction action) { + prefs.edit().putBoolean(action.type + SUFFIX_HIDDEN, true).apply(); + } + + public static final class StatsAction { + public final String type; + public final int value; + + public StatsAction(String type, int value) { + this.type = type; + this.value = value; + } + } +} -- cgit v1.2.3