+122
-1
@@ -5,10 +5,15 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
@@ -27,6 +32,7 @@ import de.markusfisch.android.binaryeye.app.hasBluetoothPermission
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.bluetooth.setBluetoothHosts
|
||||
import de.markusfisch.android.binaryeye.media.beepConfirm
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
import de.markusfisch.android.binaryeye.preference.UrlPreference
|
||||
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
|
||||
import de.markusfisch.android.binaryeye.view.systemBarRecyclerViewScrollListener
|
||||
@@ -34,6 +40,13 @@ import de.markusfisch.android.binaryeye.widget.toast
|
||||
|
||||
class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
private var lastProfile: String? = null
|
||||
private val pickBeepTone = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
setCustomBeepTone(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private val changeListener = object : OnSharedPreferenceChangeListener {
|
||||
override fun onSharedPreferenceChanged(
|
||||
@@ -87,6 +100,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
setSummaries(preferenceScreen)
|
||||
setIcons()
|
||||
|
||||
wireBeepTonePicker()
|
||||
wireProfiles()
|
||||
wireAutomatedActions()
|
||||
wireIgnoreCodes()
|
||||
@@ -107,6 +121,27 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun wireBeepTonePicker() {
|
||||
findPreference<ListPreference>(BEEP_TONE_NAME)?.apply {
|
||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
val toneName = newValue as? String ?: return@OnPreferenceChangeListener false
|
||||
if (toneName == Preferences.CUSTOM_BEEP_TONE_NAME) {
|
||||
pickBeepTone.launch(arrayOf("audio/*"))
|
||||
false
|
||||
} else {
|
||||
if (prefs.usesCustomBeepTone()) {
|
||||
clearCustomBeepTone()
|
||||
}
|
||||
prefs.beepToneName = toneName
|
||||
value = toneName
|
||||
setSummary(this)
|
||||
activity?.beepConfirm()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun wireAutomatedActions() {
|
||||
findPreference<Preference>(AUTOMATED_ACTIONS)?.apply {
|
||||
updateAutomatedActionsSummary(this)
|
||||
@@ -158,7 +193,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
// adding network suggestions on Q as well, so we
|
||||
// need to keep this option.
|
||||
setOnPreferenceClickListener {
|
||||
context?.askToClearNetworkSuggestions()
|
||||
context.askToClearNetworkSuggestions()
|
||||
true
|
||||
}
|
||||
} else {
|
||||
@@ -247,6 +282,10 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
when (preference.key) {
|
||||
AUTOMATED_ACTIONS -> return updateAutomatedActionsSummary(preference)
|
||||
IGNORE_CODES -> return updateIgnoreCodesSummary(preference)
|
||||
BEEP_TONE_NAME -> {
|
||||
setBeepToneSummary(preference)
|
||||
return
|
||||
}
|
||||
}
|
||||
when (preference) {
|
||||
is EditTextPreference -> preference.summary = preference.text
|
||||
@@ -259,6 +298,21 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setBeepToneSummary(preference: Preference) {
|
||||
preference.summary = if (prefs.usesCustomBeepTone()) {
|
||||
getBeepToneLabel(prefs.beepToneUri.toUri())
|
||||
} else {
|
||||
findPreference<ListPreference>(BEEP_TONE_NAME)?.let {
|
||||
val index = it.findIndexOfValue(prefs.beepToneName)
|
||||
if (index >= 0) {
|
||||
it.entries[index]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSortedSummary(preference: MultiSelectListPreference): String {
|
||||
val labelsByValue = mutableMapOf<String, String>()
|
||||
val entries = preference.entries
|
||||
@@ -353,6 +407,73 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
lastProfile = prefs.profile
|
||||
}
|
||||
|
||||
private fun setCustomBeepTone(uri: Uri) {
|
||||
val context = requireContext()
|
||||
val resolver = context.contentResolver
|
||||
clearCustomBeepTone()
|
||||
try {
|
||||
resolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
val message = e.message
|
||||
if (!message.isNullOrEmpty()) {
|
||||
context.toast(message)
|
||||
}
|
||||
return
|
||||
}
|
||||
prefs.beepToneUri = uri.toString()
|
||||
prefs.beepToneName = Preferences.CUSTOM_BEEP_TONE_NAME
|
||||
findPreference<ListPreference>(BEEP_TONE_NAME)?.apply {
|
||||
value = Preferences.CUSTOM_BEEP_TONE_NAME
|
||||
setSummary(this)
|
||||
}
|
||||
activity?.beepConfirm()
|
||||
}
|
||||
|
||||
private fun clearCustomBeepTone(releaseSelection: Boolean = true) {
|
||||
if (releaseSelection) {
|
||||
releaseBeepTonePermission(prefs.beepToneUri)
|
||||
}
|
||||
prefs.beepToneUri = ""
|
||||
}
|
||||
|
||||
private fun releaseBeepTonePermission(uriString: String) {
|
||||
if (uriString.isEmpty()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
requireContext().contentResolver.releasePersistableUriPermission(
|
||||
uriString.toUri(),
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
} catch (_: SecurityException) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBeepToneLabel(uri: Uri): String {
|
||||
var cursor: Cursor? = null
|
||||
return try {
|
||||
cursor = requireContext().contentResolver.query(
|
||||
uri,
|
||||
arrayOf(OpenableColumns.DISPLAY_NAME),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
if (cursor?.moveToFirst() == true) {
|
||||
cursor.getString(0)
|
||||
} else {
|
||||
uri.lastPathSegment ?: uri.toString()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
uri.lastPathSegment ?: uri.toString()
|
||||
} finally {
|
||||
cursor?.close()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PROFILE = "profile"
|
||||
private const val AUTOMATED_ACTIONS = "automated_actions"
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
package de.markusfisch.android.binaryeye.media
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import android.media.ToneGenerator
|
||||
import androidx.core.net.toUri
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
|
||||
private var confirmToneGenerator: ToneGenerator? = null
|
||||
private var confirmToneStream = AudioManager.STREAM_MUSIC
|
||||
private var confirmMediaPlayer: MediaPlayer? = null
|
||||
private var errorToneGenerator: ToneGenerator? = null
|
||||
|
||||
fun beepConfirm() {
|
||||
fun Context.beepConfirm() {
|
||||
val stream = prefs.beepStream()
|
||||
if (prefs.usesCustomBeepTone() &&
|
||||
playCustomConfirmTone(prefs.beepToneUri, stream)
|
||||
) {
|
||||
return
|
||||
}
|
||||
val tg = if (confirmToneGenerator == null || confirmToneStream != stream) {
|
||||
confirmToneGenerator?.release()
|
||||
ToneGenerator(stream, ToneGenerator.MAX_VOLUME).also {
|
||||
@@ -23,6 +33,48 @@ fun beepConfirm() {
|
||||
tg.startTone(prefs.beepTone())
|
||||
}
|
||||
|
||||
private fun Context.playCustomConfirmTone(
|
||||
uriString: String,
|
||||
stream: Int
|
||||
): Boolean {
|
||||
return try {
|
||||
confirmToneGenerator?.release()
|
||||
confirmToneGenerator = null
|
||||
confirmToneStream = stream
|
||||
confirmMediaPlayer?.release()
|
||||
confirmMediaPlayer = MediaPlayer().also { mp ->
|
||||
mp.apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setLegacyStreamType(stream)
|
||||
.build()
|
||||
)
|
||||
setDataSource(this@playCustomConfirmTone, uriString.toUri())
|
||||
setOnCompletionListener { completed ->
|
||||
if (confirmMediaPlayer === completed) {
|
||||
confirmMediaPlayer = null
|
||||
}
|
||||
completed.release()
|
||||
}
|
||||
setOnErrorListener { failed, _, _ ->
|
||||
if (confirmMediaPlayer === failed) {
|
||||
confirmMediaPlayer = null
|
||||
}
|
||||
failed.release()
|
||||
true
|
||||
}
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
}
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
confirmMediaPlayer?.release()
|
||||
confirmMediaPlayer = null
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun beepError() {
|
||||
val tg = errorToneGenerator ?: ToneGenerator(
|
||||
AudioManager.STREAM_ALARM,
|
||||
@@ -36,6 +88,8 @@ fun releaseToneGenerators() {
|
||||
confirmToneGenerator?.release()
|
||||
confirmToneGenerator = null
|
||||
confirmToneStream = AudioManager.STREAM_MUSIC
|
||||
confirmMediaPlayer?.release()
|
||||
confirmMediaPlayer = null
|
||||
errorToneGenerator?.release()
|
||||
errorToneGenerator = null
|
||||
}
|
||||
|
||||
@@ -129,6 +129,11 @@ class Preferences {
|
||||
apply(BEEP_STREAM_NAME, value)
|
||||
field = value
|
||||
}
|
||||
var beepToneUri = ""
|
||||
set(value) {
|
||||
apply(BEEP_TONE_URI, value)
|
||||
field = value
|
||||
}
|
||||
var useHistory = false
|
||||
set(value) {
|
||||
apply(USE_HISTORY, value)
|
||||
@@ -373,6 +378,7 @@ class Preferences {
|
||||
json.put(BEEP, p, defaults.beep)
|
||||
json.put(BEEP_TONE_NAME, p, defaults.beepToneName)
|
||||
json.put(BEEP_STREAM_NAME, p, defaults.beepStreamName)
|
||||
json.put(BEEP_TONE_URI, p, defaults.beepToneUri)
|
||||
json.put(USE_HISTORY, p, defaults.useHistory)
|
||||
json.put(IGNORE_DUPLICATES_NAME, p, defaults.ignoreDuplicatesName)
|
||||
json.put(IGNORE_CODES, p, ignoreCodesToJsonArray(defaults.ignoreCodes))
|
||||
@@ -471,6 +477,7 @@ class Preferences {
|
||||
putBoolean(BEEP, json)
|
||||
putString(BEEP_TONE_NAME, json)
|
||||
putString(BEEP_STREAM_NAME, json)
|
||||
putString(BEEP_TONE_URI, json)
|
||||
putBoolean(USE_HISTORY, json)
|
||||
putString(IGNORE_DUPLICATES_NAME, json)
|
||||
putString(IGNORE_CODES, json)
|
||||
@@ -562,6 +569,9 @@ class Preferences {
|
||||
preferences.getString(BEEP_STREAM_NAME, beepStreamName)?.also {
|
||||
beepStreamName = it
|
||||
}
|
||||
preferences.getString(BEEP_TONE_URI, beepToneUri)?.also {
|
||||
beepToneUri = it
|
||||
}
|
||||
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
|
||||
|
||||
// Map old setting to new one if it wasn't on default value.
|
||||
@@ -709,6 +719,9 @@ class Preferences {
|
||||
else -> android.media.AudioManager.STREAM_MUSIC
|
||||
}
|
||||
|
||||
fun usesCustomBeepTone() = beepToneName == CUSTOM_BEEP_TONE_NAME &&
|
||||
beepToneUri.isNotEmpty()
|
||||
|
||||
fun ignoreDuplicates() = when (ignoreDuplicatesName) {
|
||||
"ignore_consecutive_duplicates" -> IgnoreDuplicates.Consecutive
|
||||
"ignore_any_duplicates" -> IgnoreDuplicates.Any
|
||||
@@ -789,6 +802,7 @@ class Preferences {
|
||||
putBoolean(BEEP, defaults.beep)
|
||||
putString(BEEP_TONE_NAME, defaults.beepToneName)
|
||||
putString(BEEP_STREAM_NAME, defaults.beepStreamName)
|
||||
putString(BEEP_TONE_URI, defaults.beepToneUri)
|
||||
putBoolean(USE_HISTORY, defaults.useHistory)
|
||||
putString(IGNORE_DUPLICATES_NAME, defaults.ignoreDuplicatesName)
|
||||
putString(IGNORE_CODES, ignoreCodesToJsonArray(defaults.ignoreCodes))
|
||||
@@ -895,6 +909,7 @@ class Preferences {
|
||||
private const val BEEP = "beep"
|
||||
private const val BEEP_TONE_NAME = "beep_tone_name"
|
||||
private const val BEEP_STREAM_NAME = "beep_stream_name"
|
||||
private const val BEEP_TONE_URI = "beep_tone_uri"
|
||||
private const val USE_HISTORY = "use_history"
|
||||
private const val IGNORE_DUPLICATES_NAME = "ignore_duplicates_name"
|
||||
private const val IGNORE_CODES = "ignore_codes"
|
||||
@@ -925,6 +940,7 @@ class Preferences {
|
||||
private const val AUTOMATED_ACTIONS = "automated_actions"
|
||||
private const val DYNAMIC_COLORS = "dynamic_colors"
|
||||
private const val DEFAULT_IGNORE_CODE_PATTERN = "(?i)^fido:/.*"
|
||||
const val CUSTOM_BEEP_TONE_NAME = "tone_custom_file"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<item>@string/tone_prop_ack</item>
|
||||
<item>@string/tone_prop_beep</item>
|
||||
<item>@string/tone_prop_beep2</item>
|
||||
<item>@string/pick_file</item>
|
||||
</string-array>
|
||||
<string-array name="beep_tones_values">
|
||||
<item>tone_cdma_confirm</item>
|
||||
@@ -12,5 +13,6 @@
|
||||
<item>tone_prop_ack</item>
|
||||
<item>tone_prop_beep</item>
|
||||
<item>tone_prop_beep2</item>
|
||||
<item>tone_custom_file</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user