From 1099102e29eaa16ced6f0ad9944516f39f98b22e Mon Sep 17 00:00:00 2001 From: Peter Storch Date: Sun, 9 Jan 2022 19:34:19 +0100 Subject: [PATCH] Change handling of Uri and DocumentFile handling for #1080 The Uris from Intent.getData() and DocumentFile.getUri() seems to be different. I think PreferencesUtils should only work with the original Uri as well as the permission handling logic. IntentUtils.toDocumentFile can be used to convert it to a DocumentFile later on to actually work with. Adds "No file manager found" message if no activity with OPEN_DOCUMENT_TREE is found. #1095 Removed deprecation of `startActivityForResult`. --- .../importer/DirectoryChooserActivity.java | 85 ++++++++----------- .../ImportExportSettingsFragment.java | 12 ++- .../opentracks/settings/PreferencesUtils.java | 17 ++-- .../opentracks/util/ExportUtils.java | 4 +- .../opentracks/util/IntentUtils.java | 19 ++++- src/main/res/values/strings.xml | 1 + 6 files changed, 74 insertions(+), 64 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/DirectoryChooserActivity.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/DirectoryChooserActivity.java index 5772f1f43..4a3c07508 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/DirectoryChooserActivity.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/DirectoryChooserActivity.java @@ -1,23 +1,34 @@ package de.dennisguse.opentracks.io.file.importer; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.DocumentsContract; +import android.widget.Toast; -import androidx.annotation.Nullable; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.documentfile.provider.DocumentFile; +import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.io.file.exporter.ExportActivity; import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.settings.PreferencesUtils; public abstract class DirectoryChooserActivity extends AppCompatActivity { - private static final int DIRECTORY_PICKER_REQUEST_CODE = 6; + protected final ActivityResultLauncher directoryIntentLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { + onActivityResultCustom(result.getData()); + } + finish(); + }); @Override public void onCreate(Bundle savedInstanceState) { @@ -26,9 +37,13 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); DocumentFile directoryUri = configureDirectoryChooserIntent(intent); if (!isDirectoryValid(directoryUri)) { - startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE); + try { + directoryIntentLauncher.launch(intent); + } catch (final ActivityNotFoundException exception) { + Toast.makeText(this, R.string.no_compatible_file_manager_installed, Toast.LENGTH_LONG).show(); + } } else { - startActivity(createNextActivityIntent(directoryUri.getUri())); //TODO Refactor to DocumentFile + startActivity(createNextActivityIntent(directoryUri.getUri())); finish(); } } @@ -37,25 +52,13 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity { return directoryUri != null && directoryUri.isDirectory() && directoryUri.canRead(); } - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) { - super.onActivityResult(requestCode, resultCode, resultData); - onActivityResultCustom(requestCode, resultCode, resultData); - } + protected void onActivityResultCustom(@NonNull Intent resultData) { + Uri directoryUri = resultData.getData(); + int takeFlags = resultData.getFlags(); + takeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + getContentResolver().takePersistableUriPermission(directoryUri, takeFlags); - protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) { - if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) { - if (resultCode == Activity.RESULT_OK && resultData != null) { - Uri directoryUri = resultData.getData(); - - int takeFlags = resultData.getFlags(); - takeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - getContentResolver().takePersistableUriPermission(directoryUri, takeFlags); - - startActivity(createNextActivityIntent(directoryUri)); - } - } - finish(); + startActivity(createNextActivityIntent(directoryUri)); } /** @@ -84,8 +87,7 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity { protected DocumentFile configureDirectoryChooserIntent(Intent intent) { super.configureDirectoryChooserIntent(intent); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - - return PreferencesUtils.getDefaultExportDirectoryUri(this); + return IntentUtils.toDocumentFile(this, PreferencesUtils.getDefaultExportDirectoryUri()); } @Override @@ -105,29 +107,15 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity { public static class DefaultTrackExportDirectoryChooserActivity extends DirectoryChooserActivity { @Override - protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) { - if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) { - DocumentFile oldDirectoryUri = PreferencesUtils.getDefaultExportDirectoryUri(this); - switch (resultCode) { - case RESULT_OK: - if (resultData != null) { - Uri newDirectoryUri = resultData.getData(); - if (oldDirectoryUri != null && !newDirectoryUri.equals(oldDirectoryUri.getUri())) { - IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), oldDirectoryUri); - } - - PreferencesUtils.setDefaultExportDirectoryUri(newDirectoryUri); - IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), newDirectoryUri, resultData.getFlags()); - } - break; - case RESULT_CANCELED: - IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), oldDirectoryUri); - PreferencesUtils.setDefaultExportDirectoryUri(null); - break; - } - + protected void onActivityResultCustom(@NonNull Intent resultData) { + Uri oldDirectoryUri = PreferencesUtils.getDefaultExportDirectoryUri(); + Uri newDirectoryUri = resultData.getData(); + if (oldDirectoryUri != null && !newDirectoryUri.equals(oldDirectoryUri)) { + IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), oldDirectoryUri); } - finish(); + + PreferencesUtils.setDefaultExportDirectoryUri(newDirectoryUri); + IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), newDirectoryUri, resultData.getFlags()); } @Override @@ -139,9 +127,9 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity { protected DocumentFile configureDirectoryChooserIntent(Intent intent) { super.configureDirectoryChooserIntent(intent); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - if (PreferencesUtils.isDefaultExportDirectoryUri(this)) { + if (PreferencesUtils.isDefaultExportDirectoryUri()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri()); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri()); } } return null; @@ -149,7 +137,6 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity { @Override protected Intent createNextActivityIntent(Uri directoryUri) { - finish(); return null; } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java index df53242a8..26efc67b9 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java @@ -1,5 +1,8 @@ package de.dennisguse.opentracks.settings; +import android.content.Intent; +import android.content.UriPermission; +import android.net.Uri; import android.os.Bundle; import androidx.documentfile.provider.DocumentFile; @@ -7,10 +10,12 @@ import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import java.util.List; import java.util.Locale; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.io.file.TrackFileFormat; +import de.dennisguse.opentracks.util.IntentUtils; public class ImportExportSettingsFragment extends PreferenceFragmentCompat { @@ -35,7 +40,7 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat { setExportDirectorySummary(); Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key)); - instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext())); + instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri()); } private void setExportTrackFileFormatOptions() { @@ -64,13 +69,14 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat { private void setExportDirectorySummary() { Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key)); instantExportDirectoryPreference.setSummaryProvider(preference -> { - DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(getContext()); + Uri directoryUri = PreferencesUtils.getDefaultExportDirectoryUri(); + DocumentFile directory = IntentUtils.toDocumentFile(getContext(), directoryUri); //Use same value for not set as Androidx ListPreference and EditTextPreference if (directory == null) { return getString(R.string.not_set); } - return directory.getUri().toString() + (directory.canWrite() ? "" : getString(R.string.export_dir_not_writable)); + return directoryUri.toString() + (directory.canWrite() ? "" : getString(R.string.export_dir_not_writable)); }); } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 8e0f42844..28e9eaf0e 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -497,9 +497,9 @@ public class PreferencesUtils { } - public static boolean shouldInstantExportAfterWorkout(Context context) { + public static boolean shouldInstantExportAfterWorkout() { final boolean INSTANT_POST_WORKOUT_EXPORT_DEFAULT = resources.getBoolean(R.bool.post_workout_export_enabled_default); - return getBoolean(R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT) && isDefaultExportDirectoryUri(context); + return getBoolean(R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT) && isDefaultExportDirectoryUri(); } public static TrackFileFormat getExportTrackFileFormat() { @@ -535,26 +535,29 @@ public class PreferencesUtils { PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain); } - public static DocumentFile getDefaultExportDirectoryUri(Context context) { + public static Uri getDefaultExportDirectoryUri() { String singleExportDirectory = getString(R.string.settings_default_export_directory_key, null); if (singleExportDirectory == null) { return null; } try { - return DocumentFile.fromTreeUri(context, Uri.parse(singleExportDirectory)); + Log.d(TAG, "DefaultExportDirectoryUri: " + singleExportDirectory); + return Uri.parse(singleExportDirectory); } catch (Exception e) { - Log.w(TAG, "Could not decode default export directory: " + e.getMessage()); + Log.w(TAG, "Could not parse default export directory Uri: " + e.getMessage()); } return null; } public static void setDefaultExportDirectoryUri(Uri directoryUri) { String value = directoryUri != null ? directoryUri.toString() : null; + Log.d(TAG, "Set ExportDirectoryUri: " + directoryUri); + setString(R.string.settings_default_export_directory_key, value); } - public static boolean isDefaultExportDirectoryUri(Context context) { - return getDefaultExportDirectoryUri(context) != null; + public static boolean isDefaultExportDirectoryUri() { + return getDefaultExportDirectoryUri() != null; } public static int getLayoutColumnsByDefault() { diff --git a/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java b/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java index 58ebfd869..ba826276c 100644 --- a/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java @@ -31,9 +31,9 @@ public class ExportUtils { private static final String TAG = ExportUtils.class.getSimpleName(); public static void postWorkoutExport(Context context, Track.Id trackId, ExportServiceResultReceiver resultReceiver) { - if (PreferencesUtils.shouldInstantExportAfterWorkout(context)) { + if (PreferencesUtils.shouldInstantExportAfterWorkout()) { TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(); - DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(context); + DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri()); if (directory == null || !directory.canWrite()) { Toast.makeText(context, R.string.export_cannot_write_to_dir, Toast.LENGTH_LONG).show(); diff --git a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java index 3d7947d26..da7d5b8f8 100644 --- a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.content.UriPermission; import android.net.Uri; +import android.util.Log; import androidx.documentfile.provider.DocumentFile; @@ -79,15 +80,27 @@ public class IntentUtils { context.getContentResolver().takePersistableUriPermission(directoryUri, newFlags); } - public static void releaseDirectoryAccessPermission(Context context, final DocumentFile documentFile) { - if (documentFile == null) { + public static void releaseDirectoryAccessPermission(Context context, final Uri documentUri) { + if (documentUri == null) { return; } - final Uri documentUri = documentFile.getUri(); + context.getContentResolver().getPersistedUriPermissions().stream() .map(UriPermission::getUri) .filter(documentUri::equals) .forEach(u -> context.getContentResolver().releasePersistableUriPermission(u, 0)); } + public static DocumentFile toDocumentFile(Context context, Uri directoryUri) { + if (directoryUri == null) { + return null; + } + try { + return DocumentFile.fromTreeUri(context, directoryUri); + } catch (Exception e) { + Log.w(TAG, "Could not decode directory: " + e.getMessage()); + } + return null; + } + } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ece3f8480..defbbeb1f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -639,4 +639,5 @@ limitations under the License. Primary Secondary Remove + No compatible file manager installed