From e5f9b038e8e29be5e5383562fc6383d93ffc8dfa Mon Sep 17 00:00:00 2001 From: Dmitry Valter Date: Thu, 29 Apr 2021 01:54:34 +0300 Subject: [PATCH] Fix import/export for Android R+ Don't use DocumentFile and pick file via ACTION_OPEN_DOCUMENT so a right Uri with the right permissions is passed to the import class. --- .../twidere/util/DataImportExportUtils.java | 8 ++-- .../twidere/activity/DataExportActivity.kt | 48 +++++++++++++------ .../twidere/activity/DataImportActivity.kt | 26 +++++----- 3 files changed, 49 insertions(+), 33 deletions(-) diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java index dc1966a10..d4fcc7ef3 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java @@ -84,8 +84,8 @@ public class DataImportExportUtils implements Constants { | FLAG_HOST_MAPPING | FLAG_KEYBOARD_SHORTCUTS | FLAG_FILTERS | FLAG_TABS; @WorkerThread - public static void exportData(final Context context, @NonNull final DocumentFile dst, final int flags) throws IOException { - try (OutputStream fos = context.getContentResolver().openOutputStream(dst.getUri()); + public static void exportData(final Context context, @NonNull final Uri dst, final int flags) throws IOException { + try (OutputStream fos = context.getContentResolver().openOutputStream(dst); ZipOutputStream zos = new ZipOutputStream(fos)) { if (hasFlag(flags, FLAG_PREFERENCES)) { exportSharedPreferencesData(zos, context, SHARED_PREFERENCES_NAME, ENTRY_PREFERENCES, @@ -199,9 +199,9 @@ public class DataImportExportUtils implements Constants { } } - public static void importData(final Context context, final DocumentFile src, final int flags) throws IOException { + public static void importData(final Context context, final Uri src, final int flags) throws IOException { if (src == null) throw new FileNotFoundException(); - try (InputStream inputStream = context.getContentResolver().openInputStream(src.getUri()); + try (InputStream inputStream = context.getContentResolver().openInputStream(src); ZipInputStream zipInputStream = new ZipInputStream(inputStream) ) { ZipEntry entry; diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt index e5d25081c..4056e46be 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.os.AsyncTask import android.os.Build import android.os.Bundle +import android.provider.DocumentsContract import android.util.Log import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.DialogFragment @@ -17,7 +18,6 @@ import org.mariotaku.twidere.fragment.DataExportImportTypeSelectorDialogFragment import org.mariotaku.twidere.fragment.ProgressDialogFragment import org.mariotaku.twidere.util.DataImportExportUtils import java.io.File -import java.io.IOException import java.text.SimpleDateFormat import java.util.* @@ -67,12 +67,7 @@ class DataExportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra return } if (task == null || task!!.status != AsyncTask.Status.RUNNING) { - val folder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - DocumentFile.fromTreeUri(this, path) - } else { - DocumentFile.fromFile(File(path.path)) - } - task = ExportSettingsTask(this, folder, flags) + task = ExportSettingsTask(this, path, flags) task!!.execute() } } @@ -85,30 +80,53 @@ class DataExportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState == null) { - val intent = Intent(this, FileSelectorActivity::class.java) - intent.action = IntentConstants.INTENT_ACTION_PICK_DIRECTORY + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + i + } else { + val i = Intent(this, FileSelectorActivity::class.java) + i.action = IntentConstants.INTENT_ACTION_PICK_DIRECTORY + i + } startActivityForResult(intent, REQUEST_PICK_DIRECTORY) } } internal class ExportSettingsTask( private val activity: DataExportActivity, - private val folder: DocumentFile?, + private val folderUri: Uri?, private val flags: Int ) : AsyncTask() { override fun doInBackground(vararg params: Any): Boolean? { - if (folder == null || !folder.isDirectory) return false + if (folderUri == null) return false val sdf = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US) val fileName = String.format("Twidere_Settings_%s.zip", sdf.format(Date())) - val file = folder.findFile(fileName) ?: folder.createFile("application/zip", fileName) - ?: return false // val file = File(folder, fileName) // file.delete() return try { - DataImportExportUtils.exportData(activity, file, flags) + val createdDocumentUri = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val docId = DocumentsContract.getTreeDocumentId(folderUri) + val dirUri = DocumentsContract.buildDocumentUriUsingTree(folderUri, docId) + DocumentsContract.createDocument( + activity.contentResolver, + dirUri, + "application/zip", + fileName) + } else { + val folder = DocumentFile.fromFile(File(folderUri.path!!)) + val file = folder.findFile(fileName) + ?: folder.createFile("application/zip", fileName) ?: return false + file.uri + }) + ?: return false + + DataImportExportUtils.exportData(activity, createdDocumentUri, flags) true - } catch (e: IOException) { + } catch (e: Throwable) { Log.w(LOGTAG, e) false } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt index af32b31fa..157d10f22 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt @@ -76,12 +76,7 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra return } if (importSettingsTask == null || importSettingsTask!!.status != AsyncTask.Status.RUNNING) { - val file = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - DocumentFile.fromSingleUri(this, path) - } else { - DocumentFile.fromFile(File(path.path)) - } - importSettingsTask = ImportSettingsTask(this, file, flags) + importSettingsTask = ImportSettingsTask(this, path, flags) importSettingsTask!!.execute() } } @@ -94,25 +89,28 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState == null) { - val intent = Intent(this, FileSelectorActivity::class.java) - intent.action = INTENT_ACTION_PICK_FILE + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val i = Intent(Intent.ACTION_OPEN_DOCUMENT) + i.type = "*/*" + i + } else { + val i = Intent(this, FileSelectorActivity::class.java) + i.action = INTENT_ACTION_PICK_FILE + i + } startActivityForResult(intent, REQUEST_PICK_FILE) } } internal class ImportSettingsTask( private val activity: DataImportActivity, - private val file: DocumentFile?, + private val uri: Uri?, private val flags: Int ) : AsyncTask() { override fun doInBackground(vararg params: Any): Boolean? { - if (file == null) { - return false - } - if (!file.isFile) return false return try { - DataImportExportUtils.importData(activity, file, flags) + DataImportExportUtils.importData(activity, uri, flags) true } catch (e: IOException) { Log.w(LOGTAG, e)