mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
streaming media
// FREEBIE
This commit is contained in:
parent
a09e0afbd6
commit
07bb07c342
@ -101,6 +101,24 @@ public class AttachmentCipherInputStream extends FileInputStream {
|
||||
else return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long byteCount) throws IOException {
|
||||
long skipped = 0L;
|
||||
while (skipped < byteCount) {
|
||||
byte[] buf = new byte[Math.min(4096, (int)(byteCount-skipped))];
|
||||
int read = read(buf);
|
||||
|
||||
skipped += read;
|
||||
}
|
||||
|
||||
return skipped;
|
||||
}
|
||||
|
||||
private int readFinal(byte[] buffer, int offset, int length) throws IOException {
|
||||
try {
|
||||
int flourish = cipher.doFinal(buffer, offset);
|
||||
|
@ -378,7 +378,13 @@ public class PushServiceSocket {
|
||||
URL uploadUrl = new URL(url);
|
||||
HttpsURLConnection connection = (HttpsURLConnection) uploadUrl.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setFixedLengthStreamingMode((int) AttachmentCipherOutputStream.getCiphertextLength(dataSize));
|
||||
|
||||
if (dataSize > 0) {
|
||||
connection.setFixedLengthStreamingMode((int) AttachmentCipherOutputStream.getCiphertextLength(dataSize));
|
||||
} else {
|
||||
connection.setChunkedStreamingMode(0);
|
||||
}
|
||||
|
||||
connection.setRequestMethod(method);
|
||||
connection.setRequestProperty("Content-Type", "application/octet-stream");
|
||||
connection.connect();
|
||||
|
@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@ -463,7 +464,7 @@ public class ConversationItem extends LinearLayout {
|
||||
Log.w(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.setDataAndType(slide.getUri(), slide.getContentType());
|
||||
intent.setDataAndType(PartAuthority.getPublicPartUri(slide.getUri()), slide.getContentType());
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
|
@ -17,7 +17,6 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentUris;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
@ -37,8 +36,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
@ -133,12 +131,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream getInputStream(Uri uri, MasterSecret masterSecret) throws IOException {
|
||||
if (PartProvider.isAuthority(uri)) {
|
||||
return DatabaseFactory.getEncryptingPartDatabase(this, masterSecret).getPartStream(ContentUris.parseId(uri));
|
||||
} else {
|
||||
throw new AssertionError("Given a URI that is not handled by our app.");
|
||||
}
|
||||
private InputStream getMediaInputStream() throws IOException {
|
||||
return PartAuthority.getPartStream(this, masterSecret, mediaUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -162,8 +156,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity {
|
||||
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeParams, 0);
|
||||
int maxTextureSize = Math.max(maxTextureSizeParams[0], 2048);
|
||||
Log.w(TAG, "reported GL_MAX_TEXTURE_SIZE: " + maxTextureSize);
|
||||
return BitmapUtil.createScaledBitmap(getInputStream(mediaUri, masterSecret),
|
||||
getInputStream(mediaUri, masterSecret),
|
||||
return BitmapUtil.createScaledBitmap(getMediaInputStream(),
|
||||
getMediaInputStream(),
|
||||
maxTextureSize, maxTextureSize);
|
||||
} catch (IOException | BitmapDecodingException e) {
|
||||
return null;
|
||||
|
@ -97,6 +97,24 @@ public class DecryptingPartInputStream extends FileInputStream {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long byteCount) throws IOException {
|
||||
long skipped = 0L;
|
||||
while (skipped < byteCount) {
|
||||
byte[] buf = new byte[Math.min(4096, (int)(byteCount-skipped))];
|
||||
int read = read(buf);
|
||||
|
||||
skipped += read;
|
||||
}
|
||||
|
||||
return skipped;
|
||||
}
|
||||
|
||||
private int readFinal(byte[] buffer, int offset, int length) throws IOException {
|
||||
try {
|
||||
int flourish = cipher.doFinal(buffer, offset);
|
||||
|
@ -39,6 +39,7 @@ import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
|
||||
@ -56,14 +57,14 @@ public class DatabaseFactory {
|
||||
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
||||
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
|
||||
private static final int INTRODUCED_DELIVERY_RECEIPTS = 13;
|
||||
private static final int DATABASE_VERSION = 13;
|
||||
private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14;
|
||||
private static final int DATABASE_VERSION = 14;
|
||||
|
||||
|
||||
private static final String DATABASE_NAME = "messages.db";
|
||||
private static final Object lock = new Object();
|
||||
|
||||
private static DatabaseFactory instance;
|
||||
private static EncryptingPartDatabase encryptingPartInstance;
|
||||
|
||||
private DatabaseHelper databaseHelper;
|
||||
|
||||
@ -117,17 +118,6 @@ public class DatabaseFactory {
|
||||
return getInstance(context).part;
|
||||
}
|
||||
|
||||
public static EncryptingPartDatabase getEncryptingPartDatabase(Context context, MasterSecret masterSecret) {
|
||||
synchronized (lock) {
|
||||
if (encryptingPartInstance == null) {
|
||||
DatabaseFactory factory = getInstance(context);
|
||||
encryptingPartInstance = new EncryptingPartDatabase(context, factory.databaseHelper, masterSecret);
|
||||
}
|
||||
|
||||
return encryptingPartInstance;
|
||||
}
|
||||
}
|
||||
|
||||
public static MmsAddressDatabase getMmsAddressDatabase(Context context) {
|
||||
return getInstance(context).mmsAddress;
|
||||
}
|
||||
@ -360,12 +350,12 @@ public class DatabaseFactory {
|
||||
boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1;
|
||||
File dataFile = new File(dataLocation);
|
||||
|
||||
FileInputStream fin;
|
||||
InputStream is;
|
||||
|
||||
if (encrypted) fin = new DecryptingPartInputStream(dataFile, masterSecret);
|
||||
else fin = new FileInputStream(dataFile);
|
||||
if (encrypted) is = new DecryptingPartInputStream(dataFile, masterSecret);
|
||||
else is = new FileInputStream(dataFile);
|
||||
|
||||
body = (body == null) ? Util.readFully(fin) : body + " " + Util.readFully(fin);
|
||||
body = (body == null) ? Util.readFullyAsString(is) : body + " " + Util.readFullyAsString(is);
|
||||
|
||||
dataFile.delete();
|
||||
db.delete("part", "_id = ?", new String[] {partId+""});
|
||||
@ -711,6 +701,10 @@ public class DatabaseFactory {
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);");
|
||||
}
|
||||
|
||||
if (oldVersion < INTRODUCED_PART_DATA_SIZE_VERSION) {
|
||||
db.execSQL("ALTER TABLE part ADD COLUMN data_size INTEGER DEFAULT 0;");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class EncryptingPartDatabase extends PartDatabase {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public EncryptingPartDatabase(Context context, SQLiteOpenHelper databaseHelper, MasterSecret masterSecret) {
|
||||
super(context, databaseHelper);
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileInputStream getPartInputStream(File path, PduPart part) throws FileNotFoundException {
|
||||
Log.w("EncryptingPartDatabase", "Getting part at: " + path.getAbsolutePath());
|
||||
if (!part.getEncrypted())
|
||||
return super.getPartInputStream(path, part);
|
||||
|
||||
return new DecryptingPartInputStream(path, masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileOutputStream getPartOutputStream(File path, PduPart part) throws FileNotFoundException {
|
||||
Log.w("EncryptingPartDatabase", "Writing part to: " + path.getAbsolutePath());
|
||||
part.setEncrypted(true);
|
||||
return new EncryptingPartOutputStream(path, masterSecret);
|
||||
}
|
||||
}
|
@ -467,7 +467,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
throws MmsException, NoSuchMessageException
|
||||
{
|
||||
MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context);
|
||||
PartDatabase partDatabase = getPartDatabase(masterSecret);
|
||||
PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
Cursor cursor = null;
|
||||
@ -485,7 +485,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
PduHeaders headers = getHeadersFromCursor(cursor);
|
||||
addr.getAddressesForId(messageId, headers);
|
||||
|
||||
PduBody body = getPartsAsBody(partDatabase.getParts(messageId, true));
|
||||
PduBody body = getPartsAsBody(partDatabase.getParts(messageId));
|
||||
|
||||
try {
|
||||
if (!TextUtils.isEmpty(messageText) && Types.isSymmetricEncryption(outboxType)) {
|
||||
@ -707,8 +707,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
ContentValues contentValues)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
PartDatabase partsDatabase = getPartDatabase(masterSecret);
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
PartDatabase partsDatabase = DatabaseFactory.getPartDatabase(context);
|
||||
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
|
||||
|
||||
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) {
|
||||
@ -725,7 +725,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
long messageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
|
||||
addressDatabase.insertAddressesForId(messageId, headers);
|
||||
partsDatabase.insertParts(messageId, body);
|
||||
partsDatabase.insertParts(masterSecret, messageId, body);
|
||||
|
||||
notifyConversationListeners(contentValues.getAsLong(THREAD_ID));
|
||||
DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID));
|
||||
@ -894,14 +894,6 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
return cvb.getContentValues();
|
||||
}
|
||||
|
||||
|
||||
protected PartDatabase getPartDatabase(MasterSecret masterSecret) {
|
||||
if (masterSecret == null)
|
||||
return DatabaseFactory.getPartDatabase(context);
|
||||
else
|
||||
return DatabaseFactory.getEncryptingPartDatabase(context, masterSecret);
|
||||
}
|
||||
|
||||
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
|
||||
return new Reader(masterSecret, cursor);
|
||||
}
|
||||
@ -1073,8 +1065,9 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
if (masterSecret == null)
|
||||
return null;
|
||||
|
||||
PduBody body = getPartsAsBody(getPartDatabase(masterSecret).getParts(id, false));
|
||||
SlideDeck slideDeck = new SlideDeck(context, masterSecret, body);
|
||||
PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
|
||||
PduBody body = getPartsAsBody(partDatabase.getParts(id));
|
||||
SlideDeck slideDeck = new SlideDeck(context, masterSecret, body);
|
||||
|
||||
if (!body.containsPushInProgress()) {
|
||||
slideCache.put(id, new SoftReference<SlideDeck>(slideDeck));
|
||||
|
@ -26,17 +26,18 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ -46,6 +47,7 @@ import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
public class PartDatabase extends Database {
|
||||
private static final String TAG = PartDatabase.class.getSimpleName();
|
||||
|
||||
private static final String TABLE_NAME = "part";
|
||||
private static final String ID = "_id";
|
||||
@ -63,14 +65,15 @@ public class PartDatabase extends Database {
|
||||
private static final String ENCRYPTED = "encrypted";
|
||||
private static final String DATA = "_data";
|
||||
private static final String PENDING_PUSH_ATTACHMENT = "pending_push";
|
||||
private static final String SIZE = "data_size";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " +
|
||||
CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " +
|
||||
CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " +
|
||||
CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " +
|
||||
CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " +
|
||||
PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT);";
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " +
|
||||
CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " +
|
||||
CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " +
|
||||
CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " +
|
||||
CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " +
|
||||
PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
|
||||
@ -81,6 +84,108 @@ public class PartDatabase extends Database {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public InputStream getPartStream(MasterSecret masterSecret, long partId)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
return getDataStream(masterSecret, partId, DATA);
|
||||
}
|
||||
|
||||
public void updateFailedDownloadedPart(long messageId, long partId, PduPart part)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
part.setContentDisposition(new byte[0]);
|
||||
part.setPendingPush(false);
|
||||
|
||||
ContentValues values = getContentValuesForPart(part);
|
||||
|
||||
values.put(DATA, (String)null);
|
||||
|
||||
database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""});
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public PduPart getPart(long partId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, ID_WHERE, new String[] {partId+""}, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) return getPart(cursor);
|
||||
else return null;
|
||||
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<Long, PduPart>> getParts(long mmsId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<Pair<Long, PduPart>> results = new LinkedList<>();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""},
|
||||
null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
PduPart part = getPart(cursor);
|
||||
results.add(new Pair<>(cursor.getLong(cursor.getColumnIndexOrThrow(ID)),
|
||||
part));
|
||||
}
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteParts(long mmsId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[] {DATA}, MMS_ID + " = ?",
|
||||
new String[] {mmsId+""}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String data = cursor.getString(0);
|
||||
|
||||
if (!TextUtils.isEmpty(data)) {
|
||||
new File(data).delete();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId+""});
|
||||
}
|
||||
|
||||
public void deleteAllParts() {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, null, null);
|
||||
|
||||
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
|
||||
File[] parts = partsDirectory.listFiles();
|
||||
|
||||
for (File part : parts) {
|
||||
part.delete();
|
||||
}
|
||||
}
|
||||
|
||||
void insertParts(MasterSecret masterSecret, long mmsId, PduBody body) throws MmsException {
|
||||
for (int i=0;i<body.getPartsNum();i++) {
|
||||
long partId = insertPart(masterSecret, body.getPart(i), mmsId);
|
||||
Log.w(TAG, "Inserted part at ID: " + partId);
|
||||
}
|
||||
}
|
||||
|
||||
private void getPartValues(PduPart part, Cursor cursor) {
|
||||
int charsetColumn = cursor.getColumnIndexOrThrow(CHARSET);
|
||||
|
||||
@ -126,8 +231,12 @@ public class PartDatabase extends Database {
|
||||
|
||||
if (!cursor.isNull(pendingPushColumn))
|
||||
part.setPendingPush(cursor.getInt(pendingPushColumn) == 1);
|
||||
}
|
||||
|
||||
int sizeColumn = cursor.getColumnIndexOrThrow(SIZE);
|
||||
|
||||
if (!cursor.isNull(sizeColumn))
|
||||
part.setDataSize(cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)));
|
||||
}
|
||||
|
||||
private ContentValues getContentValuesForPart(PduPart part) throws MmsException {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@ -172,123 +281,37 @@ public class PartDatabase extends Database {
|
||||
return contentValues;
|
||||
}
|
||||
|
||||
protected FileInputStream getPartInputStream(File file, PduPart part) throws FileNotFoundException {
|
||||
Log.w("PartDatabase", "Reading non-encrypted part from: " + file.getAbsolutePath());
|
||||
return new FileInputStream(file);
|
||||
private InputStream getPartInputStream(MasterSecret masterSecret, File path)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
Log.w(TAG, "Getting part at: " + path.getAbsolutePath());
|
||||
return new DecryptingPartInputStream(path, masterSecret);
|
||||
}
|
||||
|
||||
protected FileOutputStream getPartOutputStream(File file, PduPart part) throws FileNotFoundException {
|
||||
Log.w("PartDatabase", "Writing non-encrypted part to: " + file.getAbsolutePath());
|
||||
return new FileOutputStream(file);
|
||||
protected OutputStream getPartOutputStream(MasterSecret masterSecret, File path, PduPart part)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
Log.w(TAG, "Writing part to: " + path.getAbsolutePath());
|
||||
part.setEncrypted(true);
|
||||
return new EncryptingPartOutputStream(path, masterSecret);
|
||||
}
|
||||
|
||||
private void readPartData(PduPart part, String filename) {
|
||||
try {
|
||||
File dataFile = new File(filename);
|
||||
FileInputStream fin = getPartInputStream(dataFile, part);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream((int)dataFile.length());
|
||||
byte[] buffer = new byte[512];
|
||||
int read;
|
||||
|
||||
while ((read = fin.read(buffer)) != -1)
|
||||
baos.write(buffer, 0, read);
|
||||
|
||||
part.setData(baos.toByteArray());
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.w("PartDatabase", ioe);
|
||||
part.setData(null);
|
||||
}
|
||||
}
|
||||
|
||||
private File writePartData(PduPart part, InputStream in) throws MmsException {
|
||||
try {
|
||||
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
|
||||
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
|
||||
FileOutputStream fout = getPartOutputStream(dataFile, part);
|
||||
|
||||
byte[] buf = new byte[512];
|
||||
int read;
|
||||
|
||||
while ((read = in.read(buf)) != -1) {
|
||||
fout.write(buf, 0, read);
|
||||
}
|
||||
|
||||
fout.close();
|
||||
in.close();
|
||||
|
||||
return dataFile;
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private File writePartData(PduPart part) throws MmsException {
|
||||
try {
|
||||
if (part.getData() != null) {
|
||||
Log.w("PartDatabase", "Writing part data from buffer");
|
||||
return writePartData(part, new ByteArrayInputStream(part.getData()));
|
||||
} else if (part.getDataUri() != null) {
|
||||
Log.w("PartDatabase", "Writing part dat from URI");
|
||||
InputStream in = context.getContentResolver().openInputStream(part.getDataUri());
|
||||
return writePartData(part, in);
|
||||
} else {
|
||||
throw new MmsException("Part is empty!");
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PduPart getPart(Cursor cursor, boolean includeData) {
|
||||
PduPart part = new PduPart();
|
||||
String dataLocation = cursor.getString(cursor.getColumnIndexOrThrow(DATA));
|
||||
long partId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
|
||||
getPartValues(part, cursor);
|
||||
if (includeData && !part.isPendingPush())
|
||||
readPartData(part, dataLocation);
|
||||
part.setDataUri(ContentUris.withAppendedId(PartProvider.CONTENT_URI, partId));
|
||||
|
||||
return part;
|
||||
}
|
||||
|
||||
private long insertPart(PduPart part, long mmsId) throws MmsException {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
File dataFile = null;
|
||||
|
||||
if (!part.isPendingPush()) {
|
||||
dataFile = writePartData(part);
|
||||
Log.w("PartDatabase", "Wrote part to file: " + dataFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
ContentValues contentValues = getContentValuesForPart(part);
|
||||
contentValues.put(MMS_ID, mmsId);
|
||||
|
||||
if (dataFile != null) {
|
||||
contentValues.put(DATA, dataFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
return database.insert(TABLE_NAME, null, contentValues);
|
||||
}
|
||||
|
||||
public InputStream getPartStream(long partId) throws FileNotFoundException {
|
||||
private InputStream getDataStream(MasterSecret masterSecret, long partId, String dataType)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
Cursor cursor = null;
|
||||
|
||||
Log.w("PartDatabase", "Getting part at ID: " + partId);
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[]{DATA, ENCRYPTED}, ID_WHERE, new String[] {partId+""}, null, null, null);
|
||||
cursor = database.query(TABLE_NAME, new String[]{dataType}, ID_WHERE,
|
||||
new String[] {partId+""}, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
PduPart part = new PduPart();
|
||||
part.setEncrypted(cursor.getInt(1) == 1);
|
||||
|
||||
if (cursor.isNull(0)) {
|
||||
throw new FileNotFoundException("No part data for id: " + partId);
|
||||
}
|
||||
|
||||
return getPartInputStream(new File(cursor.getString(0)), part);
|
||||
return getPartInputStream(masterSecret, new File(cursor.getString(0)));
|
||||
} else {
|
||||
throw new FileNotFoundException("No part for id: " + partId);
|
||||
}
|
||||
@ -298,18 +321,79 @@ public class PartDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
void insertParts(long mmsId, PduBody body) throws MmsException {
|
||||
for (int i=0;i<body.getPartsNum();i++) {
|
||||
long partId = insertPart(body.getPart(i), mmsId);
|
||||
Log.w("PartDatabase", "Inserted part at ID: " + partId);
|
||||
private Pair<File, Long> writePartData(MasterSecret masterSecret, PduPart part, InputStream in)
|
||||
throws MmsException
|
||||
{
|
||||
try {
|
||||
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
|
||||
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
|
||||
OutputStream out = getPartOutputStream(masterSecret, dataFile, part);
|
||||
long plaintextLength = Util.copy(in, out);
|
||||
|
||||
return new Pair<>(dataFile, plaintextLength);
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDownloadedPart(long messageId, long partId, PduPart part, InputStream data)
|
||||
private Pair<File, Long> writePartData(MasterSecret masterSecret, PduPart part)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
File partData = writePartData(part, data);
|
||||
try {
|
||||
if (part.getData() != null) {
|
||||
Log.w(TAG, "Writing part data from buffer");
|
||||
return writePartData(masterSecret, part, new ByteArrayInputStream(part.getData()));
|
||||
} else if (part.getDataUri() != null) {
|
||||
Log.w(TAG, "Writing part data from URI");
|
||||
InputStream in = context.getContentResolver().openInputStream(part.getDataUri());
|
||||
return writePartData(masterSecret, part, in);
|
||||
} else {
|
||||
throw new MmsException("Part is empty!");
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PduPart getPart(Cursor cursor) {
|
||||
PduPart part = new PduPart();
|
||||
String dataLocation = cursor.getString(cursor.getColumnIndexOrThrow(DATA));
|
||||
long partId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
|
||||
getPartValues(part, cursor);
|
||||
|
||||
part.setDataUri(ContentUris.withAppendedId(PartAuthority.PART_CONTENT_URI, partId));
|
||||
|
||||
return part;
|
||||
}
|
||||
|
||||
private long insertPart(MasterSecret masterSecret, PduPart part, long mmsId) throws MmsException {
|
||||
Log.w(TAG, "inserting part to mms " + mmsId);
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Pair<File, Long> partData = null;
|
||||
|
||||
if (!part.isPendingPush()) {
|
||||
partData = writePartData(masterSecret, part);
|
||||
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
|
||||
}
|
||||
|
||||
ContentValues contentValues = getContentValuesForPart(part);
|
||||
contentValues.put(MMS_ID, mmsId);
|
||||
|
||||
if (partData != null) {
|
||||
contentValues.put(DATA, partData.first.getAbsolutePath());
|
||||
contentValues.put(SIZE, partData.second);
|
||||
}
|
||||
|
||||
return database.insert(TABLE_NAME, null, contentValues);
|
||||
}
|
||||
|
||||
public void updateDownloadedPart(MasterSecret masterSecret, long messageId,
|
||||
long partId, PduPart part, InputStream data)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Pair<File, Long> partData = writePartData(masterSecret, part, data);
|
||||
|
||||
part.setContentDisposition(new byte[0]);
|
||||
part.setPendingPush(false);
|
||||
@ -317,120 +401,12 @@ public class PartDatabase extends Database {
|
||||
ContentValues values = getContentValuesForPart(part);
|
||||
|
||||
if (partData != null) {
|
||||
values.put(DATA, partData.getAbsolutePath());
|
||||
values.put(DATA, partData.first.getAbsolutePath());
|
||||
values.put(SIZE, partData.second);
|
||||
}
|
||||
|
||||
database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""});
|
||||
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void updateFailedDownloadedPart(long messageId, long partId, PduPart part)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
part.setContentDisposition(new byte[0]);
|
||||
part.setPendingPush(false);
|
||||
|
||||
ContentValues values = getContentValuesForPart(part);
|
||||
|
||||
values.put(DATA, (String)null);
|
||||
|
||||
database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""});
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public PduPart getPart(long partId, boolean includeData) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, ID_WHERE, new String[] {partId+""}, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst())
|
||||
return getPart(cursor, includeData);
|
||||
else
|
||||
return null;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<Long, PduPart>> getParts(long mmsId, boolean includeData) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<Pair<Long, PduPart>> results = new LinkedList<Pair<Long, PduPart>>();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
PduPart part = getPart(cursor, includeData);
|
||||
results.add(new Pair<Long, PduPart>(cursor.getLong(cursor.getColumnIndexOrThrow(ID)),
|
||||
part));
|
||||
}
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<Long, Pair<Long, PduPart>>> getPushPendingParts() {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<Pair<Long, Pair<Long, PduPart>>> results = new LinkedList<Pair<Long, Pair<Long, PduPart>>>();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, PENDING_PUSH_ATTACHMENT + " = ?", new String[] {"1"}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
PduPart part = getPart(cursor, false);
|
||||
results.add(new Pair<Long, Pair<Long, PduPart>>(cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
new Pair<Long, PduPart>(cursor.getLong(cursor.getColumnIndexOrThrow(ID)),
|
||||
part)));
|
||||
}
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void deleteParts(long mmsId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[] {DATA}, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String data = cursor.getString(0);
|
||||
if (!TextUtils.isEmpty(data)) {
|
||||
new File(cursor.getString(0)).delete();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId+""});
|
||||
}
|
||||
|
||||
public void deleteAllParts() {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, null, null);
|
||||
|
||||
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
|
||||
File[] parts = partsDirectory.listFiles();
|
||||
|
||||
for (int i=0;i<parts.length;i++) {
|
||||
parts[i].delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import android.util.Pair;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingPartDatabase;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
@ -54,11 +53,11 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws IOException {
|
||||
PartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret);
|
||||
PartDatabase database = DatabaseFactory.getPartDatabase(context);
|
||||
|
||||
Log.w(TAG, "Downloading push parts for: " + messageId);
|
||||
|
||||
List<Pair<Long, PduPart>> parts = database.getParts(messageId, false);
|
||||
List<Pair<Long, PduPart>> parts = database.getParts(messageId);
|
||||
|
||||
for (Pair<Long, PduPart> partPair : parts) {
|
||||
retrievePart(masterSecret, partPair.second, messageId, partPair.first);
|
||||
@ -69,7 +68,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
PartDatabase database = DatabaseFactory.getPartDatabase(context);
|
||||
List<Pair<Long, PduPart>> parts = database.getParts(messageId, false);
|
||||
List<Pair<Long, PduPart>> parts = database.getParts(messageId);
|
||||
|
||||
for (Pair<Long, PduPart> partPair : parts) {
|
||||
markFailed(messageId, partPair.second, partPair.first);
|
||||
@ -78,16 +77,14 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
if (exception instanceof PushNetworkException) return true;
|
||||
|
||||
return false;
|
||||
return (exception instanceof PushNetworkException);
|
||||
}
|
||||
|
||||
private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId, long partId)
|
||||
throws IOException
|
||||
{
|
||||
EncryptingPartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret);
|
||||
File attachmentFile = null;
|
||||
PartDatabase database = DatabaseFactory.getPartDatabase(context);
|
||||
File attachmentFile = null;
|
||||
|
||||
try {
|
||||
attachmentFile = createTempFile();
|
||||
@ -95,7 +92,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, part);
|
||||
InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile);
|
||||
|
||||
database.updateDownloadedPart(messageId, partId, part, attachment);
|
||||
database.updateDownloadedPart(masterSecret, messageId, partId, part, attachment);
|
||||
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
markFailed(messageId, part, partId);
|
||||
|
@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
@ -23,17 +24,22 @@ import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduComposer;
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.SendConf;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
@ -60,10 +66,12 @@ public class MmsSendJob extends MasterSecretJob {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws MmsException, NoSuchMessageException {
|
||||
public void onRun(MasterSecret masterSecret) throws MmsException, NoSuchMessageException, IOException {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
|
||||
populatePartData(message.getBody(), masterSecret);
|
||||
|
||||
try {
|
||||
MmsSendResult result = deliver(masterSecret, message);
|
||||
|
||||
@ -94,6 +102,20 @@ public class MmsSendJob extends MasterSecretJob {
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
}
|
||||
|
||||
private void populatePartData(PduPart part, MasterSecret masterSecret) throws IOException {
|
||||
ByteArrayOutputStream os = part.getDataSize() > 0 && part.getDataSize() < Integer.MAX_VALUE
|
||||
? new ByteArrayOutputStream((int)part.getDataSize())
|
||||
: new ByteArrayOutputStream();
|
||||
Util.copy(PartAuthority.getPartStream(context, masterSecret, part.getDataUri()), os);
|
||||
part.setData(os.toByteArray());
|
||||
}
|
||||
|
||||
private void populatePartData(PduBody body, MasterSecret masterSecret) throws IOException {
|
||||
for (int i=body.getPartsNum()-1; i>=0; i--) {
|
||||
populatePartData(body.getPart(i), masterSecret);
|
||||
}
|
||||
}
|
||||
|
||||
public MmsSendResult deliver(MasterSecret masterSecret, SendReq message)
|
||||
throws UndeliverableMessageException, InsecureFallbackApprovalException
|
||||
{
|
||||
|
@ -114,7 +114,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
|
||||
Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
|
||||
List<PushAddress> addresses = getPushAddresses(recipients);
|
||||
List<TextSecureAttachment> attachments = getAttachments(message);
|
||||
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
||||
|
||||
if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) ||
|
||||
MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()))
|
||||
|
@ -109,7 +109,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
try {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination, false);
|
||||
PushAddress address = getPushAddress(recipients.getPrimaryRecipient());
|
||||
List<TextSecureAttachment> attachments = getAttachments(message);
|
||||
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
||||
String body = PartParser.getMessageText(message.getBody());
|
||||
TextSecureMessage mediaMessage = new TextSecureMessage(message.getSentTimestamp(), attachments, body);
|
||||
|
||||
|
@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
@ -20,10 +22,13 @@ import org.whispersystems.textsecure.api.push.PushAddress;
|
||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public abstract class PushSendJob extends MasterSecretJob {
|
||||
@ -82,18 +87,23 @@ public abstract class PushSendJob extends MasterSecretJob {
|
||||
return (isSmsFallbackSupported(context, destination, media) && TextSecurePreferences.isFallbackSmsAskRequired(context));
|
||||
}
|
||||
|
||||
protected List<TextSecureAttachment> getAttachments(SendReq message) {
|
||||
protected List<TextSecureAttachment> getAttachments(final MasterSecret masterSecret, final SendReq message) {
|
||||
List<TextSecureAttachment> attachments = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<message.getBody().getPartsNum();i++) {
|
||||
String contentType = Util.toIsoString(message.getBody().getPart(i).getContentType());
|
||||
PduPart part = message.getBody().getPart(i);
|
||||
String contentType = Util.toIsoString(part.getContentType());
|
||||
if (ContentType.isImageType(contentType) ||
|
||||
ContentType.isAudioType(contentType) ||
|
||||
ContentType.isVideoType(contentType))
|
||||
{
|
||||
byte[] data = message.getBody().getPart(i).getData();
|
||||
Log.w(TAG, "Adding attachment...");
|
||||
attachments.add(new TextSecureAttachmentStream(new ByteArrayInputStream(data), contentType, data.length));
|
||||
|
||||
try {
|
||||
InputStream is = PartAuthority.getPartStream(context, masterSecret, part.getDataUri());
|
||||
attachments.add(new TextSecureAttachmentStream(is, contentType, part.getDataSize()));
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, "Couldn't open attachment", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,8 +70,7 @@ public class AudioSlide extends Slide {
|
||||
public static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException {
|
||||
PduPart part = new PduPart();
|
||||
|
||||
if (getMediaSize(context, uri) > MAX_MESSAGE_SIZE)
|
||||
throw new MediaTooLargeException("Audio track larger than size maximum.");
|
||||
assertMediaSize(context, uri);
|
||||
|
||||
Cursor cursor = null;
|
||||
|
||||
|
45
src/org/thoughtcrime/securesms/mms/PartAuthority.java
Normal file
45
src/org/thoughtcrime/securesms/mms/PartAuthority.java
Normal file
@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class PartAuthority {
|
||||
|
||||
private static final String PART_URI_STRING = "content://org.thoughtcrime.securesms/part";
|
||||
public static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
|
||||
private static final int PART_ROW = 1;
|
||||
|
||||
private static final UriMatcher uriMatcher;
|
||||
|
||||
static {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI("org.thoughtcrime.securesms", "part/#", PART_ROW);
|
||||
}
|
||||
|
||||
public static InputStream getPartStream(Context context, MasterSecret masterSecret, Uri uri)
|
||||
throws FileNotFoundException
|
||||
{
|
||||
PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
|
||||
int match = uriMatcher.match(uri);
|
||||
|
||||
switch (match) {
|
||||
case PART_ROW: return partDatabase.getPartStream(masterSecret, ContentUris.parseId(uri));
|
||||
default: return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public static Uri getPublicPartUri(Uri uri) {
|
||||
return ContentUris.withAppendedId(PartProvider.CONTENT_URI, ContentUris.parseId(uri));
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.w3c.dom.smil.SMILDocument;
|
||||
import org.w3c.dom.smil.SMILMediaElement;
|
||||
import org.w3c.dom.smil.SMILRegionElement;
|
||||
@ -56,34 +57,19 @@ public abstract class Slide {
|
||||
}
|
||||
|
||||
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;
|
||||
byte[] buffer = new byte[512];
|
||||
int read;
|
||||
|
||||
while ((read = in.read(buffer)) != -1)
|
||||
size += read;
|
||||
|
||||
return size;
|
||||
return PartAuthority.getPartStream(context, masterSecret, part.getDataUri());
|
||||
}
|
||||
|
||||
protected byte[] getPartData() {
|
||||
if (part.getData() != null)
|
||||
return part.getData();
|
||||
try {
|
||||
if (part.getData() != null)
|
||||
return part.getData();
|
||||
|
||||
long partId = ContentUris.parseId(part.getDataUri());
|
||||
return DatabaseFactory.getEncryptingPartDatabase(context, masterSecret).getPart(partId, true).getData();
|
||||
return Util.readFully(PartAuthority.getPartStream(context, masterSecret, part.getDataUri()));
|
||||
} catch (IOException e) {
|
||||
Log.w("Slide", e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
@ -133,4 +119,18 @@ public abstract class Slide {
|
||||
public abstract SMILRegionElement getSmilRegion(SMILDocument document);
|
||||
|
||||
public abstract SMILMediaElement getMediaElement(SMILDocument document);
|
||||
|
||||
protected static void assertMediaSize(Context context, Uri uri)
|
||||
throws MediaTooLargeException, IOException
|
||||
{
|
||||
InputStream in = context.getContentResolver().openInputStream(uri);
|
||||
long size = 0;
|
||||
byte[] buffer = new byte[512];
|
||||
int read;
|
||||
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
size += read;
|
||||
if (size > MAX_MESSAGE_SIZE) throw new MediaTooLargeException("Media exceeds maximum message size.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,9 +91,7 @@ public class VideoSlide extends Slide {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
if (getMediaSize(context, uri) > MAX_MESSAGE_SIZE)
|
||||
throw new MediaTooLargeException("Video exceeds maximum message size.");
|
||||
|
||||
assertMediaSize(context, uri);
|
||||
part.setDataUri(uri);
|
||||
part.setContentId((System.currentTimeMillis()+"").getBytes());
|
||||
part.setName(("Video" + System.currentTimeMillis()).getBytes());
|
||||
|
@ -68,7 +68,7 @@ public class PartProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
private File copyPartToTemporaryFile(MasterSecret masterSecret, long partId) throws IOException {
|
||||
InputStream in = DatabaseFactory.getEncryptingPartDatabase(getContext(), masterSecret).getPartStream(partId);
|
||||
InputStream in = DatabaseFactory.getPartDatabase(getContext()).getPartStream(masterSecret, partId);
|
||||
File tmpDir = getContext().getDir("tmp", 0);
|
||||
File tmpFile = File.createTempFile("test", ".jpg", tmpDir);
|
||||
FileOutputStream fout = new FileOutputStream(tmpFile);
|
||||
|
@ -14,7 +14,7 @@ import android.widget.Toast;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@ -60,7 +60,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
|
||||
}
|
||||
|
||||
File mediaFile = constructOutputFile(attachment.contentType, attachment.date);
|
||||
InputStream inputStream = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret).getPartStream(ContentUris.parseId(attachment.uri));
|
||||
InputStream inputStream = PartAuthority.getPartStream(context, masterSecret, attachment.uri);
|
||||
OutputStream outputStream = new FileOutputStream(mediaFile);
|
||||
|
||||
Util.copy(inputStream, outputStream);
|
||||
@ -139,9 +139,6 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
|
||||
if (uri == null || contentType == null || date < 0) {
|
||||
throw new AssertionError("uri, content type, and date must all be specified");
|
||||
}
|
||||
if (!PartProvider.isAuthority(uri)) {
|
||||
throw new AssertionError("attachment must be a TextSecure attachment");
|
||||
}
|
||||
this.uri = uri;
|
||||
this.contentType = contentType;
|
||||
this.date = date;
|
||||
|
@ -146,7 +146,7 @@ public class Util {
|
||||
else return canonicalizeNumber(context, number);
|
||||
}
|
||||
|
||||
public static String readFully(InputStream in) throws IOException {
|
||||
public static byte[] readFully(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
@ -157,19 +157,27 @@ public class Util {
|
||||
|
||||
in.close();
|
||||
|
||||
return new String(bout.toByteArray());
|
||||
return bout.toByteArray();
|
||||
}
|
||||
|
||||
public static void copy(InputStream in, OutputStream out) throws IOException {
|
||||
public static String readFullyAsString(InputStream in) throws IOException {
|
||||
return new String(readFully(in));
|
||||
}
|
||||
|
||||
public static long copy(InputStream in, OutputStream out) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
long total = 0;
|
||||
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
|
||||
in.close();
|
||||
out.close();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public static String getDeviceE164Number(Context context) {
|
||||
|
@ -123,6 +123,7 @@ public class PduPart {
|
||||
|
||||
private boolean isEncrypted;
|
||||
private boolean isPendingPush;
|
||||
private long dataSize;
|
||||
|
||||
/**
|
||||
* Empty Constructor.
|
||||
@ -134,12 +135,21 @@ public class PduPart {
|
||||
public void setEncrypted(boolean isEncrypted) {
|
||||
this.isEncrypted = isEncrypted;
|
||||
}
|
||||
|
||||
|
||||
public boolean getEncrypted() {
|
||||
return isEncrypted;
|
||||
}
|
||||
|
||||
public void setPendingPush(boolean isPendingPush) {
|
||||
public void setDataSize(long dataSize) {
|
||||
this.dataSize = dataSize;
|
||||
}
|
||||
|
||||
public long getDataSize() {
|
||||
return this.dataSize;
|
||||
}
|
||||
|
||||
|
||||
public void setPendingPush(boolean isPendingPush) {
|
||||
this.isPendingPush = isPendingPush;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user