diff --git a/app/src/main/java/androidx/documentfile/provider/DocumentFileHelper.java b/app/src/main/java/androidx/documentfile/provider/DocumentFileHelper.java new file mode 100644 index 000000000..122b748dd --- /dev/null +++ b/app/src/main/java/androidx/documentfile/provider/DocumentFileHelper.java @@ -0,0 +1,41 @@ +package androidx.documentfile.provider; + +import android.content.Context; +import android.net.Uri; +import android.provider.DocumentsContract; + +import androidx.annotation.RequiresApi; + +import org.signal.core.util.logging.Log; + +/** + * Located in androidx package as {@link TreeDocumentFile} is package protected. + */ +public class DocumentFileHelper { + + private static final String TAG = Log.tag(DocumentFileHelper.class); + + /** + * System implementation swallows the exception and we are having problems with the rename. This inlines the + * same call and logs the exception. Note this implementation does not update the passed in document file like + * the system implementation. Do not use the provided document file after calling this method. + * + * @return true if rename successful + */ + @RequiresApi(21) + public static boolean renameTo(Context context, DocumentFile documentFile, String displayName) { + if (documentFile instanceof TreeDocumentFile) { + Log.d(TAG, "Renaming document directly"); + try { + final Uri result = DocumentsContract.renameDocument(context.getContentResolver(), documentFile.getUri(), displayName); + return result != null; + } catch (Exception e) { + Log.w(TAG, "Unable to rename document file", e); + return false; + } + } else { + Log.d(TAG, "Letting OS rename document: " + documentFile.getClass().getSimpleName()); + return documentFile.renameTo(displayName); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java index b78376413..e9ec4f2f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java @@ -1,11 +1,13 @@ package org.thoughtcrime.securesms.jobs; +import android.annotation.SuppressLint; import android.net.Uri; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.documentfile.provider.DocumentFile; +import androidx.documentfile.provider.DocumentFileHelper; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -29,8 +31,8 @@ import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.service.NotificationController; import org.thoughtcrime.securesms.util.BackupUtil; -import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; @@ -51,9 +53,15 @@ public final class LocalBackupJobApi29 extends BaseJob { public static final String TEMP_BACKUP_FILE_PREFIX = ".backup"; public static final String TEMP_BACKUP_FILE_SUFFIX = ".tmp"; - private static final int MAX_VERIFY_ATTEMPTS = 5; - private static final int MAX_RENAME_ATTEMPTS = 5; - private static final long WAIT_FOR_SCOPED_STORAGE = TimeUnit.SECONDS.toMillis(2); + private static final int MAX_STORAGE_ATTEMPTS = 5; + + private static final long[] WAIT_FOR_SCOPED_STORAGE = new long[] { + TimeUnit.SECONDS.toMillis(0), + TimeUnit.SECONDS.toMillis(2), + TimeUnit.SECONDS.toMillis(10), + TimeUnit.SECONDS.toMillis(20), + TimeUnit.SECONDS.toMillis(30) + }; LocalBackupJobApi29(@NonNull Parameters parameters) { super(parameters); @@ -173,28 +181,31 @@ public final class LocalBackupJobApi29 extends BaseJob { Boolean valid = null; int attempts = 0; - while (attempts < MAX_VERIFY_ATTEMPTS && valid == null) { - try { - valid = BackupVerifier.verifyFile(context.getContentResolver().openInputStream(temporaryFile.getUri()), backupPassword, finishedEvent.getCount()); - } catch (FileNotFoundException e) { - Log.w(TAG, "Unable to find backup file, attempt: " + (attempts + 1) + "/" + MAX_VERIFY_ATTEMPTS); - ThreadUtil.sleep(WAIT_FOR_SCOPED_STORAGE); + while (attempts < MAX_STORAGE_ATTEMPTS && valid == null) { + ThreadUtil.sleep(WAIT_FOR_SCOPED_STORAGE[attempts]); + + try (InputStream cipherStream = context.getContentResolver().openInputStream(temporaryFile.getUri())) { + valid = BackupVerifier.verifyFile(cipherStream, backupPassword, finishedEvent.getCount()); + } catch (IOException e) { attempts++; + Log.w(TAG, "Unable to find backup file, attempt: " + attempts + "/" + MAX_STORAGE_ATTEMPTS); } } + return valid != null ? valid : false; } + @SuppressLint("NewApi") private void renameBackup(String fileName, DocumentFile temporaryFile) throws IOException { int attempts = 0; - while (attempts < MAX_RENAME_ATTEMPTS && !temporaryFile.renameTo(fileName)) { - Log.w(TAG, "Unable to rename backup file, attempt: " + (attempts + 1) + "/" + MAX_RENAME_ATTEMPTS); - ThreadUtil.sleep(WAIT_FOR_SCOPED_STORAGE); + while (attempts < MAX_STORAGE_ATTEMPTS && !DocumentFileHelper.renameTo(context, temporaryFile, fileName)) { + ThreadUtil.sleep(WAIT_FOR_SCOPED_STORAGE[attempts]); attempts++; + Log.w(TAG, "Unable to rename backup file, attempt: " + attempts + "/" + MAX_STORAGE_ATTEMPTS); } - if (attempts >= MAX_RENAME_ATTEMPTS) { + if (attempts >= MAX_STORAGE_ATTEMPTS) { Log.w(TAG, "Failed to rename temp file"); throw new IOException("Renaming temporary backup file failed!"); }