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`.
This commit is contained in:
Peter Storch
2022-01-09 19:34:19 +01:00
parent f64c1feb1a
commit 1099102e29
6 changed files with 74 additions and 64 deletions
@@ -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<Intent> 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;
}
}
@@ -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));
});
}
}
@@ -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() {
@@ -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();
@@ -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;
}
}
+1
View File
@@ -639,4 +639,5 @@ limitations under the License.
<string name="field_set_primary">Primary</string>
<string name="field_set_secondary">Secondary</string>
<string name="field_remove_from_layout">Remove</string>
<string name="no_compatible_file_manager_installed">No compatible file manager installed</string>
</resources>