diff --git a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt index 7ec8536..ba394ef 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt @@ -441,23 +441,28 @@ abstract class BatchesFolder( } fun checkIfFolderIsAccessible(): Boolean { - return when (type) { - BatchType.INTERNAL -> true - BatchType.CUSTOM -> getCustomDefinedFolder().canWrite() && getCustomDefinedFolder().canRead() - BatchType.MEDIA -> { - if (SUPPORTS_SCOPED_STORAGE) { - return true - } + try { + return when (type) { + BatchType.INTERNAL -> true + BatchType.CUSTOM -> getCustomDefinedFolder().canWrite() && getCustomDefinedFolder().canRead() + BatchType.MEDIA -> { + if (SUPPORTS_SCOPED_STORAGE) { + return true + } - return PermissionHelper.hasGranted( - context, - Manifest.permission.READ_EXTERNAL_STORAGE - ) && - PermissionHelper.hasGranted( - context, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) + return PermissionHelper.hasGranted( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) && + PermissionHelper.hasGranted( + context, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + } } + } catch (error: NullPointerException) { + error.printStackTrace() + return false } } @@ -586,6 +591,22 @@ abstract class BatchesFolder( // 350 MiB sounds like a good default return 350 * 1024 * 1024 } + + fun canAccessFolder(context: Context, uri: Uri): Boolean { + return try { + // Create temp file + val tempFile = DocumentFile.fromSingleUri(context, uri)!!.createFile( + "application/octet-stream", + "temp" + )!! + tempFile.delete() + + true + } catch (error: RuntimeException) { + error.printStackTrace() + false + } + } } } diff --git a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt index 6783ef2..89e21f4 100644 --- a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt @@ -65,6 +65,8 @@ abstract class IntervalRecorderService : if (!batchesFolder.checkIfFolderIsAccessible()) { onBatchesFolderNotAccessible() + + throw AvoidErrorDialogError() } createTimer() diff --git a/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt index fa9f2a8..a49c60d 100644 --- a/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt @@ -1,13 +1,11 @@ package app.myzel394.alibi.services import android.annotation.SuppressLint -import android.app.ActivityManager import android.app.Notification import android.content.Intent import android.os.Binder import android.os.IBinder import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat.getSystemService import androidx.lifecycle.LifecycleService import app.myzel394.alibi.NotificationHelper import app.myzel394.alibi.enums.RecorderState @@ -63,7 +61,16 @@ abstract class RecorderService : LifecycleService() { startForegroundService() changeState(RecorderState.RECORDING) - start() + + try { + start() + } catch (error: RuntimeException) { + error.printStackTrace() + + if (error !is AvoidErrorDialogError) { + onError() + } + } } suspend fun stopRecording() { @@ -194,4 +201,9 @@ abstract class RecorderService : LifecycleService() { } } } + + + // Throw this error if you show a dialog yourself. + // This will prevent the service from showing their generic error dialog. + class AvoidErrorDialogError : RuntimeException() } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index 99a43d7..ce137e9 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -356,17 +356,16 @@ fun RecorderEventsHandler( progress = processingProgress, ) - if (showRecorderError) - RecorderErrorDialog( - onClose = { - showRecorderError = false - }, - ) - if (showBatchesInaccessibleError) BatchesInaccessibleDialog( onClose = { showBatchesInaccessibleError = false }, ) + else if (showRecorderError) + RecorderErrorDialog( + onClose = { + showRecorderError = false + }, + ) } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt index 2d815ca..4d40c27 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt @@ -2,7 +2,6 @@ package app.myzel394.alibi.ui.components.SettingsScreen.Tiles import android.Manifest import android.content.Intent -import android.net.Uri import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -22,6 +21,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.filled.CameraAlt import androidx.compose.material.icons.filled.Cancel +import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Mic @@ -62,6 +62,7 @@ import app.myzel394.alibi.R import app.myzel394.alibi.dataStore import app.myzel394.alibi.db.AppSettings import app.myzel394.alibi.helpers.AudioBatchesFolder +import app.myzel394.alibi.helpers.BatchesFolder import app.myzel394.alibi.helpers.VideoBatchesFolder import app.myzel394.alibi.ui.AUDIO_RECORDING_BATCHES_SUBFOLDER_NAME import app.myzel394.alibi.ui.MEDIA_SUBFOLDER_NAME @@ -91,22 +92,44 @@ fun SaveFolderTile( val context = LocalContext.current val dataStore = context.dataStore + var showError by remember { mutableStateOf(false) } + val successMessage = stringResource(R.string.ui_settings_option_saveFolder_success) fun updateValue(path: String?) { - if (settings.saveFolder != null && settings.saveFolder != RECORDER_MEDIA_SELECTED_VALUE) { - runCatching { - context.contentResolver.releasePersistableUriPermission( - Uri.parse(settings.saveFolder), - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) + if (path != null && path != RECORDER_MEDIA_SELECTED_VALUE) { + context.contentResolver.takePersistableUriPermission( + path.toUri(), + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + + if (!BatchesFolder.canAccessFolder(context, path.toUri())) { + showError = true + + runCatching { + context.contentResolver.releasePersistableUriPermission( + path.toUri(), + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } + return } } - if (path != null && path != RECORDER_MEDIA_SELECTED_VALUE) { - context.contentResolver.takePersistableUriPermission( - Uri.parse(path), - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) + runCatching { + // Clean up + val grantedURIs = context.contentResolver.persistedUriPermissions; + + grantedURIs.forEach { permission -> + if (permission.uri == path?.toUri()) { + return@forEach + } + + context.contentResolver.releasePersistableUriPermission( + permission.uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } } scope.launch { @@ -130,6 +153,45 @@ fun SaveFolderTile( } } + if (showError) { + AlertDialog( + onDismissRequest = { + showError = false + }, + icon = { + Icon( + Icons.Default.Error, + contentDescription = null, + ) + }, + title = { + Text(stringResource(R.string.ui_error_occurred_title)) + }, + confirmButton = { + Button(onClick = { + showError = false + }) { + Text(stringResource(R.string.dialog_close_neutral_label)) + } + }, + text = { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + Text( + stringResource(R.string.ui_settings_option_saveFolder_batchesFolderInaccessible_error), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface, + ) + } + } + ) + } + if (selectionVisible) { SelectionSheet( sheetState = selectionSheetState, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec7cd34..94ec34c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,4 +222,6 @@ Get Support If you have any questions, feedback or face any issues, please don\'t hesitate to contact me. I\'m happy to help you! Below is a list of ways to get in touch with me: 1 Minute + There was an error + Alibi can\'t access this folder. Please select a different one \ No newline at end of file