unjankify incoming media sharing

Closes #4374
Fixes #3989
// FREEBIE
This commit is contained in:
Jake McGinty 2015-10-15 14:40:45 -07:00 committed by Moxie Marlinspike
parent 60ab71099f
commit 59f2446a2b
7 changed files with 216 additions and 109 deletions

View File

@ -1,9 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
xmlns:wheel="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.pnikosis.materialishprogress.ProgressWheel android:id="@+id/progress_wheel"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
wheel:matProg_progressIndeterminate="true" />
</FrameLayout>

View File

@ -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);
}

View File

@ -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);
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<Uri, Void, Uri>() {
@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().<Uri>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 (resolvedExtra != null) {
if (ContentType.isImageType(type)) {
intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, streamExtra);
intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, resolvedExtra);
} else if (ContentType.isAudioType(type)) {
intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, streamExtra);
intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, resolvedExtra);
} else if (ContentType.isVideoType(type)) {
intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, streamExtra);
intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, resolvedExtra);
}
}
intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, textExtra);

View File

@ -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> 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();
}
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,
final boolean isCapture)
@NonNull final MediaConstraints constraints)
{
new AsyncTask<Void, Void, Slide>() {
@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();
}
}

View File

@ -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:

View File

@ -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();
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;
public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH) {{
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<byte[]> cache = new SparseArrayCompat<>();
private final Map<Long, byte[]> 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<Void, Void, Void>() {
@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;
}
}
}

View File

@ -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/*/#";