mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Created new BlobProvider.
One unified place to create blobs for different lifespans.
This commit is contained in:
parent
22ed8caed3
commit
a122bb4899
@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
@ -119,6 +120,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
initializeWebRtc();
|
initializeWebRtc();
|
||||||
initializePendingMessages();
|
initializePendingMessages();
|
||||||
initializeUnidentifiedDeliveryAbilityRefresh();
|
initializeUnidentifiedDeliveryAbilityRefresh();
|
||||||
|
initializeBlobProvider();
|
||||||
NotificationChannels.create(this);
|
NotificationChannels.create(this);
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||||
}
|
}
|
||||||
@ -312,4 +314,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
jobManager.add(new RefreshUnidentifiedDeliveryAbilityJob(this));
|
jobManager.add(new RefreshUnidentifiedDeliveryAbilityJob(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeBlobProvider() {
|
||||||
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||||
|
BlobProvider.getInstance().onSessionStart(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
@ -132,7 +132,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
|||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
if (!isPassingAlongMedia && resolvedExtra != null) {
|
if (!isPassingAlongMedia && resolvedExtra != null) {
|
||||||
PersistentBlobProvider.getInstance(this).delete(this, resolvedExtra);
|
BlobProvider.getInstance().delete(this, resolvedExtra);
|
||||||
|
|
||||||
if (!isFinishing()) {
|
if (!isFinishing()) {
|
||||||
finish();
|
finish();
|
||||||
@ -324,7 +324,11 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
|||||||
if (cursor != null) cursor.close();
|
if (cursor != null) cursor.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return PersistentBlobProvider.getInstance(context).create(context, inputStream, mimeType, fileName, fileSize);
|
return BlobProvider.getInstance()
|
||||||
|
.forData(inputStream, fileSize == null ? 0 : fileSize)
|
||||||
|
.withMimeType(mimeType)
|
||||||
|
.withFileName(fileName)
|
||||||
|
.createForMultipleSessionsOnDisk(context);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.w(TAG, ioe);
|
Log.w(TAG, ioe);
|
||||||
return null;
|
return null;
|
||||||
|
@ -9,7 +9,7 @@ import android.support.annotation.NonNull;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ThreadUtil;
|
import org.thoughtcrime.securesms.util.ThreadUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@ -26,15 +26,13 @@ public class AudioRecorder {
|
|||||||
|
|
||||||
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final PersistentBlobProvider blobProvider;
|
|
||||||
|
|
||||||
private AudioCodec audioCodec;
|
private AudioCodec audioCodec;
|
||||||
private Uri captureUri;
|
private Uri captureUri;
|
||||||
|
|
||||||
public AudioRecorder(@NonNull Context context) {
|
public AudioRecorder(@NonNull Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.blobProvider = PersistentBlobProvider.getInstance(context.getApplicationContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startRecording() {
|
public void startRecording() {
|
||||||
@ -49,9 +47,11 @@ public class AudioRecorder {
|
|||||||
|
|
||||||
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
||||||
|
|
||||||
captureUri = blobProvider.create(context, new ParcelFileDescriptor.AutoCloseInputStream(fds[0]),
|
captureUri = BlobProvider.getInstance()
|
||||||
MediaUtil.AUDIO_AAC, null, null);
|
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||||
audioCodec = new AudioCodec();
|
.withMimeType(MediaUtil.AUDIO_AAC)
|
||||||
|
.createForSingleSessionOnDisk(context);
|
||||||
|
audioCodec = new AudioCodec();
|
||||||
|
|
||||||
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||||
@ -85,7 +85,7 @@ public class CameraActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
} else {
|
} else {
|
||||||
if (editorFragment != null && captureUri != null) {
|
if (editorFragment != null && captureUri != null) {
|
||||||
Log.i(TAG, "Cleaning up unused capture: " + captureUri);
|
Log.i(TAG, "Cleaning up unused capture: " + captureUri);
|
||||||
MemoryBlobProvider.getInstance().delete(captureUri);
|
BlobProvider.getInstance().delete(this, captureUri);
|
||||||
captureUri = null;
|
captureUri = null;
|
||||||
}
|
}
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
@ -99,7 +99,7 @@ public class CameraActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
|
|
||||||
if (captureUri != null) {
|
if (captureUri != null) {
|
||||||
Log.i(TAG, "Cleaning up capture in onDestroy: " + captureUri);
|
Log.i(TAG, "Cleaning up capture in onDestroy: " + captureUri);
|
||||||
MemoryBlobProvider.getInstance().delete(captureUri);
|
BlobProvider.getInstance().delete(this, captureUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ public class CameraActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
public void onImageCaptured(@NonNull byte[] data) {
|
public void onImageCaptured(@NonNull byte[] data) {
|
||||||
Log.i(TAG, "Fast image captured.");
|
Log.i(TAG, "Fast image captured.");
|
||||||
|
|
||||||
captureUri = MemoryBlobProvider.getInstance().createUri(data);
|
captureUri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory();
|
||||||
Log.i(TAG, "Fast image stored: " + captureUri.toString());
|
Log.i(TAG, "Fast image stored: " + captureUri.toString());
|
||||||
|
|
||||||
SettableFuture<Boolean> result = new SettableFuture<>();
|
SettableFuture<Boolean> result = new SettableFuture<>();
|
||||||
|
@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress;
|
|||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -153,8 +153,8 @@ public class ContactRepository {
|
|||||||
Log.w(TAG, "Failed to parse the vcard.", e);
|
Log.w(TAG, "Failed to parse the vcard.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PersistentBlobProvider.AUTHORITY.equals(uri.getAuthority())) {
|
if (BlobProvider.AUTHORITY.equals(uri.getAuthority())) {
|
||||||
PersistentBlobProvider.getInstance(context).delete(context, uri);
|
BlobProvider.getInstance().delete(context, uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contact;
|
return contact;
|
||||||
|
@ -177,8 +177,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||||
@ -592,7 +591,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
Stream.of(slideDeck.getSlides())
|
Stream.of(slideDeck.getSlides())
|
||||||
.map(Slide::getUri)
|
.map(Slide::getUri)
|
||||||
.withoutNulls()
|
.withoutNulls()
|
||||||
.forEach(uri -> PersistentBlobProvider.getInstance(context).delete(context, uri));
|
.forEach(uri -> BlobProvider.getInstance().delete(context, uri));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1974,9 +1973,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
bodyText = rawText.substring(0, maxPrimaryMessageSize);
|
bodyText = rawText.substring(0, maxPrimaryMessageSize);
|
||||||
|
|
||||||
byte[] textData = rawText.getBytes();
|
byte[] textData = rawText.getBytes();
|
||||||
Uri textUri = MemoryBlobProvider.getInstance().createUri(textData);
|
|
||||||
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date());
|
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date());
|
||||||
String filename = String.format("signal-%s.txt", timestamp);
|
String filename = String.format("signal-%s.txt", timestamp);
|
||||||
|
Uri textUri = BlobProvider.getInstance()
|
||||||
|
.forData(textData)
|
||||||
|
.withMimeType(MediaUtil.LONG_TEXT)
|
||||||
|
.withFileName(filename)
|
||||||
|
.createForSingleSessionInMemory();
|
||||||
|
|
||||||
textSlide = Optional.of(new TextSlide(this, textUri, filename, textData.length));
|
textSlide = Optional.of(new TextSlide(this, textUri, filename, textData.length));
|
||||||
}
|
}
|
||||||
@ -2299,7 +2302,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
new AsyncTask<Void, Void, Void>() {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first);
|
BlobProvider.getInstance().delete(ConversationActivity.this, result.first);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
@ -2328,7 +2331,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
new AsyncTask<Void, Void, Void>() {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first);
|
BlobProvider.getInstance().delete(ConversationActivity.this, result.first);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
@ -20,12 +20,14 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
||||||
@ -115,8 +117,11 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
|||||||
try {
|
try {
|
||||||
byte[] data = viewHolder.getData(forMms);
|
byte[] data = viewHolder.getData(forMms);
|
||||||
|
|
||||||
return PersistentBlobProvider.getInstance(GiphyActivity.this).create(GiphyActivity.this, data, "image/gif", null);
|
return BlobProvider.getInstance()
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
.forData(data)
|
||||||
|
.withMimeType(MediaUtil.IMAGE_GIF)
|
||||||
|
.createForSingleSessionOnDisk(GiphyActivity.this);
|
||||||
|
} catch (InterruptedException | ExecutionException | IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
@ -111,7 +111,7 @@ public class GroupManager {
|
|||||||
GroupContext groupContext = groupContextBuilder.build();
|
GroupContext groupContext = groupContextBuilder.build();
|
||||||
|
|
||||||
if (avatar != null) {
|
if (avatar != null) {
|
||||||
Uri avatarUri = MemoryBlobProvider.getInstance().createSingleUseUri(avatar);
|
Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory();
|
||||||
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null);
|
avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ import com.google.android.mms.pdu_alt.RetrieveConf;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
@ -30,7 +29,7 @@ import org.thoughtcrime.securesms.mms.MmsException;
|
|||||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||||
import org.thoughtcrime.securesms.mms.PartParser;
|
import org.thoughtcrime.securesms.mms.PartParser;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@ -196,7 +195,6 @@ public class MmsDownloadJob extends ContextJob {
|
|||||||
LegacyMessageException
|
LegacyMessageException
|
||||||
{
|
{
|
||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
MemoryBlobProvider provider = MemoryBlobProvider.getInstance();
|
|
||||||
Optional<Address> group = Optional.absent();
|
Optional<Address> group = Optional.absent();
|
||||||
Set<Address> members = new HashSet<>();
|
Set<Address> members = new HashSet<>();
|
||||||
String body = null;
|
String body = null;
|
||||||
@ -235,7 +233,7 @@ public class MmsDownloadJob extends ContextJob {
|
|||||||
PduPart part = media.getPart(i);
|
PduPart part = media.getPart(i);
|
||||||
|
|
||||||
if (part.getData() != null) {
|
if (part.getData() != null) {
|
||||||
Uri uri = provider.createSingleUseUri(part.getData());
|
Uri uri = BlobProvider.getInstance().forData(part.getData()).createForSingleUseInMemory();
|
||||||
String name = null;
|
String name = null;
|
||||||
|
|
||||||
if (part.getName() != null) name = Util.toIsoString(part.getName());
|
if (part.getName() != null) name = Util.toIsoString(part.getName());
|
||||||
|
@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.net.CallRequestController;
|
|||||||
import org.thoughtcrime.securesms.net.CompositeRequestController;
|
import org.thoughtcrime.securesms.net.CompositeRequestController;
|
||||||
import org.thoughtcrime.securesms.net.ContentProxySelector;
|
import org.thoughtcrime.securesms.net.ContentProxySelector;
|
||||||
import org.thoughtcrime.securesms.net.RequestController;
|
import org.thoughtcrime.securesms.net.RequestController;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
@ -145,7 +145,7 @@ public class LinkPreviewRepository {
|
|||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||||
|
|
||||||
byte[] bytes = baos.toByteArray();
|
byte[] bytes = baos.toByteArray();
|
||||||
Uri uri = MemoryBlobProvider.getInstance().createUri(bytes);
|
Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory();
|
||||||
Optional<Attachment> thumbnail = Optional.of(new UriAttachment(uri,
|
Optional<Attachment> thumbnail = Optional.of(new UriAttachment(uri,
|
||||||
uri,
|
uri,
|
||||||
MediaUtil.IMAGE_JPEG,
|
MediaUtil.IMAGE_JPEG,
|
||||||
|
@ -5,21 +5,11 @@ import android.arch.lifecycle.MutableLiveData;
|
|||||||
import android.arch.lifecycle.ViewModel;
|
import android.arch.lifecycle.ViewModel;
|
||||||
import android.arch.lifecycle.ViewModelProvider;
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
|
||||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.net.RequestController;
|
import org.thoughtcrime.securesms.net.RequestController;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
|
||||||
import org.thoughtcrime.securesms.util.Debouncer;
|
import org.thoughtcrime.securesms.util.Debouncer;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.ScribbleView;
|
import org.thoughtcrime.securesms.scribbles.widget.ScribbleView;
|
||||||
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
|
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
|||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -433,13 +434,17 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||||
|
|
||||||
Uri uri = PersistentBlobProvider.getInstance(context).create(context, baos.toByteArray(), MediaUtil.IMAGE_JPEG, null);
|
Uri uri = BlobProvider.getInstance()
|
||||||
|
.forData(baos.toByteArray())
|
||||||
|
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||||
|
.createForSingleSessionOnDisk(context);
|
||||||
|
|
||||||
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 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);
|
||||||
}
|
}
|
||||||
|
@ -57,12 +57,14 @@ import org.thoughtcrime.securesms.components.location.SignalMapView;
|
|||||||
import org.thoughtcrime.securesms.components.location.SignalPlace;
|
import org.thoughtcrime.securesms.components.location.SignalPlace;
|
||||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
|
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||||
@ -165,14 +167,16 @@ public class AttachmentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void cleanup(final @Nullable Uri uri) {
|
private void cleanup(final @Nullable Uri uri) {
|
||||||
if (uri != null && PersistentBlobProvider.isAuthority(context, uri)) {
|
if (uri != null && DeprecatedPersistentBlobProvider.isAuthority(context, uri)) {
|
||||||
Log.d(TAG, "cleaning up " + uri);
|
Log.d(TAG, "cleaning up " + uri);
|
||||||
PersistentBlobProvider.getInstance(context).delete(context, uri);
|
DeprecatedPersistentBlobProvider.getInstance(context).delete(context, uri);
|
||||||
|
} else if (uri != null && BlobProvider.isAuthority(uri)) {
|
||||||
|
BlobProvider.getInstance().delete(context, uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markGarbage(@Nullable Uri uri) {
|
private void markGarbage(@Nullable Uri uri) {
|
||||||
if (uri != null && PersistentBlobProvider.isAuthority(context, uri)) {
|
if (uri != null && (DeprecatedPersistentBlobProvider.isAuthority(context, uri) || BlobProvider.isAuthority(uri))) {
|
||||||
Log.d(TAG, "Marking garbage that needs cleaning: " + uri);
|
Log.d(TAG, "Marking garbage that needs cleaning: " + uri);
|
||||||
garbage.add(uri);
|
garbage.add(uri);
|
||||||
}
|
}
|
||||||
@ -206,13 +210,17 @@ public class AttachmentManager {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(@NonNull Bitmap result) {
|
public void onSuccess(@NonNull Bitmap result) {
|
||||||
byte[] blob = BitmapUtil.toByteArray(result);
|
byte[] blob = BitmapUtil.toByteArray(result);
|
||||||
Uri uri = PersistentBlobProvider.getInstance(context)
|
Uri uri = BlobProvider.getInstance()
|
||||||
.create(context, blob, MediaUtil.IMAGE_PNG, null);
|
.forData(blob)
|
||||||
|
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||||
|
.createForSingleSessionInMemory();
|
||||||
LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place);
|
LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place);
|
||||||
|
|
||||||
setSlide(locationSlide);
|
Util.runOnMain(() -> {
|
||||||
attachmentListener.onAttachmentChanged();
|
setSlide(locationSlide);
|
||||||
returnResult.set(true);
|
attachmentListener.onAttachmentChanged();
|
||||||
|
returnResult.set(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -443,7 +451,7 @@ public class AttachmentManager {
|
|||||||
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
if (captureIntent.resolveActivity(activity.getPackageManager()) != null) {
|
if (captureIntent.resolveActivity(activity.getPackageManager()) != null) {
|
||||||
if (captureUri == null) {
|
if (captureUri == null) {
|
||||||
captureUri = PersistentBlobProvider.getInstance(context).createForExternal(context, MediaUtil.IMAGE_JPEG);
|
captureUri = DeprecatedPersistentBlobProvider.getInstance(context).createForExternal(context, MediaUtil.IMAGE_JPEG);
|
||||||
}
|
}
|
||||||
Log.d(TAG, "captureUri path is " + captureUri.getPath());
|
Log.d(TAG, "captureUri path is " + captureUri.getPath());
|
||||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri);
|
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri);
|
||||||
|
@ -10,9 +10,9 @@ import android.support.annotation.Nullable;
|
|||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
|
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -27,7 +27,7 @@ public class PartAuthority {
|
|||||||
private static final int PART_ROW = 1;
|
private static final int PART_ROW = 1;
|
||||||
private static final int THUMB_ROW = 2;
|
private static final int THUMB_ROW = 2;
|
||||||
private static final int PERSISTENT_ROW = 3;
|
private static final int PERSISTENT_ROW = 3;
|
||||||
private static final int SINGLE_USE_ROW = 4;
|
private static final int BLOB_ROW = 4;
|
||||||
|
|
||||||
private static final UriMatcher uriMatcher;
|
private static final UriMatcher uriMatcher;
|
||||||
|
|
||||||
@ -35,9 +35,9 @@ public class PartAuthority {
|
|||||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
uriMatcher.addURI("org.thoughtcrime.securesms", "part/*/#", PART_ROW);
|
uriMatcher.addURI("org.thoughtcrime.securesms", "part/*/#", PART_ROW);
|
||||||
uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/*/#", THUMB_ROW);
|
uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/*/#", THUMB_ROW);
|
||||||
uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
||||||
uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
||||||
uriMatcher.addURI(MemoryBlobProvider.AUTHORITY, MemoryBlobProvider.PATH, SINGLE_USE_ROW);
|
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InputStream getAttachmentStream(@NonNull Context context, @NonNull Uri uri)
|
public static InputStream getAttachmentStream(@NonNull Context context, @NonNull Uri uri)
|
||||||
@ -48,8 +48,8 @@ public class PartAuthority {
|
|||||||
switch (match) {
|
switch (match) {
|
||||||
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
|
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
|
||||||
case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(new PartUriParser(uri).getPartId());
|
case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(new PartUriParser(uri).getPartId());
|
||||||
case PERSISTENT_ROW: return PersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||||
case SINGLE_USE_ROW: return MemoryBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
|
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||||
default: return context.getContentResolver().openInputStream(uri);
|
default: return context.getContentResolver().openInputStream(uri);
|
||||||
}
|
}
|
||||||
} catch (SecurityException se) {
|
} catch (SecurityException se) {
|
||||||
@ -68,8 +68,9 @@ public class PartAuthority {
|
|||||||
if (attachment != null) return attachment.getFileName();
|
if (attachment != null) return attachment.getFileName();
|
||||||
else return null;
|
else return null;
|
||||||
case PERSISTENT_ROW:
|
case PERSISTENT_ROW:
|
||||||
return PersistentBlobProvider.getFileName(context, uri);
|
return DeprecatedPersistentBlobProvider.getFileName(context, uri);
|
||||||
case SINGLE_USE_ROW:
|
case BLOB_ROW:
|
||||||
|
return BlobProvider.getFileName(uri);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -86,8 +87,9 @@ public class PartAuthority {
|
|||||||
if (attachment != null) return attachment.getSize();
|
if (attachment != null) return attachment.getSize();
|
||||||
else return null;
|
else return null;
|
||||||
case PERSISTENT_ROW:
|
case PERSISTENT_ROW:
|
||||||
return PersistentBlobProvider.getFileSize(context, uri);
|
return DeprecatedPersistentBlobProvider.getFileSize(context, uri);
|
||||||
case SINGLE_USE_ROW:
|
case BLOB_ROW:
|
||||||
|
return BlobProvider.getFileSize(uri);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -104,8 +106,9 @@ public class PartAuthority {
|
|||||||
if (attachment != null) return attachment.getContentType();
|
if (attachment != null) return attachment.getContentType();
|
||||||
else return null;
|
else return null;
|
||||||
case PERSISTENT_ROW:
|
case PERSISTENT_ROW:
|
||||||
return PersistentBlobProvider.getMimeType(context, uri);
|
return DeprecatedPersistentBlobProvider.getMimeType(context, uri);
|
||||||
case SINGLE_USE_ROW:
|
case BLOB_ROW:
|
||||||
|
return BlobProvider.getMimeType(uri);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -132,7 +135,7 @@ public class PartAuthority {
|
|||||||
case PART_ROW:
|
case PART_ROW:
|
||||||
case THUMB_ROW:
|
case THUMB_ROW:
|
||||||
case PERSISTENT_ROW:
|
case PERSISTENT_ROW:
|
||||||
case SINGLE_USE_ROW:
|
case BLOB_ROW:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
386
src/org/thoughtcrime/securesms/providers/BlobProvider.java
Normal file
386
src/org/thoughtcrime/securesms/providers/BlobProvider.java
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
package org.thoughtcrime.securesms.providers;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.IntRange;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||||
|
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||||
|
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 java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows for the creation and retrieval of blobs.
|
||||||
|
*/
|
||||||
|
public class BlobProvider {
|
||||||
|
|
||||||
|
private static final String TAG = BlobProvider.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String MULTI_SESSION_DIRECTORY = "multi_session_blobs";
|
||||||
|
private static final String SINGLE_SESSION_DIRECTORY = "single_session_blobs";
|
||||||
|
|
||||||
|
public static final Uri CONTENT_URI = Uri.parse("content://org.thoughtcrime.securesms/blob");
|
||||||
|
public static final String AUTHORITY = "org.thoughtcrime.securesms";
|
||||||
|
public static final String PATH = "blob/*/*/*/*/*";
|
||||||
|
|
||||||
|
private static final int STORAGE_TYPE_PATH_SEGMENT = 1;
|
||||||
|
private static final int MIMETYPE_PATH_SEGMENT = 2;
|
||||||
|
private static final int FILENAME_PATH_SEGMENT = 3;
|
||||||
|
private static final int FILESIZE_PATH_SEGMENT = 4;
|
||||||
|
private static final int ID_PATH_SEGMENT = 5;
|
||||||
|
|
||||||
|
private static final int MATCH = 1;
|
||||||
|
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH) {{
|
||||||
|
addURI(AUTHORITY, PATH, MATCH);
|
||||||
|
}};
|
||||||
|
|
||||||
|
private static final BlobProvider INSTANCE = new BlobProvider();
|
||||||
|
|
||||||
|
private final Map<Uri, byte[]> memoryBlobs = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static BlobProvider getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin building a blob for the provided data. Allows for the creation of in-memory blobs.
|
||||||
|
*/
|
||||||
|
public MemoryBlobBuilder forData(@NonNull byte[] data) {
|
||||||
|
return new MemoryBlobBuilder(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin building a blob for the provided input stream.
|
||||||
|
*/
|
||||||
|
public BlobBuilder forData(@NonNull InputStream data, long fileSize) {
|
||||||
|
return new BlobBuilder(data, fileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a stream for the content with the specified URI.
|
||||||
|
* @throws IOException If the stream fails to open or the spec of the URI doesn't match.
|
||||||
|
*/
|
||||||
|
public synchronized @NonNull InputStream getStream(@NonNull Context context, @NonNull Uri uri) throws IOException {
|
||||||
|
if (isAuthority(uri)) {
|
||||||
|
StorageType storageType = StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT));
|
||||||
|
|
||||||
|
if (storageType.isMemory()) {
|
||||||
|
byte[] data = memoryBlobs.get(uri);
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
if (storageType == StorageType.SINGLE_USE_MEMORY) {
|
||||||
|
memoryBlobs.remove(uri);
|
||||||
|
}
|
||||||
|
return new ByteArrayInputStream(data);
|
||||||
|
} else {
|
||||||
|
throw new IOException("Failed to find in-memory blob for: " + uri);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String id = uri.getPathSegments().get(ID_PATH_SEGMENT);
|
||||||
|
String directory = getDirectory(storageType);
|
||||||
|
File file = new File(getOrCreateCacheDirectory(context, directory), buildFileName(id));
|
||||||
|
|
||||||
|
return ModernDecryptingPartInputStream.createFor(AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), file, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IOException("Provided URI does not match this spec. Uri: " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the content with the specified URI.
|
||||||
|
*/
|
||||||
|
public synchronized void delete(@NonNull Context context, @NonNull Uri uri) {
|
||||||
|
if (!isAuthority(uri)) {
|
||||||
|
Log.d(TAG, "Can't delete. Not the authority for uri: " + uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StorageType storageType = StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT));
|
||||||
|
|
||||||
|
if (storageType.isMemory()) {
|
||||||
|
memoryBlobs.remove(uri);
|
||||||
|
} else {
|
||||||
|
String id = uri.getPathSegments().get(ID_PATH_SEGMENT);
|
||||||
|
String directory = getDirectory(storageType);
|
||||||
|
File file = new File(getOrCreateCacheDirectory(context, directory), buildFileName(id));
|
||||||
|
|
||||||
|
if (!file.delete()) {
|
||||||
|
throw new IOException("File wasn't deleted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to delete uri: " + uri, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates a new app session has started, allowing old single-session blobs to be deleted.
|
||||||
|
*/
|
||||||
|
public synchronized void onSessionStart(@NonNull Context context) {
|
||||||
|
File directory = getOrCreateCacheDirectory(context, SINGLE_SESSION_DIRECTORY);
|
||||||
|
for (File file : directory.listFiles()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable String getMimeType(@NonNull Uri uri) {
|
||||||
|
if (isAuthority(uri)) {
|
||||||
|
return uri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable String getFileName(@NonNull Uri uri) {
|
||||||
|
if (isAuthority(uri)) {
|
||||||
|
return uri.getPathSegments().get(FILENAME_PATH_SEGMENT);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable Long getFileSize(@NonNull Uri uri) {
|
||||||
|
if (isAuthority(uri)) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(uri.getPathSegments().get(FILESIZE_PATH_SEGMENT));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAuthority(@NonNull Uri uri) {
|
||||||
|
return URI_MATCHER.match(uri) == MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec) 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);
|
||||||
|
|
||||||
|
return buildUri(blobSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized @NonNull Uri writeBlobSpecToMemory(@NonNull BlobSpec blobSpec, @NonNull byte[] data) {
|
||||||
|
Uri uri = buildUri(blobSpec);
|
||||||
|
memoryBlobs.put(uri, data);
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull String buildFileName(@NonNull String id) {
|
||||||
|
return id + ".blob";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull String getDirectory(@NonNull StorageType storageType) {
|
||||||
|
return storageType == StorageType.MULTI_SESSION_DISK ? MULTI_SESSION_DIRECTORY : SINGLE_SESSION_DIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Uri buildUri(@NonNull BlobSpec blobSpec) {
|
||||||
|
return CONTENT_URI.buildUpon()
|
||||||
|
.appendPath(blobSpec.getStorageType().encode())
|
||||||
|
.appendPath(blobSpec.getMimeType())
|
||||||
|
.appendPath(blobSpec.getFileName())
|
||||||
|
.appendEncodedPath(String.valueOf(blobSpec.getFileSize()))
|
||||||
|
.appendPath(blobSpec.getId())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getOrCreateCacheDirectory(@NonNull Context context, @NonNull String directory) {
|
||||||
|
File file = new File(context.getCacheDir(), directory);
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.mkdir();
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlobBuilder {
|
||||||
|
|
||||||
|
private InputStream data;
|
||||||
|
private String id;
|
||||||
|
private String mimeType;
|
||||||
|
private String fileName;
|
||||||
|
private long fileSize;
|
||||||
|
|
||||||
|
private BlobBuilder(@NonNull InputStream data, long fileSize) {
|
||||||
|
this.id = UUID.randomUUID().toString();
|
||||||
|
this.data = data;
|
||||||
|
this.fileSize = fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlobBuilder withMimeType(@NonNull String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlobBuilder withFileName(@NonNull String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BlobSpec buildBlobSpec(@NonNull StorageType storageType) {
|
||||||
|
return new BlobSpec(data, id, storageType, mimeType, fileName, fileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a blob that will exist for a single app session. An app session is defined as the
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a blob that will exist for multiple app sessions. It is the caller's responsibility to
|
||||||
|
* 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 class MemoryBlobBuilder extends BlobBuilder {
|
||||||
|
|
||||||
|
private byte[] data;
|
||||||
|
|
||||||
|
private MemoryBlobBuilder(@NonNull byte[] data) {
|
||||||
|
super(new ByteArrayInputStream(data), data.length);
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MemoryBlobBuilder withMimeType(@NonNull String mimeType) {
|
||||||
|
super.withMimeType(mimeType);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MemoryBlobBuilder withFileName(@NonNull String fileName) {
|
||||||
|
super.withFileName(fileName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a blob that is stored in memory and can only be read a single time. After a single
|
||||||
|
* read, it will be removed from storage. Useful for when a Uri is needed to read transient data.
|
||||||
|
*/
|
||||||
|
public Uri createForSingleUseInMemory() {
|
||||||
|
return writeBlobSpecToMemory(buildBlobSpec(StorageType.SINGLE_USE_MEMORY), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a blob that is stored in memory. Will persist for a single app session. You should
|
||||||
|
* always try to call {@link BlobProvider#delete(Context, Uri)} after you're done with the blob
|
||||||
|
* to free up memory.
|
||||||
|
*/
|
||||||
|
public Uri createForSingleSessionInMemory() {
|
||||||
|
return writeBlobSpecToMemory(buildBlobSpec(StorageType.SINGLE_SESSION_MEMORY), data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BlobSpec {
|
||||||
|
|
||||||
|
private final InputStream data;
|
||||||
|
private final String id;
|
||||||
|
private final StorageType storageType;
|
||||||
|
private final String mimeType;
|
||||||
|
private final String fileName;
|
||||||
|
private final long fileSize;
|
||||||
|
|
||||||
|
private BlobSpec(@NonNull InputStream data,
|
||||||
|
@NonNull String id,
|
||||||
|
@NonNull StorageType storageType,
|
||||||
|
@NonNull String mimeType,
|
||||||
|
@Nullable String fileName,
|
||||||
|
@IntRange(from = 0) long fileSize)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
this.id = id;
|
||||||
|
this.storageType = storageType;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.fileSize = fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull InputStream getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull StorageType getStorageType() {
|
||||||
|
return storageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getFileSize() {
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum StorageType {
|
||||||
|
|
||||||
|
SINGLE_USE_MEMORY("single-use-memory", true),
|
||||||
|
SINGLE_SESSION_MEMORY("single-session-memory", true),
|
||||||
|
SINGLE_SESSION_DISK("single-session-disk", false),
|
||||||
|
MULTI_SESSION_DISK("multi-session-disk", false);
|
||||||
|
|
||||||
|
private final String encoded;
|
||||||
|
private final boolean inMemory;
|
||||||
|
|
||||||
|
StorageType(String encoded, boolean inMemory) {
|
||||||
|
this.encoded = encoded;
|
||||||
|
this.inMemory = inMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encode() {
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMemory() {
|
||||||
|
return inMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StorageType decode(@NonNull String encoded) throws IOException {
|
||||||
|
for (StorageType storageType : StorageType.values()) {
|
||||||
|
if (storageType.encoded.equals(encoded)) {
|
||||||
|
return storageType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to decode lifespan.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package org.thoughtcrime.securesms.providers;
|
package org.thoughtcrime.securesms.providers;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
@ -9,31 +8,28 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
|
||||||
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
public class PersistentBlobProvider {
|
/**
|
||||||
|
* @deprecated Use {@link BlobProvider} instead. Keeping in read-only mode due to the number of
|
||||||
|
* legacy URIs it handles. Given that this was largely used for drafts, and that files were stored
|
||||||
|
* in the cache directory, it's possible that we could remove this class after a reasonable amount
|
||||||
|
* of time has passed.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public class DeprecatedPersistentBlobProvider {
|
||||||
|
|
||||||
private static final String TAG = PersistentBlobProvider.class.getSimpleName();
|
private static final String TAG = DeprecatedPersistentBlobProvider.class.getSimpleName();
|
||||||
|
|
||||||
private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture-new";
|
private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture-new";
|
||||||
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
||||||
@ -54,82 +50,29 @@ public class PersistentBlobProvider {
|
|||||||
addURI(AUTHORITY, EXPECTED_PATH_NEW, MATCH_NEW);
|
addURI(AUTHORITY, EXPECTED_PATH_NEW, MATCH_NEW);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private static volatile PersistentBlobProvider instance;
|
private static volatile DeprecatedPersistentBlobProvider instance;
|
||||||
|
|
||||||
public static PersistentBlobProvider getInstance(Context context) {
|
/**
|
||||||
|
* @deprecated Use {@link BlobProvider} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static DeprecatedPersistentBlobProvider getInstance(Context context) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
synchronized (PersistentBlobProvider.class) {
|
synchronized (DeprecatedPersistentBlobProvider.class) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new PersistentBlobProvider(context);
|
instance = new DeprecatedPersistentBlobProvider(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UseSparseArrays")
|
|
||||||
private final Map<Long, byte[]> cache = Collections.synchronizedMap(new HashMap<Long, byte[]>());
|
|
||||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
|
||||||
|
|
||||||
private final AttachmentSecret attachmentSecret;
|
private final AttachmentSecret attachmentSecret;
|
||||||
|
|
||||||
private PersistentBlobProvider(@NonNull Context context) {
|
private DeprecatedPersistentBlobProvider(@NonNull Context context) {
|
||||||
this.attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
this.attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri create(@NonNull Context context,
|
|
||||||
@NonNull byte[] blobBytes,
|
|
||||||
@NonNull String mimeType,
|
|
||||||
@Nullable String fileName)
|
|
||||||
{
|
|
||||||
final long id = System.currentTimeMillis();
|
|
||||||
cache.put(id, blobBytes);
|
|
||||||
return create(context, attachmentSecret, new ByteArrayInputStream(blobBytes), id, mimeType, fileName, (long) blobBytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri create(@NonNull Context context,
|
|
||||||
@NonNull InputStream input,
|
|
||||||
@NonNull String mimeType,
|
|
||||||
@Nullable String fileName,
|
|
||||||
@Nullable Long fileSize)
|
|
||||||
{
|
|
||||||
return create(context, attachmentSecret, input, System.currentTimeMillis(), mimeType, fileName, fileSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Uri create(@NonNull Context context,
|
|
||||||
@NonNull AttachmentSecret attachmentSecret,
|
|
||||||
@NonNull InputStream input,
|
|
||||||
long id,
|
|
||||||
@NonNull String mimeType,
|
|
||||||
@Nullable String fileName,
|
|
||||||
@Nullable Long fileSize)
|
|
||||||
{
|
|
||||||
persistToDisk(context, attachmentSecret, id, input);
|
|
||||||
final Uri uniqueUri = CONTENT_URI.buildUpon()
|
|
||||||
.appendPath(mimeType)
|
|
||||||
.appendPath(fileName)
|
|
||||||
.appendEncodedPath(String.valueOf(fileSize))
|
|
||||||
.appendEncodedPath(String.valueOf(System.currentTimeMillis()))
|
|
||||||
.build();
|
|
||||||
return ContentUris.withAppendedId(uniqueUri, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persistToDisk(@NonNull Context context,
|
|
||||||
@NonNull AttachmentSecret attachmentSecret,
|
|
||||||
final long id, final InputStream input)
|
|
||||||
{
|
|
||||||
executor.submit(() -> {
|
|
||||||
try {
|
|
||||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, getFile(context, id).file, true);
|
|
||||||
Util.copy(input, output.second);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.remove(id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri createForExternal(@NonNull Context context, @NonNull String mimeType) throws IOException {
|
public Uri createForExternal(@NonNull Context context, @NonNull String mimeType) throws IOException {
|
||||||
File target = new File(getExternalDir(context), String.valueOf(System.currentTimeMillis()) + "." + getExtensionFromMimeType(mimeType));
|
File target = new File(getExternalDir(context), String.valueOf(System.currentTimeMillis()) + "." + getExtensionFromMimeType(mimeType));
|
||||||
return FileProviderUtil.getUriFor(context, target);
|
return FileProviderUtil.getUriFor(context, target);
|
||||||
@ -140,7 +83,6 @@ public class PersistentBlobProvider {
|
|||||||
case MATCH_OLD:
|
case MATCH_OLD:
|
||||||
case MATCH_NEW:
|
case MATCH_NEW:
|
||||||
long id = ContentUris.parseId(uri);
|
long id = ContentUris.parseId(uri);
|
||||||
cache.remove(id);
|
|
||||||
return getFile(context, ContentUris.parseId(uri)).file.delete();
|
return getFile(context, ContentUris.parseId(uri)).file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,12 +95,6 @@ public class PersistentBlobProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull InputStream getStream(@NonNull Context context, long id) throws IOException {
|
public @NonNull InputStream getStream(@NonNull Context context, long id) throws IOException {
|
||||||
final byte[] cached = cache.get(id);
|
|
||||||
|
|
||||||
if (cached != null) {
|
|
||||||
return new ByteArrayInputStream(cached);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileData fileData = getFile(context, id);
|
FileData fileData = getFile(context, id);
|
||||||
|
|
||||||
if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, fileData.file, 0);
|
if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, fileData.file, 0);
|
@ -1,105 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.providers;
|
|
||||||
|
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class MemoryBlobProvider {
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final String TAG = MemoryBlobProvider.class.getSimpleName();
|
|
||||||
|
|
||||||
public static final String AUTHORITY = "org.thoughtcrime.securesms";
|
|
||||||
public static final String PATH = "memory/*/#";
|
|
||||||
private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/memory");
|
|
||||||
|
|
||||||
private final Map<Long, Entry> cache = new HashMap<>();
|
|
||||||
|
|
||||||
private static final MemoryBlobProvider instance = new MemoryBlobProvider();
|
|
||||||
|
|
||||||
public static MemoryBlobProvider getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MemoryBlobProvider() {}
|
|
||||||
|
|
||||||
public synchronized Uri createSingleUseUri(@NonNull byte[] blob) {
|
|
||||||
return createUriInternal(blob, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized Uri createUri(@NonNull byte[] blob) {
|
|
||||||
return createUriInternal(blob, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void delete(@NonNull Uri uri) {
|
|
||||||
cache.remove(ContentUris.parseId(uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized @NonNull byte[] getBlob(@NonNull Uri uri) {
|
|
||||||
long id = ContentUris.parseId(uri);
|
|
||||||
Entry entry = cache.get(ContentUris.parseId(uri));
|
|
||||||
|
|
||||||
if (entry == null) {
|
|
||||||
throw new IllegalArgumentException("ID not found: " + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.isSingleUse()) {
|
|
||||||
cache.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.getBlob();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized @NonNull InputStream getStream(long id) throws IOException {
|
|
||||||
Entry entry = cache.get(id);
|
|
||||||
|
|
||||||
if (entry == null) {
|
|
||||||
throw new IOException("ID not found: " + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.isSingleUse()) {
|
|
||||||
cache.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ByteArrayInputStream(entry.getBlob());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Uri createUriInternal(@NonNull byte[] blob, boolean singleUse) {
|
|
||||||
try {
|
|
||||||
long id = Math.abs(SecureRandom.getInstance("SHA1PRNG").nextLong());
|
|
||||||
cache.put(id, new Entry(blob, singleUse));
|
|
||||||
|
|
||||||
Uri uniqueUri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(System.currentTimeMillis()));
|
|
||||||
return ContentUris.withAppendedId(uniqueUri, id);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Entry {
|
|
||||||
|
|
||||||
private final byte[] blob;
|
|
||||||
private final boolean singleUse;
|
|
||||||
|
|
||||||
private Entry(@NonNull byte[] blob, boolean singleUse) {
|
|
||||||
this.blob = blob;
|
|
||||||
this.singleUse = singleUse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBlob() {
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSingleUse() {
|
|
||||||
return singleUse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@ -23,7 +24,8 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
|
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
|
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||||
import org.thoughtcrime.securesms.scribbles.viewmodel.Font;
|
import org.thoughtcrime.securesms.scribbles.viewmodel.Font;
|
||||||
import org.thoughtcrime.securesms.scribbles.viewmodel.Layer;
|
import org.thoughtcrime.securesms.scribbles.viewmodel.Layer;
|
||||||
import org.thoughtcrime.securesms.scribbles.viewmodel.TextLayer;
|
import org.thoughtcrime.securesms.scribbles.viewmodel.TextLayer;
|
||||||
@ -300,30 +302,40 @@ public class ScribbleFragment extends Fragment implements ScribbleHud.EventListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEditComplete(@NonNull Optional<String> message, Optional<TransportOption> transport) {
|
public void onEditComplete(@NonNull Optional<String> message, @NonNull Optional<TransportOption> transport) {
|
||||||
ListenableFuture<Bitmap> future = scribbleView.getRenderedImage(glideRequests);
|
ListenableFuture<Bitmap> future = scribbleView.getRenderedImage(glideRequests);
|
||||||
|
|
||||||
future.addListener(new ListenableFuture.Listener<Bitmap>() {
|
future.addListener(new ListenableFuture.Listener<Bitmap>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Bitmap result) {
|
public void onSuccess(Bitmap result) {
|
||||||
PersistentBlobProvider provider = PersistentBlobProvider.getInstance(getContext());
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
try {
|
||||||
result.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
result.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||||
|
|
||||||
byte[] data = baos.toByteArray();
|
byte[] data = baos.toByteArray();
|
||||||
|
Uri uri = BlobProvider.getInstance()
|
||||||
|
.forData(data)
|
||||||
|
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||||
|
.createForSingleSessionOnDisk(requireContext());
|
||||||
|
|
||||||
controller.onImageEditComplete(provider.create(getContext(), data, MediaUtil.IMAGE_JPEG, null),
|
controller.onImageEditComplete(uri,
|
||||||
result.getWidth(),
|
result.getWidth(),
|
||||||
result.getHeight(),
|
result.getHeight(),
|
||||||
data.length,
|
data.length,
|
||||||
message,
|
message,
|
||||||
transport);
|
transport);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to persist image.", e);
|
||||||
|
Util.runOnMain(() -> controller.onImageEditFailure());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(ExecutionException e) {
|
public void onFailure(ExecutionException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
controller.onImageEditFailure();
|
Util.runOnMain(() -> controller.onImageEditFailure());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
|||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
Loading…
Reference in New Issue
Block a user