Format outgoing MMS using SMIL.

// FREEBIE

Closes #1879
This commit is contained in:
Jake McGinty
2014-06-28 14:06:15 -07:00
committed by Moxie Marlinspike
parent b355991b0b
commit 7441c191a7
32 changed files with 3878 additions and 41 deletions

View File

@@ -19,16 +19,18 @@ package org.thoughtcrime.securesms.mms;
import java.io.IOException;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
import org.w3c.dom.smil.SMILRegionMediaElement;
import ws.com.google.android.mms.pdu.PduPart;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore.Audio;
import android.widget.ImageView;
public class AudioSlide extends Slide {
@@ -49,7 +51,17 @@ public class AudioSlide extends Slide {
public boolean hasAudio() {
return true;
}
@Override
public SMILRegionElement getSmilRegion(SMILDocument document) {
return null;
}
@Override
public SMILMediaElement getMediaElement(SMILDocument document) {
return SmilUtil.createMediaElement("audio", document, new String(getPart().getName()));
}
@Override
public Drawable getThumbnail(int maxWidth, int maxHeight) {
return context.getResources().getDrawable(R.drawable.ic_menu_add_sound);
@@ -81,5 +93,4 @@ public class AudioSlide extends Slide {
return part;
}
}

View File

@@ -33,6 +33,10 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
import org.whispersystems.textsecure.crypto.MasterSecret;
import java.io.FileNotFoundException;
@@ -59,7 +63,7 @@ public class ImageSlide extends Slide {
public ImageSlide(Context context, Uri uri) throws IOException, BitmapDecodingException {
super(context, constructPartFromUri(context, uri));
}
@Override
public Drawable getThumbnail(int maxWidth, int maxHeight) {
Drawable thumbnail = getCachedThumbnail();
@@ -161,12 +165,29 @@ public class ImageSlide extends Slide {
public boolean hasImage() {
return true;
}
@Override
public SMILRegionElement getSmilRegion(SMILDocument document) {
SMILRegionElement region = (SMILRegionElement) document.createElement("region");
region.setId("Image");
region.setLeft(0);
region.setTop(0);
region.setWidth(SmilUtil.ROOT_WIDTH);
region.setHeight(SmilUtil.ROOT_HEIGHT);
region.setFit("meet");
return region;
}
@Override
public SMILMediaElement getMediaElement(SMILDocument document) {
return SmilUtil.createMediaElement("img", document, new String(getPart().getName()));
}
private static PduPart constructPartFromUri(Context context, Uri uri)
throws IOException, BitmapDecodingException
{
PduPart part = new PduPart();
byte[] data = BitmapUtil.createScaledBytes(context, uri, 640, 480, (300 * 1024) - 5000);
byte[] data = BitmapUtil.createScaledBytes(context, uri, 1280, 1280, MAX_MESSAGE_SIZE);
part.setData(data);
part.setDataUri(uri);

View File

@@ -20,6 +20,9 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.providers.PartProvider;
@@ -36,61 +39,61 @@ import ws.com.google.android.mms.pdu.PduPart;
public abstract class Slide {
protected static final int MAX_MESSAGE_SIZE = 1048576;
protected static final int MAX_MESSAGE_SIZE = 280 * 1024;
protected final PduPart part;
protected final Context context;
protected MasterSecret masterSecret;
public Slide(Context context, PduPart part) {
this.part = part;
this.context = context;
}
public Slide(Context context, MasterSecret masterSecret, PduPart part) {
this(context, part);
this.masterSecret = masterSecret;
}
public InputStream getPartDataInputStream() throws FileNotFoundException {
Uri partUri = part.getDataUri();
Log.w("Slide", "Loading Part URI: " + partUri);
if (PartProvider.isAuthority(partUri))
return DatabaseFactory.getEncryptingPartDatabase(context, masterSecret).getPartStream(ContentUris.parseId(partUri));
else
return context.getContentResolver().openInputStream(partUri);
}
protected static long getMediaSize(Context context, Uri uri) throws IOException {
InputStream in = context.getContentResolver().openInputStream(uri);
long size = 0;
long size = 0;
byte[] buffer = new byte[512];
int read;
while ((read = in.read(buffer)) != -1)
size += read;
return size;
}
protected byte[] getPartData() {
if (part.getData() != null)
return part.getData();
long partId = ContentUris.parseId(part.getDataUri());
return DatabaseFactory.getEncryptingPartDatabase(context, masterSecret).getPart(partId, true).getData();
}
public String getContentType() {
return new String(part.getContentType());
}
public Uri getUri() {
return part.getDataUri();
}
public Drawable getThumbnail(int maxWidth, int maxHeight) {
throw new AssertionError("getThumbnail() called on non-thumbnail producing slide!");
}
@@ -102,28 +105,32 @@ public abstract class Slide {
public boolean hasImage() {
return false;
}
public boolean hasVideo() {
return false;
}
public boolean hasAudio() {
return false;
}
public Bitmap getImage() {
throw new AssertionError("getImage() called on non-image slide!");
}
public boolean hasText() {
return false;
}
public String getText() {
throw new AssertionError("getText() called on non-text slide!");
}
public PduPart getPart() {
return part;
}
public abstract SMILRegionElement getSmilRegion(SMILDocument document);
public abstract SMILMediaElement getMediaElement(SMILDocument document);
}

View File

@@ -18,8 +18,11 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.whispersystems.textsecure.crypto.MasterSecret;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
@@ -27,9 +30,9 @@ import java.util.List;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
public class SlideDeck {
private final List<Slide> slides = new LinkedList<Slide>();
public SlideDeck(Context context, MasterSecret masterSecret, PduBody body) {
@@ -44,7 +47,7 @@ public class SlideDeck {
slides.add(new AudioSlide(context, body.getPart(i)));
else if (ContentType.isTextType(contentType))
slides.add(new TextSlide(context, masterSecret, body.getPart(i)));
}
}
} catch (UnsupportedEncodingException uee) {
throw new AssertionError(uee);
}
@@ -52,7 +55,7 @@ public class SlideDeck {
public SlideDeck() {
}
public void clear() {
slides.clear();
}
@@ -63,7 +66,16 @@ public class SlideDeck {
for (Slide slide : slides) {
body.addPart(slide.getPart());
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(SmilUtil.createSmilDocument(this), out);
PduPart smilPart = new PduPart();
smilPart.setContentId("smil".getBytes());
smilPart.setContentLocation("smil.xml".getBytes());
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
smilPart.setData(out.toByteArray());
body.addPart(0, smilPart);
return body;
}

View File

@@ -20,6 +20,10 @@ import android.content.Context;
import android.net.Uri;
import android.util.Log;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.LRUCache;
@@ -74,7 +78,24 @@ public class TextSlide extends Slide {
return new String(getPartData());
}
}
@Override
public SMILRegionElement getSmilRegion(SMILDocument document) {
SMILRegionElement region = (SMILRegionElement) document.createElement("region");
region.setId("Text");
region.setLeft(0);
region.setTop(SmilUtil.ROOT_HEIGHT);
region.setWidth(SmilUtil.ROOT_WIDTH);
region.setHeight(50);
region.setFit("meet");
return region;
}
@Override
public SMILMediaElement getMediaElement(SMILDocument document) {
return SmilUtil.createMediaElement("text", document, new String(getPart().getName()));
}
private static PduPart getPartForMessage(String message) {
PduPart part = new PduPart();

View File

@@ -19,18 +19,19 @@ package org.thoughtcrime.securesms.mms;
import java.io.IOException;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
import ws.com.google.android.mms.pdu.PduPart;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.ImageView;
public class VideoSlide extends Slide {
@@ -41,7 +42,7 @@ public class VideoSlide extends Slide {
public VideoSlide(Context context, Uri uri) throws IOException, MediaTooLargeException {
super(context, constructPartFromUri(context, uri));
}
@Override
public Drawable getThumbnail(int width, int height) {
return context.getResources().getDrawable(R.drawable.ic_launcher_video_player);
@@ -51,12 +52,29 @@ public class VideoSlide extends Slide {
public boolean hasImage() {
return true;
}
@Override
public boolean hasVideo() {
return true;
}
@Override
public SMILRegionElement getSmilRegion(SMILDocument document) {
SMILRegionElement region = (SMILRegionElement) document.createElement("region");
region.setId("Image");
region.setLeft(0);
region.setTop(0);
region.setWidth(SmilUtil.ROOT_WIDTH);
region.setHeight(SmilUtil.ROOT_HEIGHT);
region.setFit("meet");
return region;
}
@Override
public SMILMediaElement getMediaElement(SMILDocument document) {
return SmilUtil.createMediaElement("video", document, new String(getPart().getName()));
}
private static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException {
PduPart part = new PduPart();
ContentResolver resolver = context.getContentResolver();