mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 08:47:46 +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.UncaughtExceptionLogger;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
@ -119,6 +120,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
initializeWebRtc();
|
||||
initializePendingMessages();
|
||||
initializeUnidentifiedDeliveryAbilityRefresh();
|
||||
initializeBlobProvider();
|
||||
NotificationChannels.create(this);
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||
}
|
||||
@ -312,4 +314,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
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.mediasend.Media;
|
||||
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.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
@ -132,7 +132,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (!isPassingAlongMedia && resolvedExtra != null) {
|
||||
PersistentBlobProvider.getInstance(this).delete(this, resolvedExtra);
|
||||
BlobProvider.getInstance().delete(this, resolvedExtra);
|
||||
|
||||
if (!isFinishing()) {
|
||||
finish();
|
||||
@ -324,7 +324,11 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
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) {
|
||||
Log.w(TAG, ioe);
|
||||
return null;
|
||||
|
@ -9,7 +9,7 @@ import android.support.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
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.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -26,15 +26,13 @@ public class AudioRecorder {
|
||||
|
||||
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
||||
|
||||
private final Context context;
|
||||
private final PersistentBlobProvider blobProvider;
|
||||
private final Context context;
|
||||
|
||||
private AudioCodec audioCodec;
|
||||
private Uri captureUri;
|
||||
|
||||
public AudioRecorder(@NonNull Context context) {
|
||||
this.context = context;
|
||||
this.blobProvider = PersistentBlobProvider.getInstance(context.getApplicationContext());
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
@ -49,9 +47,11 @@ public class AudioRecorder {
|
||||
|
||||
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
||||
|
||||
captureUri = blobProvider.create(context, new ParcelFileDescriptor.AutoCloseInputStream(fds[0]),
|
||||
MediaUtil.AUDIO_AAC, null, null);
|
||||
audioCodec = new AudioCodec();
|
||||
captureUri = BlobProvider.getInstance()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType(MediaUtil.AUDIO_AAC)
|
||||
.createForSingleSessionOnDisk(context);
|
||||
audioCodec = new AudioCodec();
|
||||
|
||||
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||
} catch (IOException e) {
|
||||
|
@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
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.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
@ -85,7 +85,7 @@ public class CameraActivity extends PassphraseRequiredActionBarActivity implemen
|
||||
} else {
|
||||
if (editorFragment != null && captureUri != null) {
|
||||
Log.i(TAG, "Cleaning up unused capture: " + captureUri);
|
||||
MemoryBlobProvider.getInstance().delete(captureUri);
|
||||
BlobProvider.getInstance().delete(this, captureUri);
|
||||
captureUri = null;
|
||||
}
|
||||
super.onBackPressed();
|
||||
@ -99,7 +99,7 @@ public class CameraActivity extends PassphraseRequiredActionBarActivity implemen
|
||||
|
||||
if (captureUri != null) {
|
||||
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) {
|
||||
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());
|
||||
|
||||
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.logging.Log;
|
||||
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 java.io.IOException;
|
||||
@ -153,8 +153,8 @@ public class ContactRepository {
|
||||
Log.w(TAG, "Failed to parse the vcard.", e);
|
||||
}
|
||||
|
||||
if (PersistentBlobProvider.AUTHORITY.equals(uri.getAuthority())) {
|
||||
PersistentBlobProvider.getInstance(context).delete(context, uri);
|
||||
if (BlobProvider.AUTHORITY.equals(uri.getAuthority())) {
|
||||
BlobProvider.getInstance().delete(context, uri);
|
||||
}
|
||||
|
||||
return contact;
|
||||
|
@ -177,8 +177,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
|
||||
import org.thoughtcrime.securesms.providers.MemoryBlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
@ -592,7 +591,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Stream.of(slideDeck.getSlides())
|
||||
.map(Slide::getUri)
|
||||
.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);
|
||||
|
||||
byte[] textData = rawText.getBytes();
|
||||
Uri textUri = MemoryBlobProvider.getInstance().createUri(textData);
|
||||
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date());
|
||||
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));
|
||||
}
|
||||
@ -2299,7 +2302,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first);
|
||||
BlobProvider.getInstance().delete(ConversationActivity.this, result.first);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
@ -2328,7 +2331,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first);
|
||||
BlobProvider.getInstance().delete(ConversationActivity.this, result.first);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
@ -20,12 +20,14 @@ import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
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.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
||||
@ -115,8 +117,11 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
||||
try {
|
||||
byte[] data = viewHolder.getData(forMms);
|
||||
|
||||
return PersistentBlobProvider.getInstance(GiphyActivity.this).create(GiphyActivity.this, data, "image/gif", null);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
return BlobProvider.getInstance()
|
||||
.forData(data)
|
||||
.withMimeType(MediaUtil.IMAGE_GIF)
|
||||
.createForSingleSessionOnDisk(GiphyActivity.this);
|
||||
} catch (InterruptedException | ExecutionException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
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.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
@ -111,7 +111,7 @@ public class GroupManager {
|
||||
GroupContext groupContext = groupContextBuilder.build();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import com.google.android.mms.pdu_alt.RetrieveConf;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
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.PartParser;
|
||||
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.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -196,7 +195,6 @@ public class MmsDownloadJob extends ContextJob {
|
||||
LegacyMessageException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
MemoryBlobProvider provider = MemoryBlobProvider.getInstance();
|
||||
Optional<Address> group = Optional.absent();
|
||||
Set<Address> members = new HashSet<>();
|
||||
String body = null;
|
||||
@ -235,7 +233,7 @@ public class MmsDownloadJob extends ContextJob {
|
||||
PduPart part = media.getPart(i);
|
||||
|
||||
if (part.getData() != null) {
|
||||
Uri uri = provider.createSingleUseUri(part.getData());
|
||||
Uri uri = BlobProvider.getInstance().forData(part.getData()).createForSingleUseInMemory();
|
||||
String name = null;
|
||||
|
||||
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.ContentProxySelector;
|
||||
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.concurrent.SignalExecutors;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@ -145,7 +145,7 @@ public class LinkPreviewRepository {
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||
|
||||
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,
|
||||
uri,
|
||||
MediaUtil.IMAGE_JPEG,
|
||||
|
@ -5,21 +5,11 @@ import android.arch.lifecycle.MutableLiveData;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
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.providers.MemoryBlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
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.mediapreview.MediaRailAdapter;
|
||||
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.util.CharacterCalculator.CharacterState;
|
||||
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 java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -433,13 +434,17 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
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());
|
||||
|
||||
updatedMedia.add(updated);
|
||||
renderTimer.split("item");
|
||||
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
} catch (InterruptedException | ExecutionException | IOException e) {
|
||||
Log.w(TAG, "Failed to render image. Using base image.");
|
||||
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.giph.ui.GiphyActivity;
|
||||
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.scribbles.ScribbleActivity;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
@ -165,14 +167,16 @@ public class AttachmentManager {
|
||||
}
|
||||
|
||||
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);
|
||||
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) {
|
||||
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);
|
||||
garbage.add(uri);
|
||||
}
|
||||
@ -206,13 +210,17 @@ public class AttachmentManager {
|
||||
@Override
|
||||
public void onSuccess(@NonNull Bitmap result) {
|
||||
byte[] blob = BitmapUtil.toByteArray(result);
|
||||
Uri uri = PersistentBlobProvider.getInstance(context)
|
||||
.create(context, blob, MediaUtil.IMAGE_PNG, null);
|
||||
Uri uri = BlobProvider.getInstance()
|
||||
.forData(blob)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place);
|
||||
|
||||
setSlide(locationSlide);
|
||||
attachmentListener.onAttachmentChanged();
|
||||
returnResult.set(true);
|
||||
Util.runOnMain(() -> {
|
||||
setSlide(locationSlide);
|
||||
attachmentListener.onAttachmentChanged();
|
||||
returnResult.set(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -443,7 +451,7 @@ public class AttachmentManager {
|
||||
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (captureIntent.resolveActivity(activity.getPackageManager()) != 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());
|
||||
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.AttachmentId;
|
||||
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.MemoryBlobProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -27,7 +27,7 @@ public class PartAuthority {
|
||||
private static final int PART_ROW = 1;
|
||||
private static final int THUMB_ROW = 2;
|
||||
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;
|
||||
|
||||
@ -35,9 +35,9 @@ public class PartAuthority {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI("org.thoughtcrime.securesms", "part/*/#", PART_ROW);
|
||||
uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/*/#", THUMB_ROW);
|
||||
uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(MemoryBlobProvider.AUTHORITY, MemoryBlobProvider.PATH, SINGLE_USE_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
|
||||
}
|
||||
|
||||
public static InputStream getAttachmentStream(@NonNull Context context, @NonNull Uri uri)
|
||||
@ -48,8 +48,8 @@ public class PartAuthority {
|
||||
switch (match) {
|
||||
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 PERSISTENT_ROW: return PersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case SINGLE_USE_ROW: return MemoryBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
|
||||
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||
default: return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
@ -68,8 +68,9 @@ public class PartAuthority {
|
||||
if (attachment != null) return attachment.getFileName();
|
||||
else return null;
|
||||
case PERSISTENT_ROW:
|
||||
return PersistentBlobProvider.getFileName(context, uri);
|
||||
case SINGLE_USE_ROW:
|
||||
return DeprecatedPersistentBlobProvider.getFileName(context, uri);
|
||||
case BLOB_ROW:
|
||||
return BlobProvider.getFileName(uri);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -86,8 +87,9 @@ public class PartAuthority {
|
||||
if (attachment != null) return attachment.getSize();
|
||||
else return null;
|
||||
case PERSISTENT_ROW:
|
||||
return PersistentBlobProvider.getFileSize(context, uri);
|
||||
case SINGLE_USE_ROW:
|
||||
return DeprecatedPersistentBlobProvider.getFileSize(context, uri);
|
||||
case BLOB_ROW:
|
||||
return BlobProvider.getFileSize(uri);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -104,8 +106,9 @@ public class PartAuthority {
|
||||
if (attachment != null) return attachment.getContentType();
|
||||
else return null;
|
||||
case PERSISTENT_ROW:
|
||||
return PersistentBlobProvider.getMimeType(context, uri);
|
||||
case SINGLE_USE_ROW:
|
||||
return DeprecatedPersistentBlobProvider.getMimeType(context, uri);
|
||||
case BLOB_ROW:
|
||||
return BlobProvider.getMimeType(uri);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -132,7 +135,7 @@ public class PartAuthority {
|
||||
case PART_ROW:
|
||||
case THUMB_ROW:
|
||||
case PERSISTENT_ROW:
|
||||
case SINGLE_USE_ROW:
|
||||
case BLOB_ROW:
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
@ -9,31 +8,28 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
||||
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.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";
|
||||
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
||||
@ -54,82 +50,29 @@ public class PersistentBlobProvider {
|
||||
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) {
|
||||
synchronized (PersistentBlobProvider.class) {
|
||||
synchronized (DeprecatedPersistentBlobProvider.class) {
|
||||
if (instance == null) {
|
||||
instance = new PersistentBlobProvider(context);
|
||||
instance = new DeprecatedPersistentBlobProvider(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 PersistentBlobProvider(@NonNull Context context) {
|
||||
private DeprecatedPersistentBlobProvider(@NonNull Context context) {
|
||||
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 {
|
||||
File target = new File(getExternalDir(context), String.valueOf(System.currentTimeMillis()) + "." + getExtensionFromMimeType(mimeType));
|
||||
return FileProviderUtil.getUriFor(context, target);
|
||||
@ -140,7 +83,6 @@ public class PersistentBlobProvider {
|
||||
case MATCH_OLD:
|
||||
case MATCH_NEW:
|
||||
long id = ContentUris.parseId(uri);
|
||||
cache.remove(id);
|
||||
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 {
|
||||
final byte[] cached = cache.get(id);
|
||||
|
||||
if (cached != null) {
|
||||
return new ByteArrayInputStream(cached);
|
||||
}
|
||||
|
||||
FileData fileData = getFile(context, id);
|
||||
|
||||
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.PointF;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
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.mms.GlideApp;
|
||||
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.Layer;
|
||||
import org.thoughtcrime.securesms.scribbles.viewmodel.TextLayer;
|
||||
@ -300,30 +302,40 @@ public class ScribbleFragment extends Fragment implements ScribbleHud.EventListe
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
future.addListener(new ListenableFuture.Listener<Bitmap>() {
|
||||
@Override
|
||||
public void onSuccess(Bitmap result) {
|
||||
PersistentBlobProvider provider = PersistentBlobProvider.getInstance(getContext());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 80, baos);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
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),
|
||||
result.getWidth(),
|
||||
result.getHeight(),
|
||||
data.length,
|
||||
message,
|
||||
transport);
|
||||
controller.onImageEditComplete(uri,
|
||||
result.getWidth(),
|
||||
result.getHeight(),
|
||||
data.length,
|
||||
message,
|
||||
transport);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to persist image.", e);
|
||||
Util.runOnMain(() -> controller.onImageEditFailure());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException 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.TextSlide;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
Loading…
x
Reference in New Issue
Block a user