diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index cbb0d39e57..9b74ce2c04 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -48,11 +48,10 @@
diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml
index 3c53989fb3..be4defe5db 100644
--- a/res/layout/conversation_item_sent.xml
+++ b/res/layout/conversation_item_sent.xml
@@ -69,15 +69,14 @@
android:paddingBottom="7dip">
+ android:id="@+id/image_view"
+ android:layout_width="230dip"
+ android:layout_height="174dip"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"
+ android:adjustViewBounds="true"
+ android:background="@android:drawable/picture_frame"
+ android:visibility="gone" />
initializeCache() {
return new LinkedHashMap() {
@Override
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index e5003f8228..e458df0e9d 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -169,6 +169,7 @@ public class ConversationFragment extends SherlockListFragment
if (this.recipients != null && this.threadId != -1) {
this.setListAdapter(new ConversationAdapter(recipients, threadId, getActivity(),
masterSecret, new FailedIconClickHandler()));
+ getListView().setRecyclerListener((ConversationAdapter)getListAdapter());
getLoaderManager().initLoader(0, null, this);
}
}
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index b659fd6322..32399856a0 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -21,6 +21,8 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
@@ -44,20 +46,19 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.database.model.MessageRecord.GroupData;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
+import org.thoughtcrime.securesms.util.FutureTaskListener;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Iterator;
-import java.util.List;
/**
* A view that displays an individual conversation item within a conversation
@@ -85,10 +86,12 @@ public class ConversationItem extends LinearLayout {
private ImageView mmsThumbnail;
private Button mmsDownloadButton;
private TextView mmsDownloadingLabel;
+ private ListenableFutureTask slideDeck;
private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener();
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final ClickListener clickListener = new ClickListener();
+ private final Handler handler = new Handler();
private final Context context;
public ConversationItem(Context context) {
@@ -141,6 +144,11 @@ public class ConversationItem extends LinearLayout {
}
}
+ public void unbind() {
+ if (slideDeck != null)
+ slideDeck.setListener(null);
+ }
+
public MessageRecord getMessageRecord() {
return messageRecord;
}
@@ -231,26 +239,40 @@ public class ConversationItem extends LinearLayout {
}
private void setMediaMmsAttributes(MediaMmsMessageRecord messageRecord) {
- SlideDeck slideDeck = messageRecord.getSlideDeck();
-
- if (slideDeck != null) {
- List slides = slideDeck.getSlides();
-
- Iterator iterator = slides.iterator();
-
- while (iterator.hasNext()) {
- Slide slide = iterator.next();
- if (slide.hasImage()) {
- mmsThumbnail.setImageBitmap(slide.getThumbnail());
- mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
- mmsThumbnail.setOnLongClickListener(new ThumbnailSaveListener(slide));
- mmsThumbnail.setVisibility(View.VISIBLE);
- return;
- }
- }
+ if (messageRecord.getPartCount() > 0) {
+ mmsThumbnail.setVisibility(View.VISIBLE);
+ mmsThumbnail.setImageDrawable(new ColorDrawable(Color.TRANSPARENT));
}
- mmsThumbnail.setVisibility(View.GONE);
+ slideDeck = messageRecord.getSlideDeck();
+ slideDeck.setListener(new FutureTaskListener() {
+ @Override
+ public void onSuccess(final SlideDeck result) {
+ if (result == null)
+ return;
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (Slide slide : result.getSlides()) {
+ if (slide.hasImage()) {
+ slide.setThumbnailOn(mmsThumbnail);
+// mmsThumbnail.setImageBitmap(slide.getThumbnail());
+ mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
+ mmsThumbnail.setOnLongClickListener(new ThumbnailSaveListener(slide));
+ mmsThumbnail.setVisibility(View.VISIBLE);
+ return;
+ }
+ }
+
+ mmsThumbnail.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(Throwable error) {}
+ });
}
/// Helper Methods
diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
index 7f0b185722..397b0f17a9 100644
--- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
+++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
@@ -21,6 +21,7 @@ import java.util.TreeSet;
public class DatabaseUpgradeActivity extends Activity {
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
+ public static final int MMS_BODY_VERSION = 46;
private static final String LAST_VERSION_CODE = "last_version_code";
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index 052b123ebf..65a2f392d5 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -24,10 +24,17 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
+import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.util.InvalidMessageException;
+import org.thoughtcrime.securesms.util.Util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import ws.com.google.android.mms.ContentType;
public class DatabaseFactory {
@@ -36,7 +43,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_DATE_SENT_VERSION = 4;
private static final int INTRODUCED_DRAFTS_VERSION = 5;
private static final int INTRODUCED_NEW_TYPES_VERSION = 6;
- private static final int DATABASE_VERSION = 6;
+ private static final int INTRODUCED_MMS_BODY_VERSION = 7;
+ private static final int DATABASE_VERSION = 7;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -148,23 +156,31 @@ public class DatabaseFactory {
MasterCipher masterCipher = new MasterCipher(masterSecret);
int count = 0;
SQLiteDatabase db = databaseHelper.getWritableDatabase();
- Cursor cursor = db.query("sms",
+ Cursor smsCursor = db.query("sms",
new String[] {"_id", "type", "body"},
"type & " + 0x80000000 + " != 0",
null, null, null, null);
- if (cursor != null)
- count = cursor.getCount();
+ Cursor threadCursor = db.query("thread",
+ new String[] {"_id", "snippet_type", "snippet"},
+ "snippet_type & " + 0x80000000 + " != 0",
+ null, null, null, null);
+
+ if (smsCursor != null)
+ count = smsCursor.getCount();
+
+ if (threadCursor != null)
+ count += threadCursor.getCount();
db.beginTransaction();
- while (cursor != null && cursor.moveToNext()) {
- listener.setProgress(cursor.getPosition(), count);
+ while (smsCursor != null && smsCursor.moveToNext()) {
+ listener.setProgress(smsCursor.getPosition(), count);
try {
- String body = masterCipher.decryptBody(cursor.getString(cursor.getColumnIndexOrThrow("body")));
- long type = cursor.getLong(cursor.getColumnIndexOrThrow("type"));
- long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
+ String body = masterCipher.decryptBody(smsCursor.getString(smsCursor.getColumnIndexOrThrow("body")));
+ long type = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("type"));
+ long id = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("_id"));
if (body.startsWith(KEY_EXCHANGE)) {
body = body.substring(KEY_EXCHANGE.length());
@@ -193,6 +209,114 @@ public class DatabaseFactory {
}
}
+ while (threadCursor != null && threadCursor.moveToNext()) {
+ listener.setProgress(smsCursor.getCount() + threadCursor.getPosition(), count);
+
+ try {
+ String snippet = masterCipher.decryptBody(threadCursor.getString(threadCursor.getColumnIndexOrThrow("snippet")));
+ long snippetType = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("snippet_type"));
+ long id = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id"));
+
+ if (snippet.startsWith(KEY_EXCHANGE)) {
+ snippet = snippet.substring(KEY_EXCHANGE.length());
+ snippet = masterCipher.encryptBody(snippet);
+ snippetType |= 0x8000;
+
+ db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
+ new String[] {snippet, snippetType+"", id+""});
+ } else if (snippet.startsWith(PROCESSED_KEY_EXCHANGE)) {
+ snippet = snippet.substring(PROCESSED_KEY_EXCHANGE.length());
+ snippet = masterCipher.encryptBody(snippet);
+ snippetType |= 0x2000;
+
+ db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
+ new String[] {snippet, snippetType+"", id+""});
+ } else if (snippet.startsWith(STALE_KEY_EXCHANGE)) {
+ snippet = snippet.substring(STALE_KEY_EXCHANGE.length());
+ snippet = masterCipher.encryptBody(snippet);
+ snippetType |= 0x4000;
+
+ db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
+ new String[] {snippet, snippetType+"", id+""});
+ }
+ } catch (InvalidMessageException e) {
+ Log.w("DatabaseFactory", e);
+ }
+ }
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ smsCursor.close();
+ threadCursor.close();
+ }
+
+ if (fromVersion < DatabaseUpgradeActivity.MMS_BODY_VERSION) {
+ Log.w("DatabaseFactory", "Update MMS bodies...");
+ MasterCipher masterCipher = new MasterCipher(masterSecret);
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+
+ db.beginTransaction();
+
+ Cursor mmsCursor = db.query("mms", new String[] {"_id"},
+ "msg_box & " + 0x80000000L + " != 0",
+ null, null, null, null);
+
+ Log.w("DatabaseFactory", "Got MMS rows: " + (mmsCursor == null ? "null" : mmsCursor.getCount()));
+
+ while (mmsCursor != null && mmsCursor.moveToNext()) {
+ listener.setProgress(mmsCursor.getPosition(), mmsCursor.getCount());
+
+ long mmsId = mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow("_id"));
+ String body = null;
+ int partCount = 0;
+ Cursor partCursor = db.query("part", new String[] {"_id", "ct", "_data", "encrypted"},
+ "mid = ?", new String[] {mmsId+""}, null, null, null);
+
+ while (partCursor != null && partCursor.moveToNext()) {
+ String contentType = partCursor.getString(partCursor.getColumnIndexOrThrow("ct"));
+
+ if (ContentType.isTextType(contentType)) {
+ try {
+ long partId = partCursor.getLong(partCursor.getColumnIndexOrThrow("_id"));
+ String dataLocation = partCursor.getString(partCursor.getColumnIndexOrThrow("_data"));
+ boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1;
+ File dataFile = new File(dataLocation);
+
+ FileInputStream fin;
+
+ if (encrypted) fin = new DecryptingPartInputStream(dataFile, masterSecret);
+ else fin = new FileInputStream(dataFile);
+
+ body = (body == null) ? Util.readFully(fin) : body + " " + Util.readFully(fin);
+
+ Log.w("DatabaseFactory", "Read body: " + body);
+
+ dataFile.delete();
+ db.delete("part", "_id = ?", new String[] {partId+""});
+ } catch (IOException e) {
+ Log.w("DatabaseFactory", e);
+ }
+ } else if (ContentType.isAudioType(contentType) ||
+ ContentType.isImageType(contentType) ||
+ ContentType.isVideoType(contentType))
+ {
+ partCount++;
+ }
+ }
+
+ if (!Util.isEmpty(body)) {
+ body = masterCipher.encryptBody(body);
+ db.execSQL("UPDATE mms SET body = ?, part_count = ? WHERE _id = ?",
+ new String[] {body, partCount+"", mmsId+""});
+ } else {
+ db.execSQL("UPDATE mms SET part_count = ? WHERE _id = ?",
+ new String[] {partCount+"", mmsId+""});
+ }
+
+ Log.w("DatabaseFactory", "Updated body: " + body + " and part_count: " + partCount);
+ }
+
db.setTransactionSuccessful();
db.endTransaction();
}
@@ -328,17 +452,17 @@ public class DatabaseFactory {
// MMS Updates
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {20L+"", 1+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {23L+"", 2+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {21L+"", 4+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {24L+"", 12+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L)+"", 1+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L)+"", 2+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L)+"", 4+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(24L | 0x80000000L)+"", 12+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x800000L) +"", 5+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x800000L) +"", 6+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L | 0x20000000L) +"", 7+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L) +"", 8+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L | 0x08000000L) +"", 9+""});
- db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x800000L | 0x10000000L) +"", 10+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L | 0x800000L) +"", 5+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L | 0x800000L) +"", 6+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x20000000L | 0x800000L) +"", 7+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L | 0x800000L) +"", 8+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x08000000L | 0x800000L) +"", 9+""});
+ db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x10000000L | 0x800000L) +"", 10+""});
// Thread Updates
@@ -367,6 +491,11 @@ public class DatabaseFactory {
db.setTransactionSuccessful();
db.endTransaction();
}
+
+ if (oldVersion < INTRODUCED_MMS_BODY_VERSION) {
+ db.execSQL("ALTER TABLE mms ADD COLUMN body TEXT");
+ db.execSQL("ALTER TABLE mms ADD COLUMN part_count INTEGER");
+ }
}
private void updateSmsBodyAndType(SQLiteDatabase db, Cursor cursor, String prefix, long typeMask)
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index e5da57317b..2ba11798e7 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -23,24 +23,31 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
+import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
+import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
+import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.InvalidMessageException;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Trimmer;
import org.thoughtcrime.securesms.util.Util;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
import ws.com.google.android.mms.InvalidHeaderValueException;
import ws.com.google.android.mms.MmsException;
@@ -88,11 +95,13 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
private static final String RESPONSE_TEXT = "resp_txt";
private static final String DELIVERY_TIME = "d_tm";
private static final String DELIVERY_REPORT = "d_rpt";
+ static final String PART_COUNT = "part_count";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " +
READ + " INTEGER DEFAULT 0, " + MESSAGE_ID + " TEXT, " + SUBJECT + " TEXT, " +
- SUBJECT_CHARSET + " INTEGER, " + CONTENT_TYPE + " TEXT, " + CONTENT_LOCATION + " TEXT, " +
+ SUBJECT_CHARSET + " INTEGER, " + BODY + " TEXT, " + PART_COUNT + " INTEGER, " +
+ CONTENT_TYPE + " TEXT, " + CONTENT_LOCATION + " TEXT, " +
EXPIRY + " INTEGER, " + MESSAGE_CLASS + " TEXT, " + MESSAGE_TYPE + " INTEGER, " +
MMS_VERSION + " INTEGER, " + MESSAGE_SIZE + " INTEGER, " + PRIORITY + " INTEGER, " +
READ_REPORT + " INTEGER, " + REPORT_ALLOWED + " INTEGER, " + RESPONSE_STATUS + " INTEGER, " +
@@ -115,9 +124,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
CONTENT_LOCATION, EXPIRY, MESSAGE_CLASS, MESSAGE_TYPE, MMS_VERSION,
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
- DELIVERY_TIME, DELIVERY_REPORT
+ DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT
};
+ public static final ExecutorService slideResolver = Util.newSingleThreadedLifoExecutor();
+
public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -288,7 +299,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new NotificationInd(headers);
}
- public MultimediaMessagePdu getMediaMessage(long messageId)
+ private MultimediaMessagePdu getMediaMessage(long messageId)
throws MmsException
{
PduHeaders headers = getHeadersForId(messageId);
@@ -340,8 +351,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
}
}
- private long insertMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
- String contentLocation, long threadId, long mailbox)
+ private Pair insertMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
+ String contentLocation, long threadId, long mailbox)
throws MmsException
{
PduHeaders headers = retrieved.getPduHeaders();
@@ -364,34 +375,42 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
if (!contentValues.containsKey(DATE_SENT))
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
- return insertMediaMessage(masterSecret, retrieved, contentValues);
+ long messageId = insertMediaMessage(masterSecret, retrieved, contentValues);
+
+ notifyConversationListeners(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
+ Trimmer.trimThread(context, threadId);
+
+ return new Pair(threadId, messageId);
}
- public long insertMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
- String contentLocation, long threadId)
+ public Pair insertMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
+ String contentLocation, long threadId)
throws MmsException
{
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId,
Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT);
}
- public long insertSecureMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
- String contentLocation, long threadId)
+ public Pair insertSecureMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
+ String contentLocation, long threadId)
throws MmsException
{
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId,
Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.ENCRYPTION_REMOTE_BIT);
}
- public long insertSecureDecryptedMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved,
- long threadId)
+ public Pair insertSecureDecryptedMessageInbox(MasterSecret masterSecret,
+ RetrieveConf retrieved,
+ long threadId)
throws MmsException
{
return insertMessageInbox(masterSecret, retrieved, "", threadId,
Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.ENCRYPTION_SYMMETRIC_BIT);
}
- public long insertMessageInbox(NotificationInd notification) {
+ public Pair insertMessageInbox(NotificationInd notification) {
try {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
PduHeaders headers = notification.getPduHeaders();
@@ -412,23 +431,30 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long messageId = db.insert(TABLE_NAME, null, contentValues);
addressDatabase.insertAddressesForId(messageId, headers);
- notifyConversationListeners(threadId);
- DatabaseFactory.getThreadDatabase(context).update(threadId);
- DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
- Trimmer.trimThread(context, threadId);
+// notifyConversationListeners(threadId);
+// DatabaseFactory.getThreadDatabase(context).update(threadId);
+// DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
+// Trimmer.trimThread(context, threadId);
- return messageId;
+ return new Pair(messageId, threadId);
} catch (RecipientFormattingException rfe) {
Log.w("MmsDatabase", rfe);
- return -1;
+ return new Pair(-1L, -1L);
}
}
+ public void markIncomingNotificationReceived(long threadId) {
+ notifyConversationListeners(threadId);
+ DatabaseFactory.getThreadDatabase(context).update(threadId);
+ DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
+ Trimmer.trimThread(context, threadId);
+ }
+
public long insertMessageOutbox(MasterSecret masterSecret, SendReq sendRequest,
long threadId, boolean isSecure)
throws MmsException
{
- long type = Types.BASE_OUTBOX_TYPE;
+ long type = Types.BASE_OUTBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT;
PduHeaders headers = sendRequest.getPduHeaders();
ContentValues contentValues = getContentValuesFromHeader(headers);
@@ -453,10 +479,22 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
throws MmsException
{
SQLiteDatabase db = databaseHelper.getWritableDatabase();
- long messageId = db.insert(TABLE_NAME, null, contentValues);
- PduBody body = message.getBody();
PartDatabase partsDatabase = getPartDatabase(masterSecret);
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
+ PduBody body = message.getBody();
+
+ if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) {
+ String messageText = PartParser.getMessageText(body);
+ body = PartParser.getNonTextParts(body);
+
+ if (!Util.isEmpty(messageText)) {
+ contentValues.put(BODY, new MasterCipher(masterSecret).encryptBody(messageText));
+ }
+ }
+
+ contentValues.put(PART_COUNT, body.getPartsNum());
+
+ long messageId = db.insert(TABLE_NAME, null, contentValues);
addressDatabase.insertAddressesForId(messageId, message.getPduHeaders());
partsDatabase.insertParts(messageId, body);
@@ -480,7 +518,6 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
notifyConversationListeners(threadId);
}
-
public void deleteThread(long threadId) {
Set singleThreadSet = new HashSet();
singleThreadSet.add(threadId);
@@ -692,12 +729,16 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
public class Reader {
- private final Cursor cursor;
+ private final Cursor cursor;
private final MasterSecret masterSecret;
+ private final MasterCipher masterCipher;
public Reader(MasterSecret masterSecret, Cursor cursor) {
this.cursor = cursor;
this.masterSecret = masterSecret;
+
+ if (masterSecret != null) masterCipher = new MasterCipher(masterSecret);
+ else masterCipher = null;
}
public MessageRecord getNext() {
@@ -723,28 +764,57 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED));
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
+ String body = getBody(cursor);
+ int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
Recipient recipient = getMessageRecipient(id);
- SlideDeck slideDeck;
-
- try {
- MultimediaMessagePdu pdu = getMediaMessage(id);
- slideDeck = getSlideDeck(masterSecret, pdu);
- } catch (MmsException me) {
- Log.w("ConversationAdapter", me);
- slideDeck = null;
- }
+ ListenableFutureTask slideDeck = getSlideDeck(masterSecret, id);
return new MediaMmsMessageRecord(context, id, new Recipients(recipient), recipient,
- dateSent, dateReceived, threadId,
- slideDeck, box);
+ dateSent, dateReceived, threadId, body,
+ slideDeck, partCount, box);
}
- protected SlideDeck getSlideDeck(MasterSecret masterSecret, MultimediaMessagePdu pdu) {
- if (masterSecret == null)
- return null;
+ private String getBody(Cursor cursor) {
+ try {
+ String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
+ long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
- return new SlideDeck(context, masterSecret, pdu.getBody());
+ if (body != null && masterCipher != null && Types.isSymmetricEncryption(box)) {
+ return masterCipher.decryptBody(body);
+ }
+
+ return body;
+ } catch (InvalidMessageException e) {
+ Log.w("MmsDatabase", e);
+ return "Error decrypting message.";
+ }
+ }
+
+ private ListenableFutureTask getSlideDeck(final MasterSecret masterSecret,
+ final long id)
+ {
+ Callable task = new Callable() {
+ @Override
+ public SlideDeck call() throws Exception {
+ try {
+ if (masterSecret == null)
+ return null;
+
+ MultimediaMessagePdu pdu = getMediaMessage(id);
+
+ return new SlideDeck(context, masterSecret, pdu.getBody());
+ } catch (MmsException me) {
+ Log.w("MmsDatabase", me);
+ return null;
+ }
+ }
+ };
+
+ ListenableFutureTask future = new ListenableFutureTask(task, null);
+ slideResolver.execute(future);
+
+ return future;
}
private NotificationMmsMessageRecord getNotificationMmsMessageRecord(Cursor cursor) {
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
index 66e25246d4..94d7214a20 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
@@ -7,6 +7,7 @@ public interface MmsSmsColumns {
public static final String NORMALIZED_DATE_RECEIVED = "date_received";
public static final String THREAD_ID = "thread_id";
public static final String READ = "read";
+ public static final String BODY = "body";
public static class Types {
protected static final long TOTAL_MASK = 0xFFFFFFFF;
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index dcea692577..92ec5d6332 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -94,7 +94,7 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
- SmsDatabase.STATUS, TRANSPORT};
+ SmsDatabase.STATUS, MmsDatabase.PART_COUNT, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
@@ -108,11 +108,12 @@ public class MmsSmsDatabase extends Database {
public Cursor getConversationSnippet(long threadId) {
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
+ MmsSmsColumns.THREAD_ID,
SmsDatabase.ADDRESS, SmsDatabase.SUBJECT,
MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
- TRANSPORT};
+ SmsDatabase.STATUS, MmsDatabase.PART_COUNT, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@@ -126,7 +127,7 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
- TRANSPORT};
+ MmsDatabase.PART_COUNT, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.READ + " = 0";
@@ -145,13 +146,13 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
- MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, TRANSPORT};
+ MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, TRANSPORT};
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
- MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, TRANSPORT};
+ MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, TRANSPORT};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
@@ -171,10 +172,12 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsDatabase.DATE_RECEIVED);
mmsColumnsPresent.add(MmsSmsColumns.READ);
mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
+ mmsColumnsPresent.add(MmsSmsColumns.BODY);
+ mmsColumnsPresent.add(MmsDatabase.PART_COUNT);
Set smsColumnsPresent = new HashSet();
smsColumnsPresent.add(MmsSmsColumns.ID);
- smsColumnsPresent.add(SmsDatabase.BODY);
+ smsColumnsPresent.add(MmsSmsColumns.BODY);
smsColumnsPresent.add(SmsDatabase.TYPE);
smsColumnsPresent.add(SmsDatabase.ADDRESS);
smsColumnsPresent.add(SmsDatabase.SUBJECT);
@@ -242,5 +245,9 @@ public class MmsSmsDatabase extends Database {
return smsReader.getCurrent();
}
}
+
+ public void close() {
+ cursor.close();
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java
index 048758aeff..9f981722ec 100644
--- a/src/org/thoughtcrime/securesms/database/PartDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java
@@ -25,12 +25,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.thoughtcrime.securesms.providers.PartProvider;
-
-import ws.com.google.android.mms.ContentType;
-import ws.com.google.android.mms.MmsException;
-import ws.com.google.android.mms.pdu.CharacterSets;
-import ws.com.google.android.mms.pdu.PduBody;
-import ws.com.google.android.mms.pdu.PduPart;
+import org.thoughtcrime.securesms.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -39,7 +34,11 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+
+import ws.com.google.android.mms.ContentType;
+import ws.com.google.android.mms.MmsException;
+import ws.com.google.android.mms.pdu.PduBody;
+import ws.com.google.android.mms.pdu.PduPart;
public class PartDatabase extends Database {
@@ -83,32 +82,32 @@ public class PartDatabase extends Database {
int contentTypeColumn = cursor.getColumnIndexOrThrow(CONTENT_TYPE);
if (!cursor.isNull(contentTypeColumn))
- part.setContentType(getBytes(cursor.getString(contentTypeColumn)));
+ part.setContentType(Util.toIsoBytes(cursor.getString(contentTypeColumn)));
int nameColumn = cursor.getColumnIndexOrThrow(NAME);
if (!cursor.isNull(nameColumn))
- part.setName(getBytes(cursor.getString(nameColumn)));
+ part.setName(Util.toIsoBytes(cursor.getString(nameColumn)));
int fileNameColumn = cursor.getColumnIndexOrThrow(FILENAME);
if (!cursor.isNull(fileNameColumn))
- part.setFilename(getBytes(cursor.getString(fileNameColumn)));
+ part.setFilename(Util.toIsoBytes(cursor.getString(fileNameColumn)));
int contentDispositionColumn = cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION);
if (!cursor.isNull(contentDispositionColumn))
- part.setContentDisposition(getBytes(cursor.getString(contentDispositionColumn)));
+ part.setContentDisposition(Util.toIsoBytes(cursor.getString(contentDispositionColumn)));
int contentIdColumn = cursor.getColumnIndexOrThrow(CONTENT_ID);
if (!cursor.isNull(contentIdColumn))
- part.setContentId(getBytes(cursor.getString(contentIdColumn)));
+ part.setContentId(Util.toIsoBytes(cursor.getString(contentIdColumn)));
int contentLocationColumn = cursor.getColumnIndexOrThrow(CONTENT_LOCATION);
if (!cursor.isNull(contentLocationColumn))
- part.setContentLocation(getBytes(cursor.getString(contentLocationColumn)));
+ part.setContentLocation(Util.toIsoBytes(cursor.getString(contentLocationColumn)));
int encryptedColumn = cursor.getColumnIndexOrThrow(ENCRYPTED);
@@ -125,9 +124,9 @@ public class PartDatabase extends Database {
}
if (part.getContentType() != null) {
- contentValues.put(CONTENT_TYPE, toIsoString(part.getContentType()));
+ contentValues.put(CONTENT_TYPE, Util.toIsoString(part.getContentType()));
- if (toIsoString(part.getContentType()).equals(ContentType.APP_SMIL))
+ if (Util.toIsoString(part.getContentType()).equals(ContentType.APP_SMIL))
contentValues.put(SEQUENCE, -1);
} else {
throw new MmsException("There is no content type for this part.");
@@ -142,15 +141,15 @@ public class PartDatabase extends Database {
}
if (part.getContentDisposition() != null) {
- contentValues.put(CONTENT_DISPOSITION, toIsoString(part.getContentDisposition()));
+ contentValues.put(CONTENT_DISPOSITION, Util.toIsoString(part.getContentDisposition()));
}
if (part.getContentId() != null) {
- contentValues.put(CONTENT_ID, toIsoString(part.getContentId()));
+ contentValues.put(CONTENT_ID, Util.toIsoString(part.getContentId()));
}
if (part.getContentLocation() != null) {
- contentValues.put(CONTENT_LOCATION, toIsoString(part.getContentLocation()));
+ contentValues.put(CONTENT_LOCATION, Util.toIsoString(part.getContentLocation()));
}
contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0);
@@ -267,7 +266,7 @@ public class PartDatabase extends Database {
}
}
- public void insertParts(long mmsId, PduBody body) throws MmsException {
+ void insertParts(long mmsId, PduBody body) throws MmsException {
for (int i=0;i slideDeck;
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
Recipient individualRecipient, long dateSent, long dateReceived,
- long threadId, SlideDeck slideDeck, long mailbox)
+ long threadId, String body, ListenableFutureTask slideDeck,
+ int partCount, long mailbox)
{
- super(context, id, getBodyFromSlidesIfAvailable(slideDeck), recipients,
- individualRecipient, dateSent, dateReceived,
+ super(context, id, body, recipients, individualRecipient, dateSent, dateReceived,
threadId, DELIVERY_STATUS_NONE, mailbox);
this.context = context.getApplicationContext();
+ this.partCount = partCount;
this.slideDeck = slideDeck;
}
- public SlideDeck getSlideDeck() {
+ public ListenableFutureTask getSlideDeck() {
return slideDeck;
}
+ public int getPartCount() {
+ return partCount;
+ }
+
@Override
public boolean isMms() {
return true;
@@ -73,16 +80,16 @@ public class MediaMmsMessageRecord extends MessageRecord {
return super.getDisplayBody();
}
- private static String getBodyFromSlidesIfAvailable(SlideDeck slideDeck) {
- if (slideDeck == null)
- return "";
-
- for (Slide slide : slideDeck.getSlides()) {
- if (slide.hasText())
- return slide.getText();
- }
-
- return "";
- }
+// private static String getBodyFromSlidesIfAvailable(SlideDeck slideDeck) {
+// if (slideDeck == null)
+// return "";
+//
+// for (Slide slide : slideDeck.getSlides()) {
+// if (slide.hasText())
+// return slide.getText();
+// }
+//
+// return "";
+// }
}
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index 3970518a9b..87f2f8dbb2 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -107,6 +107,10 @@ public abstract class MessageRecord extends DisplayRecord {
return individualRecipient;
}
+ public long getType() {
+ return type;
+ }
+
protected SpannableString emphasisAdded(String sequence) {
SpannableString spannable = new SpannableString(sequence);
spannable.setSpan(new ForegroundColorSpan(context.getResources().getColor(android.R.color.darker_gray)), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index a5ff172464..8d9ff54c95 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -54,21 +54,21 @@ public class AttachmentManager {
public void setImage(Uri image) throws IOException {
ImageSlide slide = new ImageSlide(context, image);
slideDeck.addSlide(slide);
- thumbnail.setImageBitmap(slide.getThumbnail());
+ thumbnail.setImageBitmap(slide.getThumbnail(345, 261));
attachmentView.setVisibility(View.VISIBLE);
}
public void setVideo(Uri video) throws IOException, MediaTooLargeException {
VideoSlide slide = new VideoSlide(context, video);
slideDeck.addSlide(slide);
- thumbnail.setImageBitmap(slide.getThumbnail());
+ thumbnail.setImageBitmap(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight()));
attachmentView.setVisibility(View.VISIBLE);
}
public void setAudio(Uri audio)throws IOException, MediaTooLargeException {
AudioSlide slide = new AudioSlide(context, audio);
slideDeck.addSlide(slide);
- thumbnail.setImageBitmap(slide.getThumbnail());
+ thumbnail.setImageBitmap(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight()));
attachmentView.setVisibility(View.VISIBLE);
}
diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
index 7f576627bc..aa62e527db 100644
--- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
@@ -27,6 +27,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore.Audio;
+import android.widget.ImageView;
public class AudioSlide extends Slide {
@@ -49,10 +50,10 @@ public class AudioSlide extends Slide {
}
@Override
- public Bitmap getThumbnail() {
+ public Bitmap getThumbnail(int maxWidth, int maxHeight) {
return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_menu_add_sound);
}
-
+
public static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException {
PduPart part = new PduPart();
diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
index 23424ddd0c..620706497c 100644
--- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
@@ -16,25 +16,33 @@
*/
package org.thoughtcrime.securesms.mms;
-import java.io.ByteArrayOutputStream;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.ImageView;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.util.BitmapUtil;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
-import java.util.Map.Entry;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.crypto.MasterSecret;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Bitmap.CompressFormat;
-import android.net.Uri;
-import android.util.Log;
public class ImageSlide extends Slide {
@@ -56,67 +64,98 @@ public class ImageSlide extends Slide {
}
@Override
- public Bitmap getThumbnail() {
- if (thumbnailCache.containsKey(part.getDataUri())) {
- Log.w("ImageSlide", "Cached thumbnail...");
- Bitmap bitmap = thumbnailCache.get(part.getDataUri()).get();
- if (bitmap != null) return bitmap;
- else thumbnailCache.remove(part.getDataUri());
- }
+ public Bitmap getThumbnail(int maxWidth, int maxHeight) {
+ Bitmap thumbnail = getCachedThumbnail();
+
+ if (thumbnail != null)
+ return thumbnail;
try {
- BitmapFactory.Options options = getImageDimensions(getPartDataInputStream());
- int imageWidth = options.outWidth;
- int imageHeight = options.outHeight;
-
- int scaler = 1;
- while ((imageWidth / scaler > 480) || (imageHeight / scaler > 480))
- scaler *= 2;
-
- options.inSampleSize = scaler;
- options.inJustDecodeBounds = false;
-
- Bitmap thumbnail = BitmapFactory.decodeStream(getPartDataInputStream(), null, options);
+ InputStream measureStream = getPartDataInputStream();
+ InputStream dataStream = getPartDataInputStream();
+
+ thumbnail = BitmapUtil.createScaledBitmap(measureStream, dataStream, maxWidth, maxHeight);
thumbnailCache.put(part.getDataUri(), new SoftReference(thumbnail));
return thumbnail;
- } catch (FileNotFoundException fnfe) {
- Log.w("ImageSlide", fnfe);
+ } catch (FileNotFoundException e) {
+ Log.w("ImageSlide", e);
return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_missing_thumbnail_picture);
}
}
-
- private static BitmapFactory.Options getImageDimensions(InputStream inputStream) throws FileNotFoundException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(inputStream, null, options);
- return options;
- }
-
- private static BitmapFactory.Options getImageDimensions(Context context, Uri uri) throws FileNotFoundException {
- InputStream in = context.getContentResolver().openInputStream(uri);
- return getImageDimensions(in);
- }
-
@Override
- public boolean hasImage() {
+ public void setThumbnailOn(ImageView imageView) {
+ Bitmap thumbnail = getCachedThumbnail();
+
+ if (thumbnail != null) {
+ Log.w("ImageSlide", "Setting cached thumbnail...");
+ setThumbnailOn(imageView, thumbnail, true);
+ return;
+ }
+
+ final ColorDrawable temporaryDrawable = new ColorDrawable(Color.TRANSPARENT);
+ final WeakReference weakImageView = new WeakReference(imageView);
+ final Handler handler = new Handler();
+ final int maxWidth = imageView.getWidth();
+ final int maxHeight = imageView.getHeight();
+
+ imageView.setImageDrawable(temporaryDrawable);
+
+ MmsDatabase.slideResolver.execute(new Runnable() {
+ @Override
+ public void run() {
+ final Bitmap bitmap = getThumbnail(maxWidth, maxHeight);
+ final ImageView destination = weakImageView.get();
+ if (destination != null && destination.getDrawable() == temporaryDrawable) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ setThumbnailOn(destination, bitmap, false);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ private void setThumbnailOn(ImageView imageView, Bitmap thumbnail, boolean fromMemory) {
+ if (fromMemory) {
+ imageView.setImageBitmap(thumbnail);
+ } else {
+ BitmapDrawable result = new BitmapDrawable(context.getResources(), thumbnail);
+ TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), result});
+ imageView.setImageDrawable(fadingResult);
+ fadingResult.startTransition(300);
+ }
+ }
+
+ private Bitmap getCachedThumbnail() {
+ synchronized (thumbnailCache) {
+ SoftReference bitmapReference = thumbnailCache.get(part.getDataUri());
+ Log.w("ImageSlide", "Got soft reference: " + bitmapReference);
+
+ if (bitmapReference != null) {
+ Bitmap bitmap = bitmapReference.get();
+ Log.w("ImageSlide", "Got cached bitmap: " + bitmap);
+ if (bitmap != null) return bitmap;
+ else thumbnailCache.remove(part.getDataUri());
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean hasImage() {
return true;
}
private static PduPart constructPartFromUri(Context context, Uri uri) throws IOException {
PduPart part = new PduPart();
-
- BitmapFactory.Options options = getImageDimensions(context, uri);
- long size = getMediaSize(context, uri);
-
- if (options.outWidth > 640 || options.outHeight > 480 || size > (1024*1024)) {
- byte[] data = scaleImage(context, uri, options, size, 640, 480, 1024*1024);
- part.setData(data);
- Log.w("ImageSlide", "Setting actual part data...");
- }
-
- Log.w("ImageSlide", "Setting part data URI..");
+ byte[] data = BitmapUtil.createScaledBytes(context, uri, 640, 480, (300 * 1024) - 5000);
+
+ part.setData(data);
part.setDataUri(uri);
part.setContentType(ContentType.IMAGE_JPEG.getBytes());
part.setContentId((System.currentTimeMillis()+"").getBytes());
@@ -124,25 +163,4 @@ public class ImageSlide extends Slide {
return part;
}
-
- private static byte[] scaleImage(Context context, Uri uri, BitmapFactory.Options options, long size, int maxWidth, int maxHeight, int maxSize) throws FileNotFoundException {
- int scaler = 1;
- while ((options.outWidth / scaler > maxWidth) || (options.outHeight / scaler > maxHeight))
- scaler *= 2;
-
- options.inSampleSize = scaler;
- options.inJustDecodeBounds = false;
-
- Bitmap bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri), null, options);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int quality = 80;
-
- do {
- bitmap.compress(CompressFormat.JPEG, quality, baos);
- if (baos.size() > maxSize)
- quality = quality * maxSize / baos.size();
- } while (baos.size() > maxSize);
-
- return baos.toByteArray();
- }
}
diff --git a/src/org/thoughtcrime/securesms/mms/PartParser.java b/src/org/thoughtcrime/securesms/mms/PartParser.java
new file mode 100644
index 0000000000..fad3bea7a1
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/PartParser.java
@@ -0,0 +1,47 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.util.Log;
+
+import org.thoughtcrime.securesms.util.Util;
+
+import java.io.UnsupportedEncodingException;
+
+import ws.com.google.android.mms.ContentType;
+import ws.com.google.android.mms.pdu.CharacterSets;
+import ws.com.google.android.mms.pdu.PduBody;
+
+public class PartParser {
+ public static String getMessageText(PduBody body) {
+ String bodyText = null;
+
+ for (int i=0;i messageAndThreadId;
+
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
- long messageId = mmsDatabase.insertSecureMessageInbox(item.getMasterSecret(), retrieved,
- item.getContentLocation(),
- item.getThreadId());
+ messageAndThreadId = mmsDatabase.insertSecureMessageInbox(item.getMasterSecret(), retrieved,
+ item.getContentLocation(),
+ item.getThreadId());
if (item.getMasterSecret() != null)
- DecryptingQueue.scheduleDecryption(context, item.getMasterSecret(), messageId, item.getThreadId(), retrieved);
+ DecryptingQueue.scheduleDecryption(context, item.getMasterSecret(), messageAndThreadId.first,
+ messageAndThreadId.second, retrieved);
} else {
- mmsDatabase.insertMessageInbox(item.getMasterSecret(), retrieved, item.getContentLocation(),
- item.getThreadId());
+ messageAndThreadId = mmsDatabase.insertMessageInbox(item.getMasterSecret(), retrieved,
+ item.getContentLocation(),
+ item.getThreadId());
}
mmsDatabase.delete(item.getMessageId());
+ MessageNotifier.updateNotification(context, item.getMasterSecret(), messageAndThreadId.second);
}
protected void handleConnectivityChange() {
@@ -153,18 +162,38 @@ public class MmsDownloader extends MmscProcessor {
} else if (!isConnected() && !isConnectivityPossible()) {
pendingMessages.clear();
-
- for (DownloadItem item : downloadItems) {
- DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY);
- }
-
- toastHandler.makeToast(context
- .getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later));
-
+ handleDownloadError(downloadItems, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY,
+ context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later));
finishConnectivity();
}
}
+ private void handleDownloadError(List items, int downloadStatus, String error) {
+ MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
+
+ for (DownloadItem item : items) {
+ db.markDownloadState(item.getMessageId(), downloadStatus);
+
+ if (item.isAutomatic()) {
+ db.markIncomingNotificationReceived(item.getThreadId());
+ MessageNotifier.updateNotification(context, item.getMasterSecret(), item.getThreadId());
+ }
+ }
+
+ toastHandler.makeToast(error);
+ }
+
+ private void handleDownloadError(DownloadItem item, int downloadStatus, String error) {
+ MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
+ db.markDownloadState(item.getMessageId(), downloadStatus);
+
+ if (item.isAutomatic()) {
+ db.markIncomingNotificationReceived(item.getThreadId());
+ MessageNotifier.updateNotification(context, item.getMasterSecret(), item.getThreadId());
+ }
+
+ toastHandler.makeToast(error);
+ }
private void scheduleDownloadWithRadioMode(DownloadItem item) {
item.mmsRadioMode = true;
@@ -186,9 +215,11 @@ public class MmsDownloader extends MmscProcessor {
private long messageId;
private byte[] transactionId;
private String contentLocation;
+ private boolean automatic;
public DownloadItem(MasterSecret masterSecret, boolean mmsRadioMode, boolean proxyIfPossible,
- long messageId, long threadId, String contentLocation, byte[] transactionId)
+ long messageId, long threadId, boolean automatic, String contentLocation,
+ byte[] transactionId)
{
this.masterSecret = masterSecret;
this.mmsRadioMode = mmsRadioMode;
@@ -197,6 +228,7 @@ public class MmsDownloader extends MmscProcessor {
this.messageId = messageId;
this.contentLocation = contentLocation;
this.transactionId = transactionId;
+ this.automatic = automatic;
}
public long getThreadId() {
@@ -226,6 +258,10 @@ public class MmsDownloader extends MmscProcessor {
public boolean useMmsRadioMode() {
return mmsRadioMode;
}
+
+ public boolean isAutomatic() {
+ return automatic;
+ }
}
@Override
diff --git a/src/org/thoughtcrime/securesms/service/MmsReceiver.java b/src/org/thoughtcrime/securesms/service/MmsReceiver.java
index 367c0a2dd3..907e88ccc8 100644
--- a/src/org/thoughtcrime/securesms/service/MmsReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/MmsReceiver.java
@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
+import android.util.Pair;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -44,6 +45,7 @@ public class MmsReceiver {
intent.putExtra("message_id", messageId);
intent.putExtra("transaction_id", pdu.getTransactionId());
intent.putExtra("thread_id", threadId);
+ intent.putExtra("automatic", true);
context.startService(intent);
}
@@ -54,12 +56,12 @@ public class MmsReceiver {
GenericPdu pdu = parser.parse();
if (pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
- MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
- long messageId = database.insertMessageInbox((NotificationInd)pdu);
- long threadId = database.getThreadIdForMessage(messageId);
+ MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
+ Pair messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu);
+// long threadId = database.getThreadIdForMessage(messageId);
- MessageNotifier.updateNotification(context, masterSecret, threadId);
- scheduleDownload((NotificationInd)pdu, messageId, threadId);
+// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
+ scheduleDownload((NotificationInd)pdu, messageAndThreadId.first, messageAndThreadId.second);
Log.w("MmsReceiverService", "Inserted received notification...");
}
diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
new file mode 100644
index 0000000000..a007dbe1ad
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
@@ -0,0 +1,84 @@
+package org.thoughtcrime.securesms.util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class BitmapUtil {
+
+ private static final int MAX_COMPRESSION_QUALITY = 95;
+ private static final int MIN_COMPRESSION_QUALITY = 50;
+ private static final int MAX_COMPRESSION_ATTEMPTS = 4;
+
+ public static byte[] createScaledBytes(Context context, Uri uri, int maxWidth,
+ int maxHeight, int maxSize)
+ throws IOException
+ {
+ InputStream measure = context.getContentResolver().openInputStream(uri);
+ InputStream data = context.getContentResolver().openInputStream(uri);
+ Bitmap bitmap = createScaledBitmap(measure, data, maxWidth, maxHeight);
+ int quality = MAX_COMPRESSION_QUALITY;
+ int attempts = 0;
+
+ ByteArrayOutputStream baos;
+
+ do {
+ baos = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
+
+ quality = Math.max((quality * maxSize) / baos.size(), MIN_COMPRESSION_QUALITY);
+ } while (baos.size() > maxSize && attempts++ < MAX_COMPRESSION_ATTEMPTS);
+
+ bitmap.recycle();
+
+ if (baos.size() <= maxSize) return baos.toByteArray();
+ else throw new IOException("Unable to scale image below: " + baos.size());
+ }
+
+ public static Bitmap createScaledBitmap(InputStream measure, InputStream data,
+ int maxWidth, int maxHeight)
+ {
+ BitmapFactory.Options options = getImageDimensions(measure);
+ int imageWidth = options.outWidth;
+ int imageHeight = options.outHeight;
+
+ int scaler = 1;
+
+ while ((imageWidth / scaler > maxWidth) && (imageHeight / scaler > maxHeight))
+ scaler *= 2;
+
+ if (scaler > 1)
+ scaler /= 2;
+
+ options.inSampleSize = scaler;
+ options.inJustDecodeBounds = false;
+
+ Bitmap roughThumbnail = BitmapFactory.decodeStream(data, null, options);
+
+ if (imageWidth > maxWidth || imageHeight > maxHeight) {
+ Log.w("BitmapUtil", "Scaling to max width and height: " + maxWidth + "," + maxHeight);
+ Bitmap scaledThumbnail = Bitmap.createScaledBitmap(roughThumbnail, maxWidth, maxHeight, true);
+ roughThumbnail.recycle();
+ return scaledThumbnail;
+ } else {
+ return roughThumbnail;
+ }
+ }
+
+ private static BitmapFactory.Options getImageDimensions(InputStream inputStream) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+
+ return options;
+ }
+
+
+}
diff --git a/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java b/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java
index 362421b126..89d2512f2b 100644
--- a/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java
+++ b/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java
@@ -1,20 +1,30 @@
package org.thoughtcrime.securesms.util;
+import java.lang.ref.WeakReference;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ListenableFutureTask extends FutureTask {
+// private WeakReference> listener;
private FutureTaskListener listener;
public ListenableFutureTask(Callable callable, FutureTaskListener listener) {
super(callable);
this.listener = listener;
+// if (listener == null) {
+// this.listener = null;
+// } else {
+// this.listener = new WeakReference>(listener);
+// }
}
public synchronized void setListener(FutureTaskListener listener) {
+// if (listener != null) this.listener = new WeakReference>(listener);
+// else this.listener = null;
this.listener = listener;
+
if (this.isDone()) {
callback();
}
@@ -27,12 +37,16 @@ public class ListenableFutureTask extends FutureTask {
private void callback() {
if (this.listener != null) {
- try {
- this.listener.onSuccess(get());
- } catch (ExecutionException ee) {
- this.listener.onFailure(ee);
- } catch (InterruptedException e) {
- throw new AssertionError(e);
+ FutureTaskListener nestedListener = this.listener;
+// FutureTaskListener nestedListener = this.listener.get();
+ if (nestedListener != null) {
+ try {
+ nestedListener.onSuccess(get());
+ } catch (ExecutionException ee) {
+ nestedListener.onFailure(ee);
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
}
}
}
diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java
index 895be207a4..d52c647480 100644
--- a/src/org/thoughtcrime/securesms/util/Util.java
+++ b/src/org/thoughtcrime/securesms/util/Util.java
@@ -22,17 +22,20 @@ import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StyleSpan;
+import android.util.Log;
import android.widget.EditText;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
public class Util {
@@ -129,6 +132,25 @@ public class Util {
return spanned;
}
+ public static String toIsoString(byte[] bytes) {
+ try {
+ return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException e) {
+ // Impossible to reach here!
+ Log.e("MmsDatabase", "ISO_8859_1 must be supported!", e);
+ return "";
+ }
+ }
+
+ public static byte[] toIsoBytes(String isoString) {
+ try {
+ return isoString.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException e) {
+ Log.w("Util", "ISO_8859_1 must be supported!", e);
+ return new byte[0];
+ }
+ }
+
public static void showAlertDialog(Context context, String title, String message) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(title);