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; package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.DocumentsContract; 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.appcompat.app.AppCompatActivity;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.exporter.ExportActivity; import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
public abstract class DirectoryChooserActivity extends AppCompatActivity { 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -26,9 +37,13 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
DocumentFile directoryUri = configureDirectoryChooserIntent(intent); DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
if (!isDirectoryValid(directoryUri)) { 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 { } else {
startActivity(createNextActivityIntent(directoryUri.getUri())); //TODO Refactor to DocumentFile startActivity(createNextActivityIntent(directoryUri.getUri()));
finish(); finish();
} }
} }
@@ -37,25 +52,13 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
return directoryUri != null && directoryUri.isDirectory() && directoryUri.canRead(); return directoryUri != null && directoryUri.isDirectory() && directoryUri.canRead();
} }
@Override protected void onActivityResultCustom(@NonNull Intent resultData) {
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) { Uri directoryUri = resultData.getData();
super.onActivityResult(requestCode, resultCode, resultData); int takeFlags = resultData.getFlags();
onActivityResultCustom(requestCode, resultCode, resultData); 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) { startActivity(createNextActivityIntent(directoryUri));
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();
} }
/** /**
@@ -84,8 +87,7 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
protected DocumentFile configureDirectoryChooserIntent(Intent intent) { protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
super.configureDirectoryChooserIntent(intent); super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
return IntentUtils.toDocumentFile(this, PreferencesUtils.getDefaultExportDirectoryUri());
return PreferencesUtils.getDefaultExportDirectoryUri(this);
} }
@Override @Override
@@ -105,29 +107,15 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
public static class DefaultTrackExportDirectoryChooserActivity extends DirectoryChooserActivity { public static class DefaultTrackExportDirectoryChooserActivity extends DirectoryChooserActivity {
@Override @Override
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) { protected void onActivityResultCustom(@NonNull Intent resultData) {
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) { Uri oldDirectoryUri = PreferencesUtils.getDefaultExportDirectoryUri();
DocumentFile oldDirectoryUri = PreferencesUtils.getDefaultExportDirectoryUri(this); Uri newDirectoryUri = resultData.getData();
switch (resultCode) { if (oldDirectoryUri != null && !newDirectoryUri.equals(oldDirectoryUri)) {
case RESULT_OK: IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), oldDirectoryUri);
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;
}
} }
finish();
PreferencesUtils.setDefaultExportDirectoryUri(newDirectoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), newDirectoryUri, resultData.getFlags());
} }
@Override @Override
@@ -139,9 +127,9 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
protected DocumentFile configureDirectoryChooserIntent(Intent intent) { protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
super.configureDirectoryChooserIntent(intent); super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 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) { 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; return null;
@@ -149,7 +137,6 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
@Override @Override
protected Intent createNextActivityIntent(Uri directoryUri) { protected Intent createNextActivityIntent(Uri directoryUri) {
finish();
return null; return null;
} }
} }
@@ -1,5 +1,8 @@
package de.dennisguse.opentracks.settings; package de.dennisguse.opentracks.settings;
import android.content.Intent;
import android.content.UriPermission;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
@@ -7,10 +10,12 @@ import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.IntentUtils;
public class ImportExportSettingsFragment extends PreferenceFragmentCompat { public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
@@ -35,7 +40,7 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
setExportDirectorySummary(); setExportDirectorySummary();
Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key)); Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key));
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext())); instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri());
} }
private void setExportTrackFileFormatOptions() { private void setExportTrackFileFormatOptions() {
@@ -64,13 +69,14 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
private void setExportDirectorySummary() { private void setExportDirectorySummary() {
Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key)); Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key));
instantExportDirectoryPreference.setSummaryProvider(preference -> { 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 //Use same value for not set as Androidx ListPreference and EditTextPreference
if (directory == null) { if (directory == null) {
return getString(R.string.not_set); 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); 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() { public static TrackFileFormat getExportTrackFileFormat() {
@@ -535,26 +535,29 @@ public class PreferencesUtils {
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain); 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); String singleExportDirectory = getString(R.string.settings_default_export_directory_key, null);
if (singleExportDirectory == null) { if (singleExportDirectory == null) {
return null; return null;
} }
try { try {
return DocumentFile.fromTreeUri(context, Uri.parse(singleExportDirectory)); Log.d(TAG, "DefaultExportDirectoryUri: " + singleExportDirectory);
return Uri.parse(singleExportDirectory);
} catch (Exception e) { } 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; return null;
} }
public static void setDefaultExportDirectoryUri(Uri directoryUri) { public static void setDefaultExportDirectoryUri(Uri directoryUri) {
String value = directoryUri != null ? directoryUri.toString() : null; String value = directoryUri != null ? directoryUri.toString() : null;
Log.d(TAG, "Set ExportDirectoryUri: " + directoryUri);
setString(R.string.settings_default_export_directory_key, value); setString(R.string.settings_default_export_directory_key, value);
} }
public static boolean isDefaultExportDirectoryUri(Context context) { public static boolean isDefaultExportDirectoryUri() {
return getDefaultExportDirectoryUri(context) != null; return getDefaultExportDirectoryUri() != null;
} }
public static int getLayoutColumnsByDefault() { public static int getLayoutColumnsByDefault() {
@@ -31,9 +31,9 @@ public class ExportUtils {
private static final String TAG = ExportUtils.class.getSimpleName(); private static final String TAG = ExportUtils.class.getSimpleName();
public static void postWorkoutExport(Context context, Track.Id trackId, ExportServiceResultReceiver resultReceiver) { public static void postWorkoutExport(Context context, Track.Id trackId, ExportServiceResultReceiver resultReceiver) {
if (PreferencesUtils.shouldInstantExportAfterWorkout(context)) { if (PreferencesUtils.shouldInstantExportAfterWorkout()) {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(); TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(context); DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri());
if (directory == null || !directory.canWrite()) { if (directory == null || !directory.canWrite()) {
Toast.makeText(context, R.string.export_cannot_write_to_dir, Toast.LENGTH_LONG).show(); 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.Intent;
import android.content.UriPermission; import android.content.UriPermission;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
@@ -79,15 +80,27 @@ public class IntentUtils {
context.getContentResolver().takePersistableUriPermission(directoryUri, newFlags); context.getContentResolver().takePersistableUriPermission(directoryUri, newFlags);
} }
public static void releaseDirectoryAccessPermission(Context context, final DocumentFile documentFile) { public static void releaseDirectoryAccessPermission(Context context, final Uri documentUri) {
if (documentFile == null) { if (documentUri == null) {
return; return;
} }
final Uri documentUri = documentFile.getUri();
context.getContentResolver().getPersistedUriPermissions().stream() context.getContentResolver().getPersistedUriPermissions().stream()
.map(UriPermission::getUri) .map(UriPermission::getUri)
.filter(documentUri::equals) .filter(documentUri::equals)
.forEach(u -> context.getContentResolver().releasePersistableUriPermission(u, 0)); .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_primary">Primary</string>
<string name="field_set_secondary">Secondary</string> <string name="field_set_secondary">Secondary</string>
<string name="field_remove_from_layout">Remove</string> <string name="field_remove_from_layout">Remove</string>
<string name="no_compatible_file_manager_installed">No compatible file manager installed</string>
</resources> </resources>