From a2f478570a1e2582c11e7a52c4d2fffa8b543fe1 Mon Sep 17 00:00:00 2001
From: Geonu Kang <blamethegnu@unrulygnu.com>
Date: Sat, 21 Nov 2015 16:18:19 +0900
Subject: [PATCH] Add MIME type to PersistentBlobProvider

Fixes #4536
Closes #4689
---
 .../securesms/ConversationActivity.java       |  4 +-
 .../thoughtcrime/securesms/ShareActivity.java | 20 ++++---
 .../securesms/audio/AudioRecorder.java        |  6 +-
 .../securesms/mms/AttachmentManager.java      |  6 +-
 .../providers/PersistentBlobProvider.java     | 60 ++++++++++++-------
 .../securesms/util/MediaUtil.java             |  7 ++-
 6 files changed, 68 insertions(+), 35 deletions(-)

diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 0e470fc78a..fbf651d436 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -1402,7 +1402,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
 
   @Override
   public void onImageCapture(@NonNull final byte[] imageBytes) {
-    setMedia(PersistentBlobProvider.getInstance(this).create(masterSecret, imageBytes), MediaType.IMAGE);
+    setMedia(PersistentBlobProvider.getInstance(this)
+                                   .create(masterSecret, imageBytes, ContentType.IMAGE_JPEG),
+             MediaType.IMAGE);
     quickAttachmentDrawer.hide(false);
   }
 
diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java
index 367c15c489..0195942085 100644
--- a/src/org/thoughtcrime/securesms/ShareActivity.java
+++ b/src/org/thoughtcrime/securesms/ShareActivity.java
@@ -23,6 +23,7 @@ import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -59,6 +60,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
   private ViewGroup    fragmentContainer;
   private View         progressWheel;
   private Uri          resolvedExtra;
+  private String       mimeType;
   private boolean      isPassingAlongMedia;
 
   @Override
@@ -110,6 +112,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
     isPassingAlongMedia = false;
 
     Uri streamExtra = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+    mimeType        = getMimeType(streamExtra);
     if (streamExtra != null && PartAuthority.isLocalUri(streamExtra)) {
       isPassingAlongMedia = true;
       resolvedExtra       = streamExtra;
@@ -166,19 +169,18 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
   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 = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
-    final String type        = streamExtra != null ? getMimeType(streamExtra)
-                                                   : MediaUtil.getCorrectedMimeType(getIntent().getType());
     intent.putExtra(ConversationActivity.TEXT_EXTRA, textExtra);
-    if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, type);
+    if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType);
 
     return intent;
   }
 
-  private String getMimeType(Uri uri) {
-    final String type = MediaUtil.getMimeType(getApplicationContext(), uri);
-    return type == null ? MediaUtil.getCorrectedMimeType(getIntent().getType())
-                        : type;
+  private String getMimeType(@Nullable Uri uri) {
+    if (uri != null) {
+      final String mimeType = MediaUtil.getMimeType(getApplicationContext(), uri);
+      if (mimeType != null) return mimeType;
+    }
+    return MediaUtil.getCorrectedMimeType(getIntent().getType());
   }
 
   private class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
@@ -200,7 +202,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
           return null;
         }
 
-        return PersistentBlobProvider.getInstance(context).create(masterSecret, input);
+        return PersistentBlobProvider.getInstance(context).create(masterSecret, input, mimeType);
       } catch (IOException ioe) {
         Log.w(TAG, ioe);
         return null;
diff --git a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java
index 9c1060b546..d110d8a614 100644
--- a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java
+++ b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java
@@ -20,6 +20,8 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
 import java.io.IOException;
 import java.util.concurrent.ExecutorService;
 
+import ws.com.google.android.mms.ContentType;
+
 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 public class AudioRecorder {
 
@@ -54,7 +56,9 @@ public class AudioRecorder {
 
           ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
 
-          captureUri  = blobProvider.create(masterSecret, new ParcelFileDescriptor.AutoCloseInputStream(fds[0]));
+          captureUri  = blobProvider.create(masterSecret,
+                                            new ParcelFileDescriptor.AutoCloseInputStream(fds[0]),
+                                            ContentType.AUDIO_AAC);
           audioCodec  = new AudioCodec();
 
           audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 86f3df9e10..ded9179015 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -159,7 +159,8 @@ public class AttachmentManager {
       @Override
       public void onSuccess(@NonNull Bitmap result) {
         byte[]        blob          = BitmapUtil.toByteArray(result);
-        Uri           uri           = PersistentBlobProvider.getInstance(context).create(masterSecret, blob);
+        Uri           uri           = PersistentBlobProvider.getInstance(context)
+                                                            .create(masterSecret, blob, ContentType.IMAGE_PNG);
         LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place);
 
         setSlide(locationSlide);
@@ -273,7 +274,8 @@ 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();
+          captureUri = PersistentBlobProvider.getInstance(context)
+                                             .createForExternal(ContentType.IMAGE_JPEG);
         }
         Log.w(TAG, "captureUri path is " + captureUri.getPath());
         captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri);
diff --git a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
index 2ee7a3a20c..2ee0d206ed 100644
--- a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
+++ b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
@@ -1,16 +1,18 @@
 package org.thoughtcrime.securesms.providers;
 
+import android.annotation.SuppressLint;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.UriMatcher;
 import android.net.Uri;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
+import android.webkit.MimeTypeMap;
 
 import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
 import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
 import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.recipients.Recipients;
 import org.thoughtcrime.securesms.util.Util;
 
 import java.io.ByteArrayInputStream;
@@ -18,7 +20,6 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -29,12 +30,14 @@ 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) {{
+  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        MIMETYPE_PATH_SEGMENT = 1;
+  private static final String     BLOB_EXTENSION        = "blob";
+  private static final int        MATCH                 = 1;
+  private static final UriMatcher MATCHER               = new UriMatcher(UriMatcher.NO_MATCH) {{
     addURI(AUTHORITY, EXPECTED_PATH, MATCH);
   }};
 
@@ -51,7 +54,8 @@ public class PersistentBlobProvider {
     return instance;
   }
 
-  private final Context context;
+  private final Context           context;
+  @SuppressLint("UseSparseArrays")
   private final Map<Long, byte[]> cache    = Collections.synchronizedMap(new HashMap<Long, byte[]>());
   private final ExecutorService   executor = Executors.newCachedThreadPool();
 
@@ -60,22 +64,27 @@ public class PersistentBlobProvider {
   }
 
   public Uri create(@NonNull MasterSecret masterSecret,
-                    @NonNull byte[] imageBytes)
+                    @NonNull byte[] blobBytes,
+                    @NonNull String mimeType)
   {
     final long id = System.currentTimeMillis();
-    cache.put(id, imageBytes);
-    return create(masterSecret, new ByteArrayInputStream(imageBytes), id);
+    cache.put(id, blobBytes);
+    return create(masterSecret, new ByteArrayInputStream(blobBytes), id, mimeType);
   }
 
   public Uri create(@NonNull MasterSecret masterSecret,
-                    @NonNull InputStream input)
+                    @NonNull InputStream input,
+                    @NonNull String mimeType)
   {
-    return create(masterSecret, input, System.currentTimeMillis());
+    return create(masterSecret, input, System.currentTimeMillis(), mimeType);
   }
 
-  private Uri create(MasterSecret masterSecret, InputStream input, long id) {
+  private Uri create(MasterSecret masterSecret, InputStream input, long id, String mimeType) {
     persistToDisk(masterSecret, id, input);
-    final Uri uniqueUri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(System.currentTimeMillis()));
+    final Uri uniqueUri = CONTENT_URI.buildUpon()
+                                     .appendPath(mimeType)
+                                     .appendEncodedPath(String.valueOf(System.currentTimeMillis()))
+                                     .build();
     return ContentUris.withAppendedId(uniqueUri, id);
   }
 
@@ -97,10 +106,9 @@ public class PersistentBlobProvider {
     });
   }
 
-  public Uri createForExternal() throws IOException {
-    return Uri.fromFile(new File(getExternalDir(context), String.valueOf(System.currentTimeMillis()) + ".jpg"))
-              .buildUpon()
-              .build();
+  public Uri createForExternal(@NonNull String mimeType) throws IOException {
+    return Uri.fromFile(new File(getExternalDir(context),
+                        String.valueOf(System.currentTimeMillis()) + "." + getExtensionFromMimeType(mimeType)));
   }
 
   public boolean delete(@NonNull Uri uri) {
@@ -121,7 +129,17 @@ public class PersistentBlobProvider {
   }
 
   private File getFile(long id) {
-    return new File(context.getDir("captures", Context.MODE_PRIVATE), id + ".jpg");
+    return new File(context.getDir("captures", Context.MODE_PRIVATE), id + "." + BLOB_EXTENSION);
+  }
+
+  public static @Nullable String getMimeType(@NonNull Context context, @NonNull Uri persistentBlobUri) {
+    if (!isAuthority(context, persistentBlobUri)) return null;
+    return persistentBlobUri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
+  }
+
+  private static @NonNull String getExtensionFromMimeType(String mimeType) {
+    final String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+    return extension != null ? extension : BLOB_EXTENSION;
   }
 
   private static @NonNull File getExternalDir(Context context) throws IOException {
diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java
index 45f1db9b69..883a2787fc 100644
--- a/src/org/thoughtcrime/securesms/util/MediaUtil.java
+++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.mms.ImageSlide;
 import org.thoughtcrime.securesms.mms.PartAuthority;
 import org.thoughtcrime.securesms.mms.Slide;
 import org.thoughtcrime.securesms.mms.VideoSlide;
+import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -71,10 +72,14 @@ public class MediaUtil {
   }
 
   public static @Nullable String getMimeType(Context context, Uri uri) {
+    if (PersistentBlobProvider.isAuthority(context, uri)) {
+      return PersistentBlobProvider.getMimeType(context, uri);
+    }
+
     String type = context.getContentResolver().getType(uri);
     if (type == null) {
       final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
-      type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+      type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
     }
     return getCorrectedMimeType(type);
   }