From 26e3000b76c7b7eb5fc9a82628a6169f79b4875c Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Costa Date: Tue, 26 Mar 2024 21:58:14 -0300 Subject: [PATCH] Add option to also intercept ACTION_VIEW intents Fixes #19 --- .idea/.gitignore | 1 + .idea/kotlinScripting.xml | 8 --- app/src/main/AndroidManifest.xml | 23 +++++++- .../apps/share2storage/DetailsActivity.kt | 7 +-- .../apps/share2storage/MainActivity.kt | 2 +- .../apps/share2storage/SettingsViewModel.kt | 58 ++++++++++++++++--- .../share2storage/screens/SettingsScreen.kt | 51 +++++++++++++++- .../utils/SharedPreferenceKeys.kt | 5 +- app/src/main/res/values-pt-rBR/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 10 files changed, 133 insertions(+), 26 deletions(-) delete mode 100644 .idea/kotlinScripting.xml diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d3352..15161eb 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,4 @@ # Default ignored files /shelf/ /workspace.xml +/deploymentTargetDropDown.xml \ No newline at end of file diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml deleted file mode 100644 index 26da98e..0000000 --- a/.idea/kotlinScripting.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - 2147483647 - - - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 48e55b4..c16f060 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/DetailsActivity.kt b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/DetailsActivity.kt index 4e4daf6..736e4bd 100644 --- a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/DetailsActivity.kt +++ b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/DetailsActivity.kt @@ -70,9 +70,9 @@ class DetailsActivity : ComponentActivity() { private fun getPreferences() { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) skipFileDetails = - sharedPreferences.getBoolean(SharedPreferenceKeys.skipFileDetailsKey, false) + sharedPreferences.getBoolean(SharedPreferenceKeys.SKIP_FILE_DETAILS_KEY, false) val defaultSaveLocationRaw = - sharedPreferences.getString(SharedPreferenceKeys.defaultSaveLocationKey, null) + sharedPreferences.getString(SharedPreferenceKeys.DEFAULT_SAVE_LOCATION_KEY, null) Log.d("details] defaultSaveLocationRaw", defaultSaveLocationRaw.toString()) defaultSaveLocation = if (defaultSaveLocationRaw != null) Uri.parse(defaultSaveLocationRaw) else null @@ -84,8 +84,7 @@ class DetailsActivity : ComponentActivity() { if (intent.action == Intent.ACTION_SEND) { val fileUri: Uri? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) intent.getParcelableExtra( - Intent.EXTRA_STREAM, - Uri::class.java + Intent.EXTRA_STREAM, Uri::class.java ) else @Suppress("DEPRECATION") intent.getParcelableExtra(Intent.EXTRA_STREAM) Log.d("fileUri", fileUri.toString()) diff --git a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/MainActivity.kt b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/MainActivity.kt index 676f1e6..1fd9202 100644 --- a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/MainActivity.kt +++ b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/MainActivity.kt @@ -46,7 +46,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() super.onCreate(savedInstanceState) - settingsViewModel.receiveContext(applicationContext) + settingsViewModel.initializeWithContext(applicationContext) settingsViewModel.assignSaveLocationDirIntent(getSaveLocationDirIntent) settingsViewModel.initPreferences() diff --git a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/SettingsViewModel.kt b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/SettingsViewModel.kt index 0ea1024..66ecc18 100644 --- a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/SettingsViewModel.kt @@ -17,10 +17,15 @@ package com.mateusrodcosta.apps.share2storage +import android.content.ComponentName import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED +import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED +import android.content.pm.PackageManager.DONT_KILL_APP import android.net.Uri import android.util.Log import androidx.activity.result.ActivityResultLauncher @@ -34,8 +39,10 @@ import kotlinx.coroutines.flow.StateFlow class SettingsViewModel : ViewModel() { private lateinit var sharedPreferences: SharedPreferences + private lateinit var packageManager: PackageManager private lateinit var contentResolver: ContentResolver private lateinit var getSaveLocationDirIntent: ActivityResultLauncher + private lateinit var packageName: String private val _defaultSaveLocation = MutableStateFlow(null) val defaultSaveLocation: StateFlow = _defaultSaveLocation @@ -43,6 +50,10 @@ class SettingsViewModel : ViewModel() { private val _skipFileDetails = MutableStateFlow(false) val skipFileDetails: StateFlow = _skipFileDetails + + private val _interceptActionViewIntents = MutableStateFlow(false) + val interceptActionViewIntents: StateFlow = _interceptActionViewIntents + fun assignSaveLocationDirIntent(intent: ActivityResultLauncher) { getSaveLocationDirIntent = intent } @@ -51,14 +62,16 @@ class SettingsViewModel : ViewModel() { return getSaveLocationDirIntent } - fun receiveContext(context: Context) { + fun initializeWithContext(context: Context) { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) contentResolver = context.contentResolver + packageManager = context.packageManager + packageName = context.packageName } fun initPreferences() { val spDefaultSaveLocationRaw = - sharedPreferences.getString(SharedPreferenceKeys.defaultSaveLocationKey, null) + sharedPreferences.getString(SharedPreferenceKeys.DEFAULT_SAVE_LOCATION_KEY, null) val spDefaultSaveLocation = if (spDefaultSaveLocationRaw != null) try { val uri = Uri.parse(spDefaultSaveLocationRaw) @@ -72,22 +85,30 @@ class SettingsViewModel : ViewModel() { else null val spSkipFileDetails = - sharedPreferences.getBoolean(SharedPreferenceKeys.skipFileDetailsKey, false) + sharedPreferences.getBoolean(SharedPreferenceKeys.SKIP_FILE_DETAILS_KEY, false) Log.d("settings] initSharedPreferences] skipFileDetails", spSkipFileDetails.toString()) + val spInterceptActionViewIntents = sharedPreferences.getBoolean( + SharedPreferenceKeys.INTERCEPT_ACTION_VIEW_INTENTS_KEY, false + ) + Log.d( + "settings] initSharedPreferences] interceptActionViewIntents", + spInterceptActionViewIntents.toString() + ) _defaultSaveLocation.value = spDefaultSaveLocation _skipFileDetails.value = spSkipFileDetails + _interceptActionViewIntents.value = spInterceptActionViewIntents } fun updateDefaultSaveLocation(value: Uri?) { val currentSaveLocationRaw = - sharedPreferences.getString(SharedPreferenceKeys.defaultSaveLocationKey, null) + sharedPreferences.getString(SharedPreferenceKeys.DEFAULT_SAVE_LOCATION_KEY, null) sharedPreferences.edit(commit = true) { if (value != null) putString( - SharedPreferenceKeys.defaultSaveLocationKey, value.toString() + SharedPreferenceKeys.DEFAULT_SAVE_LOCATION_KEY, value.toString() ) - else remove(SharedPreferenceKeys.defaultSaveLocationKey) + else remove(SharedPreferenceKeys.DEFAULT_SAVE_LOCATION_KEY) } @@ -126,9 +147,32 @@ class SettingsViewModel : ViewModel() { fun updateSkipFileDetails(value: Boolean) { sharedPreferences.edit(commit = true) { - putBoolean(SharedPreferenceKeys.skipFileDetailsKey, value) + putBoolean(SharedPreferenceKeys.SKIP_FILE_DETAILS_KEY, value) } _skipFileDetails.value = value } + + fun updateInterceptActionViewIntents(value: Boolean) { + sharedPreferences.edit(commit = true) { + putBoolean(SharedPreferenceKeys.INTERCEPT_ACTION_VIEW_INTENTS_KEY, value) + Log.e("settings] updateInterceptActionViewIntents", value.toString()) + + try { + val component = ComponentName( + packageName, + "com.mateusrodcosta.apps.share2storage.DetailsActivityActionViewIntentInterceptor" + ) + packageManager.setComponentEnabledSetting( + component, + if (value) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED, + DONT_KILL_APP + ) + + _interceptActionViewIntents.value = value + } catch (e: Exception) { + Log.e("settings] updateInterceptActionViewIntents", e.toString()) + } + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/screens/SettingsScreen.kt b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/screens/SettingsScreen.kt index 1675c17..eaa6dbf 100644 --- a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/screens/SettingsScreen.kt +++ b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/screens/SettingsScreen.kt @@ -63,25 +63,29 @@ import kotlinx.coroutines.flow.StateFlow fun SettingsScreenPreview() { val mockDefaultSaveLocation = MutableStateFlow(null) val mockSkipFileDetails = MutableStateFlow(false) + val mockInterceptActionViewIntents = MutableStateFlow(false) SettingsScreenContent( spDefaultSaveLocation = mockDefaultSaveLocation, spSkipFileDetails = mockSkipFileDetails, + spInterceptActionViewIntents = mockInterceptActionViewIntents, ) } @Composable fun SettingsScreen(navController: NavController, settingsViewModel: SettingsViewModel) { - SettingsScreenContent( - navController = navController, + SettingsScreenContent(navController = navController, spDefaultSaveLocation = settingsViewModel.defaultSaveLocation, spSkipFileDetails = settingsViewModel.skipFileDetails, + spInterceptActionViewIntents = settingsViewModel.interceptActionViewIntents, launchFilePicker = { settingsViewModel.getSaveLocationDirIntent().launch(null) }, clearSaveDirectory = { settingsViewModel.clearSaveDirectory() }, updateSkipFileDetails = { value: Boolean -> settingsViewModel.updateSkipFileDetails(value) }, - ) + updateInterceptActionViewIntents = { value: Boolean -> + settingsViewModel.updateInterceptActionViewIntents(value) + }) } @OptIn(ExperimentalMaterial3Api::class) @@ -90,9 +94,11 @@ fun SettingsScreenContent( navController: NavController? = null, spDefaultSaveLocation: StateFlow, spSkipFileDetails: StateFlow, + spInterceptActionViewIntents: StateFlow, launchFilePicker: () -> Unit = {}, clearSaveDirectory: () -> Unit = {}, updateSkipFileDetails: (Boolean) -> Unit = {}, + updateInterceptActionViewIntents: (Boolean) -> Unit = {}, ) { val settingsPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) @@ -123,6 +129,12 @@ fun SettingsScreenContent( paddingValues = settingsPadding, ) AppBasicDivider() + InterceptActionViewIntentsSetting( + updateInterceptActionViewIntents = updateInterceptActionViewIntents, + spInterceptActionViewIntents = spInterceptActionViewIntents, + paddingValues = settingsPadding, + ) + AppBasicDivider() DefaultSaveLocationSetting( launchFilePicker = launchFilePicker, clearSaveDirectory = clearSaveDirectory, @@ -167,6 +179,39 @@ fun SkipFileDetailsSetting( } } + +@Composable +fun InterceptActionViewIntentsSetting( + updateInterceptActionViewIntents: (Boolean) -> Unit, + spInterceptActionViewIntents: StateFlow, + paddingValues: PaddingValues, +) { + + val interceptActionViewIntents by spInterceptActionViewIntents.collectAsState() + + + Row(modifier = Modifier + .clickable { updateInterceptActionViewIntents(!interceptActionViewIntents) } + .padding(paddingValues) + .heightIn(min = 48.dp), + verticalAlignment = Alignment.CenterVertically) { + Column(modifier = Modifier.weight(1.0f)) { + Text( + stringResource(id = R.string.settings_intercept_action_view_intents), + style = MaterialTheme.typography.titleLarge, + ) + Text( + stringResource(R.string.settings_intercept_action_view_intents_info), + style = MaterialTheme.typography.bodyLarge + ) + } + Spacer(modifier = Modifier.width(8.dp)) + Switch(checked = interceptActionViewIntents, onCheckedChange = { value -> + updateInterceptActionViewIntents(value) + }) + } +} + @Composable fun DefaultSaveLocationSetting( launchFilePicker: () -> Unit, diff --git a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/utils/SharedPreferenceKeys.kt b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/utils/SharedPreferenceKeys.kt index 842f014..b279a49 100644 --- a/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/utils/SharedPreferenceKeys.kt +++ b/app/src/main/kotlin/com/mateusrodcosta/apps/share2storage/utils/SharedPreferenceKeys.kt @@ -18,6 +18,7 @@ package com.mateusrodcosta.apps.share2storage.utils object SharedPreferenceKeys { - const val skipFileDetailsKey: String = "skip_file_details" - const val defaultSaveLocationKey: String = "default_save_location" + const val SKIP_FILE_DETAILS_KEY: String = "skip_file_details" + const val DEFAULT_SAVE_LOCATION_KEY: String = "default_save_location" + const val INTERCEPT_ACTION_VIEW_INTENTS_KEY: String = "intercept_action_view_intents" } \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3d5c85d..bb4bf45 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -17,6 +17,8 @@ Configurações Pular página de Detalhes do arquivo Abrir seletor de arquivos assim que receber arquivo + Intercepta intents do tipo ACTION_VIEW + Permite registrar como uma opção quando aplicativos tentam automaticamente abrir um visualizador de arquivos Local de salvamento padrão Última pasta usada Limpar diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6f73c6..1572bca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,8 @@ Settings Skip File Details page Open file picker as soon as file is received + Intercept ACTION_VIEW intents + Allow registering as an option when apps try to automatically open a file viewer Default save location Last used folder Clear