Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shortcut for opening the output directory in DocumentsUI #383

Merged
merged 1 commit into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/src/main/java/com/chiller3/bcr/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package com.chiller3.bcr
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.util.Log
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.chiller3.bcr.extension.DOCUMENTSUI_AUTHORITY
import com.chiller3.bcr.extension.safTreeToDocument
import com.chiller3.bcr.format.Format
import com.chiller3.bcr.format.SampleRate
import com.chiller3.bcr.output.Retention
Expand Down Expand Up @@ -164,6 +168,24 @@ class Preferences(private val context: Context) {
val outputDirOrDefault: Uri
get() = outputDir ?: Uri.fromFile(defaultOutputDir)

/**
* Build an [Intent] for opening DocumentsUI to the user-specified output directory or the
* default if none was set.
*/
val outputDirOrDefaultIntent: Intent
get() = Intent(Intent.ACTION_VIEW).apply {
val uri = outputDir?.safTreeToDocument() ?: run {
// Opening a file:// URI will fail with FileUriExposedException. We need to hackily
// build our own URI for the directory. Luckily, the implementation details have
// never changed...
val externalDir = Environment.getExternalStorageDirectory()
val relPath = defaultOutputDir.relativeTo(externalDir)

DocumentsContract.buildDocumentUri(DOCUMENTSUI_AUTHORITY, "primary:$relPath")
}
setDataAndType(uri, "vnd.android.document/directory")
}

/** The user-specified filename template. */
var filenameTemplate: Template?
get() = prefs.getString(PREF_FILENAME_TEMPLATE, null)?.let { Template(it) }
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/java/com/chiller3/bcr/extension/UriExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package com.chiller3.bcr.extension

import android.content.ContentResolver
import android.net.Uri
import android.provider.DocumentsContract
import android.telecom.PhoneAccount

val DOCUMENTSUI_AUTHORITY = "com.android.externalstorage.documents"

val Uri.formattedString: String
get() = when (scheme) {
ContentResolver.SCHEME_FILE -> path!!
ContentResolver.SCHEME_CONTENT -> {
val prefix = when (authority) {
"com.android.externalstorage.documents" -> ""
DOCUMENTSUI_AUTHORITY -> ""
// Include the authority to reduce ambiguity when this isn't a SAF URI provided by
// Android's local filesystem document provider
else -> "[$authority] "
Expand All @@ -34,3 +37,10 @@ val Uri.phoneNumber: String?
PhoneAccount.SCHEME_TEL -> schemeSpecificPart
else -> null
}

fun Uri.safTreeToDocument(): Uri {
require(scheme == ContentResolver.SCHEME_CONTENT) { "Not a content URI" }

val documentId = DocumentsContract.getTreeDocumentId(this)
return DocumentsContract.buildDocumentUri(authority, documentId)
}
17 changes: 16 additions & 1 deletion app/src/main/java/com/chiller3/bcr/settings/SettingsFragment.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.chiller3.bcr.settings

import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
Expand All @@ -21,14 +22,15 @@ import com.chiller3.bcr.output.Retention
import com.chiller3.bcr.rule.RecordRulesActivity
import com.chiller3.bcr.view.LongClickablePreference
import com.chiller3.bcr.view.OnPreferenceLongClickListener
import com.google.android.material.snackbar.Snackbar

class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener, OnPreferenceLongClickListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var prefs: Preferences
private lateinit var prefCallRecording: SwitchPreferenceCompat
private lateinit var prefRecordRules: Preference
private lateinit var prefOutputDir: Preference
private lateinit var prefOutputDir: LongClickablePreference
private lateinit var prefOutputFormat: Preference
private lateinit var prefInhibitBatteryOpt: SwitchPreferenceCompat
private lateinit var prefVersion: LongClickablePreference
Expand Down Expand Up @@ -68,6 +70,7 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan

prefOutputDir = findPreference(Preferences.PREF_OUTPUT_DIR)!!
prefOutputDir.onPreferenceClickListener = this
prefOutputDir.onPreferenceLongClickListener = this
refreshOutputDir()

prefOutputFormat = findPreference(Preferences.PREF_OUTPUT_FORMAT)!!
Expand Down Expand Up @@ -189,6 +192,18 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan

override fun onPreferenceLongClick(preference: Preference): Boolean {
when (preference) {
prefOutputDir -> {
try {
startActivity(prefs.outputDirOrDefaultIntent)
} catch (e: ActivityNotFoundException) {
Snackbar.make(
requireView(),
R.string.documentsui_not_found,
Snackbar.LENGTH_LONG,
).show()
}
return true
}
prefVersion -> {
prefs.isDebugMode = !prefs.isDebugMode
refreshVersion()
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<string name="pref_record_rules_desc">Configure which calls should be automatically recorded.</string>

<string name="pref_output_dir_name">Output directory</string>
<string name="pref_output_dir_desc">Pick a directory to store recordings.</string>
<string name="pref_output_dir_desc">Pick a directory to store recordings. Long press to open in file manager.</string>

<string name="pref_output_format_name">Output format</string>
<string name="pref_output_format_desc">Select an encoding format for the recordings.</string>
Expand Down Expand Up @@ -118,4 +118,7 @@

<!-- Quick settings tile -->
<string name="quick_settings_label">Call recording</string>

<!-- Snackbar alerts -->
<string name="documentsui_not_found">Android\'s builtin file manager (DocumentsUI) is unavailable.</string>
</resources>
2 changes: 1 addition & 1 deletion app/src/main/res/xml/root_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
app:summary="@string/pref_record_rules_desc"
app:iconSpaceReserved="false" />

<Preference
<com.chiller3.bcr.view.LongClickablePreference
app:key="output_dir"
app:persistent="false"
app:title="@string/pref_output_dir_name"
Expand Down
Loading