diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java deleted file mode 100644 index 126039c8..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ /dev/null @@ -1,435 +0,0 @@ -package org.moire.ultrasonic.fragment; - -import android.app.AlertDialog; -import android.content.SharedPreferences; -import android.os.Build; -import android.os.Bundle; -import android.provider.SearchRecentSuggestions; -import android.view.View; - -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.fragment.app.DialogFragment; -import androidx.preference.CheckBoxPreference; -import androidx.preference.EditTextPreference; -import androidx.preference.ListPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceFragmentCompat; - -import org.jetbrains.annotations.NotNull; -import org.koin.java.KoinJavaComponent; -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.featureflags.Feature; -import org.moire.ultrasonic.featureflags.FeatureStorage; -import org.moire.ultrasonic.filepicker.FilePickerDialog; -import org.moire.ultrasonic.log.FileLoggerTree; -import org.moire.ultrasonic.provider.SearchSuggestionProvider; -import org.moire.ultrasonic.service.MediaPlayerController; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.FileUtil; -import org.moire.ultrasonic.util.MediaSessionHandler; -import org.moire.ultrasonic.util.PermissionUtil; -import org.moire.ultrasonic.util.Settings; -import org.moire.ultrasonic.util.ThemeChangedEventDistributor; -import org.moire.ultrasonic.util.TimeSpanPreference; -import org.moire.ultrasonic.util.TimeSpanPreferenceDialogFragmentCompat; -import org.moire.ultrasonic.util.Util; - -import java.io.File; - -import kotlin.Lazy; -import kotlin.jvm.functions.Function1; -import timber.log.Timber; - -import static org.koin.java.KoinJavaComponent.inject; - -/** - * Shows main app settings. - */ -public class SettingsFragment extends PreferenceFragmentCompat - implements SharedPreferences.OnSharedPreferenceChangeListener { - - private ListPreference theme; - private ListPreference maxBitrateWifi; - private ListPreference maxBitrateMobile; - private ListPreference cacheSize; - private Preference cacheLocation; - private ListPreference preloadCount; - private ListPreference bufferLength; - private ListPreference incrementTime; - private ListPreference networkTimeout; - private ListPreference maxAlbums; - private ListPreference maxSongs; - private ListPreference maxArtists; - private ListPreference defaultAlbums; - private ListPreference defaultSongs; - private ListPreference defaultArtists; - private ListPreference chatRefreshInterval; - private ListPreference directoryCacheTime; - private CheckBoxPreference mediaButtonsEnabled; - private CheckBoxPreference lockScreenEnabled; - private CheckBoxPreference sendBluetoothNotifications; - private CheckBoxPreference sendBluetoothAlbumArt; - private CheckBoxPreference showArtistPicture; - private ListPreference viewRefresh; - private EditTextPreference sharingDefaultDescription; - private EditTextPreference sharingDefaultGreeting; - private TimeSpanPreference sharingDefaultExpiration; - private Preference resumeOnBluetoothDevice; - private Preference pauseOnBluetoothDevice; - private CheckBoxPreference debugLogToFile; - - private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); - private final Lazy permissionUtil = inject(PermissionUtil.class); - private final Lazy themeChangedEventDistributor = inject(ThemeChangedEventDistributor.class); - private final Lazy mediaSessionHandler = inject(MediaSessionHandler.class); - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.settings, rootKey); - } - - @Override - public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - FragmentTitle.Companion.setTitle(this, R.string.menu_settings); - - theme = findPreference(Constants.PREFERENCES_KEY_THEME); - maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); - maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); - cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); - cacheLocation = findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION); - preloadCount = findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT); - bufferLength = findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH); - incrementTime = findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME); - networkTimeout = findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT); - maxAlbums = findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS); - maxSongs = findPreference(Constants.PREFERENCES_KEY_MAX_SONGS); - maxArtists = findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS); - defaultArtists = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS); - defaultSongs = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS); - defaultAlbums = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS); - chatRefreshInterval = findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL); - directoryCacheTime = findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME); - mediaButtonsEnabled = findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS); - lockScreenEnabled = findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS); - sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART); - sendBluetoothNotifications = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS); - viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH); - sharingDefaultDescription = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION); - sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING); - sharingDefaultExpiration = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION); - resumeOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE); - pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE); - debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE); - showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE); - - sharingDefaultGreeting.setText(Settings.getShareGreeting()); - setupClearSearchPreference(); - setupFeatureFlagsPreferences(); - setupCacheLocationPreference(); - setupBluetoothDevicePreferences(); - - // After API26 foreground services must be used for music playback, and they must have a notification - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PreferenceCategory notificationsCategory = findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS); - Preference preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION); - if (preferenceToRemove != null) - notificationsCategory.removePreference(preferenceToRemove); - preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION); - if (preferenceToRemove != null) - notificationsCategory.removePreference(preferenceToRemove); - } - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - update(); - } - - @Override - public void onResume() { - super.onResume(); - SharedPreferences preferences = Settings.getPreferences(); - preferences.registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onPause() { - super.onPause(); - SharedPreferences prefs = Settings.getPreferences(); - prefs.unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - Timber.d("Preference changed: %s", key); - update(); - - if (Constants.PREFERENCES_KEY_HIDE_MEDIA.equals(key)) { - setHideMedia(sharedPreferences.getBoolean(key, false)); - } else if (Constants.PREFERENCES_KEY_MEDIA_BUTTONS.equals(key)) { - setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true)); - } else if (Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS.equals(key)) { - setBluetoothPreferences(sharedPreferences.getBoolean(key, true)); - } else if (Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE.equals(key)) { - setDebugLogToFile(sharedPreferences.getBoolean(key, false)); - } else if (Constants.PREFERENCES_KEY_ID3_TAGS.equals(key)) { - showArtistPicture.setEnabled(sharedPreferences.getBoolean(key, false)); - } else if (Constants.PREFERENCES_KEY_THEME.equals(key)) { - themeChangedEventDistributor.getValue().RaiseThemeChangedEvent(); - } - } - - @Override - public void onDisplayPreferenceDialog(Preference preference) { - DialogFragment dialogFragment = null; - if (preference instanceof TimeSpanPreference) { - dialogFragment = new TimeSpanPreferenceDialogFragmentCompat(); - Bundle bundle = new Bundle(1); - bundle.putString("key", preference.getKey()); - dialogFragment.setArguments(bundle); - } - - if (dialogFragment != null) { - dialogFragment.setTargetFragment(this, 0); - dialogFragment.show(this.getParentFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG"); - } else { - super.onDisplayPreferenceDialog(preference); - } - } - - private void setupCacheLocationPreference() { - cacheLocation.setSummary(Settings.getCacheLocation()); - - cacheLocation.setOnPreferenceClickListener(preference -> { - // If the user tries to change the cache location, we must first check to see if we have write access. - PermissionUtil.requestInitialPermission(getActivity(), hasPermission -> { - if (hasPermission) { - FilePickerDialog filePickerDialog = FilePickerDialog.Companion.createFilePickerDialog(getContext()); - filePickerDialog.setDefaultDirectory(FileUtil.getDefaultMusicDirectory().getPath()); - filePickerDialog.setInitialDirectory(cacheLocation.getSummary().toString()); - filePickerDialog.setOnFileSelectedListener((file, path) -> { - Settings.setCacheLocation(path); - setCacheLocation(path); - }); - filePickerDialog.show(); - } - }); - return true; - }); - } - - private void setupBluetoothDevicePreferences() { - final int resumeSetting = Settings.getResumeOnBluetoothDevice(); - final int pauseSetting = Settings.getPauseOnBluetoothDevice(); - - resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(resumeSetting)); - pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(pauseSetting)); - - resumeOnBluetoothDevice.setOnPreferenceClickListener(preference -> { - showBluetoothDevicePreferenceDialog( - R.string.settings_playback_resume_on_bluetooth_device, - Settings.getResumeOnBluetoothDevice(), - choice -> { - SharedPreferences.Editor editor = resumeOnBluetoothDevice.getSharedPreferences().edit(); - editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice); - editor.apply(); - resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice)); - return null; - }); - return true; - }); - - pauseOnBluetoothDevice.setOnPreferenceClickListener(preference -> { - showBluetoothDevicePreferenceDialog( - R.string.settings_playback_pause_on_bluetooth_device, - Settings.getPauseOnBluetoothDevice(), choice -> - { - Settings.setPauseOnBluetoothDevice(choice); - pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice)); - return null; - }); - return true; - }); - } - - private void showBluetoothDevicePreferenceDialog(@StringRes int title, int defaultChoice, final Function1 onChosen) { - final int[] choice = {defaultChoice}; - new AlertDialog.Builder(getActivity()).setTitle(title) - .setSingleChoiceItems(R.array.bluetoothDeviceSettingNames, defaultChoice, - (dialogInterface, i) -> choice[0] = i) - .setNegativeButton(R.string.common_cancel, (dialogInterface, i) -> dialogInterface.cancel()) - .setPositiveButton(R.string.common_ok, (dialogInterface, i) -> { - onChosen.invoke(choice[0]); - dialogInterface.dismiss(); - }) - .create().show(); - } - - private String bluetoothDevicePreferenceToString(int preferenceValue) { - switch (preferenceValue) { - case Constants.PREFERENCE_VALUE_ALL: - return getString(R.string.settings_playback_bluetooth_all); - case Constants.PREFERENCE_VALUE_A2DP: - return getString(R.string.settings_playback_bluetooth_a2dp); - case Constants.PREFERENCE_VALUE_DISABLED: - return getString(R.string.settings_playback_bluetooth_disabled); - default: - return ""; - } - } - - private void setupClearSearchPreference() { - Preference clearSearchPreference = findPreference(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY); - - if (clearSearchPreference != null) { - clearSearchPreference.setOnPreferenceClickListener(preference -> { - SearchRecentSuggestions suggestions = - new SearchRecentSuggestions(getActivity(), - SearchSuggestionProvider.AUTHORITY, - SearchSuggestionProvider.MODE); - suggestions.clearHistory(); - Util.toast(getActivity(), R.string.settings_search_history_cleared); - return false; - }); - } - } - - private void setupFeatureFlagsPreferences() { - final FeatureStorage featureStorage = KoinJavaComponent.get(FeatureStorage.class); - - CheckBoxPreference useFiveStarRating = (CheckBoxPreference) findPreference( - Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING); - - if (useFiveStarRating != null) { - useFiveStarRating.setChecked(featureStorage.isFeatureEnabled(Feature.FIVE_STAR_RATING)); - useFiveStarRating.setOnPreferenceChangeListener((preference, o) -> { - featureStorage.changeFeatureFlag(Feature.FIVE_STAR_RATING, (Boolean) o); - return true; - }); - } - - } - - private void update() { - theme.setSummary(theme.getEntry()); - maxBitrateWifi.setSummary(maxBitrateWifi.getEntry()); - maxBitrateMobile.setSummary(maxBitrateMobile.getEntry()); - cacheSize.setSummary(cacheSize.getEntry()); - preloadCount.setSummary(preloadCount.getEntry()); - bufferLength.setSummary(bufferLength.getEntry()); - incrementTime.setSummary(incrementTime.getEntry()); - networkTimeout.setSummary(networkTimeout.getEntry()); - maxAlbums.setSummary(maxAlbums.getEntry()); - maxArtists.setSummary(maxArtists.getEntry()); - maxSongs.setSummary(maxSongs.getEntry()); - defaultAlbums.setSummary(defaultAlbums.getEntry()); - defaultArtists.setSummary(defaultArtists.getEntry()); - defaultSongs.setSummary(defaultSongs.getEntry()); - chatRefreshInterval.setSummary(chatRefreshInterval.getEntry()); - directoryCacheTime.setSummary(directoryCacheTime.getEntry()); - viewRefresh.setSummary(viewRefresh.getEntry()); - sharingDefaultExpiration.setSummary(sharingDefaultExpiration.getText()); - sharingDefaultDescription.setSummary(sharingDefaultDescription.getText()); - sharingDefaultGreeting.setSummary(sharingDefaultGreeting.getText()); - cacheLocation.setSummary(Settings.getCacheLocation()); - - if (!mediaButtonsEnabled.isChecked()) { - lockScreenEnabled.setChecked(false); - lockScreenEnabled.setEnabled(false); - } - - if (!sendBluetoothNotifications.isChecked()) { - sendBluetoothAlbumArt.setChecked(false); - sendBluetoothAlbumArt.setEnabled(false); - } - - if (debugLogToFile.isChecked()) { - debugLogToFile.setSummary(getString(R.string.settings_debug_log_path, - FileUtil.getUltrasonicDirectory(), FileLoggerTree.FILENAME)); - } else { - debugLogToFile.setSummary(""); - } - - showArtistPicture.setEnabled(Settings.getShouldUseId3Tags()); - } - - - private void setHideMedia(boolean hide) { - File nomediaDir = new File(FileUtil.getUltrasonicDirectory(), ".nomedia"); - if (hide && !nomediaDir.exists()) { - if (!nomediaDir.mkdir()) { - Timber.w("Failed to create %s", nomediaDir); - } - } else if (nomediaDir.exists()) { - if (!nomediaDir.delete()) { - Timber.w("Failed to delete %s", nomediaDir); - } - } - Util.toast(getActivity(), R.string.settings_hide_media_toast, false); - } - - private void setMediaButtonsEnabled(boolean enabled) { - lockScreenEnabled.setEnabled(enabled); - mediaSessionHandler.getValue().updateMediaButtonReceiver(); - } - - private void setBluetoothPreferences(boolean enabled) { - sendBluetoothAlbumArt.setEnabled(enabled); - } - - private void setCacheLocation(String path) { - File dir = new File(path); - - if (!FileUtil.ensureDirectoryExistsAndIsReadWritable(dir)) { - permissionUtil.getValue().handlePermissionFailed(hasPermission -> { - String currentPath = Settings.getCacheLocation(); - cacheLocation.setSummary(currentPath); - }); - } else { - cacheLocation.setSummary(path); - } - - // Clear download queue. - mediaPlayerControllerLazy.getValue().clear(); - } - - private void setDebugLogToFile(boolean writeLog) { - if (writeLog) { - FileLoggerTree.Companion.plantToTimberForest(); - Timber.i("Enabled debug logging to file"); - } else { - FileLoggerTree.Companion.uprootFromTimberForest(); - Timber.i("Disabled debug logging to file"); - - int fileNum = FileLoggerTree.Companion.getLogFileNumber(); - long fileSize = FileLoggerTree.Companion.getLogFileSizes(); - String message = getString(R.string.settings_debug_log_summary, - String.valueOf(fileNum), - String.valueOf(Math.ceil(fileSize / 1000000d)), - FileUtil.getUltrasonicDirectory()); - - new AlertDialog.Builder(getActivity()) - .setMessage(message) - .setIcon(android.R.drawable.ic_dialog_info) - .setNegativeButton(R.string.settings_debug_log_keep, (dialogInterface, i) -> dialogInterface.cancel()) - .setPositiveButton(R.string.settings_debug_log_delete, (dialogInterface, i) -> { - FileLoggerTree.Companion.deleteLogFiles(); - Timber.i("Deleted debug log files"); - dialogInterface.dismiss(); - new AlertDialog.Builder(getActivity()).setMessage(R.string.settings_debug_log_deleted) - .setPositiveButton(R.string.common_ok, (dialogInterface1, i1) -> dialogInterface1.dismiss()).create().show(); - }) - .create().show(); - } - } -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt new file mode 100644 index 00000000..f648aacb --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt @@ -0,0 +1,466 @@ +package org.moire.ultrasonic.fragment + +import android.app.AlertDialog +import android.content.DialogInterface +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.os.Build +import android.os.Bundle +import android.provider.SearchRecentSuggestions +import android.view.View +import androidx.annotation.StringRes +import androidx.fragment.app.DialogFragment +import androidx.preference.CheckBoxPreference +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import org.koin.core.component.KoinComponent +import org.koin.java.KoinJavaComponent.get +import org.koin.java.KoinJavaComponent.inject +import org.moire.ultrasonic.R +import org.moire.ultrasonic.featureflags.Feature +import org.moire.ultrasonic.featureflags.FeatureStorage +import org.moire.ultrasonic.filepicker.FilePickerDialog.Companion.createFilePickerDialog +import org.moire.ultrasonic.filepicker.OnFileSelectedListener +import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle +import org.moire.ultrasonic.log.FileLoggerTree +import org.moire.ultrasonic.log.FileLoggerTree.Companion.deleteLogFiles +import org.moire.ultrasonic.log.FileLoggerTree.Companion.getLogFileNumber +import org.moire.ultrasonic.log.FileLoggerTree.Companion.getLogFileSizes +import org.moire.ultrasonic.log.FileLoggerTree.Companion.plantToTimberForest +import org.moire.ultrasonic.log.FileLoggerTree.Companion.uprootFromTimberForest +import org.moire.ultrasonic.provider.SearchSuggestionProvider +import org.moire.ultrasonic.service.MediaPlayerController +import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.FileUtil.defaultMusicDirectory +import org.moire.ultrasonic.util.FileUtil.ensureDirectoryExistsAndIsReadWritable +import org.moire.ultrasonic.util.FileUtil.ultrasonicDirectory +import org.moire.ultrasonic.util.MediaSessionHandler +import org.moire.ultrasonic.util.PermissionUtil +import org.moire.ultrasonic.util.PermissionUtil.Companion.requestInitialPermission +import org.moire.ultrasonic.util.PermissionUtil.PermissionRequestFinishedCallback +import org.moire.ultrasonic.util.Settings +import org.moire.ultrasonic.util.Settings.preferences +import org.moire.ultrasonic.util.Settings.shareGreeting +import org.moire.ultrasonic.util.Settings.shouldUseId3Tags +import org.moire.ultrasonic.util.ThemeChangedEventDistributor +import org.moire.ultrasonic.util.TimeSpanPreference +import org.moire.ultrasonic.util.TimeSpanPreferenceDialogFragmentCompat +import org.moire.ultrasonic.util.Util.toast +import timber.log.Timber +import java.io.File +import kotlin.math.ceil + +/** + * Shows main app settings. + */ +class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener, KoinComponent { + private var theme: ListPreference? = null + private var maxBitrateWifi: ListPreference? = null + private var maxBitrateMobile: ListPreference? = null + private var cacheSize: ListPreference? = null + private var cacheLocation: Preference? = null + private var preloadCount: ListPreference? = null + private var bufferLength: ListPreference? = null + private var incrementTime: ListPreference? = null + private var networkTimeout: ListPreference? = null + private var maxAlbums: ListPreference? = null + private var maxSongs: ListPreference? = null + private var maxArtists: ListPreference? = null + private var defaultAlbums: ListPreference? = null + private var defaultSongs: ListPreference? = null + private var defaultArtists: ListPreference? = null + private var chatRefreshInterval: ListPreference? = null + private var directoryCacheTime: ListPreference? = null + private var mediaButtonsEnabled: CheckBoxPreference? = null + private var lockScreenEnabled: CheckBoxPreference? = null + private var sendBluetoothNotifications: CheckBoxPreference? = null + private var sendBluetoothAlbumArt: CheckBoxPreference? = null + private var showArtistPicture: CheckBoxPreference? = null + private var viewRefresh: ListPreference? = null + private var sharingDefaultDescription: EditTextPreference? = null + private var sharingDefaultGreeting: EditTextPreference? = null + private var sharingDefaultExpiration: TimeSpanPreference? = null + private var resumeOnBluetoothDevice: Preference? = null + private var pauseOnBluetoothDevice: Preference? = null + private var debugLogToFile: CheckBoxPreference? = null + + private val mediaPlayerControllerLazy = inject( + MediaPlayerController::class.java + ) + private val permissionUtil = inject( + PermissionUtil::class.java + ) + private val themeChangedEventDistributor = inject( + ThemeChangedEventDistributor::class.java + ) + private val mediaSessionHandler = inject( + MediaSessionHandler::class.java + ) + + override fun onCreatePreferences(savedInstanceState: Bundle, rootKey: String) { + setPreferencesFromResource(R.xml.settings, rootKey) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setTitle(this, R.string.menu_settings) + theme = findPreference(Constants.PREFERENCES_KEY_THEME) + maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI) + maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE) + cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE) + cacheLocation = findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION) + preloadCount = findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT) + bufferLength = findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH) + incrementTime = findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME) + networkTimeout = findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT) + maxAlbums = findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS) + maxSongs = findPreference(Constants.PREFERENCES_KEY_MAX_SONGS) + maxArtists = findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS) + defaultArtists = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS) + defaultSongs = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS) + defaultAlbums = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS) + chatRefreshInterval = findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL) + directoryCacheTime = findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME) + mediaButtonsEnabled = findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS) + lockScreenEnabled = findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS) + sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART) + sendBluetoothNotifications = + findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS) + viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH) + sharingDefaultDescription = + findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION) + sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING) + sharingDefaultExpiration = + findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION) + resumeOnBluetoothDevice = + findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE) + pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE) + debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE) + showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE) + sharingDefaultGreeting!!.text = shareGreeting + setupClearSearchPreference() + setupFeatureFlagsPreferences() + setupCacheLocationPreference() + setupBluetoothDevicePreferences() + + // After API26 foreground services must be used for music playback, and they must have a notification + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationsCategory = + findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS) + var preferenceToRemove = + findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION) + if (preferenceToRemove != null) notificationsCategory!!.removePreference( + preferenceToRemove + ) + preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION) + if (preferenceToRemove != null) notificationsCategory!!.removePreference( + preferenceToRemove + ) + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + update() + } + + override fun onResume() { + super.onResume() + val preferences = preferences + preferences.registerOnSharedPreferenceChangeListener(this) + } + + override fun onPause() { + super.onPause() + val prefs = preferences + prefs.unregisterOnSharedPreferenceChangeListener(this) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + Timber.d("Preference changed: %s", key) + update() + when (key) { + Constants.PREFERENCES_KEY_HIDE_MEDIA -> { + setHideMedia(sharedPreferences.getBoolean(key, false)) + } + Constants.PREFERENCES_KEY_MEDIA_BUTTONS -> { + setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true)) + } + Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS -> { + setBluetoothPreferences(sharedPreferences.getBoolean(key, true)) + } + Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE -> { + setDebugLogToFile(sharedPreferences.getBoolean(key, false)) + } + Constants.PREFERENCES_KEY_ID3_TAGS -> { + showArtistPicture!!.isEnabled = sharedPreferences.getBoolean(key, false) + } + Constants.PREFERENCES_KEY_THEME -> { + themeChangedEventDistributor.value.RaiseThemeChangedEvent() + } + } + } + + override fun onDisplayPreferenceDialog(preference: Preference) { + var dialogFragment: DialogFragment? = null + if (preference is TimeSpanPreference) { + dialogFragment = TimeSpanPreferenceDialogFragmentCompat() + val bundle = Bundle(1) + bundle.putString("key", preference.getKey()) + dialogFragment.setArguments(bundle) + } + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0) + dialogFragment.show( + this.parentFragmentManager, + "android.support.v7.preference.PreferenceFragment.DIALOG" + ) + } else { + super.onDisplayPreferenceDialog(preference) + } + } + + private fun setupCacheLocationPreference() { + cacheLocation!!.summary = Settings.cacheLocation + cacheLocation!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + // If the user tries to change the cache location, we must first check to see if we have write access. + requestInitialPermission( + requireActivity(), + object : PermissionRequestFinishedCallback { + override fun onPermissionRequestFinished(hasPermission: Boolean) { + if (hasPermission) { + val filePickerDialog = createFilePickerDialog( + context!! + ) + filePickerDialog.setDefaultDirectory(defaultMusicDirectory.path) + filePickerDialog.setInitialDirectory(cacheLocation!!.summary.toString()) + filePickerDialog.setOnFileSelectedListener(object : + OnFileSelectedListener { + override fun onFileSelected(file: File?, path: String?) { + if (path != null) { + Settings.cacheLocation = path + setCacheLocation(path) + } + } + }) + filePickerDialog.show() + } + } + }) + true + } + } + + private fun setupBluetoothDevicePreferences() { + val resumeSetting = Settings.resumeOnBluetoothDevice + val pauseSetting = Settings.pauseOnBluetoothDevice + resumeOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(resumeSetting) + pauseOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(pauseSetting) + resumeOnBluetoothDevice!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + showBluetoothDevicePreferenceDialog( + R.string.settings_playback_resume_on_bluetooth_device, + Settings.resumeOnBluetoothDevice + ) { choice: Int -> + val editor = resumeOnBluetoothDevice!!.sharedPreferences.edit() + editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice) + editor.apply() + resumeOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(choice) + } + true + } + pauseOnBluetoothDevice!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + showBluetoothDevicePreferenceDialog( + R.string.settings_playback_pause_on_bluetooth_device, + Settings.pauseOnBluetoothDevice + ) { choice: Int -> + Settings.pauseOnBluetoothDevice = choice + pauseOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(choice) + } + true + } + } + + private fun showBluetoothDevicePreferenceDialog( + @StringRes title: Int, + defaultChoice: Int, + onChosen: (Int) -> Unit + ) { + val choice = intArrayOf(defaultChoice) + AlertDialog.Builder(activity).setTitle(title) + .setSingleChoiceItems( + R.array.bluetoothDeviceSettingNames, defaultChoice + ) { _: DialogInterface?, i: Int -> choice[0] = i } + .setNegativeButton(R.string.common_cancel) { dialogInterface: DialogInterface, _: Int -> + dialogInterface.cancel() + } + .setPositiveButton(R.string.common_ok) { dialogInterface: DialogInterface, _: Int -> + onChosen(choice[0]) + dialogInterface.dismiss() + } + .create().show() + } + + private fun bluetoothDevicePreferenceToString(preferenceValue: Int): String { + return when (preferenceValue) { + Constants.PREFERENCE_VALUE_ALL -> getString(R.string.settings_playback_bluetooth_all) + Constants.PREFERENCE_VALUE_A2DP -> getString(R.string.settings_playback_bluetooth_a2dp) + Constants.PREFERENCE_VALUE_DISABLED -> getString(R.string.settings_playback_bluetooth_disabled) + else -> "" + } + } + + private fun setupClearSearchPreference() { + val clearSearchPreference = + findPreference(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY) + if (clearSearchPreference != null) { + clearSearchPreference.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val suggestions = SearchRecentSuggestions( + activity, + SearchSuggestionProvider.AUTHORITY, + SearchSuggestionProvider.MODE + ) + suggestions.clearHistory() + toast(activity, R.string.settings_search_history_cleared) + false + } + } + } + + private fun setupFeatureFlagsPreferences() { + val featureStorage = get(FeatureStorage::class.java) + val useFiveStarRating = findPreference( + Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING + ) as CheckBoxPreference? + if (useFiveStarRating != null) { + useFiveStarRating.isChecked = featureStorage.isFeatureEnabled(Feature.FIVE_STAR_RATING) + useFiveStarRating.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, o: Any? -> + featureStorage.changeFeatureFlag(Feature.FIVE_STAR_RATING, (o as Boolean?)!!) + true + } + } + } + + private fun update() { + theme!!.summary = theme!!.entry + maxBitrateWifi!!.summary = maxBitrateWifi!!.entry + maxBitrateMobile!!.summary = maxBitrateMobile!!.entry + cacheSize!!.summary = cacheSize!!.entry + preloadCount!!.summary = preloadCount!!.entry + bufferLength!!.summary = bufferLength!!.entry + incrementTime!!.summary = incrementTime!!.entry + networkTimeout!!.summary = networkTimeout!!.entry + maxAlbums!!.summary = maxAlbums!!.entry + maxArtists!!.summary = maxArtists!!.entry + maxSongs!!.summary = maxSongs!!.entry + defaultAlbums!!.summary = defaultAlbums!!.entry + defaultArtists!!.summary = defaultArtists!!.entry + defaultSongs!!.summary = defaultSongs!!.entry + chatRefreshInterval!!.summary = chatRefreshInterval!!.entry + directoryCacheTime!!.summary = directoryCacheTime!!.entry + viewRefresh!!.summary = viewRefresh!!.entry + sharingDefaultExpiration!!.summary = sharingDefaultExpiration!!.text + sharingDefaultDescription!!.summary = sharingDefaultDescription!!.text + sharingDefaultGreeting!!.summary = sharingDefaultGreeting!!.text + cacheLocation!!.summary = Settings.cacheLocation + if (!mediaButtonsEnabled!!.isChecked) { + lockScreenEnabled!!.isChecked = false + lockScreenEnabled!!.isEnabled = false + } + if (!sendBluetoothNotifications!!.isChecked) { + sendBluetoothAlbumArt!!.isChecked = false + sendBluetoothAlbumArt!!.isEnabled = false + } + if (debugLogToFile!!.isChecked) { + debugLogToFile!!.summary = getString( + R.string.settings_debug_log_path, + ultrasonicDirectory, FileLoggerTree.FILENAME + ) + } else { + debugLogToFile!!.summary = "" + } + showArtistPicture!!.isEnabled = shouldUseId3Tags + } + + private fun setHideMedia(hide: Boolean) { + val nomediaDir = File(ultrasonicDirectory, ".nomedia") + if (hide && !nomediaDir.exists()) { + if (!nomediaDir.mkdir()) { + Timber.w("Failed to create %s", nomediaDir) + } + } else if (nomediaDir.exists()) { + if (!nomediaDir.delete()) { + Timber.w("Failed to delete %s", nomediaDir) + } + } + toast(activity, R.string.settings_hide_media_toast, false) + } + + private fun setMediaButtonsEnabled(enabled: Boolean) { + lockScreenEnabled!!.isEnabled = enabled + mediaSessionHandler.value.updateMediaButtonReceiver() + } + + private fun setBluetoothPreferences(enabled: Boolean) { + sendBluetoothAlbumArt!!.isEnabled = enabled + } + + private fun setCacheLocation(path: String) { + val dir = File(path) + if (!ensureDirectoryExistsAndIsReadWritable(dir)) { + permissionUtil.value.handlePermissionFailed(object : PermissionRequestFinishedCallback { + override fun onPermissionRequestFinished(hasPermission: Boolean) { + val currentPath = Settings.cacheLocation + cacheLocation!!.summary = currentPath + } + }) + } else { + cacheLocation!!.summary = path + } + + // Clear download queue. + mediaPlayerControllerLazy.value.clear() + } + + private fun setDebugLogToFile(writeLog: Boolean) { + if (writeLog) { + plantToTimberForest() + Timber.i("Enabled debug logging to file") + } else { + uprootFromTimberForest() + Timber.i("Disabled debug logging to file") + val fileNum = getLogFileNumber() + val fileSize = getLogFileSizes() + val message = getString( + R.string.settings_debug_log_summary, + fileNum.toString(), + ceil(fileSize / 1000 * 1000.0).toString(), + ultrasonicDirectory + ) + AlertDialog.Builder(activity) + .setMessage(message) + .setIcon(android.R.drawable.ic_dialog_info) + .setNegativeButton(R.string.settings_debug_log_keep) { dialogInterface: DialogInterface, _: Int -> + dialogInterface.cancel() + } + .setPositiveButton(R.string.settings_debug_log_delete) { dialogInterface: DialogInterface, _: Int -> + deleteLogFiles() + Timber.i("Deleted debug log files") + dialogInterface.dismiss() + AlertDialog.Builder(activity).setMessage(R.string.settings_debug_log_deleted) + .setPositiveButton(R.string.common_ok) { dialogInterface1: DialogInterface, _: Int -> + dialogInterface1.dismiss() + } + .create().show() + } + .create().show() + } + } +}