diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f486b08f11..a1441f818d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -165,6 +165,9 @@
+
+
+
() {
+ new AsyncTask() {
@Override
protected void onPreExecute() {
thumbnail.clear();
@@ -285,11 +285,23 @@ public class AttachmentManager {
}
private @NonNull Slide getManuallyCalculatedSlideInfo(Uri uri) throws IOException {
- long start = System.currentTimeMillis();
- long mediaSize = MediaUtil.getMediaSize(context, masterSecret, uri);
+ long start = System.currentTimeMillis();
+ Long mediaSize = null;
+ String fileName = null;
+ String mimeType = null;
+
+ if (PartAuthority.isLocalUri(uri)) {
+ mediaSize = PartAuthority.getAttachmentSize(context, masterSecret, uri);
+ fileName = PartAuthority.getAttachmentFileName(context, masterSecret, uri);
+ mimeType = PartAuthority.getAttachmentContentType(context, masterSecret, uri);
+ }
+
+ if (mediaSize == null) {
+ mediaSize = MediaUtil.getMediaSize(context, masterSecret, uri);
+ }
Log.w(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms");
- return mediaType.createSlide(context, uri, null, null, mediaSize);
+ return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize);
}
}.execute();
}
@@ -466,7 +478,8 @@ public class AttachmentManager {
if (MediaUtil.isImageType(mimeType)) return IMAGE;
if (MediaUtil.isAudioType(mimeType)) return AUDIO;
if (MediaUtil.isVideoType(mimeType)) return VIDEO;
- return null;
+
+ return DOCUMENT;
}
}
diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
index ffca98b99f..3fa2391ae2 100644
--- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java
+++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
@@ -5,7 +5,9 @@ import android.content.Context;
import android.content.UriMatcher;
import android.net.Uri;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -34,7 +36,8 @@ 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, PERSISTENT_ROW);
+ uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
+ uriMatcher.addURI(PersistentBlobProvider.AUTHORITY, PersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
uriMatcher.addURI(SingleUseBlobProvider.AUTHORITY, SingleUseBlobProvider.PATH, SINGLE_USE_ROW);
}
@@ -44,24 +47,71 @@ public class PartAuthority {
int match = uriMatcher.match(uri);
try {
switch (match) {
- case PART_ROW:
- PartUriParser partUri = new PartUriParser(uri);
- return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(masterSecret, partUri.getPartId());
- case THUMB_ROW:
- partUri = new PartUriParser(uri);
- return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(masterSecret, partUri.getPartId());
- case PERSISTENT_ROW:
- return PersistentBlobProvider.getInstance(context).getStream(masterSecret, ContentUris.parseId(uri));
- case SINGLE_USE_ROW:
- return SingleUseBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
- default:
- return context.getContentResolver().openInputStream(uri);
+ case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(masterSecret, new PartUriParser(uri).getPartId());
+ case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(masterSecret, new PartUriParser(uri).getPartId());
+ case PERSISTENT_ROW: return PersistentBlobProvider.getInstance(context).getStream(masterSecret, ContentUris.parseId(uri));
+ case SINGLE_USE_ROW: return SingleUseBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
+ default: return context.getContentResolver().openInputStream(uri);
}
} catch (SecurityException se) {
throw new IOException(se);
}
}
+ public static @Nullable String getAttachmentFileName(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) {
+ int match = uriMatcher.match(uri);
+
+ switch (match) {
+ case THUMB_ROW:
+ case PART_ROW:
+ Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, new PartUriParser(uri).getPartId());
+
+ if (attachment != null) return attachment.getFileName();
+ else return null;
+ case PERSISTENT_ROW:
+ return PersistentBlobProvider.getFileName(context, masterSecret, uri);
+ case SINGLE_USE_ROW:
+ default:
+ return null;
+ }
+ }
+
+ public static @Nullable Long getAttachmentSize(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) {
+ int match = uriMatcher.match(uri);
+
+ switch (match) {
+ case THUMB_ROW:
+ case PART_ROW:
+ Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, new PartUriParser(uri).getPartId());
+
+ if (attachment != null) return attachment.getSize();
+ else return null;
+ case PERSISTENT_ROW:
+ return PersistentBlobProvider.getFileSize(context, uri);
+ case SINGLE_USE_ROW:
+ default:
+ return null;
+ }
+ }
+
+ public static @Nullable String getAttachmentContentType(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) {
+ int match = uriMatcher.match(uri);
+
+ switch (match) {
+ case THUMB_ROW:
+ case PART_ROW:
+ Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, new PartUriParser(uri).getPartId());
+
+ if (attachment != null) return attachment.getContentType();
+ else return null;
+ case PERSISTENT_ROW:
+ return PersistentBlobProvider.getMimeType(context, uri);
+ case SINGLE_USE_ROW:
+ default:
+ return null;
+ }
+ }
+
public static Uri getAttachmentPublicUri(Uri uri) {
PartUriParser partUri = new PartUriParser(uri);
return PartProvider.getContentUri(partUri.getPartId());
diff --git a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
index f4755893ae..931b7d3f68 100644
--- a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
+++ b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java
@@ -12,8 +12,10 @@ import android.webkit.MimeTypeMap;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
+import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.Util;
+import org.whispersystems.libsignal.InvalidMessageException;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -30,15 +32,23 @@ public class PersistentBlobProvider {
private static final String TAG = PersistentBlobProvider.class.getSimpleName();
- private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture";
+ private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture-new";
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
public static final String AUTHORITY = "org.thoughtcrime.securesms";
- public static final String EXPECTED_PATH = "capture/*/*/#";
+ public static final String EXPECTED_PATH_OLD = "capture/*/*/#";
+ public static final String EXPECTED_PATH_NEW = "capture-new/*/*/*/*/#";
+
private static final int MIMETYPE_PATH_SEGMENT = 1;
+ private static final int FILENAME_PATH_SEGMENT = 2;
+ private static final int FILESIZE_PATH_SEGMENT = 3;
+
private static final String BLOB_EXTENSION = "blob";
- private static final int MATCH = 1;
+ private static final int MATCH_OLD = 1;
+ private static final int MATCH_NEW = 2;
+
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH) {{
- addURI(AUTHORITY, EXPECTED_PATH, MATCH);
+ addURI(AUTHORITY, EXPECTED_PATH_OLD, MATCH_OLD);
+ addURI(AUTHORITY, EXPECTED_PATH_NEW, MATCH_NEW);
}};
private static volatile PersistentBlobProvider instance;
@@ -63,26 +73,37 @@ public class PersistentBlobProvider {
this.context = context.getApplicationContext();
}
- public Uri create(@NonNull MasterSecret masterSecret,
- @NonNull byte[] blobBytes,
- @NonNull String mimeType)
+ public Uri create(@NonNull MasterSecret masterSecret,
+ @NonNull byte[] blobBytes,
+ @NonNull String mimeType,
+ @Nullable String fileName)
{
final long id = System.currentTimeMillis();
cache.put(id, blobBytes);
- return create(masterSecret, new ByteArrayInputStream(blobBytes), id, mimeType);
+ return create(masterSecret, new ByteArrayInputStream(blobBytes), id, mimeType, fileName, (long) blobBytes.length);
}
- public Uri create(@NonNull MasterSecret masterSecret,
- @NonNull InputStream input,
- @NonNull String mimeType)
+ public Uri create(@NonNull MasterSecret masterSecret,
+ @NonNull InputStream input,
+ @NonNull String mimeType,
+ @Nullable String fileName,
+ @Nullable Long fileSize)
{
- return create(masterSecret, input, System.currentTimeMillis(), mimeType);
+ return create(masterSecret, input, System.currentTimeMillis(), mimeType, fileName, fileSize);
}
- private Uri create(MasterSecret masterSecret, InputStream input, long id, String mimeType) {
+ private Uri create(@NonNull MasterSecret masterSecret,
+ @NonNull InputStream input,
+ long id,
+ @NonNull String mimeType,
+ @Nullable String fileName,
+ @Nullable Long fileSize)
+ {
persistToDisk(masterSecret, id, input);
final Uri uniqueUri = CONTENT_URI.buildUpon()
.appendPath(mimeType)
+ .appendPath(getEncryptedFileName(masterSecret, fileName))
+ .appendEncodedPath(String.valueOf(fileSize))
.appendEncodedPath(String.valueOf(System.currentTimeMillis()))
.build();
return ContentUris.withAppendedId(uniqueUri, id);
@@ -113,13 +134,14 @@ public class PersistentBlobProvider {
public boolean delete(@NonNull Uri uri) {
switch (MATCHER.match(uri)) {
- case MATCH:
+ case MATCH_OLD:
+ case MATCH_NEW:
long id = ContentUris.parseId(uri);
cache.remove(id);
return getFile(ContentUris.parseId(uri)).delete();
- default:
- return new File(uri.getPath()).delete();
}
+
+ return false;
}
public @NonNull InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
@@ -132,6 +154,11 @@ public class PersistentBlobProvider {
return new File(context.getDir("captures", Context.MODE_PRIVATE), id + "." + BLOB_EXTENSION);
}
+ private @Nullable String getEncryptedFileName(@NonNull MasterSecret masterSecret, @Nullable String fileName) {
+ if (fileName == null) return null;
+ return new MasterCipher(masterSecret).encryptBody(fileName);
+ }
+
public static @Nullable String getMimeType(@NonNull Context context, @NonNull Uri persistentBlobUri) {
if (!isAuthority(context, persistentBlobUri)) return null;
return isExternalBlobUri(context, persistentBlobUri)
@@ -139,6 +166,35 @@ public class PersistentBlobProvider {
: persistentBlobUri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
}
+ public static @Nullable String getFileName(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri persistentBlobUri) {
+ if (!isAuthority(context, persistentBlobUri)) return null;
+ if (isExternalBlobUri(context, persistentBlobUri)) return null;
+ if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null;
+
+ String fileName = persistentBlobUri.getPathSegments().get(FILENAME_PATH_SEGMENT);
+
+ try {
+ return new MasterCipher(masterSecret).decryptBody(fileName);
+ } catch (InvalidMessageException e) {
+ Log.w(TAG, "No valid filename for URI");
+ }
+
+ return null;
+ }
+
+ public static @Nullable Long getFileSize(@NonNull Context context, Uri persistentBlobUri) {
+ if (!isAuthority(context, persistentBlobUri)) return null;
+ if (isExternalBlobUri(context, persistentBlobUri)) return null;
+ if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null;
+
+ try {
+ return Long.valueOf(persistentBlobUri.getPathSegments().get(FILESIZE_PATH_SEGMENT));
+ } catch (NumberFormatException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ }
+
private static @NonNull String getExtensionFromMimeType(String mimeType) {
final String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
return extension != null ? extension : BLOB_EXTENSION;
@@ -157,7 +213,8 @@ public class PersistentBlobProvider {
}
public static boolean isAuthority(@NonNull Context context, @NonNull Uri uri) {
- return MATCHER.match(uri) == MATCH || isExternalBlobUri(context, uri);
+ int matchResult = MATCHER.match(uri);
+ return matchResult == MATCH_NEW || matchResult == MATCH_OLD || isExternalBlobUri(context, uri);
}
private static boolean isExternalBlobUri(@NonNull Context context, @NonNull Uri uri) {
diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java
index ff986c56aa..bb06fda0c1 100644
--- a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java
+++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java
@@ -229,7 +229,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
baos = null;
result = null;
- Uri uri = provider.create(masterSecret, data, MediaUtil.IMAGE_JPEG);
+ Uri uri = provider.create(masterSecret, data, MediaUtil.IMAGE_JPEG, null);
Intent intent = new Intent();
intent.setData(uri);
setResult(RESULT_OK, intent);