Merge pull request #107 from KryptKode/feat/customise_recordings_path

allow users to customise recording folder
This commit is contained in:
Tibor Kaputa 2022-04-18 10:50:18 +02:00 committed by GitHub
commit 2073dbb9cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 34 deletions

View File

@ -62,7 +62,7 @@ android {
}
dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:e3376e4f56'
implementation 'com.github.SimpleMobileTools:Simple-Commons:202656a071'
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.github.Armen101:AudioRecordView:1.0.4'
implementation 'androidx.documentfile:documentfile:1.0.1'

View File

@ -9,7 +9,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
android:maxSdkVersion="29" />
<uses-feature
android:name="android.hardware.faketouch"

View File

@ -82,7 +82,7 @@ class MainActivity : SimpleActivity() {
}
private fun tryInitVoiceRecorder() {
if (isQPlus()) {
if (isRPlus()) {
setupViewPager()
} else {
handlePermission(PERMISSION_WRITE_STORAGE) {

View File

@ -99,13 +99,20 @@ class SettingsActivity : SimpleActivity() {
}
private fun setupSaveRecordingsFolder() {
settings_save_recordings_holder.beGoneIf(isQPlus())
settings_save_recordings.text = humanizePath(config.saveRecordingsFolder)
settings_save_recordings_holder.setOnClickListener {
FilePickerDialog(this, config.saveRecordingsFolder, false, showFAB = true) {
val path = it
handleSAFDialog(it) {
if (it) {
handleSAFDialog(path) { grantedSAF ->
if (!grantedSAF) {
return@handleSAFDialog
}
handleSAFDialogSdk30(path) { grantedSAF30 ->
if (!grantedSAF30) {
return@handleSAFDialogSdk30
}
config.saveRecordingsFolder = path
settings_save_recordings.text = humanizePath(config.saveRecordingsFolder)
}

View File

@ -116,10 +116,10 @@ class RecordingsAdapter(
private fun shareRecordings() {
val selectedItems = getSelectedItems()
val paths = if (isQPlus()) {
selectedItems.map { getAudioFileContentUri(it.id.toLong()).toString() }
} else {
selectedItems.map { it.path }
val paths = selectedItems.map {
it.path.ifEmpty {
getAudioFileContentUri(it.id.toLong()).toString()
}
}
activity.sharePathsIntent(paths, BuildConfig.APPLICATION_ID)

View File

@ -7,6 +7,10 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.os.Environment
import com.simplemobiletools.commons.extensions.internalStoragePath
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.voicerecorder.R
import com.simplemobiletools.voicerecorder.helpers.Config
import com.simplemobiletools.voicerecorder.helpers.IS_RECORDING
import com.simplemobiletools.voicerecorder.helpers.MyWidgetRecordDisplayProvider
@ -34,3 +38,16 @@ fun Context.updateWidgets(isRecording: Boolean) {
}
}
}
fun Context.getDefaultRecordingsFolder(): String {
val defaultPath = getDefaultRecordingsRelativePath()
return "$internalStoragePath/$defaultPath"
}
fun Context.getDefaultRecordingsRelativePath(): String {
return if (isQPlus()) {
"${Environment.DIRECTORY_MUSIC}/Recordings"
} else {
getString(R.string.app_name)
}
}

View File

@ -6,15 +6,18 @@ import android.graphics.drawable.Drawable
import android.media.AudioManager
import android.media.MediaMetadataRetriever
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.MediaStore.Audio.Media
import android.util.AttributeSet
import android.widget.SeekBar
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.voicerecorder.R
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
import com.simplemobiletools.voicerecorder.adapters.RecordingsAdapter
@ -29,6 +32,8 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.roundToLong
class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), RefreshRecordingsListener {
private val FAST_FORWARD_SKIP_MS = 10000
@ -162,10 +167,24 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
}
private fun getRecordings(): ArrayList<Recording> {
return if (isQPlus()) {
getMediaStoreRecordings()
} else {
getLegacyRecordings()
val recordings = ArrayList<Recording>()
return when {
isRPlus() -> {
recordings.addAll(getMediaStoreRecordings())
recordings.addAll(getSAFRecordings())
recordings
}
isQPlus() -> {
recordings.addAll(getMediaStoreRecordings())
recordings.addAll(getLegacyRecordings())
recordings
}
else -> {
recordings.addAll(getLegacyRecordings())
recordings
}
}.apply {
sortByDescending { it.timestamp }
}
}
@ -194,7 +213,7 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
var size = cursor.getIntValue(Media.SIZE)
if (duration == 0L) {
duration = getDurationFromUri(id.toLong())
duration = getDurationFromUri(getAudioFileContentUri(id.toLong()))
}
if (size == 0) {
@ -222,17 +241,34 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
val recording = Recording(id, title, path, timestamp, duration, size)
recordings.add(recording)
}
return recordings
}
private fun getSAFRecordings(): ArrayList<Recording> {
val recordings = ArrayList<Recording>()
val files = context.getDocumentSdk30(context.config.saveRecordingsFolder)?.listFiles() ?: return recordings
files.filter { it.type?.startsWith("audio") == true && !it.name.isNullOrEmpty() }.forEach {
val id = it.hashCode()
val title = it.name!!
val path = it.uri.toString()
val timestamp = (it.lastModified() / 1000).toInt()
val duration = getDurationFromUri(it.uri)
val size = it.length().toInt()
val recording = Recording(id, title, path, timestamp, duration.toInt(), size)
recordings.add(recording)
}
recordings.sortByDescending { it.timestamp }
return recordings
}
private fun getDurationFromUri(id: Long): Long {
private fun getDurationFromUri(uri: Uri): Long {
return try {
val retriever = MediaMetadataRetriever()
retriever.setDataSource(context, getAudioFileContentUri(id))
retriever.setDataSource(context, uri)
val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!
Math.round(time.toLong() / 1000.toDouble())
(time.toLong() / 1000.toDouble()).roundToLong()
} catch (e: Exception) {
0L
}
@ -279,11 +315,18 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
reset()
try {
if (isQPlus()) {
val uri = Uri.parse(recording.path)
when {
DocumentsContract.isDocumentUri(context, uri) -> {
setDataSource(context, uri)
}
recording.path.isEmpty() -> {
setDataSource(context, getAudioFileContentUri(recording.id.toLong()))
} else {
}
else -> {
setDataSource(recording.path)
}
}
} catch (e: Exception) {
context?.showErrorToast(e)
return

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.media.MediaRecorder
import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.voicerecorder.R
import com.simplemobiletools.voicerecorder.extensions.getDefaultRecordingsFolder
class Config(context: Context) : BaseConfig(context) {
companion object {
@ -15,7 +16,7 @@ class Config(context: Context) : BaseConfig(context) {
set(hideNotification) = prefs.edit().putBoolean(HIDE_NOTIFICATION, hideNotification).apply()
var saveRecordingsFolder: String
get() = prefs.getString(SAVE_RECORDINGS, "$internalStoragePath/${context.getString(R.string.app_name)}")!!
get() = prefs.getString(SAVE_RECORDINGS, context.getDefaultRecordingsFolder())!!
set(saveRecordingsFolder) = prefs.edit().putString(SAVE_RECORDINGS, saveRecordingsFolder).apply()
var extension: Int

View File

@ -9,7 +9,6 @@ import android.content.Intent
import android.media.MediaRecorder
import android.media.MediaScannerConnection
import android.os.Build
import android.os.Environment
import android.os.IBinder
import android.provider.MediaStore
import android.provider.MediaStore.Audio.Media
@ -17,10 +16,11 @@ import androidx.core.app.NotificationCompat
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.voicerecorder.R
import com.simplemobiletools.voicerecorder.activities.SplashActivity
import com.simplemobiletools.voicerecorder.extensions.config
import com.simplemobiletools.voicerecorder.extensions.getDefaultRecordingsRelativePath
import com.simplemobiletools.voicerecorder.extensions.updateWidgets
import com.simplemobiletools.voicerecorder.helpers.*
import com.simplemobiletools.voicerecorder.models.Events
@ -72,14 +72,14 @@ class RecorderService : Service() {
return
}
val baseFolder = if (isQPlus()) {
cacheDir
} else {
val defaultFolder = File(config.saveRecordingsFolder)
if (!defaultFolder.exists()) {
defaultFolder.mkdir()
}
val baseFolder = if (isRPlus() && !hasProperStoredFirstParentUri(defaultFolder.absolutePath)) {
cacheDir
} else {
defaultFolder.absolutePath
}
@ -93,7 +93,12 @@ class RecorderService : Service() {
setAudioEncodingBitRate(config.bitrate)
setAudioSamplingRate(44100)
if (!isQPlus() && isPathOnSD(currFilePath)) {
if (isRPlus() && hasProperStoredFirstParentUri(currFilePath)) {
val fileUri = createDocumentUriUsingFirstParentTreeUri(currFilePath)
createSAFFileSdk30(currFilePath)
val outputFileDescriptor = contentResolver.openFileDescriptor(fileUri, "w")!!.fileDescriptor
setOutputFile(outputFileDescriptor)
} else if (!isRPlus() && isPathOnSD(currFilePath)) {
var document = getDocumentFile(currFilePath.getParentPath())
document = document?.createFile("", currFilePath.getFilenameFromPath())
@ -132,7 +137,7 @@ class RecorderService : Service() {
release()
ensureBackgroundThread {
if (isQPlus()) {
if (isRPlus() && !hasProperStoredFirstParentUri(currFilePath) ) {
addFileInNewMediaStore()
} else {
addFileInLegacyMediaStore()
@ -184,7 +189,7 @@ class RecorderService : Service() {
put(Media.DISPLAY_NAME, storeFilename)
put(Media.TITLE, storeFilename)
put(Media.MIME_TYPE, storeFilename.getMimeType())
put(Media.RELATIVE_PATH, "${Environment.DIRECTORY_MUSIC}/Recordings")
put(Media.RELATIVE_PATH, getDefaultRecordingsRelativePath())
}
val newUri = contentResolver.insert(audioCollection, newSongDetails)