diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index d72d6c6041..94ed89e627 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -339,7 +339,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity .forData(inputStream, fileSize == null ? 0 : fileSize) .withMimeType(mimeType) .withFileName(fileName) - .createForMultipleSessionsOnDisk(context); + .createForMultipleSessionsOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e)); } catch (IOException ioe) { Log.w(TAG, ioe); return null; diff --git a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java index 9a9568a31a..48dc54808a 100644 --- a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java +++ b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java @@ -50,7 +50,7 @@ public class AudioRecorder { captureUri = BlobProvider.getInstance() .forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0) .withMimeType(MediaUtil.AUDIO_AAC) - .createForSingleSessionOnDisk(context); + .createForSingleSessionOnDisk(context, e -> Log.w(TAG, "Error during recording", e)); audioCodec = new AudioCodec(); audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1])); diff --git a/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java index e28b694343..5cf573bafd 100644 --- a/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java +++ b/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java @@ -121,7 +121,7 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity return BlobProvider.getInstance() .forData(data) .withMimeType(MediaUtil.IMAGE_GIF) - .createForSingleSessionOnDisk(GiphyActivity.this); + .createForSingleSessionOnDisk(GiphyActivity.this, e -> Log.w(TAG, "Failed to write to disk.", e)); } catch (InterruptedException | ExecutionException | IOException e) { Log.w(TAG, e); return null; diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index a5aa3d4a35..46e31d6e3c 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -297,7 +297,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple Uri uri = BlobProvider.getInstance() .forData(data) .withMimeType(MediaUtil.IMAGE_JPEG) - .createForSingleSessionOnDisk(this); + .createForSingleSessionOnDisk(this, e -> Log.w(TAG, "Failed to write to disk.", e)); return new Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java index 7588dd92de..49eb505d54 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java @@ -460,13 +460,12 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl Uri uri = BlobProvider.getInstance() .forData(baos.toByteArray()) .withMimeType(MediaUtil.IMAGE_JPEG) - .createForSingleSessionOnDisk(context); + .createForSingleSessionOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e)); Media updated = new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), baos.size(), media.getBucketId(), media.getCaption()); updatedMedia.add(updated); renderTimer.split("item"); - } catch (InterruptedException | ExecutionException | IOException e) { Log.w(TAG, "Failed to render image. Using base image."); updatedMedia.add(media); diff --git a/src/org/thoughtcrime/securesms/providers/BlobProvider.java b/src/org/thoughtcrime/securesms/providers/BlobProvider.java index 34f2c065dd..21d074b44b 100644 --- a/src/org/thoughtcrime/securesms/providers/BlobProvider.java +++ b/src/org/thoughtcrime/securesms/providers/BlobProvider.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import java.io.ByteArrayInputStream; import java.io.File; @@ -24,6 +25,7 @@ import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Executor; /** * Allows for the creation and retrieval of blobs. @@ -172,13 +174,21 @@ public class BlobProvider { } @WorkerThread - private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec) throws IOException { + private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec, @Nullable ErrorListener errorListener) throws IOException { AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); String directory = getDirectory(blobSpec.getStorageType()); File outputFile = new File(getOrCreateCacheDirectory(context, directory), buildFileName(blobSpec.id)); OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second; - Util.copy(blobSpec.getData(), outputStream); + SignalExecutors.IO.execute(() -> { + try { + Util.copy(blobSpec.getData(), outputStream); + } catch (IOException e) { + if (errorListener != null) { + errorListener.onError(e); + } + } + }); return buildUri(blobSpec); } @@ -249,8 +259,8 @@ public class BlobProvider { * period from one {@link Application#onCreate()} to the next. */ @WorkerThread - public Uri createForSingleSessionOnDisk(@NonNull Context context) throws IOException { - return writeBlobSpecToDisk(context, new BlobSpec(data, id, StorageType.SINGLE_SESSION_DISK, mimeType, fileName, fileSize)); + public Uri createForSingleSessionOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException { + return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.SINGLE_SESSION_DISK), errorListener); } /** @@ -258,8 +268,8 @@ public class BlobProvider { * eventually call {@link BlobProvider#delete(Context, Uri)} when the blob is no longer in use. */ @WorkerThread - public Uri createForMultipleSessionsOnDisk(@NonNull Context context) throws IOException { - return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK)); + public Uri createForMultipleSessionsOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException { + return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK), errorListener); } } @@ -302,6 +312,11 @@ public class BlobProvider { } } + public interface ErrorListener { + @WorkerThread + void onError(IOException e); + } + private static class BlobSpec { private final InputStream data; diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java index a5b08f8eed..16ebb16775 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java +++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java @@ -321,7 +321,7 @@ public class ScribbleFragment extends Fragment implements ScribbleHud.EventListe Uri uri = BlobProvider.getInstance() .forData(data) .withMimeType(MediaUtil.IMAGE_JPEG) - .createForSingleSessionOnDisk(requireContext()); + .createForSingleSessionOnDisk(requireContext(), e -> Log.w(TAG, "Failed to persist image.", e)); controller.onImageEditComplete(uri, result.getWidth(),