Make BlobProvider write to disk on a background thread.

Otherwise we hit some weird blocking issues with voice note recording.
This commit is contained in:
Greyson Parrelli 2019-03-19 15:34:04 -07:00
parent 5a8753de85
commit ce0058864f
7 changed files with 27 additions and 13 deletions

View File

@ -339,7 +339,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
.forData(inputStream, fileSize == null ? 0 : fileSize) .forData(inputStream, fileSize == null ? 0 : fileSize)
.withMimeType(mimeType) .withMimeType(mimeType)
.withFileName(fileName) .withFileName(fileName)
.createForMultipleSessionsOnDisk(context); .createForMultipleSessionsOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e));
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w(TAG, ioe); Log.w(TAG, ioe);
return null; return null;

View File

@ -50,7 +50,7 @@ public class AudioRecorder {
captureUri = BlobProvider.getInstance() captureUri = BlobProvider.getInstance()
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0) .forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
.withMimeType(MediaUtil.AUDIO_AAC) .withMimeType(MediaUtil.AUDIO_AAC)
.createForSingleSessionOnDisk(context); .createForSingleSessionOnDisk(context, e -> Log.w(TAG, "Error during recording", e));
audioCodec = new AudioCodec(); audioCodec = new AudioCodec();
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1])); audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));

View File

@ -121,7 +121,7 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
return BlobProvider.getInstance() return BlobProvider.getInstance()
.forData(data) .forData(data)
.withMimeType(MediaUtil.IMAGE_GIF) .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) { } catch (InterruptedException | ExecutionException | IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
return null; return null;

View File

@ -297,7 +297,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
Uri uri = BlobProvider.getInstance() Uri uri = BlobProvider.getInstance()
.forData(data) .forData(data)
.withMimeType(MediaUtil.IMAGE_JPEG) .withMimeType(MediaUtil.IMAGE_JPEG)
.createForSingleSessionOnDisk(this); .createForSingleSessionOnDisk(this, e -> Log.w(TAG, "Failed to write to disk.", e));
return new Media(uri, return new Media(uri,
MediaUtil.IMAGE_JPEG, MediaUtil.IMAGE_JPEG,
System.currentTimeMillis(), System.currentTimeMillis(),

View File

@ -460,13 +460,12 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
Uri uri = BlobProvider.getInstance() Uri uri = BlobProvider.getInstance()
.forData(baos.toByteArray()) .forData(baos.toByteArray())
.withMimeType(MediaUtil.IMAGE_JPEG) .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()); Media updated = new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), baos.size(), media.getBucketId(), media.getCaption());
updatedMedia.add(updated); updatedMedia.add(updated);
renderTimer.split("item"); renderTimer.split("item");
} catch (InterruptedException | ExecutionException | IOException e) { } catch (InterruptedException | ExecutionException | IOException e) {
Log.w(TAG, "Failed to render image. Using base image."); Log.w(TAG, "Failed to render image. Using base image.");
updatedMedia.add(media); updatedMedia.add(media);

View File

@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
@ -24,6 +25,7 @@ import java.io.OutputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executor;
/** /**
* Allows for the creation and retrieval of blobs. * Allows for the creation and retrieval of blobs.
@ -172,13 +174,21 @@ public class BlobProvider {
} }
@WorkerThread @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(); AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
String directory = getDirectory(blobSpec.getStorageType()); String directory = getDirectory(blobSpec.getStorageType());
File outputFile = new File(getOrCreateCacheDirectory(context, directory), buildFileName(blobSpec.id)); File outputFile = new File(getOrCreateCacheDirectory(context, directory), buildFileName(blobSpec.id));
OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second; OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second;
SignalExecutors.IO.execute(() -> {
try {
Util.copy(blobSpec.getData(), outputStream); Util.copy(blobSpec.getData(), outputStream);
} catch (IOException e) {
if (errorListener != null) {
errorListener.onError(e);
}
}
});
return buildUri(blobSpec); return buildUri(blobSpec);
} }
@ -249,8 +259,8 @@ public class BlobProvider {
* period from one {@link Application#onCreate()} to the next. * period from one {@link Application#onCreate()} to the next.
*/ */
@WorkerThread @WorkerThread
public Uri createForSingleSessionOnDisk(@NonNull Context context) throws IOException { public Uri createForSingleSessionOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException {
return writeBlobSpecToDisk(context, new BlobSpec(data, id, StorageType.SINGLE_SESSION_DISK, mimeType, fileName, fileSize)); 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. * eventually call {@link BlobProvider#delete(Context, Uri)} when the blob is no longer in use.
*/ */
@WorkerThread @WorkerThread
public Uri createForMultipleSessionsOnDisk(@NonNull Context context) throws IOException { public Uri createForMultipleSessionsOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException {
return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK)); 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 static class BlobSpec {
private final InputStream data; private final InputStream data;

View File

@ -321,7 +321,7 @@ public class ScribbleFragment extends Fragment implements ScribbleHud.EventListe
Uri uri = BlobProvider.getInstance() Uri uri = BlobProvider.getInstance()
.forData(data) .forData(data)
.withMimeType(MediaUtil.IMAGE_JPEG) .withMimeType(MediaUtil.IMAGE_JPEG)
.createForSingleSessionOnDisk(requireContext()); .createForSingleSessionOnDisk(requireContext(), e -> Log.w(TAG, "Failed to persist image.", e));
controller.onImageEditComplete(uri, controller.onImageEditComplete(uri,
result.getWidth(), result.getWidth(),