diff --git a/res/layout/share_activity.xml b/res/layout/share_activity.xml
index 366cf777e1..bd30ed1746 100644
--- a/res/layout/share_activity.xml
+++ b/res/layout/share_activity.xml
@@ -1,9 +1,20 @@
+ xmlns:wheel="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index e15bddab92..12dacfe0c9 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -16,7 +16,6 @@
*/
package org.thoughtcrime.securesms;
-import android.annotation.TargetApi;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -98,7 +97,7 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
-import org.thoughtcrime.securesms.providers.CaptureProvider;
+import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
@@ -201,7 +200,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
- @TargetApi(Build.VERSION_CODES.KITKAT)
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
@@ -311,13 +309,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
switch (reqCode) {
case PICK_IMAGE:
boolean isGif = MediaUtil.isGif(MediaUtil.getMimeType(this, data.getData()));
- setMedia(data.getData(), isGif ? MediaType.GIF : MediaType.IMAGE, false);
+ setMedia(data.getData(), isGif ? MediaType.GIF : MediaType.IMAGE);
break;
case PICK_VIDEO:
- setMedia(data.getData(), MediaType.VIDEO, false);
+ setMedia(data.getData(), MediaType.VIDEO);
break;
case PICK_AUDIO:
- setMedia(data.getData(), MediaType.AUDIO, false);
+ setMedia(data.getData(), MediaType.AUDIO);
break;
case PICK_CONTACT_INFO:
addAttachmentContactInfo(data.getData());
@@ -331,7 +329,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
break;
case TAKE_PHOTO:
if (attachmentManager.getCaptureUri() != null) {
- setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE, true);
+ setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE);
}
break;
}
@@ -717,9 +715,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (draftText != null) composeText.setText(draftText);
- if (draftImage != null) setMedia(draftImage, MediaType.IMAGE, false);
- else if (draftAudio != null) setMedia(draftAudio, MediaType.AUDIO, false);
- else if (draftVideo != null) setMedia(draftVideo, MediaType.VIDEO, false);
+ if (draftImage != null) setMedia(draftImage, MediaType.IMAGE);
+ else if (draftAudio != null) setMedia(draftAudio, MediaType.AUDIO);
+ else if (draftVideo != null) setMedia(draftVideo, MediaType.VIDEO);
if (draftText == null && draftImage == null && draftAudio == null && draftVideo == null) {
initializeDraftFromDatabase();
@@ -753,11 +751,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (draft.getType().equals(Draft.TEXT)) {
composeText.setText(draft.getValue());
} else if (draft.getType().equals(Draft.IMAGE)) {
- setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE, false);
+ setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE);
} else if (draft.getType().equals(Draft.AUDIO)) {
- setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO, false);
+ setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO);
} else if (draft.getType().equals(Draft.VIDEO)) {
- setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO, false);
+ setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO);
}
}
@@ -1012,8 +1010,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
- private void setMedia(Uri uri, MediaType mediaType, boolean isCapture) {
- attachmentManager.setMedia(masterSecret, uri, mediaType, getCurrentMediaConstraints(), isCapture);
+ private void setMedia(Uri uri, MediaType mediaType) {
+ attachmentManager.setMedia(masterSecret, uri, mediaType, getCurrentMediaConstraints());
}
private void addAttachmentContactInfo(Uri contactUri) {
@@ -1053,7 +1051,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
drafts.add(new Draft(Draft.TEXT, composeText.getText().toString()));
}
- for (Slide slide : attachmentManager.getSlideDeck().getSlides()) {
+ for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) {
if (slide.hasAudio()) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString()));
else if (slide.hasVideo()) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString()));
else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
@@ -1263,7 +1261,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
{
final Context context = getApplicationContext();
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(recipients,
- attachmentManager.getSlideDeck(),
+ attachmentManager.buildSlideDeck(),
getMessage(),
System.currentTimeMillis(),
distributionType);
@@ -1336,7 +1334,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onImageCapture(@NonNull final byte[] imageBytes) {
- setMedia(CaptureProvider.getInstance(this).create(masterSecret, recipients, imageBytes), MediaType.IMAGE, true);
+ setMedia(PersistentBlobProvider.getInstance(this).create(masterSecret, recipients, imageBytes), MediaType.IMAGE);
quickAttachmentDrawer.hide(false);
}
diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java
index bb69698f11..1f26924cb0 100644
--- a/src/org/thoughtcrime/securesms/ShareActivity.java
+++ b/src/org/thoughtcrime/securesms/ShareActivity.java
@@ -17,21 +17,29 @@
package org.thoughtcrime.securesms;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
+import org.thoughtcrime.securesms.util.ViewUtil;
-import java.net.URLDecoder;
+import java.io.IOException;
+import java.io.InputStream;
import ws.com.google.android.mms.ContentType;
@@ -43,9 +51,17 @@ import ws.com.google.android.mms.ContentType;
public class ShareActivity extends PassphraseRequiredActionBarActivity
implements ShareFragment.ConversationSelectedListener
{
+ private static final String TAG = ShareActivity.class.getSimpleName();
+
private final DynamicTheme dynamicTheme = new DynamicTheme ();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
+ private MasterSecret masterSecret;
+ private ViewGroup fragmentContainer;
+ private View progressWheel;
+ private Uri resolvedExtra;
+ private boolean isPassingAlongMedia;
+
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
@@ -54,14 +70,21 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
+ this.masterSecret = masterSecret;
setContentView(R.layout.share_activity);
+
+ fragmentContainer = ViewUtil.findById(this, R.id.drawer_layout);
+ progressWheel = ViewUtil.findById(this, R.id.progress_wheel);
+
initFragment(R.id.drawer_layout, new ShareFragment(), masterSecret);
+ initializeMedia();
}
@Override
protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
+ super.onNewIntent(intent);
+ setIntent(intent);
+ initializeMedia();
}
@Override
@@ -75,7 +98,46 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
@Override
public void onPause() {
super.onPause();
- if (!isFinishing()) finish();
+ if (!isPassingAlongMedia && resolvedExtra != null) {
+ PersistentBlobProvider.getInstance(this).delete(resolvedExtra);
+ }
+ if (!isFinishing()) {
+ finish();
+ }
+ }
+
+ private void initializeMedia() {
+ final Context context = this;
+ isPassingAlongMedia = false;
+ fragmentContainer.setVisibility(View.GONE);
+ progressWheel.setVisibility(View.VISIBLE);
+ new AsyncTask() {
+ @Override
+ protected Uri doInBackground(Uri... uris) {
+ try {
+ if (uris.length != 1 || uris[0] == null) {
+ return null;
+ }
+
+ InputStream input = context.getContentResolver().openInputStream(uris[0]);
+ if (input == null) {
+ return null;
+ }
+
+ return PersistentBlobProvider.getInstance(context).create(masterSecret, input);
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Uri uri) {
+ resolvedExtra = uri;
+ ViewUtil.fadeIn(fragmentContainer, 300);
+ ViewUtil.fadeOut(progressWheel, 300);
+ }
+ }.execute(getIntent().getParcelableExtra(Intent.EXTRA_STREAM));
}
@Override
@@ -100,6 +162,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
private void handleNewConversation() {
Intent intent = getBaseShareIntent(NewConversationActivity.class);
+ isPassingAlongMedia = true;
startActivity(intent);
}
@@ -114,38 +177,24 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
+ isPassingAlongMedia = true;
startActivity(intent);
}
- private Uri getStreamExtra() {
- Uri streamUri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
- if (streamUri == null) {
- return null;
- }
-
- if (streamUri.getAuthority().equals("com.google.android.apps.photos.contentprovider") &&
- streamUri.toString().endsWith("/ACTUAL"))
- {
- String[] parts = streamUri.toString().split("/");
- if (parts.length > 3) {
- return Uri.parse(URLDecoder.decode(parts[parts.length - 2]));
- }
- }
- return streamUri;
- }
-
- private Intent getBaseShareIntent(final Class> target) {
+ private Intent getBaseShareIntent(final @NonNull Class> target) {
final Intent intent = new Intent(this, target);
final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT);
- final Uri streamExtra = getStreamExtra();
+ final Uri streamExtra = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
final String type = streamExtra != null ? getMimeType(streamExtra) : getIntent().getType();
- if (ContentType.isImageType(type)) {
- intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, streamExtra);
- } else if (ContentType.isAudioType(type)) {
- intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, streamExtra);
- } else if (ContentType.isVideoType(type)) {
- intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, streamExtra);
+ if (resolvedExtra != null) {
+ if (ContentType.isImageType(type)) {
+ intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, resolvedExtra);
+ } else if (ContentType.isAudioType(type)) {
+ intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, resolvedExtra);
+ } else if (ContentType.isVideoType(type)) {
+ intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, resolvedExtra);
+ }
}
intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, textExtra);
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index b8b103d597..cdc8a2a7d2 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -38,9 +38,11 @@ import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.RemovableMediaView;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.providers.CaptureProvider;
+import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.MediaUtil;
+import org.thoughtcrime.securesms.util.ViewUtil;
+import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.IOException;
@@ -53,18 +55,17 @@ public class AttachmentManager {
private final @NonNull RemovableMediaView removableMediaView;
private final @NonNull ThumbnailView thumbnail;
private final @NonNull AudioView audioView;
- private final @NonNull SlideDeck slideDeck;
private final @NonNull AttachmentListener attachmentListener;
- private Uri captureUri;
+ private @NonNull Optional slide = Optional.absent();
+ private @Nullable Uri captureUri;
- public AttachmentManager(@NonNull Activity view, @NonNull AttachmentListener listener) {
- this.attachmentView = view.findViewById(R.id.attachment_editor);
- this.thumbnail = (ThumbnailView) view.findViewById(R.id.attachment_thumbnail);
- this.audioView = (AudioView) view.findViewById(R.id.attachment_audio);
- this.removableMediaView = (RemovableMediaView) view.findViewById(R.id.removable_media_view);
- this.slideDeck = new SlideDeck();
- this.context = view;
+ public AttachmentManager(@NonNull Activity activity, @NonNull AttachmentListener listener) {
+ this.attachmentView = ViewUtil.findById(activity, R.id.attachment_editor);
+ this.thumbnail = ViewUtil.findById(activity, R.id.attachment_thumbnail);
+ this.audioView = ViewUtil.findById(activity, R.id.attachment_audio);
+ this.removableMediaView = ViewUtil.findById(activity, R.id.removable_media_view);
+ this.context = activity;
this.attachmentListener = listener;
removableMediaView.setRemoveClickListener(new RemoveButtonListener());
@@ -76,11 +77,13 @@ public class AttachmentManager {
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
+
@Override
public void onAnimationRepeat(Animation animation) {}
+
@Override
public void onAnimationEnd(Animation animation) {
- slideDeck.clear();
+ slide = Optional.absent();
thumbnail.clear();
attachmentView.setVisibility(View.GONE);
attachmentListener.onAttachmentChanged();
@@ -92,26 +95,39 @@ public class AttachmentManager {
}
public void cleanup() {
- if (captureUri != null) CaptureProvider.getInstance(context).delete(captureUri);
+ cleanup(captureUri);
+ cleanup(getSlideUri());
+
captureUri = null;
+ slide = Optional.absent();
}
- public void setMedia(@NonNull final MasterSecret masterSecret,
- @NonNull final Uri uri,
- @NonNull final MediaType mediaType,
- @NonNull final MediaConstraints constraints,
- final boolean isCapture)
+ private void cleanup(final @Nullable Uri uri) {
+ if (uri != null && PersistentBlobProvider.isAuthority(context, uri)) {
+ Log.w(TAG, "cleaning up " + uri);
+ PersistentBlobProvider.getInstance(context).delete(uri);
+ }
+ }
+
+ private void setSlide(@NonNull Slide slide) {
+ if (getSlideUri() != null) cleanup(getSlideUri());
+ if (captureUri != null && slide.getUri() != captureUri) cleanup(captureUri);
+
+ this.captureUri = null;
+ this.slide = Optional.of(slide);
+ }
+
+ public void setMedia(@NonNull final MasterSecret masterSecret,
+ @NonNull final Uri uri,
+ @NonNull final MediaType mediaType,
+ @NonNull final MediaConstraints constraints)
{
new AsyncTask() {
@Override
protected void onPreExecute() {
- slideDeck.clear();
thumbnail.clear();
thumbnail.showProgressSpinner();
attachmentView.setVisibility(View.VISIBLE);
-
- if (isCapture) captureUri = uri;
- if (!uri.equals(captureUri)) cleanup();
}
@Override
@@ -141,7 +157,7 @@ public class AttachmentManager {
R.string.ConversationActivity_attachment_exceeds_size_limits,
Toast.LENGTH_SHORT).show();
} else {
- slideDeck.addSlide(slide);
+ setSlide(slide);
attachmentView.setVisibility(View.VISIBLE);
if (slide.hasAudio()) {
@@ -162,9 +178,10 @@ public class AttachmentManager {
return attachmentView.getVisibility() == View.VISIBLE;
}
-
- public @NonNull SlideDeck getSlideDeck() {
- return slideDeck;
+ public @NonNull SlideDeck buildSlideDeck() {
+ SlideDeck deck = new SlideDeck();
+ if (slide.isPresent()) deck.addSlide(slide.get());
+ return deck;
}
public static void selectVideo(Activity activity, int requestCode) {
@@ -184,7 +201,11 @@ public class AttachmentManager {
activity.startActivityForResult(intent, requestCode);
}
- public Uri getCaptureUri() {
+ private @Nullable Uri getSlideUri() {
+ return slide.isPresent() ? slide.get().getUri() : null;
+ }
+
+ public @Nullable Uri getCaptureUri() {
return captureUri;
}
@@ -192,7 +213,10 @@ public class AttachmentManager {
try {
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (captureIntent.resolveActivity(activity.getPackageManager()) != null) {
- captureUri = CaptureProvider.getInstance(context).createForExternal(recipients);
+ if (captureUri == null) {
+ captureUri = PersistentBlobProvider.getInstance(context).createForExternal(recipients);
+ }
+ Log.w(TAG, "captureUri path is " + captureUri.getPath());
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri);
activity.startActivityForResult(captureIntent, requestCode);
}
@@ -237,8 +261,8 @@ public class AttachmentManager {
private class RemoveButtonListener implements View.OnClickListener {
@Override
public void onClick(View v) {
- clear();
cleanup();
+ clear();
}
}
diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
index 55b8b9888b..53224c2025 100644
--- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java
+++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
@@ -9,7 +9,7 @@ import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.providers.CaptureProvider;
+import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.providers.PartProvider;
import org.thoughtcrime.securesms.providers.SingleUseBlobProvider;
@@ -25,7 +25,7 @@ public class PartAuthority {
private static final int PART_ROW = 1;
private static final int THUMB_ROW = 2;
- private static final int CAPTURE_ROW = 3;
+ private static final int PERSISTENT_ROW = 3;
private static final int SINGLE_USE_ROW = 4;
private static final UriMatcher uriMatcher;
@@ -34,7 +34,7 @@ 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(CaptureProvider.AUTHORITY, CaptureProvider.EXPECTED_PATH, CAPTURE_ROW);
+ uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH, PERSISTENT_ROW);
uriMatcher.addURI(SingleUseBlobProvider.AUTHORITY, SingleUseBlobProvider.PATH, SINGLE_USE_ROW);
}
@@ -50,8 +50,8 @@ public class PartAuthority {
case THUMB_ROW:
partUri = new PartUriParser(uri);
return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(masterSecret, partUri.getPartId());
- case CAPTURE_ROW:
- return CaptureProvider.getInstance(context).getStream(masterSecret, ContentUris.parseId(uri));
+ case PERSISTENT_ROW:
+ return PersistentBlobProvider.getInstance(context).getStream(masterSecret, ContentUris.parseId(uri));
case SINGLE_USE_ROW:
return SingleUseBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
default:
diff --git a/src/org/thoughtcrime/securesms/providers/CaptureProvider.java b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
similarity index 57%
rename from src/org/thoughtcrime/securesms/providers/CaptureProvider.java
rename to src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
index 1211de6685..4f5d9c8bcf 100644
--- a/src/org/thoughtcrime/securesms/providers/CaptureProvider.java
+++ b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
@@ -6,7 +6,6 @@ import android.content.UriMatcher;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
-import android.support.v4.util.SparseArrayCompat;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
@@ -21,25 +20,27 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
-public class CaptureProvider {
- private static final String TAG = CaptureProvider.class.getSimpleName();
- private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture";
- public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
- public static final String AUTHORITY = "org.thoughtcrime.securesms";
- public static final String EXPECTED_PATH = "capture/*/#";
- private static final int MATCH = 1;
- public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH) {{
+public class PersistentBlobProvider {
+ private static final String TAG = PersistentBlobProvider.class.getSimpleName();
+ private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture";
+ public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
+ public static final String AUTHORITY = "org.thoughtcrime.securesms";
+ public static final String EXPECTED_PATH = "capture/*/#";
+ private static final int MATCH = 1;
+ private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH) {{
addURI(AUTHORITY, EXPECTED_PATH, MATCH);
}};
- private static volatile CaptureProvider instance;
+ private static volatile PersistentBlobProvider instance;
- public static CaptureProvider getInstance(Context context) {
+ public static PersistentBlobProvider getInstance(Context context) {
if (instance == null) {
- synchronized (CaptureProvider.class) {
+ synchronized (PersistentBlobProvider.class) {
if (instance == null) {
- instance = new CaptureProvider(context);
+ instance = new PersistentBlobProvider(context);
}
}
}
@@ -47,9 +48,9 @@ public class CaptureProvider {
}
private final Context context;
- private final SparseArrayCompat cache = new SparseArrayCompat<>();
+ private final Map cache = new HashMap<>();
- private CaptureProvider(Context context) {
+ private PersistentBlobProvider(Context context) {
this.context = context.getApplicationContext();
}
@@ -57,19 +58,31 @@ public class CaptureProvider {
@NonNull Recipients recipients,
@NonNull byte[] imageBytes)
{
- final int id = generateId(recipients);
+ final long id = generateId(recipients);
cache.put(id, imageBytes);
- persistToDisk(masterSecret, id, imageBytes);
+ return create(masterSecret, new ByteArrayInputStream(imageBytes), id);
+ }
+
+ public Uri create(@NonNull MasterSecret masterSecret,
+ @NonNull InputStream input)
+ {
+ return create(masterSecret, input, System.currentTimeMillis());
+ }
+
+ private Uri create(MasterSecret masterSecret, InputStream input, long id) {
+ persistToDisk(masterSecret, id, input);
final Uri uniqueUri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(System.currentTimeMillis()));
return ContentUris.withAppendedId(uniqueUri, id);
}
- private void persistToDisk(final MasterSecret masterSecret, final int id, final byte[] imageBytes) {
+ private void persistToDisk(final MasterSecret masterSecret, final long id,
+ final InputStream input)
+ {
new AsyncTask() {
@Override protected Void doInBackground(Void... params) {
try {
final OutputStream output = new EncryptingPartOutputStream(getFile(id), masterSecret);
- Util.copy(new ByteArrayInputStream(imageBytes), output);
+ Util.copy(input, output);
} catch (IOException e) {
Log.w(TAG, e);
}
@@ -83,23 +96,21 @@ public class CaptureProvider {
}
public Uri createForExternal(@NonNull Recipients recipients) throws IOException {
- final File externalDir = context.getExternalFilesDir(null);
- if (externalDir == null) throw new IOException("no external files directory");
- return Uri.fromFile(new File(externalDir, String.valueOf(generateId(recipients)) + ".jpg"))
+ return Uri.fromFile(new File(getExternalDir(context), String.valueOf(generateId(recipients)) + ".jpg"))
.buildUpon()
.appendQueryParameter("unique", String.valueOf(System.currentTimeMillis()))
.build();
}
public boolean delete(@NonNull Uri uri) {
- switch (uriMatcher.match(uri)) {
+ switch (MATCHER.match(uri)) {
case MATCH: return getFile(ContentUris.parseId(uri)).delete();
default: return new File(uri.getPath()).delete();
}
}
public @NonNull InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
- final byte[] cached = cache.get((int)id);
+ final byte[] cached = cache.get(id);
return cached != null ? new ByteArrayInputStream(cached)
: new DecryptingPartInputStream(getFile(id), masterSecret);
}
@@ -111,4 +122,18 @@ public class CaptureProvider {
private File getFile(long id) {
return new File(context.getDir("captures", Context.MODE_PRIVATE), id + ".jpg");
}
+
+ private static @NonNull File getExternalDir(Context context) throws IOException {
+ final File externalDir = context.getExternalFilesDir(null);
+ if (externalDir == null) throw new IOException("no external files directory");
+ return externalDir;
+ }
+
+ public static boolean isAuthority(@NonNull Context context, @NonNull Uri uri) {
+ try {
+ return MATCHER.match(uri) == MATCH || uri.getPath().startsWith(getExternalDir(context).getAbsolutePath());
+ } catch (IOException ioe) {
+ return false;
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java b/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java
index 9f2368c272..08d1d054f5 100644
--- a/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java
+++ b/src/org/thoughtcrime/securesms/providers/SingleUseBlobProvider.java
@@ -27,7 +27,7 @@ import java.util.Map;
public class SingleUseBlobProvider {
- private static final String TAG = CaptureProvider.class.getSimpleName();
+ private static final String TAG = SingleUseBlobProvider.class.getSimpleName();
public static final String AUTHORITY = "org.thoughtcrime.securesms";
public static final String PATH = "memory/*/#";