From 09156c52435daf10d237c1484560fa78fe34e907 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Tue, 18 Dec 2018 17:00:18 -0600 Subject: [PATCH] Use storage access framework for backup files --- app/build.gradle | 4 +- .../astrid/backup/TasksXmlImporter.java | 28 +++-- .../astrid/service/StartupService.java | 22 ++++ .../org/tasks/backup/TasksJsonExporter.java | 92 ++++++--------- .../org/tasks/backup/TasksJsonImporter.java | 45 +++---- .../org/tasks/dialogs/ImportTasksDialog.java | 47 ++++++-- .../java/org/tasks/files/FileExplore.java | 1 + .../main/java/org/tasks/files/FileHelper.java | 110 +++++++++++++++++- .../main/java/org/tasks/jobs/BackupWork.java | 80 +++++++++---- .../tasks/preferences/BasicPreferences.java | 53 +++++---- .../org/tasks/preferences/Preferences.java | 87 +++++++++++--- app/src/main/res/values-bg-rBG/strings.xml | 2 - app/src/main/res/values-ca/strings.xml | 2 - app/src/main/res/values-cs/strings.xml | 2 - app/src/main/res/values-da/strings.xml | 2 - app/src/main/res/values-de/strings.xml | 2 - app/src/main/res/values-el/strings.xml | 2 - app/src/main/res/values-es/strings.xml | 2 - app/src/main/res/values-fi/strings.xml | 2 - app/src/main/res/values-fr/strings.xml | 2 - app/src/main/res/values-gl/strings.xml | 2 - app/src/main/res/values-hu/strings.xml | 2 - app/src/main/res/values-it/strings.xml | 2 - app/src/main/res/values-iw/strings.xml | 2 - app/src/main/res/values-ja/strings.xml | 2 - app/src/main/res/values-ko/strings.xml | 2 - app/src/main/res/values-lt/strings.xml | 2 - app/src/main/res/values-nb/strings.xml | 2 - app/src/main/res/values-nl/strings.xml | 2 - app/src/main/res/values-pl/strings.xml | 2 - app/src/main/res/values-pt-rBR/strings.xml | 2 - app/src/main/res/values-pt/strings.xml | 2 - app/src/main/res/values-ru/strings.xml | 2 - app/src/main/res/values-sk/strings.xml | 2 - app/src/main/res/values-sl-rSI/strings.xml | 2 - app/src/main/res/values-sv/strings.xml | 2 - app/src/main/res/values-th/strings.xml | 2 - app/src/main/res/values-tr/strings.xml | 2 - app/src/main/res/values-uk/strings.xml | 2 - app/src/main/res/values-zh-rCN/strings.xml | 2 - app/src/main/res/values-zh-rTW/strings.xml | 2 - app/src/main/res/values/strings.xml | 6 - 42 files changed, 405 insertions(+), 230 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a816f8344..5b94a3b38 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,8 +19,8 @@ android { defaultConfig { testApplicationId "org.tasks.test" applicationId "org.tasks" - versionCode 545 - versionName "6.3.1" + versionCode 546 + versionName "6.4" targetSdkVersion 28 minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java index 7c7564526..6bc356f72 100755 --- a/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java +++ b/app/src/main/java/com/todoroo/astrid/backup/TasksXmlImporter.java @@ -8,14 +8,14 @@ package com.todoroo.astrid.backup; import android.app.Activity; import android.app.ProgressDialog; import android.content.res.Resources; +import android.net.Uri; import android.os.Handler; import android.text.TextUtils; + import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; -import java.io.FileReader; -import java.io.IOException; -import javax.inject.Inject; + import org.tasks.LocalBroadcastManager; import org.tasks.R; import org.tasks.backup.XmlReader; @@ -35,8 +35,18 @@ import org.tasks.dialogs.DialogBuilder; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; + +import javax.inject.Inject; + import timber.log.Timber; +import static org.tasks.files.FileHelper.fromUri; + public class TasksXmlImporter { private static final String FORMAT2 = "2"; // $NON-NLS-1$ @@ -57,7 +67,7 @@ public class TasksXmlImporter { private int skipCount = 0; private int errorCount = 0; private ProgressDialog progressDialog; - private String input; + private Uri input; @Inject public TasksXmlImporter( @@ -85,7 +95,7 @@ public class TasksXmlImporter { handler.post(() -> progressDialog.setMessage(message)); } - public void importTasks(Activity activity, String input, ProgressDialog progressDialog) { + public void importTasks(Activity activity, Uri input, ProgressDialog progressDialog) { this.activity = activity; this.input = input; this.progressDialog = progressDialog; @@ -110,7 +120,9 @@ public class TasksXmlImporter { private void performImport() throws IOException, XmlPullParserException { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(new FileReader(input)); + InputStream inputStream = fromUri(activity, input); + InputStreamReader reader = new InputStreamReader(inputStream); + xpp.setInput(reader); try { while (xpp.next() != XmlPullParser.END_DOCUMENT) { @@ -135,6 +147,8 @@ public class TasksXmlImporter { } } } finally { + reader.close(); + inputStream.close(); localBroadcastManager.broadcastRefresh(); handler.post( () -> { @@ -154,7 +168,7 @@ public class TasksXmlImporter { .setMessage( activity.getString( R.string.import_summary_message, - input, + "", r.getQuantityString(R.plurals.Ntasks, taskCount, taskCount), r.getQuantityString(R.plurals.Ntasks, importCount, importCount), r.getQuantityString(R.plurals.Ntasks, skipCount, skipCount), diff --git a/app/src/main/java/com/todoroo/astrid/service/StartupService.java b/app/src/main/java/com/todoroo/astrid/service/StartupService.java index f98b65676..61b635fd1 100644 --- a/app/src/main/java/com/todoroo/astrid/service/StartupService.java +++ b/app/src/main/java/com/todoroo/astrid/service/StartupService.java @@ -9,6 +9,8 @@ import static com.google.common.base.Strings.isNullOrEmpty; import android.content.Context; import android.os.Environment; + +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ListMultimap; @@ -46,6 +48,7 @@ public class StartupService { private static final int V5_3_0 = 491; private static final int V6_0_beta_1 = 522; private static final int V6_0_beta_2 = 523; + private static final int V6_4 = 546; private final Database database; private final Preferences preferences; @@ -122,6 +125,9 @@ public class StartupService { if (from < V6_0_beta_2) { migrateGoogleTaskAccount(); } + if (from < V6_4) { + migrateUris(); + } tracker.reportEvent(Tracking.Events.UPGRADE, Integer.toString(from)); } preferences.setCurrentVersion(to); @@ -191,6 +197,22 @@ public class StartupService { } } + private void migrateUris() { + String backupDirectory = preferences.getStringValue(R.string.p_backup_dir); + if (!Strings.isNullOrEmpty(backupDirectory)) { + File file = new File(backupDirectory); + try { + if (file.canWrite()) { + preferences.setUri(R.string.p_backup_dir, file.toURI()); + } else { + preferences.remove(R.string.p_backup_dir); + } + } catch (SecurityException ignored) { + preferences.remove(R.string.p_backup_dir); + } + } + } + private String migrate(String input) { return input .replaceAll( diff --git a/app/src/main/java/org/tasks/backup/TasksJsonExporter.java b/app/src/main/java/org/tasks/backup/TasksJsonExporter.java index ad400e4fa..8a652b76b 100755 --- a/app/src/main/java/org/tasks/backup/TasksJsonExporter.java +++ b/app/src/main/java/org/tasks/backup/TasksJsonExporter.java @@ -1,28 +1,19 @@ package org.tasks.backup; -import static org.tasks.date.DateTimeUtils.newDateTime; - import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; +import android.net.Uri; import android.os.Handler; -import androidx.annotation.Nullable; import android.widget.Toast; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.backup.BackupConstants; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; + import org.tasks.BuildConfig; import org.tasks.R; import org.tasks.data.AlarmDao; @@ -35,9 +26,24 @@ import org.tasks.data.TagDao; import org.tasks.data.TagDataDao; import org.tasks.data.TaskAttachmentDao; import org.tasks.data.UserActivityDao; +import org.tasks.files.FileHelper; import org.tasks.preferences.Preferences; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; import timber.log.Timber; +import static org.tasks.date.DateTimeUtils.newDateTime; + public class TasksJsonExporter { // --- public interface @@ -59,7 +65,7 @@ public class TasksJsonExporter { private int exportCount = 0; private ProgressDialog progressDialog; private Handler handler; - private File backupDirectory; + private Uri backupDirectory; private String latestSetVersionName; @Inject @@ -128,16 +134,18 @@ public class TasksJsonExporter { private void runBackup(ExportType exportType) { try { - String output = setupFile(backupDirectory, exportType); - + String filename = getFileName(exportType); List tasks = taskDao.getAll(); if (tasks.size() > 0) { - doTasksExport(output, tasks); + Uri uri = FileHelper.newFile(context, backupDirectory, "application/json", filename); + OutputStream os = context.getContentResolver().openOutputStream(uri); + doTasksExport(os, tasks); + os.close(); } if (exportType == ExportType.EXPORT_TYPE_MANUAL) { - onFinishExport(output); + onFinishExport(filename); } } catch (IOException e) { Timber.e(e); @@ -153,7 +161,7 @@ public class TasksJsonExporter { } } - private void doTasksExport(String output, List tasks) throws IOException { + private void doTasksExport(OutputStream os, List tasks) throws IOException { List taskBackups = new ArrayList<>(); @@ -186,14 +194,10 @@ public class TasksJsonExporter { caldavDao.getAccounts(), caldavDao.getCalendars())); - File file = new File(output); - file.createNewFile(); - FileOutputStream fos = new FileOutputStream(file); - OutputStreamWriter out = new OutputStreamWriter(fos); + OutputStreamWriter out = new OutputStreamWriter(os); Gson gson = BuildConfig.DEBUG ? new GsonBuilder().setPrettyPrinting().create() : new Gson(); out.write(gson.toJson(data)); out.close(); - fos.close(); exportCount = taskBackups.size(); } @@ -217,38 +221,16 @@ public class TasksJsonExporter { }); } - /** - * Creates directories if necessary and returns fully qualified file - * - * @return output file name - */ - private String setupFile(File directory, ExportType exportType) throws IOException { - if (directory != null) { - // Check for /sdcard/astrid directory. If it doesn't exist, make it. - if (directory.exists() || directory.mkdir()) { - String fileName; - switch (exportType) { - case EXPORT_TYPE_SERVICE: - fileName = String.format(BackupConstants.BACKUP_FILE_NAME, getDateForExport()); - break; - case EXPORT_TYPE_MANUAL: - fileName = String.format(BackupConstants.EXPORT_FILE_NAME, getDateForExport()); - break; - case EXPORT_TYPE_ON_UPGRADE: - fileName = String.format(BackupConstants.UPGRADE_FILE_NAME, latestSetVersionName); - break; - default: - throw new IllegalArgumentException("Invalid export type"); // $NON-NLS-1$ - } - return directory.getAbsolutePath() + File.separator + fileName; - } else { - // Unable to make the /sdcard/astrid directory. - throw new IOException( - context.getString(R.string.DLG_error_sdcard, directory.getAbsolutePath())); - } - } else { - // Unable to access the sdcard because it's not in the mounted state. - throw new IOException(context.getString(R.string.DLG_error_sdcard_general)); + private String getFileName(ExportType type) { + switch (type) { + case EXPORT_TYPE_SERVICE: + return String.format(BackupConstants.BACKUP_FILE_NAME, getDateForExport()); + case EXPORT_TYPE_MANUAL: + return String.format(BackupConstants.EXPORT_FILE_NAME, getDateForExport()); + case EXPORT_TYPE_ON_UPGRADE: + return String.format(BackupConstants.UPGRADE_FILE_NAME, latestSetVersionName); + default: + throw new UnsupportedOperationException("Unhandled export type"); } } diff --git a/app/src/main/java/org/tasks/backup/TasksJsonImporter.java b/app/src/main/java/org/tasks/backup/TasksJsonImporter.java index 1c0555087..604f42d52 100644 --- a/app/src/main/java/org/tasks/backup/TasksJsonImporter.java +++ b/app/src/main/java/org/tasks/backup/TasksJsonImporter.java @@ -3,17 +3,16 @@ package org.tasks.backup; import android.app.Activity; import android.app.ProgressDialog; import android.content.res.Resources; +import android.net.Uri; import android.os.Handler; -import com.google.common.io.CharStreams; + import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; -import java.io.FileReader; -import java.io.IOException; -import javax.inject.Inject; + import org.tasks.LocalBroadcastManager; import org.tasks.R; import org.tasks.data.Alarm; @@ -40,8 +39,17 @@ import org.tasks.data.TaskAttachmentDao; import org.tasks.data.UserActivity; import org.tasks.data.UserActivityDao; import org.tasks.dialogs.DialogBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import javax.inject.Inject; + import timber.log.Timber; +import static org.tasks.files.FileHelper.fromUri; + public class TasksJsonImporter { private final TagDataDao tagDataDao; @@ -64,7 +72,7 @@ public class TasksJsonImporter { private int importCount = 0; private int skipCount = 0; private ProgressDialog progressDialog; - private String input; + private Uri input; @Inject public TasksJsonImporter( @@ -100,30 +108,21 @@ public class TasksJsonImporter { handler.post(() -> progressDialog.setMessage(message)); } - public void importTasks(Activity activity, String input, ProgressDialog progressDialog) { + public void importTasks(Activity activity, Uri input, ProgressDialog progressDialog) { this.activity = activity; this.input = input; this.progressDialog = progressDialog; handler = new Handler(); - new Thread( - () -> { - try { - performImport(); - } catch (IOException e) { - Timber.e(e); - } - }) - .start(); + new Thread(this::performImport).start(); } - private void performImport() throws IOException { - FileReader fileReader = new FileReader(input); - String string = CharStreams.toString(fileReader); - fileReader.close(); + private void performImport() { Gson gson = new Gson(); - JsonObject input = gson.fromJson(string, JsonObject.class); + InputStream is = fromUri(activity, this.input); + InputStreamReader reader = new InputStreamReader(is); + JsonObject input = gson.fromJson(reader, JsonObject.class); try { JsonElement data = input.get("data"); @@ -200,6 +199,10 @@ public class TasksJsonImporter { } importCount++; } + reader.close(); + is.close(); + } catch (IOException e) { + Timber.e(e); } finally { localBroadcastManager.broadcastRefresh(); handler.post( @@ -220,7 +223,7 @@ public class TasksJsonImporter { .setMessage( activity.getString( R.string.import_summary_message, - input, + "", r.getQuantityString(R.plurals.Ntasks, taskCount, taskCount), r.getQuantityString(R.plurals.Ntasks, importCount, importCount), r.getQuantityString(R.plurals.Ntasks, skipCount, skipCount), diff --git a/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java b/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java index d2f3710b8..b2517ecd7 100644 --- a/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java +++ b/app/src/main/java/org/tasks/dialogs/ImportTasksDialog.java @@ -2,27 +2,39 @@ package org.tasks.dialogs; import android.app.Dialog; import android.app.ProgressDialog; +import android.content.Context; +import android.net.Uri; import android.os.Bundle; +import android.webkit.MimeTypeMap; + import com.todoroo.astrid.backup.TasksXmlImporter; -import javax.inject.Inject; + import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracking; import org.tasks.backup.TasksJsonImporter; +import org.tasks.injection.ForApplication; import org.tasks.injection.InjectingNativeDialogFragment; import org.tasks.injection.NativeDialogFragmentComponent; +import java.io.IOException; + +import javax.inject.Inject; + +import timber.log.Timber; + public class ImportTasksDialog extends InjectingNativeDialogFragment { - private static final String EXTRA_PATH = "extra_path"; + private static final String EXTRA_URI = "extra_uri"; @Inject TasksXmlImporter xmlImporter; @Inject TasksJsonImporter jsonImporter; @Inject DialogBuilder dialogBuilder; @Inject Tracker tracker; + @Inject @ForApplication Context context; - public static ImportTasksDialog newImportTasksDialog(String path) { + public static ImportTasksDialog newImportTasksDialog(Uri data) { ImportTasksDialog importTasksDialog = new ImportTasksDialog(); Bundle args = new Bundle(); - args.putString(EXTRA_PATH, path); + args.putParcelable(EXTRA_URI, data); importTasksDialog.setArguments(args); return importTasksDialog; } @@ -30,21 +42,32 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Bundle arguments = getArguments(); - String path = arguments.getString(EXTRA_PATH); + Uri data = arguments.getParcelable(EXTRA_URI); ProgressDialog progressDialog = dialogBuilder.newProgressDialog(); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setCancelable(false); progressDialog.setIndeterminate(true); progressDialog.show(); setCancelable(false); - if (path.endsWith(".xml")) { - xmlImporter.importTasks(getActivity(), path, progressDialog); - tracker.reportEvent(Tracking.Events.IMPORT_XML); - } else { - jsonImporter.importTasks(getActivity(), path, progressDialog); - tracker.reportEvent(Tracking.Events.IMPORT_JSON); + try { + String extension = MimeTypeMap.getFileExtensionFromUrl(data.getPath()); + switch (extension) { + case "json": + jsonImporter.importTasks(getActivity(), data, progressDialog); + tracker.reportEvent(Tracking.Events.IMPORT_JSON); + break; + case "xml": + xmlImporter.importTasks(getActivity(), data, progressDialog); + tracker.reportEvent(Tracking.Events.IMPORT_XML); + break; + default: + throw new IOException("Invalid file type"); + } + return progressDialog; + } catch (IOException e) { + Timber.e(e); } - return progressDialog; + return null; } @Override diff --git a/app/src/main/java/org/tasks/files/FileExplore.java b/app/src/main/java/org/tasks/files/FileExplore.java index 2f3901c31..b83775677 100644 --- a/app/src/main/java/org/tasks/files/FileExplore.java +++ b/app/src/main/java/org/tasks/files/FileExplore.java @@ -93,6 +93,7 @@ public class FileExplore extends InjectingAppCompatActivity { File file = com.nononsenseapps.filepicker.Utils.getFileForUri(uri); Intent intent = new Intent(); intent.putExtra(directoryMode ? EXTRA_DIRECTORY : EXTRA_FILE, file.getAbsolutePath()); + intent.setData(uri); setResult(Activity.RESULT_OK, intent); } finish(); diff --git a/app/src/main/java/org/tasks/files/FileHelper.java b/app/src/main/java/org/tasks/files/FileHelper.java index 7e48499ef..e0f81a020 100644 --- a/app/src/main/java/org/tasks/files/FileHelper.java +++ b/app/src/main/java/org/tasks/files/FileHelper.java @@ -1,7 +1,5 @@ package org.tasks.files; -import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop; - import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -10,13 +8,94 @@ import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; -import androidx.core.content.FileProvider; + import com.todoroo.astrid.utility.Constants; + +import org.tasks.preferences.BasicPreferences; +import org.tasks.preferences.Preferences; + import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.List; +import javax.annotation.Nullable; + +import androidx.core.content.FileProvider; +import androidx.documentfile.provider.DocumentFile; +import timber.log.Timber; + +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastKitKat; +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop; + public class FileHelper { + public static void newFilePicker(Activity activity, int rc, @Nullable Uri initial, String... mimeTypes) { + if (atLeastKitKat()) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + if (initial != null) { + intent.setData(initial); + } + if (mimeTypes.length == 1) { + intent.setType(mimeTypes[0]); + } else { + intent.setType("*/*"); + if (mimeTypes.length > 1) { + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + } + } + activity.startActivityForResult(intent, rc); + } else { + Intent intent = new Intent(activity, FileExplore.class); + if (initial != null) { + intent.putExtra( + FileExplore.EXTRA_START_PATH, + new File(initial.getPath())); + } + activity.startActivityForResult(intent, rc); + } + } + + public static void newDirectoryPicker(Activity activity, int rc, @Nullable Uri initial) { + if (atLeastLollipop()) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags( + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra("android.content.extra.SHOW_ADVANCED",true); + activity.startActivityForResult(intent, rc); + } else { + Intent intent = new Intent(activity, FileExplore.class); + intent.putExtra(FileExplore.EXTRA_DIRECTORY_MODE, true); + if (initial != null) { + intent.putExtra( + FileExplore.EXTRA_START_PATH, + new File(initial.getPath())); + } + activity.startActivityForResult(intent, rc); + } + } + + public static InputStream fromUri(Context context, Uri uri) { + try { + switch (uri.getScheme()) { + case "content": + return context.getContentResolver().openInputStream(uri); + case "file": + return new FileInputStream(new File(uri.getPath())); + default: + throw new IllegalArgumentException("Unhandled scheme: " + uri.getScheme()); + } + } catch (FileNotFoundException e) { + Timber.e(e); + return null; + } + } + public static String getPathFromUri(Activity activity, Uri uri) { String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = activity.managedQuery(uri, projection, null, null, null); @@ -57,4 +136,29 @@ public class FileHelper { } } } + + public static Uri newFile(Context context, Uri destination, String mimeType, String filename) + throws IOException { + switch (destination.getScheme()) { + case "content": + DocumentFile tree = DocumentFile.fromTreeUri(context, destination); + DocumentFile f1 = tree.createFile(mimeType, filename); + if (f1 == null) { + throw new FileNotFoundException("Failed to create " + filename); + } + return f1.getUri(); + case "file": + File dir = new File(destination.getPath()); + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("Failed to create %s" + dir.getAbsolutePath()); + } + File f2 = new File(dir.getAbsolutePath() + File.separator + filename); + if (f2.createNewFile()) { + return Uri.fromFile(f2); + } + throw new FileNotFoundException("Failed to create " + filename); + default: + throw new IllegalArgumentException("Unknown URI scheme: " + destination.getScheme()); + } + } } diff --git a/app/src/main/java/org/tasks/jobs/BackupWork.java b/app/src/main/java/org/tasks/jobs/BackupWork.java index 2427633e2..3f0162f54 100644 --- a/app/src/main/java/org/tasks/jobs/BackupWork.java +++ b/app/src/main/java/org/tasks/jobs/BackupWork.java @@ -1,33 +1,46 @@ package org.tasks.jobs; -import static com.google.common.collect.Iterables.skip; -import static com.google.common.collect.Lists.newArrayList; -import static com.todoroo.andlib.utility.DateUtilities.now; -import static java.util.Collections.emptyList; - import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.WorkerParameters; +import android.net.Uri; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +import org.tasks.R; +import org.tasks.backup.TasksJsonExporter; +import org.tasks.injection.ForApplication; +import org.tasks.injection.JobComponent; +import org.tasks.preferences.Preferences; + import java.io.File; import java.io.FileFilter; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; + import javax.inject.Inject; -import org.tasks.R; -import org.tasks.backup.TasksJsonExporter; -import org.tasks.injection.ForApplication; -import org.tasks.injection.JobComponent; -import org.tasks.preferences.Preferences; + +import androidx.annotation.NonNull; +import androidx.documentfile.provider.DocumentFile; +import androidx.work.WorkerParameters; import timber.log.Timber; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.skip; +import static com.google.common.collect.Lists.newArrayList; +import static com.todoroo.andlib.utility.DateUtilities.now; +import static java.util.Collections.emptyList; + public class BackupWork extends RepeatingWorker { static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.json"; - static final FileFilter FILE_FILTER = f -> f.getName().matches(BACKUP_FILE_NAME_REGEX); + static final Predicate FILENAME_FILTER = f -> f.matches(BACKUP_FILE_NAME_REGEX); + static final FileFilter FILE_FILTER = f -> FILENAME_FILTER.apply(f.getName()); private static final Comparator BY_LAST_MODIFIED = (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()); + private static final Comparator DOCUMENT_FILE_COMPARATOR = + (d1, d2) -> Long.compare(d2.lastModified(), d1.lastModified()); private static final int DAYS_TO_KEEP_BACKUP = 7; @Inject @ForApplication Context context; @@ -61,6 +74,17 @@ public class BackupWork extends RepeatingWorker { return newArrayList(skip(files, keepNewest)); } + private static List getDeleteList(DocumentFile[] fileArray) { + if (fileArray == null) { + return emptyList(); + } + + List files = Arrays.asList(fileArray); + files = newArrayList(filter(files, file -> FILENAME_FILTER.apply(file.getName()))); + Collections.sort(files, DOCUMENT_FILE_COMPARATOR); + return newArrayList(skip(files, DAYS_TO_KEEP_BACKUP)); + } + @Override protected void inject(JobComponent component) { component.inject(this); @@ -82,17 +106,25 @@ public class BackupWork extends RepeatingWorker { } private void deleteOldBackups() { - File astridDir = preferences.getBackupDirectory(); - if (astridDir == null) { - return; - } - - // grab all backup files, sort by modified date, delete old ones - File[] fileArray = astridDir.listFiles(FILE_FILTER); - for (File file : getDeleteList(fileArray, DAYS_TO_KEEP_BACKUP)) { - if (!file.delete()) { - Timber.e("Unable to delete: %s", file); - } + Uri uri = preferences.getBackupDirectory(); + switch (uri.getScheme()) { + case "content": + DocumentFile dir = DocumentFile.fromTreeUri(context, uri); + for (DocumentFile file : getDeleteList(dir.listFiles())) { + if (!file.delete()) { + Timber.e("Unable to delete: %s", file); + } + } + break; + case "file": + File astridDir = new File(uri.getPath()); + File[] fileArray = astridDir.listFiles(FILE_FILTER); + for (File file : getDeleteList(fileArray, DAYS_TO_KEEP_BACKUP)) { + if (!file.delete()) { + Timber.e("Unable to delete: %s", file); + } + } + break; } } } diff --git a/app/src/main/java/org/tasks/preferences/BasicPreferences.java b/app/src/main/java/org/tasks/preferences/BasicPreferences.java index 259396232..5821279bb 100644 --- a/app/src/main/java/org/tasks/preferences/BasicPreferences.java +++ b/app/src/main/java/org/tasks/preferences/BasicPreferences.java @@ -1,22 +1,17 @@ package org.tasks.preferences; -import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybeanMR1; -import static org.tasks.dialogs.ExportTasksDialog.newExportTasksDialog; -import static org.tasks.dialogs.ImportTasksDialog.newImportTasksDialog; -import static org.tasks.locale.LocalePickerDialog.newLocalePickerDialog; -import static org.tasks.themes.ThemeColor.LAUNCHERS; - import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.preference.Preference; + import com.google.common.base.Strings; import com.todoroo.astrid.core.OldTaskPreferences; import com.todoroo.astrid.reminders.ReminderPreferences; -import java.io.File; -import javax.inject.Inject; + import org.tasks.BuildConfig; import org.tasks.R; import org.tasks.activities.ColorPickerActivity; @@ -28,6 +23,7 @@ import org.tasks.billing.BillingClient; import org.tasks.billing.Inventory; import org.tasks.dialogs.DialogBuilder; import org.tasks.files.FileExplore; +import org.tasks.files.FileHelper; import org.tasks.injection.ActivityComponent; import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.locale.Locale; @@ -37,6 +33,18 @@ import org.tasks.themes.ThemeBase; import org.tasks.themes.ThemeCache; import org.tasks.themes.ThemeColor; +import java.io.File; + +import javax.inject.Inject; + +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybeanMR1; +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastKitKat; +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop; +import static org.tasks.dialogs.ExportTasksDialog.newExportTasksDialog; +import static org.tasks.dialogs.ImportTasksDialog.newImportTasksDialog; +import static org.tasks.locale.LocalePickerDialog.newLocalePickerDialog; +import static org.tasks.themes.ThemeColor.LAUNCHERS; + public class BasicPreferences extends InjectingPreferenceActivity implements LocalePickerDialog.LocaleSelectionHandler { @@ -146,10 +154,7 @@ public class BasicPreferences extends InjectingPreferenceActivity findPreference(R.string.backup_BAc_import) .setOnPreferenceClickListener( preference -> { - Intent intent = new Intent(BasicPreferences.this, FileExplore.class); - intent.putExtra( - FileExplore.EXTRA_START_PATH, preferences.getBackupDirectory().getAbsolutePath()); - startActivityForResult(intent, REQUEST_PICKER); + FileHelper.newFilePicker(BasicPreferences.this, REQUEST_PICKER, preferences.getBackupDirectory()); return false; }); @@ -230,13 +235,19 @@ public class BasicPreferences extends InjectingPreferenceActivity } } else if (requestCode == REQUEST_CODE_BACKUP_DIR) { if (resultCode == RESULT_OK && data != null) { - String dir = data.getStringExtra(FileExplore.EXTRA_DIRECTORY); - preferences.setString(R.string.p_backup_dir, dir); + Uri dir = data.getData(); + if (atLeastLollipop()) { + getContentResolver() + .takePersistableUriPermission( + dir, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + preferences.setString(R.string.p_backup_dir, dir.toString()); updateBackupDirectory(); } } else if (requestCode == REQUEST_PICKER) { if (resultCode == RESULT_OK) { - newImportTasksDialog(data.getStringExtra(FileExplore.EXTRA_FILE)) + newImportTasksDialog(data.getData()) .show(getFragmentManager(), FRAG_TAG_IMPORT_TASKS); } } else { @@ -278,10 +289,8 @@ public class BasicPreferences extends InjectingPreferenceActivity findPreference(getString(R.string.p_backup_dir)) .setOnPreferenceClickListener( p -> { - Intent filesDir = new Intent(BasicPreferences.this, FileExplore.class); - filesDir.putExtra(FileExplore.EXTRA_DIRECTORY_MODE, true); - startActivityForResult(filesDir, REQUEST_CODE_BACKUP_DIR); - return true; + FileHelper.newDirectoryPicker(this, REQUEST_CODE_BACKUP_DIR, preferences.getBackupDirectory()); + return false; }); updateBackupDirectory(); } @@ -291,8 +300,10 @@ public class BasicPreferences extends InjectingPreferenceActivity } private String getBackupDirectory() { - File dir = preferences.getBackupDirectory(); - return dir == null ? "" : dir.getAbsolutePath(); + Uri uri = preferences.getBackupDirectory(); + return uri.getScheme().equals("file") + ? new File(uri.getPath()).getAbsolutePath() + : uri.toString(); } private void setLauncherIcon(int index) { diff --git a/app/src/main/java/org/tasks/preferences/Preferences.java b/app/src/main/java/org/tasks/preferences/Preferences.java index 32342b9ad..9b96d3998 100644 --- a/app/src/main/java/org/tasks/preferences/Preferences.java +++ b/app/src/main/java/org/tasks/preferences/Preferences.java @@ -1,36 +1,47 @@ package org.tasks.preferences; -import static android.content.SharedPreferences.Editor; -import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Sets.newHashSet; -import static java.util.Collections.emptySet; - import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.RingtoneManager; import android.net.Uri; +import android.os.Binder; import android.preference.PreferenceManager; -import androidx.core.app.NotificationCompat; import android.text.TextUtils; + import com.android.billingclient.api.Purchase; +import com.google.common.base.Strings; import com.google.gson.GsonBuilder; import com.todoroo.astrid.activity.BeastModePreferences; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.core.SortHelper; import com.todoroo.astrid.data.Task; -import java.io.File; -import java.util.Collection; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import javax.inject.Inject; + import org.tasks.BuildConfig; import org.tasks.R; import org.tasks.data.TaskAttachment; import org.tasks.injection.ForApplication; import org.tasks.time.DateTime; + +import java.io.File; +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.inject.Inject; + +import androidx.core.app.NotificationCompat; +import androidx.documentfile.provider.DocumentFile; import timber.log.Timber; +import static android.content.SharedPreferences.Editor; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.newHashSet; +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastKitKat; +import static java.util.Collections.emptySet; + public class Preferences { private static final String P_CURRENT_VERSION = "cv"; // $NON-NLS-1$ @@ -259,6 +270,19 @@ public class Preferences { } } + public Uri getUri(int key) { + String uri = getStringValue(key); + return Strings.isNullOrEmpty(uri) ? null : Uri.parse(uri); + } + + public void setUri(int key, java.net.URI uri) { + setString(key, uri.toString()); + } + + public void setUri(int key, Uri uri) { + setString(key, uri.toString()); + } + public void setString(int key, String newValue) { setString(context.getString(key), newValue); } @@ -405,18 +429,43 @@ public class Preferences { return dir + File.separator + name; } - public File getBackupDirectory() { - File directory = null; - String customDir = getStringValue(R.string.p_backup_dir); - if (permissionChecker.canWriteToExternalStorage() && !TextUtils.isEmpty(customDir)) { - directory = new File(customDir); + public Uri getBackupDirectory() { + Uri uri = getUri(R.string.p_backup_dir); + if (uri != null) { + switch (uri.getScheme()) { + case "file": + File file = new File(uri.getPath()); + try { + if (file.canWrite()) { + return uri; + } + } catch (SecurityException ignored) { + } + break; + case "content": + if (hasWritePermission(context, uri)) { + return uri; + } + break; + } } - if (directory == null || !directory.exists()) { - directory = getDefaultFileLocation("backups"); + if (atLeastKitKat()) { + return DocumentFile.fromFile(context.getExternalFilesDir(null)) + .createDirectory("backups") + .getUri(); + } else { + return Uri.fromFile(getDefaultFileLocation("backups")); } + } - return directory; + private boolean hasWritePermission(Context context, Uri uri) { + return PackageManager.PERMISSION_GRANTED + == context.checkUriPermission( + uri, + Binder.getCallingPid(), + Binder.getCallingUid(), + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } public int getNotificationDefaults() { diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index c419e51cf..344de3486 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -21,8 +21,6 @@ %4$s вече съществуват\n %5$s имат грешки\n Прочитане на задачи %d... - Нямате достъп до папка: %s - SD картата не може да бъде достъпена! Tasks Разрешение Сигурни ли сте че искате да отхвърлите промените си? Продължаване на редактиране diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 8b5ad23e4..895bb201c 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -14,8 +14,6 @@ Sense tasques que exportar Resum de Restauració S\'està llegint la tasca %d - No es pot accedir a la carpeta: %s - No s\'ha pogut accedir a la targeta SD. Permís de l\'Tasks Voleu suprimir aquesta tasca? Temps (hores : minuts) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 257cacf5a..0c1c977e5 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -17,8 +17,6 @@ Souhrn obnovy Soubor %1$s obsahuje %2$s.\n\n %3$s importováno,\n %4$s již existuje\n %5$s je chybných\n Načítávání úkolu %d... - Chyba v přístupu k adresáři: %s - Chyba v přístupu k SD kartě! Tasks Práva Smazat tento úkol? Čas (hodin : minut) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 33dd68aa1..09e9da594 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -16,8 +16,6 @@ Opsummering af gendannelse Filen %1$s indeholdt %2$s.\n\n %3$s importeret,\n %4$s findes allerede\n %5$s indeholdt fejl\n Læser opgave %d... - Kan ikke få adgang til mappen: %s - Kan ikke få adgang til dig SD-kort! Tasks Tilladelser Slet denne opgave? Tid (timer : minutter) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 589e55d70..38f34c0ff 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -16,8 +16,6 @@ Zusammenfassung wiederherstellen Datei %1$s enthielt %2$s Aufgaben.\n\n %3$s wurden importiert,\n %4$s waren schon vorhanden (übersprungen)\n %5$s waren fehlerhaft\n Aufgabe %d wird gelesen … - Ordner konnte nicht geöffnet werden: %s - Auf die SD-Karte konnte nicht zugegriffen werden! Tasks Zugriffsrechte Änderungen wirklich verwerfen? Weiter bearbeiten diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 4953fa7ca..be9f120e8 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -19,8 +19,6 @@ %4$s υπάρχει ήδηt\n %5$s έχει σφάλματα\n Ανάγνωση εργασίας %d... - Δεν υπάρχει πρόσβαση στον φάκελο: %s - Δεν υπάρχει πρόσβαση στην SD κάρτα! Άδεια εργασιών Διαγραφή εργασίας; Ώρα (ώρες : λεπτά) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 21e331975..20afbf37f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -18,8 +18,6 @@ Resumen de restauración El fichero %1$s contenia %2$s.\n\n %3$s importadas\n %4$s ya existian\n %5$s tenian errores\n Leyendo tarea %d... - No se puede acceder a la carpeta: %s - ¡No se pudo acceder a su tarjeta de memoria SD! Permisos de Tasks ¿Quieres descartar los cambios? Seguir editando diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 35dab7287..a6700c1db 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -21,8 +21,6 @@ %4$s jo olemassa\n %5$s virheitä\n Luetaan tehtävää %d... - Ei pääsyä kansioon: %s - Ei pääsyä SD kortillesi! Tasks käyttöoikeus Oletko varma että haluat hylätä muutokset? Jatka muokkausta diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 7e075d2fb..afcb8ddc9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -18,8 +18,6 @@ Résumé de la restauration Le fichier %1$s contenait %2$s.\n\n %3$s importées,\n %4$s existent déjà\n %5$s avaient des erreurs\n Lecture de la tâche %d… - Impossible d\'accéder au dossier : %s - Impossible d\'accéder à la carte SD ! Permissions de Tasks Êtes-vous sûr de vouloir annuler vos modifications ? Continuer l\'édition diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 0c04db0d4..8a064489a 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -18,8 +18,6 @@ Resumen de restauración El fichero %1$s contenia %2$s.\n\n %3$s importadas\n %4$s ya existian\n %5$s tenian errores\n Leyendo tarea %d... - No se puede acceder a la carpeta: %s - ¡No se pudo acceder a su tarjeta de memoria SD! Permisos de Tasks ¿Quieres descartar los cambios? Seguir editando diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index fe0949a62..9fe9adc8b 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -21,8 +21,6 @@ %4$s már létezett\n %5$s hibás\n %d feladat olvasása... - Könyvtár nem elérhető: %s - SD kártya nem elérhető Tasks Engedélyek Biztosan el akarja vetni a változtatásokat? Vissza a szerkesztéshez diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 370c0e050..b9c873ce1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -18,8 +18,6 @@ Ripristina sommario Il File %1$s contiene %2$s.\n\n %3$s importati\n %4$s esiste già\n %5$s contiene errori\n Lettura compito %d... - Impossibile accedere alla cartella: %s - Impossibile accedere alla scheda SD! Permessi attività Sicuro di voler annullare tutte le modifiche? Continua modifica diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index d8289b3c1..e524eca7a 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -18,8 +18,6 @@ סיכום פעולת השחזור \"הקובץ \' %1$s\' הכיל %2$s משימות: \n\n %3$s יובאו,\n %4$s היו קיימות כבר,\nוב־%5$s היו שגיאות.\n\" קוראת משימה %d... - לא ניתן לגשת לתיקיה: %s - לא ניתן לגשת לכרטיס ה־SD שלך! הרשאות אסטריד להתעלם מהשינויים ? המשך לערוך diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index bc94fbbeb..c15b3f369 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -18,8 +18,6 @@ 復元の概要 ファイル %1$s の %2$s 中、\n\n成功: %3$s\n既に存在: %4$s\n失敗: %5$s\n タスク %d を読み込み中 - フォルダ %s を開けません - SDカードにアクセスできません タスクの読み込み 変更を破棄してもよろしいですか? 編集を続ける diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 8cb55a062..3211f25d1 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -21,8 +21,6 @@ %4$s 개 이미 존재,\n %5$s 개 에러 발생\n 할일 %d 읽는 중... - 폴더에 접근 불가: %s - SD 카드에 접근할 수 없습니다! Tasks 권한 변경 사항을 버리시겠습니까? 계속 편집 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 89c3b80ad..ba98aac8d 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -18,8 +18,6 @@ Atstatyti suvestinę Failas %1$s turėjo %2$s.\n\n %3$s suimportuota,\n %4$s jau egzistavo\n %5$s turėjo klaidų\n Nuskaitoma užduotis %d... - Nėra prieigos prie aplanko: %s - Nėra prieigos prie SD kortelės! Tasks leidimas Ar tikrai norite atmesti pakeitimus? Tęsti redagavimą diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index b22a9242b..2734a2894 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -14,8 +14,6 @@ Gjennopprettingssammendrag Filen %1$s inneholder %2$s.\n\n %3$s importert,\n %4$s eksisterer allerede\n %5$s inneholdt feil\n Leser oppgave %d... - Får ikke tilgang til mappen: %s - Ditt SD-kort er ikke tilgjengelig! Tasks Tillatelse Slett denne oppgaven? Tid (timer : minutter) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3852e4770..3d75a70e4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -18,8 +18,6 @@ Samenvatting herstellen Bestand %1$s bevat %2$s.\n\n %3$s geïmporteerd,\n %4$s bestaan al\n %5$s had fouten\n Taak %d lezen... - Toegang geweigerd tot map: %s - Toegang tot SD-kaart geweigerd! Taak toestemmingen Weet u zeker dat u de wijzigingen niet wilt opslaan? Blijf editen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f3da9506b..96df4a3ed 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -18,8 +18,6 @@ Podsumowanie odzyskiwania Plik %1$s zawiera %2$s.\n\n %3$s zaimportowanych,\n %4$s już istnieje\n %5$s zawiera błędy\n Czytanie zadania %d... - Brak dostępu do folderu: %s - Brak dostępu do Twojej karty SD! Uprawnienia Tasks Czy jesteś pewien, że chcesz porzucić zmiany? Kontynuuj edycję diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d59be6dcc..8c0343449 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -20,8 +20,6 @@ %4$s já existiam\n %5$s tinham erros\n Lendo tarefa %d... - Não foi possível acessar a pasta: %s - Não foi possível acessar seu cartão SD! Permissões do Tasks Você tem certeza que deseja descartar suas mudanças? Continuar editando diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5efd310a9..2865100be 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -20,8 +20,6 @@ %4$s já existiam\n %5$s possuem erros\n A ler tarefa %d... - Não é possível aceder à pasta: %s - Não é possível aceder ao seu cartão SD! Permissões do Tasks Tem a certeza que deseja descartar as alterações? Continuar a editar diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1a97b0715..ac241d4d4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -18,8 +18,6 @@ Итог восстановления Файл %1$s содержал %2$s.\n\n %3$s импортировано,\n %4$s существовало\n %5$s с ошибками\n Чтение задачи %d… - Нет доступа к папке:%s - Нет доступа к карте памяти! Разрешения Tasks Вы действительно хотите отказаться от сделанных изменений? Продолжить редактировать diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index a3e2ad924..14b74ef76 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -18,8 +18,6 @@ Obnoviť zhrnutie Súbor %1$s obsahuje %2$s.\n\n %3$s importované,\n %4$s už existuje\n%5$s je chybných\n Čítanie úloh %d... - Chyba v prístupe do priečinka. %s - Chyba v prístupe k SD karte! Povolenia Úloh Naozaj chceš zmazať zmeny? Pokračuj v úpravách diff --git a/app/src/main/res/values-sl-rSI/strings.xml b/app/src/main/res/values-sl-rSI/strings.xml index 3c7d43a93..8824867ae 100644 --- a/app/src/main/res/values-sl-rSI/strings.xml +++ b/app/src/main/res/values-sl-rSI/strings.xml @@ -19,8 +19,6 @@ %4$s že obstaja\n %5$s imel napake\n Berem opravek %d... - Datoteka:%s ni dostopna - Tvoja SD kartica ni dostopna. Dovoljenje Opravkom Zbrišem ta opravek? Čas (ure : minute) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 864c7305d..0cc08e989 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -18,8 +18,6 @@ Återställningssammanfattning Filen %1$s innehöll %2$s.\n\n %3$s importerade,\n %4$s existerar redan\n %5$s hade fel\n Läser uppgift %d... - Mappåtkomst nekad: %s - SD-kort ej tillgängligt! Tasks Tillstånd Är du säker på att du inte vill spara dina ändringar? Fortsätt redigera diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 6d564057d..20609517d 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -9,8 +9,6 @@ ล้างภาพ สำรองข้อมูล กำลังอ่านแผนงาน %d... - ไม่สามารถเข้าถึงแฟ้ม: %s - ไม่สามารถเข้าถึง SD Card ได้! ลบงานนี้? เวลา (ชั่วโมง : นาที) คลิก เพื่อตั้งค่า diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index bc8ce0556..a27c3e668 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -22,8 +22,6 @@ %5$s hatalar var %d görev okunuyor.. - %s dizinine erişilemedi. - Bellek kartınıza erişilemiyor! Tasks İzni Değişikliklerinizi gözden çıkarmak istediğinize emin misiniz? Düzenlemeyi sürdür diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f6d53f332..ff3aa515b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -21,8 +21,6 @@ %4$s вже існує\n %5$s має помилку\n Читання завдання %d... - Немає доступу до папки: %s - Немає доступу до карти SD! Дозволи Tasks Дійсно хочете скасувати ваші зміни? Продовжити редагування diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cbd6eb7b9..9c70f6cc2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -18,8 +18,6 @@ 还原摘要 文件 %1$s 包含 %2$s 个任务。\n\n %3$s 个已导入,\n %4$s 个已存在\n %5$s 个存在错误\n 读取任务 %d... - 无法开启文件夹:%s - 无法访问您的 SD 卡! 清单小助理权限 确定放弃你的改变吗? 继续编辑 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9ab89834c..c28ab78f7 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -16,8 +16,6 @@ 還原摘要 檔案 %1$s 已包含 %2$s.\n\n %3$s 已匯入,\n %4$s 已存在\n %5$s 有問題\n 讀取工作 %d... - 無法開啟資料夾: %s - 無法存取您的SD卡! Tasks 權限 確認刪除? 時間 (小時:分鐘) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb6545e42..e25854ab4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,12 +46,6 @@ File %1$s contained %2$s.\n\n Reading task %d… - - Cannot access folder: %s - - - Cannot access your SD card! -